/** if given a non-null argument declare its argument in the current scope and push
it to the stack, else, declares the element on the top of the stack and leaves it
there */
- public static final byte DECLARE = -6;
+ //public static final byte DECLARE = -6;
/** push a reference to the current scope onto the stack */
- public static final byte TOPSCOPE = -7;
+ // FIXME: Document this
+ public static final byte GLOBALSCOPE = -7;
/** if given a null literal pop two elements off the stack; push stack[-1].get(stack[top])
else pop one element off the stack, push stack[top].get(literal) */
/** finish a finally block and carry out whatever instruction initiated the finally block */
public static final byte MAKE_GRAMMAR = -25;
+ // FIXME: Document these and NEWSCOPE/OLDSCOPE/TOPSCOPE changes
+ public static final byte SCOPEGET = -26;
+ public static final byte SCOPEPUT = -27;
+
public static final String[] bytecodeToString = new String[] {
- "", "", "LITERAL", "ARRAY", "OBJECT", "NEWFUNCTION", "DECLARE", "TOPSCOPE",
+ "", "", "LITERAL", "ARRAY", "OBJECT", "NEWFUNCTION", "DECLARE", "GLOBALSCOPE",
"GET", "GET_PRESERVE", "PUT", "JT", "JF", "JMP", "POP", "CALL", "PUSHKEYS",
"SWAP", "NEWSCOPE", "OLDSCOPE", "DUP", "LABEL", "LOOP", "CALLMETHOD",
- "FINALLY_DONE", "MAKE_GRAMMAR"
+ "FINALLY_DONE", "MAKE_GRAMMAR", "SCOPEGET", "SCOPEPUT"
};
}
* Create the directory object. Existing directories will be
* preserved; if a file is present it will be obliterated.
*/
+
public Directory(File f) throws IOException {
this.f = f;
if (!f.exists()) new Directory(new File(f.getParent()));
f.delete();
}
- public void put(Object key0, Object val) throws JSExn {
+ public void put(JS key0, JS val) throws JSExn {
try {
if (key0 == null) return;
String key = toString(key0);
File f2 = new File(f.getAbsolutePath() + File.separatorChar + FileNameEncoder.encode(key));
destroy(f2);
if (val == null) return;
- if (val instanceof JS) {
+ if (val instanceof JSPrimitive) {
+ OutputStream out = new FileOutputStream(f2);
+ Writer w = new OutputStreamWriter(out);
+ w.write(toString(val));
+ w.flush();
+ out.close();
+ } else {
Directory d2 = new Directory(f2);
- Enumeration e = ((JS)val).keys();
+ JS.Enumeration e = val.keys();
while(e.hasMoreElements()) {
- String k = (String)e.nextElement();
- Object v = ((JS)val).get(k);
+ JS k = e.nextElement();
+ JS v = val.get(k);
d2.put(k, v);
}
- } else {
- OutputStream out = new FileOutputStream(f2);
- try {
- Writer w = new OutputStreamWriter(out);
- w.write(toString(val));
- w.flush();
- } finally {
- out.close();
- }
}
} catch (IOException ioe) {
throw new JSExn.IO(ioe);
}
}
- public Object get(Object key0) throws JSExn {
- FileInputStream fis = null;
+ public JS get(JS key0) throws JSExn {
try {
if (key0 == null) return null;
String key = toString(key0);
if (f2.isDirectory()) return new Directory(f2);
char[] chars = new char[((int)f2.length()) * 2];
int numchars = 0;
- fis = new FileInputStream(f2);
- Reader r = new InputStreamReader(fis);
+ Reader r = new InputStreamReader(new FileInputStream(f2));
while(true) {
int numread = r.read(chars, numchars, chars.length - numchars);
- if (numread == -1) return new String(chars, 0, numchars);
+ if (numread == -1) return JS.S(new String(chars, 0, numchars));
numchars += numread;
}
} catch (IOException ioe) {
throw new JSExn.IO(ioe);
- } finally {
- try {
- if (fis != null) fis.close();
- } catch (IOException ioe) {
- throw new JSExn.IO(ioe);
- }
}
}
public Enumeration keys() {
final String[] elements = f.list();
- return new Enumeration() {
+ return new Enumeration(null) {
int i = 0;
- public boolean hasMoreElements() { return i < elements.length; }
- public Object nextElement() { return FileNameEncoder.decode(elements[i++]); }
+ public boolean _hasMoreElements() { return i < elements.length; }
+ public JS _nextElement() { return JS.S(FileNameEncoder.decode(elements[i++])); }
};
}
}
* be totally independent of the others (ie separate stream position
* and state) although they draw from the same data source.
*/
-public abstract class Fountain extends JS.Cloneable {
+public abstract class Fountain extends JS.O implements JS.Cloneable {
// Public Interface //////////////////////////////////////////////////////////////////////////////
/** HTTP or HTTPS resource */
public static class HTTP extends Fountain {
private String url;
- public String toString() { return "Stream.HTTP:" + url; }
+ //public String toString() { return "Stream.HTTP:" + url; }
public HTTP(String url) { while (url.endsWith("/")) url = url.substring(0, url.length() - 1); this.url = url; }
public Object _get(Object key) { return new HTTP(url + "/" + (String)key); }
public String getCacheKey(Vec path) throws NotCacheableException { return url; }
public static class File extends Fountain {
private String path;
public File(String path) { this.path = path; }
- public String toString() { return "file:" + path; }
+ //public String toString() { return "file:" + path; }
public String getCacheKey() throws NotCacheableException { throw new NotCacheableException(); /* already on disk */ }
public InputStream getInputStream() throws IOException { return new FileInputStream(path); }
public Object _get(Object key) { return new File(path + java.io.File.separatorChar + (String)key); }
/** Encapsulates a single JS interpreter (ie call stack) */
class Interpreter implements ByteCodes, Tokens {
-
-
// Thread-Interpreter Mapping /////////////////////////////////////////////////////////////////////////
static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); }
int pausecount; ///< the number of times pause() has been invoked; -1 indicates unpauseable
JSFunction f = null; ///< the currently-executing JSFunction
JSScope scope; ///< the current top-level scope (LIFO stack via NEWSCOPE/OLDSCOPE)
- Vec stack = new Vec(); ///< the object stack
+ final Stack stack = new Stack(); ///< the object stack
int pc = 0; ///< the program counter
- Interpreter(JSFunction f, boolean pauseable, JSArray args) { this(f, pauseable, args, true); }
- Interpreter(JSFunction f, boolean pauseable, JSArray args, boolean wrap) {
- stack.push(new Interpreter.CallMarker(this)); // the "root function returned" marker -- f==null
+ Interpreter(JSFunction f, boolean pauseable, JSArgs args) {
this.f = f;
this.pausecount = pauseable ? 0 : -1;
- this.scope = wrap ? new JSScope(f.parentScope) : f.parentScope;
- stack.push(args);
+ this.scope = f.parentScope;
+ try {
+ stack.push(new CallMarker(null)); // the "root function returned" marker -- f==null
+ stack.push(args);
+ } catch(JSExn e) {
+ throw new Error("should never happen");
+ }
+ }
+
+ Interpreter(Trap t, JS val, boolean pauseOnPut) {
+ this.pausecount = -1;
+ try {
+ setupTrap(t,val,new TrapMarker(null,t,val,pauseOnPut));
+ } catch(JSExn e) {
+ throw new Error("should never happen");
+ }
}
/** this is the only synchronization point we need in order to be threadsafe */
- synchronized Object resume() throws JSExn {
+ synchronized JS resume() throws JSExn {
+ if(f == null) throw new RuntimeException("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);
private static JSExn je(String s) { return new JSExn(getSourceName() + ":" + getLine() + " " + s); }
- // FIXME: double check the trap logic
- private Object run() throws JSExn {
+ private JS run() throws JSExn {
// if pausecount changes after a get/put/call, we know we've been paused
final int initialPauseCount = pausecount;
OUTER: for(;; pc++) {
try {
- if (f == null) return stack.pop();
int op = f.op[pc];
Object arg = f.arg[pc];
if(op == FINALLY_DONE) {
arg = fd.arg;
}
switch(op) {
- case LITERAL: stack.push(arg); break;
- case OBJECT: stack.push(new JS()); break;
- case ARRAY: stack.push(new JSArray(JS.toNumber(arg).intValue())); break;
- case DECLARE: scope.declare((String)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push(arg); break;
- case TOPSCOPE: stack.push(scope); break;
- case JT: if (JS.toBoolean(stack.pop())) pc += JS.toNumber(arg).intValue() - 1; break;
- case JF: if (!JS.toBoolean(stack.pop())) pc += JS.toNumber(arg).intValue() - 1; break;
- case JMP: pc += JS.toNumber(arg).intValue() - 1; break;
+ case LITERAL: stack.push((JS)arg); break;
+ case OBJECT: stack.push(new JS.O()); break;
+ case ARRAY: stack.push(new JSArray(JS.toInt((JS)arg))); break;
+ //case DECLARE: scope.declare((JS)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push((JS)arg); break;
+ case JT: if (JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break;
+ case JF: if (!JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break;
+ case JMP: pc += JS.toInt((JS)arg) - 1; break;
case POP: stack.pop(); break;
- case SWAP: {
- int depth = (arg == null ? 1 : JS.toInt(arg));
- Object save = stack.elementAt(stack.size() - 1);
- for(int i=stack.size() - 1; i > stack.size() - 1 - depth; i--)
- stack.setElementAt(stack.elementAt(i-1), i);
- stack.setElementAt(save, stack.size() - depth - 1);
- break; }
+ case SWAP: stack.swap(); break;
case DUP: stack.push(stack.peek()); break;
- case NEWSCOPE: scope = new JSScope(scope); break;
- case OLDSCOPE: scope = scope.getParentScope(); break;
- case ASSERT:
- if (JS.checkAssertions && !JS.toBoolean(stack.pop()))
- throw je("ibex.assertion.failed" /*FEATURE: line number*/); break;
+ case NEWSCOPE: {
+ int n = JS.toInt((JS)arg);
+ scope = new JSScope(scope,(n>>>16)&0xffff,(n>>>0)&0xffff);
+ break;
+ }
+ case OLDSCOPE: scope = scope.parent; break;
+ case GLOBALSCOPE: stack.push(scope.getGlobal()); break;
+ case SCOPEGET: stack.push(scope.get((JS)arg)); break;
+ case SCOPEPUT: scope.put((JS)arg,stack.peek()); break;
+ case ASSERT: if (!JS.toBoolean(stack.pop())) throw je("ibex.assertion.failed"); break;
case BITNOT: stack.push(JS.N(~JS.toLong(stack.pop()))); break;
case BANG: stack.push(JS.B(!JS.toBoolean(stack.pop()))); break;
case NEWFUNCTION: stack.push(((JSFunction)arg)._cloneWithNewParentScope(scope)); break;
case TYPEOF: {
Object o = stack.pop();
if (o == null) stack.push(null);
- else if (o instanceof JS) stack.push("object");
- else if (o instanceof String) stack.push("string");
- else if (o instanceof Number) stack.push("number");
- else if (o instanceof Boolean) stack.push("boolean");
- else throw new Error("this should not happen");
+ else if (o instanceof JSString) stack.push(JS.S("string"));
+ else if (o instanceof JSNumber.B) stack.push(JS.S("boolean"));
+ else if (o instanceof JSNumber) stack.push(JS.S("number"));
+ else stack.push(JS.S("object"));
break;
}
case PUSHKEYS: {
- Object o = stack.peek();
- Enumeration e = ((JS)o).keys();
- JSArray a = new JSArray();
- while(e.hasMoreElements()) a.addElement(e.nextElement());
- stack.push(a);
+ JS o = 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(Boolean.TRUE);
+ stack.push(JS.T);
break;
case BREAK:
case CONTINUE:
- while(stack.size() > 0) {
- Object o = stack.pop();
+ while(!stack.empty()) {
+ JS o = 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
if (o instanceof LoopMarker) {
if (arg == null || arg.equals(((LoopMarker)o).label)) {
int loopInstructionLocation = ((LoopMarker)o).location;
- int endOfLoop = ((Integer)f.arg[loopInstructionLocation]).intValue() + loopInstructionLocation;
+ int endOfLoop = JS.toInt((JS)f.arg[loopInstructionLocation]) + loopInstructionLocation;
scope = ((LoopMarker)o).scope;
- if (op == CONTINUE) { stack.push(o); stack.push(Boolean.FALSE); }
+ if (op == CONTINUE) { stack.push(o); stack.push(JS.F); }
pc = op == BREAK ? endOfLoop - 1 : loopInstructionLocation;
continue OUTER;
}
}
case RETURN: {
- Object retval = stack.pop();
- while(stack.size() > 0) {
+ JS retval = stack.pop();
+ while(!stack.empty()) {
Object o = stack.pop();
if (o instanceof TryMarker) {
if(((TryMarker)o).finallyLoc < 0) continue;
pc = ((TryMarker)o).finallyLoc - 1;
continue OUTER;
} else if (o instanceof CallMarker) {
- if (scope instanceof Trap.TrapScope) { // handles return component of a read trap
- Trap.TrapScope ts = (Trap.TrapScope)scope;
- if (retval != null && retval instanceof Boolean && ((Boolean)retval).booleanValue())
- ts.cascadeHappened = true;
- if (!ts.cascadeHappened) {
- ts.cascadeHappened = true;
- Trap t = ts.t.next;
- while (t != null && t.f.numFormalArgs == 0) t = t.next;
- if (t == null) {
- ((JS)ts.t.trapee).put(ts.t.name, ts.val);
- if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
- } else {
- stack.push(o);
- JSArray args = new JSArray();
- args.addElement(ts.val);
- stack.push(args);
- f = t.f;
- scope = new Trap.TrapScope(f.parentScope, t, ts.val);
- pc = -1;
+ boolean didTrapPut = false;
+ if (o instanceof TrapMarker) { // handles return component of a write trap
+ 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();
+ }
+ if(t != null) {
+ tm.t = t; // we reuse the old trap marker
+ 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);
}
}
}
- scope = ((CallMarker)o).scope;
- pc = ((CallMarker)o).pc - 1;
- f = (JSFunction)((CallMarker)o).f;
- stack.push(retval);
+ CallMarker cm = (CallMarker) o;
+ scope = cm.scope;
+ pc = cm.pc - 1;
+ f = cm.f;
+ if (didTrapPut) {
+ if (((TrapMarker)cm).pauseOnPut) { pc++; return ((TrapMarker)cm).val; }
+ if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
+ } else {
+ stack.push(retval);
+ }
+ if (f == null) return retval;
continue OUTER;
}
}
throw new Error("error: RETURN invoked but couldn't find a CallMarker!");
}
-
- case PUT: {
- Object val = stack.pop();
- Object key = stack.pop();
- Object target = stack.peek();
- if (target == null)
- throw je("tried to put a value to the " + key + " property on the null value");
- if (!(target instanceof JS))
- throw je("tried to put a value to the " + key + " property on a " + target.getClass().getName());
- if (key == null)
- throw je("tried to assign \"" + (val==null?"(null)":val.toString()) + "\" to the null key");
-
- Trap t = null;
- if (target instanceof JSScope && key.equals("cascade")) {
- Trap.TrapScope ts = null;
- JSScope p = (JSScope)target; // search the scope-path for the trap
- if (target instanceof Trap.TrapScope) {
- ts = (Trap.TrapScope)target;
- }
- else {
- while (ts == null && p.getParentScope() != null) {
- p = p.getParentScope();
- if (p instanceof Trap.TrapScope) {
- ts = (Trap.TrapScope)p;
- }
- }
- }
- t = ts.t.next;
- ts.cascadeHappened = true;
- while (t != null && t.f.numFormalArgs == 0) t = t.next;
- if (t == null) { target = ts.t.trapee; key = ts.t.name; }
-
- } else if (target instanceof Trap.TrapScope && key.equals(((Trap.TrapScope)target).t.name)) {
- throw je("tried to put to " + key + " inside a trap it owns; use cascade instead");
-
- } else if (target instanceof JS) {
- if (target instanceof JSScope) {
- JSScope p = (JSScope)target; // search the scope-path for the trap
- t = p.getTrap(key);
- while (t == null && p.getParentScope() != null) { p = p.getParentScope(); t = p.getTrap(key); }
+
+ case CASCADE: {
+ boolean write = JS.toBoolean((JS)arg);
+ JS val = write ? stack.pop() : null;
+ 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;
+ 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();
+ // 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(write) {
+ tm.cascadeHappened = true;
+ stack.push(val);
+ }
+ if(t != null) {
+ setupTrap(t,val,new TrapMarker(this,t,val,tm.pauseOnPut));
+ pc--; // we increment later
+ } else {
+ if(write) {
+ if (tm.pauseOnPut) { pc++; return val; }
+ target.put(key,val);
} else {
- t = ((JS)target).getTrap(key);
+ JS ret = target.get(key);
+ if (ret == JS.METHOD) ret = new Stub(target, key);
+ stack.push(ret);
}
- while (t != null && t.f.numFormalArgs == 0) t = t.next; // find the first write trap
+ if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
}
- if (t != null) {
- stack.push(new CallMarker(this));
- JSArray args = new JSArray();
- args.addElement(val);
- stack.push(args);
- f = t.f;
- scope = new Trap.TrapScope(f.parentScope, t, val);
- pc = -1;
- break;
+ break;
+ }
+
+ 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");
+
+ Trap t = target.getTrap(key);
+ if(t != null) t = t.writeTrap();
+
+ if(t == null && target instanceof JS.Clone) {
+ target = ((JS.Clone)target).clonee;
+ t = target.getTrap(key);
+ if(t != null) t = t.writeTrap();
}
- ((JS)target).put(key, val);
- if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
+
stack.push(val);
+
+ if(t != null) {
+ setupTrap(t,val,new TrapMarker(this,t,val));
+ pc--; // we increment later
+ } else {
+ target.put(key,val);
+ if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
+ }
break;
}
case GET:
case GET_PRESERVE: {
- Object o, v;
+ JS target, key;
if (op == GET) {
- v = arg == null ? stack.pop() : arg;
- o = stack.pop();
+ key = arg == null ? stack.pop() : (JS)arg;
+ target = stack.pop();
} else {
- v = stack.pop();
- o = stack.peek();
- stack.push(v);
+ key = stack.pop();
+ target = stack.peek();
+ stack.push(key);
}
- Object ret = null;
- if (v == null) throw je("tried to get the null key from " + o);
- if (o == null) throw je("tried to get property \"" + v + "\" from the null object");
- if (o instanceof String || o instanceof Number || o instanceof Boolean) {
- ret = getFromPrimitive(o,v);
- stack.push(ret);
- break;
- } else if (o instanceof JS) {
- Trap t = null;
- if (o instanceof Trap.TrapScope && v.equals("cascade")) {
- t = ((Trap.TrapScope)o).t.next;
- while (t != null && t.f.numFormalArgs != 0) t = t.next;
- if (t == null) { v = ((Trap.TrapScope)o).t.name; o = ((Trap.TrapScope)o).t.trapee; }
-
- } else if (o instanceof JS) {
- if (o instanceof JSScope) {
- JSScope p = (JSScope)o; // search the scope-path for the trap
- t = p.getTrap(v);
- while (t == null && p.getParentScope() != null) { p = p.getParentScope(); t = p.getTrap(v); }
- } else {
- t = ((JS)o).getTrap(v);
- }
- while (t != null && t.f.numFormalArgs != 0) t = t.next; // get first read trap
- }
- if (t != null) {
- stack.push(new CallMarker(this));
- JSArray args = new JSArray();
- stack.push(args);
- f = t.f;
- scope = new Trap.TrapScope(f.parentScope, t, null);
- ((Trap.TrapScope)scope).cascadeHappened = true;
- pc = -1;
- break;
- }
- ret = ((JS)o).get(v);
- if (ret == JS.METHOD) ret = new Stub((JS)o, v);
+ 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();
+
+ 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) {
+ setupTrap(t,null,new TrapMarker(this,t,null));
+ pc--; // we increment later
+ } 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);
- break;
}
- throw je("tried to get property " + v + " from a " + o.getClass().getName());
+ break;
}
case CALL: case CALLMETHOD: {
- int numArgs = JS.toInt(arg);
- Object method = null;
- Object ret = null;
- Object object = stack.pop();
+ 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 method = null;
+ JS ret = null;
+ JS object = stack.pop();
if (op == CALLMETHOD) {
if (object == JS.METHOD) {
method = stack.pop();
object = stack.pop();
} else if (object == null) {
- Object name = stack.pop();
- stack.pop();
- throw new JSExn("function '"+name+"' not found");
+ method = stack.pop();
+ object = stack.pop();
+ throw new JSExn("function '"+JS.debugToString(method)+"' not found in " + object.getClass().getName());
} else {
stack.pop();
stack.pop();
}
}
- Object[] rest = numArgs > 3 ? new Object[numArgs - 3] : null;
- for(int i=numArgs - 1; i>2; i--) rest[i-3] = stack.pop();
- Object a2 = numArgs <= 2 ? null : stack.pop();
- Object a1 = numArgs <= 1 ? null : stack.pop();
- Object a0 = numArgs <= 0 ? null : stack.pop();
- if (object instanceof String || object instanceof Number || object instanceof Boolean) {
- ret = callMethodOnPrimitive(object, method, a0, a1, a2, null, numArgs);
-
- } else if (object instanceof JSFunction) {
- // FIXME: use something similar to call0/call1/call2 here
- JSArray arguments = new JSArray();
- for(int i=0; i<numArgs; i++) arguments.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
+ if (object instanceof JSFunction) {
stack.push(new CallMarker(this));
- stack.push(arguments);
+ stack.push(new JSArgs(a0,a1,a2,rest,numArgs,object));
f = (JSFunction)object;
- scope = new JSScope(f.parentScope);
+ scope = f.parentScope;
pc = -1;
break;
-
- } else if (object instanceof JS) {
+ } else {
JS c = (JS)object;
ret = method == null ? c.call(a0, a1, a2, rest, numArgs) : c.callMethod(method, a0, a1, a2, rest, numArgs);
-
- } else {
- throw new JSExn("can't call a " + object + " @" + pc + "\n" + f.dump());
-
}
+
if (pausecount > initialPauseCount) { pc++; return null; }
stack.push(ret);
break;
}
case THROW:
- throw new JSExn(stack.pop(), stack, f, pc, scope);
+ throw new JSExn(stack.pop(), this);
- /* FIXME
+ /* FIXME GRAMMAR
case MAKE_GRAMMAR: {
final Grammar r = (Grammar)arg;
final JSScope final_scope = scope;
}
*/
case ADD_TRAP: case DEL_TRAP: {
- Object val = stack.pop();
- Object key = stack.pop();
- Object obj = stack.peek();
+ JS val = stack.pop();
+ JS key = stack.pop();
+ JS js = stack.peek();
// A trap addition/removal
- JS js = obj instanceof JSScope ? ((JSScope)obj).top() : (JS) obj;
+ 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);
break;
}
- case ASSIGN_SUB: case ASSIGN_ADD: {
- Object val = stack.pop();
- Object key = stack.pop();
- Object 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 = ((Number)arg).intValue();
+ int count = ((JSNumber)arg).toInt();
if(count < 2) throw new Error("this should never happen");
if(count == 2) {
// common case
- Object right = stack.pop();
- Object left = stack.pop();
- if(left instanceof String || right instanceof String)
- stack.push(JS.toString(left).concat(JS.toString(right)));
- else stack.push(JS.N(JS.toDouble(left) + JS.toDouble(right)));
+ JS right = stack.pop();
+ JS left = stack.pop();
+ JS ret;
+ if(left instanceof JSString || right instanceof JSString)
+ ret = JS.S(JS.toString(left).concat(JS.toString(right)));
+ else if(left instanceof JSNumber.D || right instanceof JSNumber.D)
+ ret = JS.N(JS.toDouble(left) + JS.toDouble(right));
+ else {
+ long l = JS.toLong(left) + JS.toLong(right);
+ if(l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) ret = JS.N(l);
+ ret = JS.N((int)l);
+ }
+ stack.push(ret);
} else {
- Object[] args = new Object[count];
+ JS[] args = new JS[count];
while(--count >= 0) args[count] = stack.pop();
- if(args[0] instanceof String) {
+ if(args[0] instanceof JSString) {
StringBuffer sb = new StringBuffer(64);
for(int i=0;i<args.length;i++) sb.append(JS.toString(args[i]));
- stack.push(sb.toString());
+ stack.push(JS.S(sb.toString()));
} else {
int numStrings = 0;
- for(int i=0;i<args.length;i++) if(args[i] instanceof String) numStrings++;
+ for(int i=0;i<args.length;i++) if(args[i] instanceof JSString) numStrings++;
if(numStrings == 0) {
double d = 0.0;
for(int i=0;i<args.length;i++) d += JS.toDouble(args[i]);
} else {
int i=0;
StringBuffer sb = new StringBuffer(64);
- if(!(args[0] instanceof String || args[1] instanceof String)) {
+ if(!(args[0] instanceof JSString || args[1] instanceof JSString)) {
double d=0.0;
do {
d += JS.toDouble(args[i++]);
- } while(!(args[i] instanceof String));
+ } while(!(args[i] instanceof JSString));
sb.append(JS.toString(JS.N(d)));
}
while(i < args.length) sb.append(JS.toString(args[i++]));
- stack.push(sb.toString());
+ stack.push(JS.S(sb.toString()));
}
}
}
}
default: {
- Object right = stack.pop();
- Object left = stack.pop();
+ JS right = stack.pop();
+ JS left = stack.pop();
switch(op) {
case BITOR: stack.push(JS.N(JS.toLong(left) | JS.toLong(right))); break;
case RSH: stack.push(JS.N(JS.toLong(left) >> JS.toLong(right))); break;
case URSH: stack.push(JS.N(JS.toLong(left) >>> JS.toLong(right))); break;
- case LT: case LE: case GT: case GE: {
- if (left == null) left = JS.N(0);
- if (right == null) right = JS.N(0);
- int result = 0;
- if (left instanceof String || right instanceof String) {
- result = left.toString().compareTo(right.toString());
- } else {
- result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right));
- }
- stack.push(JS.B((op == LT && result < 0) || (op == LE && result <= 0) ||
- (op == GT && result > 0) || (op == GE && result >= 0)));
- break;
+ //#repeat </<=/>/>= LT/LE/GT/GE
+ case LT: {
+ if(left instanceof JSString && right instanceof JSString)
+ stack.push(JS.B(JS.toString(left).compareTo(JS.toString(right)) < 0));
+ else
+ stack.push(JS.B(JS.toDouble(left) < JS.toDouble(right)));
}
+ //#end
case EQ:
case NE: {
- Object l = left;
- Object r = right;
boolean ret;
- if (l == null) { Object tmp = r; r = l; l = tmp; }
- if (l == null && r == null) ret = true;
- else if (r == null) ret = false; // l != null, so its false
- else if (l instanceof Boolean) ret = JS.B(JS.toBoolean(r)).equals(l);
- else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue();
- else if (l instanceof String) ret = r != null && l.equals(r.toString());
- else ret = l.equals(r);
+ if(left == null && right == null) ret = true;
+ else if(left == null || right == null) ret = false;
+ else ret = left.jsequals(right);
stack.push(JS.B(op == EQ ? ret : !ret)); break;
}
}
} catch(JSExn e) {
- while(stack.size() > 0) {
- Object 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.empty()) {
+ 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;
+ }
+ void setupTrap(Trap t, JS val, CallMarker cm) throws JSExn {
+ stack.push(cm);
+ stack.push(new TrapArgs(t,val));
+ f = t.f;
+ scope = f.parentScope;
+ pc = 0;
+ }
// Markers //////////////////////////////////////////////////////////////////////
- public static class CallMarker {
- int pc;
- JSScope scope;
- JSFunction f;
- public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
+ 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 CallMarker extends Marker {
+ final int pc;
+ final JSScope scope;
+ final JSFunction f;
+ public CallMarker(Interpreter cx) {
+ pc = cx == null ? -1 : cx.pc + 1;
+ scope = cx == null ? null : cx.scope;
+ f = cx == null ? null : cx.f;
+ }
}
- public static class CatchMarker { }
+ static class TrapMarker extends CallMarker {
+ 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) {
+ super(cx);
+ this.t = t;
+ this.val = val;
+ this.pauseOnPut = pauseOnPut;
+ }
+ }
+
+ static class CatchMarker extends Marker { }
private static CatchMarker catchMarker = new CatchMarker();
- public static class LoopMarker {
- public int location;
- public String label;
- public JSScope scope;
+ static class LoopMarker extends Marker {
+ final public int location;
+ final public String label;
+ final public JSScope scope;
public LoopMarker(int location, String label, JSScope scope) {
this.location = location;
this.label = label;
this.scope = scope;
}
}
- public static class TryMarker {
- public int catchLoc;
- public int finallyLoc;
- public JSScope scope;
- public JSFunction f;
+ static class TryMarker extends Marker {
+ final public int catchLoc;
+ final public int finallyLoc;
+ final public JSScope scope;
+ final public JSFunction f;
public TryMarker(int catchLoc, int finallyLoc, Interpreter cx) {
this.catchLoc = catchLoc;
this.finallyLoc = finallyLoc;
this.f = cx.f;
}
}
- public static class FinallyData {
- public int op;
- public Object arg;
- public JSExn exn;
+ static class FinallyData extends Marker {
+ final public int op;
+ final public Object arg;
+ final public JSExn exn;
public FinallyData(int op) { this(op,null); }
- public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; }
- public FinallyData(JSExn exn) { this.exn = exn; } // Just throw this exn
+ public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; this.exn = null; }
+ public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn
}
-
- // Operations on Primitives //////////////////////////////////////////////////////////////////////
-
- 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);
- }
+ static class TrapArgs extends JS {
+ 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;
//#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 ? arg0.toString() : "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 ? arg0.toString() : "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));
+ return super.get(key);
}
- 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 = o.toString();
-
- // 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
+ 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;
}
- if (returnJS) {
- final Object target = o;
- final String method = key.toString();
- 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);
- }
- };
+
+ 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);
}
- return null;
}
- private static class Stub extends JS {
- private Object method;
+ static class Stub extends JS {
+ private JS method;
JS obj;
- public Stub(JS obj, Object method) { this.obj = obj; this.method = method; }
- public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+ 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);
}
}
+
+ static class Stack {
+ private static final int MAX_STACK_SIZE = 512;
+ private JS[] stack = new JS[64];
+ 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 swap() throws JSExn {
+ if(sp < 2) throw new JSExn("stack overflow");
+ JS tmp = stack[sp-2];
+ stack[sp-2] = stack[sp-1];
+ stack[sp-1] = tmp;
+ }
+ CallMarker findCall() {
+ for(int i=sp-1;i>=0;i--) if(stack[i] instanceof CallMarker) return (CallMarker) stack[i];
+ return null;
+ }
+ void grow() throws JSExn {
+ if(stack.length >= MAX_STACK_SIZE) throw new JSExn("Stack overflow");
+ JS[] stack2 = new JS[stack.length * 2];
+ System.arraycopy(stack,0,stack2,0,stack.length);
+ }
+
+ void backtrace(JSExn e) {
+ for(int i=sp-1;i>=0;i--) {
+ if (stack[i] instanceof CallMarker) {
+ CallMarker cm = (CallMarker)stack[i];
+ 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).t.key) + ")";
+ e.addBacktrace(s);
+ }
+ }
+ }
+ }
}
import java.util.*;
/** The minimum set of functionality required for objects which are manipulated by JavaScript */
-public class JS /*extends org.ibex.util.BalancedTree*/ implements Serializable {
+public abstract class JS {
+ public static final JS METHOD = new JS() { };
- public static final long serialVersionUID = 572634985343962922L;
- public static boolean checkAssertions = false;
-
- public static final Object METHOD = new Object();
- public final JS unclone() { return _unclone(); }
- 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 { (entries==null?entries=new Hash():entries).put(key,null,val); }
- 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) throws JSExn {
+ 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 Cloneable extends JS {
- public Object jsclone() throws JSExn {
- return new Clone(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 static class Clone extends JS.Cloneable {
- protected JS.Cloneable clonee = null;
- JS _unclone() { return clonee.unclone(); }
- public JS.Cloneable getClonee() { return clonee; }
- public Clone(JS.Cloneable clonee) { this.clonee = clonee; }
- public boolean equals(Object o) {
- if (!(o instanceof JS)) return false;
- return unclone() == ((JS)o).unclone();
+
+ public interface Cloneable { }
+
+ public static class Clone extends O {
+ protected final JS clonee;
+ public Clone(JS clonee) throws JSExn {
+ if(!(clonee instanceof Cloneable)) throw new JSExn("" + clonee.getClass().getName() + " isn't cloneable");
+ this.clonee = clonee;
}
+ JS _unclone() { return clonee.unclone(); }
+ boolean jsequals(JS o) { return clonee.jsequals(o); }
+
public Enumeration keys() throws JSExn { return clonee.keys(); }
- public Object get(Object key) throws JSExn { return clonee.get(key); }
- public void put(Object key, Object val) throws JSExn { clonee.put(key, val); }
- public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
- return clonee.callMethod(method, a0, a1, a2, rest, nargs);
- }
- public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+ 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 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 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 ///////////////////////////////////////////////////////////////
/** log a message with the current JavaScript sourceName/line */
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: case ByteCodes.CALL: 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(null); }
- public void unpause(Object o) throws JSExn {
- // FIXME: if o instanceof JSExn, throw it into the JSworld
- 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 JS unpause(JSExn e) throws JSExn {
+ i.catchException(e);
+ return i.resume();
}
}
// Static Helper Methods ///////////////////////////////////////////////////////////////////////////////////
/** coerce an object to a Boolean */
- public static boolean toBoolean(Object o) {
- if (o == null) return false;
- if (o instanceof Boolean) return ((Boolean)o).booleanValue();
- if (o instanceof Long) return ((Long)o).longValue() != 0;
- if (o instanceof Integer) return ((Integer)o).intValue() != 0;
- if (o instanceof Number) {
- double d = ((Number) o).doubleValue();
- // NOTE: d == d is a test for NaN. It should be faster than Double.isNaN()
- return d != 0.0 && d == d;
- }
- if (o instanceof String) return ((String)o).length() != 0;
+ 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;
}
-
- /** coerce an object to a Long */
- public static long toLong(Object o) { return toNumber(o).longValue(); }
-
- /** coerce an object to an Int */
- public static int toInt(Object o) { return toNumber(o).intValue(); }
-
- /** coerce an object to a Double */
- public static double toDouble(Object o) { return toNumber(o).doubleValue(); }
-
+ //#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 Number toNumber(Object o) {
- if (o == null) return ZERO;
- if (o instanceof Number) return ((Number)o);
-
- // 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 N((String)o); } catch (NumberFormatException e) { return N(Double.NaN); }
- if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? N(1) : ZERO;
- throw new Error("toNumber() got object of type " + o.getClass().getName() + " which we don't know how to handle");
+ 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");
}
-
- /** coerce an object to a String */
- public static String toString(Object o) {
+ //#end
+
+ public static String toString(JS o) throws JSExn {
if(o == null) return "null";
- if(o instanceof String) return (String) o;
- if(o instanceof Integer || o instanceof Long || o instanceof Boolean) return o.toString();
- if(o instanceof JSArray) return o.toString();
- if(o instanceof JSDate) return o.toString();
- if(o instanceof Double || o instanceof Float) {
- double d = ((Number)o).doubleValue();
- if((int)d == d) return Integer.toString((int)d);
- return o.toString();
+ 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 JS) return ((JS)o).coerceToString(); // HACK for now, this will probably go away
- throw new RuntimeException("can't coerce "+o+" [" + o.getClass().getName() + "] to type String.");
+ 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 String coerceToString() {
- throw new RuntimeException("can't coerce "+this+" [" + getClass().getName() + "] to type String.");
+
+ public static boolean isString(JS o) {
+ if(o instanceof JSString) return true;
+ return false;
}
-
+
// Instance Methods ////////////////////////////////////////////////////////////////////
-
- public static final Integer ZERO = new Integer(0);
- // this gets around a wierd fluke in the Java type checking rules for ?..:
- public static final Object T = Boolean.TRUE;
- public static final Object F = Boolean.FALSE;
-
- 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;
+ 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[] 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 < smallIntCache.length && idx > 0) {
+ if (idx < CACHE_SIZE && 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);
+ 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 % largeIntCache.length)] = ret;
+ else largeIntCache[Math.abs(idx % CACHE_SIZE)] = ret;
}
return ret;
}
- private static Enumeration emptyEnumeration = new Enumeration() {
- public boolean hasMoreElements() { return false; }
- public Object nextElement() { throw new NoSuchElementException(); }
- };
+ private static Enumeration EMPTY_ENUMERATION = new EmptyEnumeration(null);
- private Hash entries = null;
-
public static JS fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
- return JSFunction._fromReader(sourceName, firstLine, sourceCode);
+ return Parser.fromReader(sourceName, firstLine, sourceCode);
}
- // HACK: caller can't know if the argument is a JSFunction or not...
- public static JS cloneWithNewParentScope(JS j, JSScope s) {
- return ((JSFunction)j)._cloneWithNewParentScope(s);
- }
-
- // HACK
- public static Vec getFormalArgs(JS j) { return ((JSFunction)j).formalArgs; }
-
- // HACK
- public static JSScope getParentScope(JS j) { return ((JSFunction)j).parentScope; }
-
- public static Object eval(JS j) throws JSExn {
- Interpreter cx = new Interpreter((JSFunction)j, false, new JSArray(), false);
- return cx.resume();
+ public static JS cloneWithNewGlobalScope(JS js, JS s) {
+ if(js instanceof JSFunction)
+ return ((JSFunction)js)._cloneWithNewParentScope(new JSScope.Top(s));
+ else
+ return js;
}
// Trap support //////////////////////////////////////////////////////////////////////////////
- /** 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 true; }
-
/** performs a put, triggering traps if present; traps are run in an unpauseable interpreter */
- public void putAndTriggerTraps(Object key, Object value) throws JSExn {
+ public final void putAndTriggerTraps(JS key, JS value) throws JSExn {
Trap t = getTrap(key);
- if (t != null) t.invoke(value);
- else put(key, value);
+ if(t == null || (t = t.writeTrap()) == null) put(key,value);
+ else new Interpreter(t,value,false).resume();
}
/** performs a get, triggering traps if present; traps are run in an unpauseable interpreter */
- public Object getAndTriggerTraps(Object key) throws JSExn {
+ public final JS getAndTriggerTraps(JS key) throws JSExn {
Trap t = getTrap(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 entries == null ? null : (Trap)entries.get(key, Trap.class);
+ if (t == null || (t = t.readTrap()) == null) return get(key);
+ else return new Interpreter(t,null,false).resume();
}
-
- /** retrieve a trap from the entries hash */
- protected final void putTrap(Object key, Trap value) {
- if (entries == null) entries = new Hash();
- entries.put(key, Trap.class, value);
+
+ 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();
}
/** adds a trap, avoiding duplicates */
- protected final void addTrap(Object name, JSFunction f) throws JSExn {
+ // 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;
- 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)));
+ 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)));
}
/** deletes a trap, if present */
- protected final void delTrap(Object name, JSFunction f) {
- Trap t = (Trap)getTrap(name);
+ // 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.name, t.next); 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; }
}
import java.util.*;
/** A JavaScript JSArray */
-public class JSArray extends JS {
-
+public class JSArray extends JS.O {
private static final Object NULL = new Object();
- private Vec vec = new Vec();
-
+ private final BalancedTree bt = new BalancedTree();
+
public JSArray() { }
public JSArray(int size) { setSize(size); }
- private static int intVal(Object o) {
+ /*private static int intVal(Object o) {
if (o instanceof Number) {
int intVal = ((Number)o).intValue();
if (intVal == ((Number)o).doubleValue()) return intVal;
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 Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
- //#switch(method)
+ 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 super.callMethod(method, a0, a1, a2, rest, nargs);
}
- public Object get(Object key) throws JSExn {
- int i = intVal(key);
- if (i != Integer.MIN_VALUE) {
+ 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(key)
+ //#switch(JS.toString(key))
case "pop": return METHOD;
case "reverse": return METHOD;
case "toString": return METHOD;
return super.get(key);
}
- public void put(Object key, Object val) throws JSExn {
- if (key.equals("length")) setSize(toInt(val));
- int i = intVal(key);
- if (i == Integer.MIN_VALUE)
- super.put(key, val);
- else {
+ 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);
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);
}
- public Enumeration keys() {
- return new Enumeration() {
- private int n = size();
- public boolean hasMoreElements() { return n > 0; }
- public Object nextElement() {
- if(n == 0) throw new NoSuchElementException();
- return new Integer(--n);
+ 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 final int length() { return size(); }
- public final Object elementAt(int i) {
+ public final JS elementAt(int i) {
if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
- Object o = vec.elementAt(i);
- return o == NULL ? null : o;
+ Object o = bt.getNode(i);
+ return o == NULL ? (JS)null : (JS)o;
}
- public final void addElement(Object o) {
- vec.insertElementAt(o==null ? NULL : o, size());
+ public final void addElement(JS o) {
+ bt.insertNode(size(),o==null ? NULL : o);
}
- public final void setElementAt(Object o, int i) {
+ public final void setElementAt(JS o, int i) {
if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
- vec.setElementAt(o==null ? NULL : o,i);
+ bt.replaceNode(i,o==null ? NULL : o);
}
- public final void insertElementAt(Object o, int i) {
+ public final void insertElementAt(JS o, int i) {
if(i < 0 || i > size()) throw new ArrayIndexOutOfBoundsException(i);
- vec.insertElementAt(o==null ? NULL : o, i);
+ bt.insertNode(i,o==null ? NULL : o);
}
- public final Object removeElementAt(int i) {
+ public final JS removeElementAt(int i) {
if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
- Object o = vec.elementAt(i);
- vec.removeElementAt(i);
- return o == NULL ? null : o;
+ Object o = bt.deleteNode(i);
+ return o == NULL ? (JS)null : (JS)o;
}
- public final int size() { return vec.size(); }
- public String typeName() { return "array"; }
-
- private Object join(String sep) {
+ public final int size() { return bt.treeSize(); }
+
+ private JS join(String sep) throws JSExn {
int length = size();
- if(length == 0) return "";
+ if(length == 0) return JS.S("");
StringBuffer sb = new StringBuffer(64);
int i=0;
while(true) {
- Object o = elementAt(i);
+ JS o = elementAt(i);
if(o != null) sb.append(JS.toString(o));
if(++i == length) break;
sb.append(sep);
}
- return sb.toString();
+ return JS.S(sb.toString());
}
// FEATURE: Implement this more efficiently
- private Object reverse() {
+ private JS reverse() {
int size = size();
if(size < 2) return this;
Vec vec = toVec();
- vec.removeAllElements();
- for(int i=size-1,j=0;i>=0;i--,j++) insertElementAt(vec.elementAt(i),j);
+ bt.clear();
+ for(int i=size-1,j=0;i>=0;i--,j++) insertElementAt((JS)vec.elementAt(i),j);
return this;
}
- private Object slice(int start, int end) {
+ private JS slice(int start, int end) {
int length = length();
if(start < 0) start = length+start;
if(end < 0) end = length+end;
a.setElementAt(elementAt(start+i),i);
return a;
}
-
+
private static final Vec.CompareFunc defaultSort = new Vec.CompareFunc() {
public int compare(Object a, Object b) {
- return JS.toString(a).compareTo(JS.toString(b));
+ try {
+ return JS.toString((JS)a).compareTo(JS.toString((JS)b));
+ } catch(JSExn e) { throw new JSExn.Wrapper(e); }
}
};
- private Object sort(Object tmp) throws JSExn {
+ private JS sort(JS tmp) throws JSExn {
Vec vec = toVec();
- if(tmp instanceof JS) {
- final JSArray funcArgs = new JSArray(2);
- final JS jsFunc = (JS) tmp;
- vec.sort(new Vec.CompareFunc() {
- public int compare(Object a, Object b) {
- try {
- funcArgs.setElementAt(a,0);
- funcArgs.setElementAt(b,1);
- return JS.toInt(jsFunc.call(a, b, null, null, 2));
- } catch (Exception e) {
- // FIXME
- throw new JSRuntimeExn(e.toString());
+ 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);
+ });
+ } else {
+ vec.sort(defaultSort);
+ }
+ } catch(JSExn.Wrapper e) {
+ throw e.refill();
}
setFromVec(vec);
return this;
}
- private Object splice(JSArray args) {
+ 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));
return ret;
}
- public Vec toVec() { return (Vec)vec.clone(); }
- public void setFromVec(Vec vec) { this.vec = (Vec)vec.clone(); }
- public String toString() { return JS.toString(join(",")); }
+ 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;
+ }
+
+ 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);
+ }
+ }
+
+ String coerceToString() throws JSExn { return JS.toString(join(",")); }
}
}
}
- public JSDate(long now) {
- if (thisTimeZone == null) {
- // j.u.TimeZone is synchronized, so setting class statics from it
- // should be OK.
- thisTimeZone = java.util.TimeZone.getDefault();
- LocalTZA = thisTimeZone.getRawOffset();
- }
- date = (double)now;
- }
-
- public String toString() { return date_format(date, FORMATSPEC_FULL); }
+ String coerceToString() { return date_format(date, FORMATSPEC_FULL); }
- public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+ public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
switch(nargs) {
case 0: {
- //#switch(method)
- case "toString": return date_format(date, FORMATSPEC_FULL);
- case "toTimeString": return date_format(date, FORMATSPEC_TIME);
- case "toDateString": return date_format(date, FORMATSPEC_DATE);
- case "toLocaleString": return toLocaleString(date);
- case "toLocaleTimeString": return toLocaleTimeString(date);
- case "toLocaleDateString": return toLocaleDateString(date);
- case "toUTCString": return toUTCString(date);
+ //#switch(JS.toString(method))
+ case "toString": return JS.S(date_format(date, FORMATSPEC_FULL));
+ case "toTimeString": return JS.S(date_format(date, FORMATSPEC_TIME));
+ case "toDateString": return JS.S(date_format(date, FORMATSPEC_DATE));
+ case "toLocaleString": return JS.S(toLocaleString(date));
+ case "toLocaleTimeString": return JS.S(toLocaleTimeString(date));
+ case "toLocaleDateString": return JS.S(toLocaleDateString(date));
+ case "toUTCString": return JS.S(toUTCString(date));
case "valueOf": return N(this.date);
case "getTime": return N(this.date);
case "getYear": return N(getYear(date));
return super.callMethod(method, a0, a1, a2, rest, nargs);
}
case 1: {
- //#switch(method)
+ //#switch(JS.toString(method))
case "setTime": return N(this.setTime(toDouble(a0)));
case "setYear": return N(this.setYear(toDouble(a0)));
//#end
// fall through
}
default: {
- Object[] args = new Object[nargs];
+ JS[] args = new JS[nargs];
for(int i=0; i<nargs; i++) args[i] = i==0 ? a0 : i==1 ? a1 : i==2 ? a2 : rest[i-3];
- //#switch(method)
+ //#switch(JS.toString(method))
case "setMilliseconds": return N(this.makeTime(args, 1, true));
case "setUTCMilliseconds": return N(this.makeTime(args, 1, false));
case "setSeconds": return N(this.makeTime(args, 2, true));
return super.callMethod(method, a0, a1, a2, rest, nargs);
}
- public Object get(Object key) throws JSExn {
- //#switch(key)
+ public JS get(JS key) throws JSExn {
+ //#switch(JS.toString(key))
case "toString": return METHOD;
case "toTimeString": return METHOD;
case "toDateString": return METHOD;
private static final int MAXARGS = 7;
- private static double jsStaticJSFunction_UTC(Object[] args) {
+ private static double jsStaticJSFunction_UTC(JS[] args) throws JSExn {
double array[] = new double[MAXARGS];
int loop;
double d;
return result.toString();
}
- private static double _toNumber(Object o) { return JS.toDouble(o); }
- private static double _toNumber(Object[] o, int index) { return JS.toDouble(o[index]); }
+ private static double _toNumber(JS o) throws JSExn { return JS.toDouble(o); }
+ 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(Object a0, Object a1, Object a2, Object[] rest, int nargs) {
+ public JSDate(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
JSDate obj = this;
switch (nargs) {
}
case 1: {
double date;
- if (a0 instanceof JS)
- a0 = ((JS) a0).toString();
- if (!(a0 instanceof String)) {
- // if it's not a string, use it as a millisecond date
+ if(isString(a0))
+ date = date_parseString(JS.toString(a0));
+ else
date = _toNumber(a0);
- } else {
- // it's a string; parse it.
- String str = (String) a0;
- date = date_parseString(str);
- }
obj.date = TimeClip(date);
return;
}
return this.date;
}
- private double makeTime(Object[] args, int maxargs, boolean local) {
+ private double makeTime(JS[] args, int maxargs, boolean local) throws JSExn {
int i;
double conv[] = new double[4];
double hour, min, sec, msec;
* d.setMilliseconds()" returns NaN. Blech.
*/
if (args.length == 0)
- args = new Object[] { null };
+ args = new JS[] { null };
for (i = 0; i < args.length && i < maxargs; i++) {
conv[i] = _toNumber(args[i]);
return date;
}
- private double setHours(Object[] args) {
+ private double setHours(JS[] args) throws JSExn {
return makeTime(args, 4, true);
}
- private double setUTCHours(Object[] args) {
+ private double setUTCHours(JS[] args) throws JSExn {
return makeTime(args, 4, false);
}
- private double makeDate(Object[] args, int maxargs, boolean local) {
+ private double makeDate(JS[] args, int maxargs, boolean local) throws JSExn {
int i;
double conv[] = new double[3];
double year, month, day;
/* See arg padding comment in makeTime.*/
if (args.length == 0)
- args = new Object[] { null };
+ args = new JS[] { null };
for (i = 0; i < args.length && i < maxargs; i++) {
conv[i] = _toNumber(args[i]);
/** An exception which can be thrown and caught by JavaScript code */
public class JSExn extends Exception {
private Vec backtrace = new Vec();
- private Object js = null;
- public JSExn(Object js) {
- this.js = js;
- if (Interpreter.current() != null)
- fill(Interpreter.current().stack, Interpreter.current().f, Interpreter.current().pc, Interpreter.current().scope);
- }
- public JSExn(Object js, Vec stack, JSFunction f, int pc, JSScope scope) { this.js = js; fill(stack, f, pc, scope); }
- private void fill(Vec stack, JSFunction f, int pc, JSScope scope) {
- addBacktrace(f.sourceName + ":" + f.line[pc]);
- 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--) {
- Object 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 + "\"");
- }
- }
+ private JS js;
+ public JSExn(String s) { this(JS.S(s)); }
+ public JSExn(JS js) { this(js,null); }
+ public JSExn(JS js, Interpreter cx) { this.js = js; fill(cx); }
+
+ private void fill(Interpreter cx) {
+ if(cx == null) cx = Interpreter.current();
+ if(cx == null) return;
+ addBacktrace(cx.f.sourceName + ":" + cx.f.line[cx.pc]);
+ cx.stack.backtrace(this);
}
public void printStackTrace() { printStackTrace(System.err); }
public void printStackTrace(PrintWriter pw) {
for(int i=0; i<backtrace.size(); i++) ps.println(" at " + (String) backtrace.elementAt(i));
super.printStackTrace(ps);
}
- public String toString() { return "JSExn: " + js; }
+ public String toString() { return "JSExn: " + JS.debugToString(js); }
public String getMessage() { return toString(); }
- public Object getObject() { return js; }
- public void addBacktrace(String line) { backtrace.addElement(line); }
-
-
+ public JS getObject() { return js; }
+
+ void addBacktrace(String line) { backtrace.addElement(line); }
+
+ public static class Wrapper extends RuntimeException {
+ public final JSExn e;
+ public Wrapper(JSExn e) { this.e = e; }
+ public JSExn refill() {
+ 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());
import org.ibex.util.*;
/** A JavaScript function, compiled into bytecode */
-class JSFunction extends JS implements ByteCodes, Tokens, Task {
+// 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 {
// Fields and Accessors ///////////////////////////////////////////////
int size = 0; ///< the number of instruction/argument pairs
JSScope parentScope; ///< the default scope to use as a parent scope when executing this
- Vec formalArgs = new Vec();
+
// 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 JSArray());
+ Interpreter i = new Interpreter(this, true, new Interpreter.JSArgs(this));
i.resume();
}
- /** parse and compile a function */
- public static JSFunction _fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
- JSFunction ret = new JSFunction(sourceName, firstLine, null);
- if (sourceCode == null) return ret;
- Parser p = new Parser(sourceCode, sourceName, firstLine);
- while(true) {
- int s = ret.size;
- p.parseStatement(ret, null);
- if (s == ret.size) break;
- }
- ret.add(-1, LITERAL, null);
- ret.add(-1, RETURN);
- return ret;
- }
-
public JSFunction _cloneWithNewParentScope(JSScope s) {
JSFunction ret = new JSFunction(sourceName, firstLine, s);
// Reuse the same op, arg, line, and size variables for the new "instance" of the function
ret.arg = this.arg;
ret.line = this.line;
ret.size = this.size;
- ret.formalArgs = this.formalArgs;
ret.numFormalArgs = this.numFormalArgs;
return ret;
}
/** Note: code gets run in an <i>unpauseable</i> context. */
- public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
- JSArray args = new JSArray();
- if (nargs > 0) args.addElement(a0);
- if (nargs > 1) args.addElement(a1);
- if (nargs > 2) args.addElement(a2);
- for(int i=3; i<nargs; i++) args.addElement(rest[i-3]);
- Interpreter cx = new Interpreter(this, false, args);
+ 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();
}
// Debugging //////////////////////////////////////////////////////////////////////
- public String toString() { return "JSFunction [" + sourceName + ":" + firstLine + "]"; }
+ String extendedToString() { return "[" + sourceName + ":" + firstLine + "]"; }
- public String dump() {
+ String dump() { return dump(""); }
+ private String dump(String prefix) {
StringBuffer sb = new StringBuffer(1024);
sb.append("\n" + sourceName + ": " + firstLine + "\n");
for (int i=0; i < size; i++) {
- sb.append(i).append(" (").append(line[i]).append(") :");
+ sb.append(prefix);
+ sb.append(i).append(" (").append(line[i]).append("): ");
if (op[i] < 0) sb.append(bytecodeToString[-op[i]]);
else sb.append(codeToString[op[i]]);
sb.append(" ");
- sb.append(arg[i] == null ? "(no arg)" : arg[i]);
+ sb.append(arg[i] == null ? "(no arg)" : arg[i] instanceof JS ? JS.debugToString((JS)arg[i]) : arg[i]);
if((op[i] == JF || op[i] == JT || op[i] == JMP) && arg[i] != null && arg[i] instanceof Number) {
sb.append(" jump to ").append(i+((Number) arg[i]).intValue());
} else if(op[i] == TRY) {
int[] jmps = (int[]) arg[i];
sb.append(" catch: ").append(jmps[0] < 0 ? "No catch block" : ""+(i+jmps[0]));
sb.append(" finally: ").append(jmps[1] < 0 ? "No finally block" : ""+(i+jmps[1]));
+ } else if(op[i] == NEWFUNCTION) {
+ sb.append(((JSFunction) arg[i]).dump(prefix + " "));
+ } else if(op[i] == NEWSCOPE) {
+ int n = ((JSNumber)arg[i]).toInt();
+ sb.append(" base: " + (n>>>16) + " size: " + (n&0xffff));
}
sb.append("\n");
}
package org.ibex.js;
/** The JavaScript Math object */
-public class JSMath extends JS {
+class JSMath extends JS {
+ private static final JS E = JS.N(java.lang.Math.E);
+ private static final JS PI = JS.N(java.lang.Math.PI);
+ private static final JS LN10 = JS.N(java.lang.Math.log(10));
+ private static final JS LN2 = JS.N(java.lang.Math.log(2));
+ private static final JS LOG10E = JS.N(1/java.lang.Math.log(10));
+ private static final JS LOG2E = JS.N(1/java.lang.Math.log(2));
+ private static final JS SQRT1_2 = JS.N(1/java.lang.Math.sqrt(2));
+ private static final JS SQRT2 = JS.N(java.lang.Math.sqrt(2));
- public static JSMath singleton = new JSMath();
-
- private static final Double E = new Double(java.lang.Math.E);
- private static final Double PI = new Double(java.lang.Math.PI);
- private static final Double LN10 = new Double(java.lang.Math.log(10));
- private static final Double LN2 = new Double(java.lang.Math.log(2));
- private static final Double LOG10E = new Double(1/java.lang.Math.log(10));
- private static final Double LOG2E = new Double(1/java.lang.Math.log(2));
- private static final Double SQRT1_2 = new Double(1/java.lang.Math.sqrt(2));
- private static final Double SQRT2 = new Double(java.lang.Math.sqrt(2));
-
- public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+ public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
switch(nargs) {
case 0: {
- //#switch(method)
- case "random": return new Double(java.lang.Math.random());
+ //#switch(JS.toString(method))
+ case "random": return JS.N(java.lang.Math.random());
//#end
break;
}
case 1: {
- //#switch(method)
- case "ceil": return new Long((long)java.lang.Math.ceil(toDouble(a0)));
- case "floor": return new Long((long)java.lang.Math.floor(toDouble(a0)));
- case "round": return new Long((long)java.lang.Math.round(toDouble(a0)));
- case "abs": return new Double(java.lang.Math.abs(toDouble(a0)));
- case "sin": return new Double(java.lang.Math.sin(toDouble(a0)));
- case "cos": return new Double(java.lang.Math.cos(toDouble(a0)));
- case "tan": return new Double(java.lang.Math.tan(toDouble(a0)));
- case "asin": return new Double(java.lang.Math.asin(toDouble(a0)));
- case "acos": return new Double(java.lang.Math.acos(toDouble(a0)));
- case "atan": return new Double(java.lang.Math.atan(toDouble(a0)));
- case "sqrt": return new Double(java.lang.Math.sqrt(toDouble(a0)));
- case "exp": return new Double(java.lang.Math.exp(toDouble(a0)));
- case "log": return new Double(java.lang.Math.log(toDouble(a0)));
+ //#switch(JS.toString(method))
+ case "ceil": return JS.N((long)java.lang.Math.ceil(toDouble(a0)));
+ case "floor": return JS.N((long)java.lang.Math.floor(toDouble(a0)));
+ case "round": return JS.N((long)java.lang.Math.round(toDouble(a0)));
+ case "abs": return JS.N(java.lang.Math.abs(toDouble(a0)));
+ case "sin": return JS.N(java.lang.Math.sin(toDouble(a0)));
+ case "cos": return JS.N(java.lang.Math.cos(toDouble(a0)));
+ case "tan": return JS.N(java.lang.Math.tan(toDouble(a0)));
+ case "asin": return JS.N(java.lang.Math.asin(toDouble(a0)));
+ case "acos": return JS.N(java.lang.Math.acos(toDouble(a0)));
+ case "atan": return JS.N(java.lang.Math.atan(toDouble(a0)));
+ case "sqrt": return JS.N(java.lang.Math.sqrt(toDouble(a0)));
+ case "exp": return JS.N(java.lang.Math.exp(toDouble(a0)));
+ case "log": return JS.N(java.lang.Math.log(toDouble(a0)));
//#end
break;
}
case 2: {
- //#switch(method)
- case "min": return new Double(java.lang.Math.min(toDouble(a0), toDouble(a1)));
- case "max": return new Double(java.lang.Math.max(toDouble(a0), toDouble(a1)));
- case "pow": return new Double(java.lang.Math.pow(toDouble(a0), toDouble(a1)));
- case "atan2": return new Double(java.lang.Math.atan2(toDouble(a0), toDouble(a1)));
+ //#switch(JS.toString(method))
+ case "min": return JS.N(java.lang.Math.min(toDouble(a0), toDouble(a1)));
+ case "max": return JS.N(java.lang.Math.max(toDouble(a0), toDouble(a1)));
+ case "pow": return JS.N(java.lang.Math.pow(toDouble(a0), toDouble(a1)));
+ case "atan2": return JS.N(java.lang.Math.atan2(toDouble(a0), toDouble(a1)));
//#end
break;
}
return super.callMethod(method, a0, a1, a2, rest, nargs);
}
- public void put(Object key, Object val) { }
-
- public Object get(Object key) throws JSExn {
- //#switch(key)
+ public JS get(JS key) throws JSExn {
+ //#switch(JS.toString(key))
case "E": return E;
case "LN10": return LN10;
case "LN2": return LN2;
/** Automatic JS-ification via Reflection (not for use in the core) */
public class JSReflection extends JS {
- public static Object wrap(Object o) throws JSExn {
+ public static JS wrap(Object o) throws JSExn {
if (o == null) return null;
- if (o instanceof String) return o;
- if (o instanceof Boolean) return o;
- if (o instanceof Number) return o;
- if (o instanceof JS) return o;
+ if (o instanceof String) return JS.S((String)o);
+ if (o instanceof Boolean) return JS.B(((Boolean)o).booleanValue());
+ if (o instanceof Number) return JS.N((Number)o);
+ if (o instanceof JS) return (JS)o;
if (o instanceof Object[]) {
// FIXME: get element type here
}
public static class Array extends JS {
final Object[] arr;
public Array(Object[] arr) { this.arr = arr; }
- public Enumeration keys() throws JSExn { return new CounterEnumeration(arr.length); }
- public Object get(Object key) throws JSExn { return wrap(arr[toInt(key)]); }
- public void put(Object key, Object val) throws JSExn { throw new JSExn("can't write to org.ibex.js.Reflection.Array's"); }
+ // FEATURE: Add a JSCounterEnumeration
+ public Enumeration keys() throws JSExn {
+ return new Enumeration(null) {
+ private int n = 0;
+ public boolean _hasMoreElements() { return n < arr.length; }
+ public JS _nextElement() {
+ return n >= arr.length ? null : JS.N(n++);
+ }
+ };
+ }
+ public JS get(JS key) throws JSExn { return wrap(arr[toInt(key)]); }
+ public void put(JS key, JS val) throws JSExn { throw new JSExn("can't write to org.ibex.js.Reflection.Array's"); }
}
// FIXME public static class Hash { }
// FIXME public Enumeration keys() throws JSExn { }
- public Object get(Object key) throws JSExn {
+ public JS get(JS key) throws JSExn {
String k = toString(key);
try {
Field f = this.getClass().getField(k);
} catch (NoSuchFieldException nfe) {
} catch (IllegalAccessException nfe) {
} catch (SecurityException nfe) { }
+
try {
Method[] methods = this.getClass().getMethods();
for(int i=0; i<methods.length; i++) if (methods[i].getName().equals(k)) return METHOD;
return null;
}
- public void put(Object key, Object val) throws JSExn {
+ public void put(JS key, JS val) throws JSExn {
throw new JSExn("put() not supported yet");
}
- public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+ public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
String k = toString(method);
try {
Method[] methods = this.getClass().getMethods();
throw new JSExn("unhandled reflected exception: " + ite.toString());
} catch (SecurityException nfe) { }
throw new JSExn("called a reflection method with the wrong number of arguments");
- }
+ }
}
private boolean global;
private GnuRegexp.RE re;
private int lastIndex;
+
+ private JS pattern;
+ private int flags;
- public JSRegexp(Object arg0, Object arg1) throws JSExn {
+ public JSRegexp(JS arg0, JS arg1) throws JSExn {
if(arg0 instanceof JSRegexp) {
JSRegexp r = (JSRegexp) arg0;
this.global = r.global;
this.re = r.re;
this.lastIndex = r.lastIndex;
+ this.pattern = pattern;
+ this.flags = flags;
} else {
- String pattern = (String)arg0;
+ String pattern = JS.toString(arg0);
String sFlags = null;
int flags = 0;
- if(arg1 != null) sFlags = (String)arg1;
+ if(arg1 != null) sFlags = JS.toString(arg1);
if(sFlags == null) sFlags = "";
for(int i=0;i<sFlags.length();i++) {
switch(sFlags.charAt(i)) {
}
}
re = newRE(pattern,flags);
- put("source", pattern);
- put("global", B(global));
- put("ignoreCase", B(flags & GnuRegexp.RE.REG_ICASE));
- put("multiline", B(flags & GnuRegexp.RE.REG_MULTILINE));
+ this.pattern = JS.S(pattern);
+ this.flags = flags;
}
}
- public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+ public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
switch(nargs) {
case 1: {
- //#switch(method)
+ //#switch(JS.toString(method))
case "exec": {
- String s = (String)a0;
+ String s = JS.toString(a0);
int start = global ? lastIndex : 0;
if(start < 0 || start >= s.length()) { lastIndex = 0; return null; }
GnuRegexp.REMatch match = re.getMatch(s,start);
return match == null ? null : matchToExecResult(match,re,s);
}
case "test": {
- String s = (String)a0;
+ String s = JS.toString(a0);
if (!global) return B(re.getMatch(s) != null);
int start = global ? lastIndex : 0;
if(start < 0 || start >= s.length()) { lastIndex = 0; return null; }
lastIndex = match != null ? s.length() : match.getEndIndex();
return B(match != null);
}
- case "toString": return toString(a0);
+ case "toString": return JS.S(a0.coerceToString());
case "stringMatch": return stringMatch(a0,a1);
case "stringSearch": return stringSearch(a0,a1);
//#end
break;
}
case 2: {
- //#switch(method)
+ //#switch(JS.toString(method))
case "stringReplace": return stringReplace(a0, a1,a2);
//#end
break;
return super.callMethod(method, a0, a1, a2, rest, nargs);
}
- public Object get(Object key) throws JSExn {
- //#switch(key)
+ public JS get(JS key) throws JSExn {
+ //#switch(JS.toString(key))
case "exec": return METHOD;
case "test": return METHOD;
case "toString": return METHOD;
case "lastIndex": return 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);
//#end
return super.get(key);
}
- public void put(Object key, Object value) throws JSExn {
- if(key.equals("lastIndex")) lastIndex = JS.toNumber(value).intValue();
+ public void put(JS key, JS value) throws JSExn {
+ if(JS.isString(key)) {
+ if(JS.toString(key).equals("lastIndex")) {
+ lastIndex = JS.toInt(value);
+ return;
+ }
+ }
super.put(key,value);
}
- private static Object matchToExecResult(GnuRegexp.REMatch match, GnuRegexp.RE re, String s) {
+ private static JS matchToExecResult(GnuRegexp.REMatch match, GnuRegexp.RE re, String s) {
try {
- JS ret = new JS();
- ret.put("index", N(match.getStartIndex()));
- ret.put("input",s);
+ JS ret = new JS.O();
+ ret.put(JS.S("index"), N(match.getStartIndex()));
+ ret.put(JS.S("input"),JS.S(s));
int n = re.getNumSubs();
- ret.put("length", N(n+1));
- ret.put("0",match.toString());
- for(int i=1;i<=n;i++) ret.put(Integer.toString(i),match.toString(i));
+ ret.put(JS.S("length"), 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;
} catch (JSExn e) {
throw new Error("this should never happen");
}
}
- public String toString() {
- try {
- StringBuffer sb = new StringBuffer();
- sb.append('/');
- sb.append(get("source"));
- sb.append('/');
- if(global) sb.append('g');
- if(Boolean.TRUE.equals(get("ignoreCase"))) sb.append('i');
- if(Boolean.TRUE.equals(get("multiline"))) sb.append('m');
- return sb.toString();
- } catch (JSExn e) {
- throw new Error("this should never happen");
- }
+ String coerceToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append('/');
+ sb.append(pattern);
+ sb.append('/');
+ if(global) sb.append('g');
+ if((flags & GnuRegexp.RE.REG_ICASE) != 0) sb.append('i');
+ if((flags & GnuRegexp.RE.REG_MULTILINE) != 0) sb.append('m');
+ return sb.toString();
}
- public static Object stringMatch(Object o, Object arg0) throws JSExn {
- String s = o.toString();
+ static JS stringMatch(JS o, JS arg0) throws JSExn {
+ String s = JS.toString(o);
GnuRegexp.RE re;
JSRegexp regexp = null;
if(arg0 instanceof JSRegexp) {
regexp = (JSRegexp) arg0;
re = regexp.re;
} else {
- re = newRE(arg0.toString(),0);
+ re = newRE(JS.toString(arg0),0);
}
if(regexp == null) {
GnuRegexp.REMatch match = re.getMatch(s);
return matchToExecResult(match,re,s);
}
- if(!regexp.global) return regexp.callMethod("exec", s, null, null, null, 1);
+ if(!regexp.global) return regexp.callMethod(JS.S("exec"), o, null, null, null, 1);
JSArray ret = new JSArray();
GnuRegexp.REMatch[] matches = re.getAllMatches(s);
- for(int i=0;i<matches.length;i++) ret.addElement(matches[i].toString());
+ for(int i=0;i<matches.length;i++) ret.addElement(JS.S(matches[i].toString()));
regexp.lastIndex = matches.length > 0 ? matches[matches.length-1].getEndIndex() : s.length();
return ret;
}
- public static Object stringSearch(Object o, Object arg0) throws JSExn {
- String s = o.toString();
- GnuRegexp.RE re = arg0 instanceof JSRegexp ? ((JSRegexp)arg0).re : newRE(arg0.toString(),0);
+ static JS stringSearch(JS o, JS arg0) throws JSExn {
+ 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());
}
- public static Object stringReplace(Object o, Object arg0, Object arg1) throws JSExn {
- String s = o.toString();
+ static JS stringReplace(JS o, JS arg0, JS arg1) throws JSExn {
+ String s = JS.toString(o);
GnuRegexp.RE re;
JSFunction replaceFunc = null;
String replaceString = null;
if(arg1 instanceof JSFunction)
replaceFunc = (JSFunction) arg1;
else
- replaceString = JS.toString(arg1.toString());
+ replaceString = JS.toString(arg1);
GnuRegexp.REMatch[] matches;
if(regexp != null && regexp.global) {
matches = re.getAllMatches(s);
if(replaceFunc != null) {
int n = (regexp == null ? 0 : re.getNumSubs());
int numArgs = 3 + n;
- Object[] rest = new Object[numArgs - 3];
- Object a0 = match.toString();
- Object a1 = null;
- Object a2 = null;
+ 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 = match.toString(j); break;
- case 2: a2 = match.toString(j); break;
- default: rest[j - 3] = match.toString(j); break;
+ 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 = s;
+ a2 = JS.S(s);
break;
case 4:
a2 = N(match.getStartIndex());
- rest[0] = s;
+ rest[0] = JS.S(s);
break;
default:
rest[rest.length - 2] = N(match.getStartIndex());
- rest[rest.length - 1] = s;
+ rest[rest.length - 1] = JS.S(s);
}
// note: can't perform pausing operations in here
- sb.append((String)replaceFunc.call(a0, a1, a2, rest, numArgs));
+ sb.append(JS.toString(replaceFunc.call(a0, a1, a2, rest, numArgs)));
} else {
sb.append(mySubstitute(match,replaceString,s));
}
int end = matches.length == 0 ? 0 : matches[matches.length-1].getEndIndex();
sb.append(sa,end,sa.length-end);
- return sb.toString();
+ return JS.S(sb.toString());
}
private static String mySubstitute(GnuRegexp.REMatch match, String s, String source) {
}
- public static Object stringSplit(String s, Object arg0, Object arg1, int nargs) {
+ static JS stringSplit(JS s_, JS arg0, JS arg1, int nargs) throws JSExn {
+ 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();
regexp = (JSRegexp) arg0;
re = regexp.re;
} else {
- sep = arg0.toString();
+ sep = JS.toString(arg0);
}
// special case this for speed. additionally, the code below doesn't properly handle
if(sep != null && sep.length()==0) {
int len = s.length();
for(int i=0;i<len;i++)
- ret.addElement(s.substring(i,i+1));
+ ret.addElement(JS.S(s.substring(i,i+1)));
return ret;
}
GnuRegexp.REMatch m = re.getMatch(s,p);
if(m == null) break OUTER;
boolean zeroLength = m.getStartIndex() == m.getEndIndex();
- ret.addElement(s.substring(p,zeroLength ? m.getStartIndex()+1 : m.getStartIndex()));
+ ret.addElement(JS.S(s.substring(p,zeroLength ? m.getStartIndex()+1 : m.getStartIndex())));
p = zeroLength ? p + 1 : m.getEndIndex();
if(!zeroLength) {
for(int i=1;i<=re.getNumSubs();i++) {
- ret.addElement(m.toString(i));
+ ret.addElement(JS.S(m.toString(i)));
if(ret.length() == limit) break OUTER;
}
}
} else {
int x = s.indexOf(sep,p);
if(x == -1) break OUTER;
- ret.addElement(s.substring(p,x));
+ ret.addElement(JS.S(s.substring(p,x)));
p = x + sep.length();
}
if(ret.length() == limit) break;
}
if(p < s.length() && ret.length() != limit)
- ret.addElement(s.substring(p));
+ ret.addElement(JS.S(s.substring(p)));
return ret;
}
-
+
public static GnuRegexp.RE newRE(String pattern, int flags) throws JSExn {
try {
return new GnuRegexp.RE(pattern,flags,GnuRegexp.RESyntax.RE_SYNTAX_PERL5);
throw new JSExn(e.toString());
}
}
-
- public String typeName() { return "regexp"; }
+
}
// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
package org.ibex.js;
-// FIXME: should allow parentScope to be a JS, not a JSScope
/** Implementation of a JavaScript Scope */
-public class JSScope extends JS {
+class JSScope extends JS.O {
+ private final int base;
+ private final JS[] vars;
+ final JSScope parent;
+
+ public static class Top extends JSScope {
+ private final JS global;
+ public Top(JS global) { super(null,0,0); this.global = global; }
+ JS get(int i) throws JSExn { throw new JSExn("scope index out of range"); }
+ void put(int i, JS o) throws JSExn { throw new JSExn("scope index out of range"); }
+ JS getGlobal() { return global; }
+ };
+
+ // NOTE: We can't just set base to parent.base + parent.vars.length
+ // sometimes we only access part of a parent's scope
+ public JSScope(JSScope parent, int base, int size) {
+ this.parent = parent;
+ this.base = base;
+ this.vars = new JS[size];
+ }
+
+ 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 static final Object NULL_PLACEHOLDER = new Object();
+ private static final JS NULL_PLACEHOLDER = new JS() { };
- public JSScope(JSScope parentScope) { this.parentScope = parentScope; }
- public void declare(String s) throws JSExn { super.put(s, NULL_PLACEHOLDER); }
+ public JSScope(JSScope parentScope) { this(parentScope, 0, 0); }
+ public void declare(JS s) throws JSExn { super.put(s, NULL_PLACEHOLDER); }
public JSScope getParentScope() { return parentScope; }
- public Object get(Object key) throws JSExn {
- Object o = super.get(key);
+ 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(Object key) throws JSExn { return super.get(key) != null; }
- public void put(Object key, Object val) throws JSExn {
+ 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 Double NaN = new Double(Double.NaN);
- private final static Double POSITIVE_INFINITY = new Double(Double.POSITIVE_INFINITY);
+ private final static JS NaN = JS.N(Double.NaN);
+ private final static JS POSITIVE_INFINITY = JS.N(Double.POSITIVE_INFINITY);
public Global() { super(null); }
- public Object get(Object key) throws JSExn {
- //#switch(key)
- case "NaN": return NaN;
+ 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 == JS.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;
case "Infinity": return POSITIVE_INFINITY;
case "undefined": return null;
- case "stringFromCharCode": return METHOD;
- case "parseInt": 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;
- case "parseInt": return METHOD;
+ 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
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 "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;
- }
- }
- return super.callMethod(method, a0, a1, a2, rest, nargs);
+ public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
+ //#switch(JS.toString(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 "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);
+ default: return super.callMethod(method, a0, a1, a2, rest, nargs);
+ //#end
+ return super.callMethod(method,a0,a1,a2,rest,nargs);
}
- private Object parseInt(Object arg, Object r) {
+ private JS parseInt(JS arg, JS r) throws JSExn {
int radix = JS.toInt(r);
- String s = (String)arg;
+ String s = JS.toString(arg);
int start = 0;
int length = s.length();
int sign = 1;
long n = 0;
- if(radix != 0 && (radix < 2 || radix > 36)) return NaN;
+ if(radix != 0 && (radix < 2 || radix > 36)) return JS.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 NaN;
+ if(length == start || Character.digit(s.charAt(start),radix) == -1) return JS.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 NaN; // overflow;
+ if(n < 0) return JS.NaN; // overflow;
}
if(n <= Integer.MAX_VALUE) return JS.N(sign*(int)n);
return JS.N((long)sign*n);
}
- private Object parseFloat(Object arg) {
- String s = (String)arg;
+ private JS parseFloat(JS arg) throws JSExn {
+ String s = JS.toString(arg);
int start = 0;
int length = s.length();
while(start < length && Character.isWhitespace(s.charAt(0))) start++;
// 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;
+ return JS.NaN;
}
}
}
case "implements": return RESERVED;
case "instanceof": return RESERVED;
case "synchronized": return RESERVED;
+ case "cascade": return CASCADE;
//#end
return -1;
}
}
}
- if (!isInteger) this.number = JS.N(dval);
- else this.number = JS.N(longval);
+ if (!isInteger) this.number = new Double(dval);
+ else if(longval >= Integer.MIN_VALUE && longval <= Integer.MAX_VALUE) this.number = new Integer((int)longval);
+ else this.number = new Long(longval);
return NUMBER;
}
// Constructors //////////////////////////////////////////////////////
- public Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); }
+ private Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); }
/** for debugging */
public static void main(String[] s) throws IOException {
System.out.println(block);
}
-
// Statics ////////////////////////////////////////////////////////////
static byte[] precedence = new byte[MAX_TOKEN + 1];
precedence[DOT] = precedence[LB] = precedence[LP] = precedence[INC] = precedence[DEC] = 15;
}
-
+ // Local variable management
+ Vec scopeStack = new Vec();
+ static class ScopeInfo {
+ int base;
+ int end;
+ int newScopeInsn;
+ Hash mapping = new Hash();
+ }
+ Hash globalCache = new Hash();
+ JS scopeKey(String name) {
+ if(globalCache.get(name) != null) return null;
+ for(int i=scopeStack.size()-1;i>=0;i--) {
+ JS key = (JS)((ScopeInfo) scopeStack.elementAt(i)).mapping.get(name);
+ if(key != null) return key;
+ }
+ globalCache.put(name,Boolean.TRUE);
+ return null;
+ }
+ void scopeDeclare(String name) throws IOException {
+ ScopeInfo si = (ScopeInfo) scopeStack.lastElement();
+ if(si.mapping.get(name) != null) throw pe("" + name + " already declared in this scope");
+ si.mapping.put(name,JS.N(si.end++));
+ globalCache.put(name,null);
+ }
+ void scopePush(JSFunction b) {
+ ScopeInfo prev = (ScopeInfo) scopeStack.lastElement();
+ ScopeInfo si = new ScopeInfo();
+ si.base = prev.end;
+ si.end = si.base;
+ si.newScopeInsn = b.size;
+ scopeStack.push(si);
+ b.add(parserLine, NEWSCOPE);
+ }
+ void scopePop(JSFunction b) {
+ ScopeInfo si = (ScopeInfo) scopeStack.pop();
+ b.add(parserLine, OLDSCOPE);
+ b.set(si.newScopeInsn,JS.N((si.base<<16)|((si.end-si.base)<<0)));
+ }
+
+
// Parsing Logic /////////////////////////////////////////////////////////
+
+ /** parse and compile a function */
+ public static JSFunction fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
+ JSFunction ret = new JSFunction(sourceName, firstLine, null);
+ if (sourceCode == null) return ret;
+ Parser p = new Parser(sourceCode, sourceName, firstLine);
+ p.scopeStack.setSize(0);
+ p.scopeStack.push(new ScopeInfo());
+ p.scopePush(ret);
+ while(true) {
+ //int s = ret.size;
+ if(p.peekToken() == -1) break; // FIXME: Check this logic one more time
+ p.parseStatement(ret, null);
+ //if (s == ret.size) break;
+ }
+ p.scopePop(ret);
+ if(p.scopeStack.size() != 1) throw new Error("scopeStack height mismatch");
+ ret.add(-1, LITERAL, null);
+ ret.add(-1, RETURN);
+ return ret;
+ }
/** gets a token and throws an exception if it is not <tt>code</tt> */
private void consume(int code) throws IOException {
case -1: throw pe("expected expression");
// all of these simply push values onto the stack
- case NUMBER: b.add(parserLine, LITERAL, number); break;
- case STRING: b.add(parserLine, LITERAL, string); break;
+ case NUMBER: b.add(parserLine, LITERAL, JS.N(number)); break;
+ case STRING: b.add(parserLine, LITERAL, JSString.intern(string)); break;
case NULL: b.add(parserLine, LITERAL, null); break;
- case TRUE: case FALSE: b.add(parserLine, LITERAL, JS.B(tok == TRUE)); break;
+ case TRUE: case FALSE: b.add(parserLine, LITERAL, tok == TRUE ? JS.T : JS.F); break;
// (.foo) syntax
case DOT: {
consume(NAME);
- b.add(parserLine, TOPSCOPE);
- b.add(parserLine, LITERAL, "");
- b.add(parserLine, GET);
- b.add(parserLine, LITERAL, string);
- b.add(parserLine, GET);
- continueExpr(b, minPrecedence);
+ b.add(parserLine, GLOBALSCOPE);
+ b.add(parserLine, GET, JS.S("",true));
+ b.add(parserLine, LITERAL, JS.S(string,true));
+ continueExprAfterAssignable(b,minPrecedence,null);
break;
}
consume(RB);
break;
}
- case SUB: { // negative literal (like "3 * -1")
- consume(NUMBER);
- b.add(parserLine, LITERAL, JS.N(number.doubleValue() * -1));
+ case SUB: case ADD: {
+ if(peekToken() == NUMBER) { // literal
+ consume(NUMBER);
+ b.add(parserLine, LITERAL, JS.N(number.doubleValue() * (tok == SUB ? -1 : 1)));
+ } else { // unary +/- operator
+ if(tok == SUB) b.add(parserLine, LITERAL, JS.ZERO);
+ // BITNOT has the same precedence as the unary +/- operators
+ startExpr(b,precedence[BITNOT]);
+ if(tok == ADD) b.add(parserLine, LITERAL, JS.ZERO); // HACK to force expr into a numeric context
+ b.add(parserLine, SUB);
+ }
break;
}
case LP: { // grouping (not calling)
case INC: case DEC: { // prefix (not postfix)
startExpr(b, precedence[tok]);
int prev = b.size - 1;
+ boolean sg = b.get(prev) == SCOPEGET;
if (b.get(prev) == GET && b.getArg(prev) != null)
b.set(prev, LITERAL, b.getArg(prev));
else if(b.get(prev) == GET)
b.pop();
- else
+ else if(!sg)
throw pe("prefixed increment/decrement can only be performed on a valid assignment target");
- b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
+ if(!sg) b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
b.add(parserLine, LITERAL, JS.N(1));
b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
- b.add(parserLine, PUT, null);
- b.add(parserLine, SWAP, null);
- b.add(parserLine, POP, null);
+ if(sg) {
+ b.add(parserLine, SCOPEPUT, b.getArg(prev));
+ } else {
+ b.add(parserLine, PUT, null);
+ b.add(parserLine, SWAP, null);
+ b.add(parserLine, POP, null);
+ }
break;
}
case BANG: case BITNOT: case TYPEOF: {
if (peekToken() != NAME && peekToken() != STRING)
throw pe("expected NAME or STRING");
getToken();
- b.add(parserLine, LITERAL, string); // grab the key
+ b.add(parserLine, LITERAL, JSString.intern(string)); // grab the key
consume(COLON);
startExpr(b, NO_COMMA); // grab the value
b.add(parserLine, PUT); // put the value into the object
break;
}
case NAME: {
- b.add(parserLine, TOPSCOPE);
- b.add(parserLine, LITERAL, string);
- continueExprAfterAssignable(b,minPrecedence);
+ JS varKey = scopeKey(string);
+ if(varKey == null) {
+ b.add(parserLine, GLOBALSCOPE);
+ b.add(parserLine, LITERAL, JSString.intern(string));
+ }
+ continueExprAfterAssignable(b,minPrecedence,varKey);
+ break;
+ }
+ case CASCADE: {
+ if(peekToken() == ASSIGN) {
+ consume(ASSIGN);
+ startExpr(b, precedence[ASSIGN]);
+ b.add(parserLine, CASCADE, JS.T);
+ } else {
+ b.add(parserLine, CASCADE, JS.F);
+ }
break;
}
+
case FUNCTION: {
consume(LP);
int numArgs = 0;
b.add(parserLine, NEWFUNCTION, b2);
// function prelude; arguments array is already on the stack
- b2.add(parserLine, TOPSCOPE);
- b2.add(parserLine, SWAP);
- b2.add(parserLine, DECLARE, "arguments"); // declare arguments (equivalent to 'var arguments;')
- b2.add(parserLine, SWAP); // set this.arguments and leave the value on the stack
- b2.add(parserLine, PUT);
+ scopePush(b2);
+ scopeDeclare("arguments");
+ b2.add(parserLine, SCOPEPUT,scopeKey("arguments"));
while(peekToken() != RP) { // run through the list of argument names
numArgs++;
if (peekToken() == NAME) {
consume(NAME); // a named argument
- String varName = string;
- b2.formalArgs.push(varName);
+
b2.add(parserLine, DUP); // dup the args array
b2.add(parserLine, GET, JS.N(numArgs - 1)); // retrieve it from the arguments array
- b2.add(parserLine, TOPSCOPE);
- b2.add(parserLine, SWAP);
- b2.add(parserLine, DECLARE, varName); // declare the name
- b2.add(parserLine, SWAP);
- b2.add(parserLine, PUT);
- b2.add(parserLine, POP); // pop the value
- b2.add(parserLine, POP); // pop the scope
+ scopeDeclare(string);
+ b2.add(parserLine, SCOPEPUT, scopeKey(string));
+ b2.add(parserLine, POP);
}
if (peekToken() == RP) break;
consume(COMMA);
b2.numFormalArgs = numArgs;
b2.add(parserLine, POP); // pop off the arguments array
- b2.add(parserLine, POP); // pop off TOPSCOPE
if(peekToken() != LC)
throw pe("JSFunctions must have a block surrounded by curly brackets");
parseBlock(b2, null); // the function body
-
+
+ scopePop(b2);
b2.add(parserLine, LITERAL, null); // in case we "fall out the bottom", return NULL
b2.add(parserLine, RETURN);
* expression that modifies the assignable. This method always
* decreases the stack depth by exactly one element.
*/
- private void continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
+ private void continueExprAfterAssignable(JSFunction b,int minPrecedence, JS varKey) throws IOException {
int saveParserLine = parserLine;
- _continueExprAfterAssignable(b,minPrecedence);
+ _continueExprAfterAssignable(b,minPrecedence,varKey);
parserLine = saveParserLine;
}
- private void _continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
+ private void _continueExprAfterAssignable(JSFunction b,int minPrecedence, JS varKey) throws IOException {
if (b == null) throw new Error("got null b; this should never happen");
int tok = getToken();
if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok])))
*/
case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH:
case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: case ASSIGN_ADD: case ASSIGN_SUB: case ADD_TRAP: case DEL_TRAP: {
- if (tok != ADD_TRAP && tok != DEL_TRAP) b.add(parserLine, GET_PRESERVE);
+ if (tok != ADD_TRAP && tok != DEL_TRAP)
+ b.add(parserLine, varKey == null ? GET_PRESERVE : SCOPEGET, varKey);
startExpr(b, precedence[tok]);
if (tok != ADD_TRAP && tok != DEL_TRAP) {
// tok-1 is always s/^ASSIGN_// (0 is BITOR, 1 is ASSIGN_BITOR, etc)
b.add(parserLine, tok - 1, tok-1==ADD ? JS.N(2) : null);
- b.add(parserLine, PUT);
- b.add(parserLine, SWAP);
- b.add(parserLine, POP);
+ if(varKey == null) {
+ b.add(parserLine, PUT);
+ b.add(parserLine, SWAP);
+ b.add(parserLine, POP);
+ } else {
+ b.add(parserLine, SCOPEPUT, varKey);
+ }
} else {
+ if(varKey != null) throw pe("cannot place traps on local variables");
b.add(parserLine, tok);
}
break;
}
case INC: case DEC: { // postfix
- b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
- b.add(parserLine, LITERAL, JS.N(1));
- b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
- b.add(parserLine, PUT, null);
- b.add(parserLine, SWAP, null);
- b.add(parserLine, POP, null);
- b.add(parserLine, LITERAL, JS.N(1));
- b.add(parserLine, tok == INC ? SUB : ADD, JS.N(2)); // undo what we just did, since this is postfix
+ if(varKey == null) {
+ b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
+ b.add(parserLine, LITERAL, JS.N(1));
+ b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
+ b.add(parserLine, PUT, null);
+ b.add(parserLine, SWAP, null);
+ b.add(parserLine, POP, null);
+ b.add(parserLine, LITERAL, JS.N(1));
+ b.add(parserLine, tok == INC ? SUB : ADD, JS.N(2)); // undo what we just did, since this is postfix
+ } else {
+ b.add(parserLine, SCOPEGET, varKey);
+ b.add(parserLine, DUP);
+ b.add(parserLine, LITERAL, JS.ONE);
+ b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
+ b.add(parserLine, SCOPEPUT, varKey);
+ }
break;
}
case ASSIGN: {
startExpr(b, precedence[tok]);
- b.add(parserLine, PUT);
- b.add(parserLine, SWAP);
- b.add(parserLine, POP);
+ if(varKey == null) {
+ b.add(parserLine, PUT);
+ b.add(parserLine, SWAP);
+ b.add(parserLine, POP);
+ } else {
+ b.add(parserLine, SCOPEPUT, varKey);
+ }
break;
}
case LP: {
-
// Method calls are implemented by doing a GET_PRESERVE
// first. If the object supports method calls, it will
// return JS.METHOD
- int n = parseArgs(b, 2);
- b.add(parserLine, GET_PRESERVE);
- b.add(parserLine, CALLMETHOD, JS.N(n));
+ b.add(parserLine, varKey == null ? GET_PRESERVE : SCOPEGET, varKey);
+ int n = parseArgs(b);
+ b.add(parserLine, varKey == null ? CALLMETHOD : CALL, JS.N(n));
break;
}
default: {
pushBackToken();
- if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null)
+ if(varKey != null)
+ b.add(parserLine, SCOPEGET, varKey);
+ else if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null)
b.set(b.size-1,GET,b.getArg(b.size-1));
else
b.add(parserLine, GET);
switch (tok) {
case LP: { // invocation (not grouping)
- int n = parseArgs(b, 1);
+ int n = parseArgs(b);
b.add(parserLine, CALL, JS.N(n));
break;
}
} else {
consume(NAME);
}
- b.add(parserLine, LITERAL, string);
- continueExprAfterAssignable(b,minPrecedence);
+ b.add(parserLine, LITERAL, JSString.intern(string));
+ continueExprAfterAssignable(b,minPrecedence,null);
break;
}
case LB: { // subscripting (not array constructor)
startExpr(b, -1);
consume(RB);
- continueExprAfterAssignable(b,minPrecedence);
+ continueExprAfterAssignable(b,minPrecedence,null);
break;
}
case HOOK: {
}
// parse a set of comma separated function arguments, assume LP has already been consumed
- // if swap is true, (because the function is already on the stack) we will SWAP after each argument to keep it on top
- private int parseArgs(JSFunction b, int pushdown) throws IOException {
+ private int parseArgs(JSFunction b) throws IOException {
int i = 0;
while(peekToken() != RP) {
i++;
if (peekToken() != COMMA) {
startExpr(b, NO_COMMA);
- b.add(parserLine, SWAP, JS.N(pushdown));
if (peekToken() == RP) break;
}
consume(COMMA);
break;
}
case VAR: {
- b.add(parserLine, TOPSCOPE); // push the current scope
while(true) {
consume(NAME);
- b.add(parserLine, DECLARE, string); // declare it
+ String var = string;
+ scopeDeclare(var);
if (peekToken() == ASSIGN) { // if there is an '=' after the variable name
consume(ASSIGN);
startExpr(b, NO_COMMA);
- b.add(parserLine, PUT); // assign it
+ b.add(parserLine, SCOPEPUT, scopeKey(var)); // assign it
b.add(parserLine, POP); // clean the stack
- } else {
- b.add(parserLine, POP); // pop the string pushed by declare
- }
+ }
if (peekToken() != COMMA) break;
consume(COMMA);
}
- b.add(parserLine, POP); // pop off the topscope
if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
break;
}
// extended Ibex catch block: catch(e faultCode "foo.bar.baz")
consume(NAME);
b.add(parserLine, DUP);
- b.add(parserLine, LITERAL, string);
+ b.add(parserLine, LITERAL, JSString.intern(string));
b.add(parserLine, GET);
b.add(parserLine, DUP);
b.add(parserLine, LITERAL, null);
}
consume(RP);
// the exception is on top of the stack; put it to the chosen name
- b.add(parserLine, NEWSCOPE);
- b.add(parserLine, TOPSCOPE);
- b.add(parserLine, SWAP);
- b.add(parserLine, LITERAL,exceptionVar);
- b.add(parserLine, DECLARE);
- b.add(parserLine, SWAP);
- b.add(parserLine, PUT);
- b.add(parserLine, POP);
+ scopePush(b);
+ scopeDeclare(exceptionVar);
+ b.add(parserLine, SCOPEPUT, scopeKey(exceptionVar));
b.add(parserLine, POP);
parseBlock(b, null);
- b.add(parserLine, OLDSCOPE);
+ scopePop(b);
b.add(parserLine, JMP);
catchEnds.addElement(new Integer(b.size-1));
consume(RP);
b.add(parserLine, PUSHKEYS);
- b.add(parserLine, DUP);
- b.add(parserLine, LITERAL, "length");
- b.add(parserLine, GET);
- // Stack is now: n, keys, obj, ...
int size = b.size;
b.add(parserLine, LOOP);
b.add(parserLine, POP);
- // Stack is now: LoopMarker, n, keys, obj, ...
- // NOTE: This will break if the interpreter ever becomes more strict
- // and prevents bytecode from messing with the Markers
- b.add(parserLine, SWAP, JS.N(3));
- // Stack is now: Tn, keys, obj, LoopMarker, ...
- b.add(parserLine, LITERAL, JS.N(1));
- b.add(parserLine, SUB);
- b.add(parserLine, DUP);
- // Stack is now: index, keys, obj, LoopMarker
- b.add(parserLine, LITERAL, JS.ZERO);
- b.add(parserLine, LT);
- // Stack is now index<0, index, keys, obj, LoopMarker, ...
+ b.add(parserLine,SWAP); // get the keys enumeration object on top
+ b.add(parserLine,DUP);
+ b.add(parserLine,GET,JS.S("hasMoreElements"));
+ int size2 = b.size;
+ b.add(parserLine,JT);
+ b.add(parserLine,SWAP);
+ b.add(parserLine,BREAK);
+ b.set(size2, JS.N(b.size - size2));
+ b.add(parserLine,DUP);
+ b.add(parserLine,GET,JS.S("nextElement"));
+
+ scopePush(b);
- b.add(parserLine, JF, JS.N(5)); // if we're >= 0 jump 5 down (to NEWSCOPE)
- // Move the LoopMarker back into place - this is sort of ugly
- b.add(parserLine, SWAP, JS.N(3));
- b.add(parserLine, SWAP, JS.N(3));
- b.add(parserLine, SWAP, JS.N(3));
- // Stack is now: LoopMarker, -1, keys, obj, ...
- b.add(parserLine, BREAK);
+ if(hadVar) scopeDeclare(varName);
+ JS varKey = scopeKey(varName);
- b.add(parserLine, NEWSCOPE);
- if(hadVar) {
- b.add(parserLine, DECLARE, varName);
- b.add(parserLine, POP);
+ if(varKey == null) {
+ b.add(parserLine,GLOBALSCOPE);
+ b.add(parserLine,SWAP);
+ b.add(parserLine, LITERAL, JSString.intern(varName));
+ b.add(parserLine,SWAP);
+ b.add(parserLine,PUT);
+ b.add(parserLine,POP);
+ } else {
+ b.add(parserLine, SCOPEPUT, varKey);
}
-
- // Stack is now: index, keys, obj, LoopMarker, ...
- b.add(parserLine, GET_PRESERVE); // key, index, keys, obj, LoopMarker, ...
- b.add(parserLine, TOPSCOPE); // scope, key, index, keys, obj, LoopMarker, ...
- b.add(parserLine, SWAP); // key, scope, index, keys, obj, LoopMarker, ...
- b.add(parserLine, LITERAL, varName); // varName, key, scope, index, keys, obj, LoopMaker, ...
- b.add(parserLine, SWAP); // key, varName, scope, index, keys, obj, LoopMarker, ...
- b.add(parserLine, PUT); // key, scope, index, keys, obj, LoopMarker, ...
- b.add(parserLine, POP); // scope, index, keys, obj, LoopMarker
- b.add(parserLine, POP); // index, keys, obj, LoopMarker, ...
- // Move the LoopMarker back into place - this is sort of ugly
- b.add(parserLine, SWAP, JS.N(3));
- b.add(parserLine, SWAP, JS.N(3));
- b.add(parserLine, SWAP, JS.N(3));
+ b.add(parserLine,POP); // pop the put'ed value
+ b.add(parserLine,SWAP); // put CallMarker back into place
parseStatement(b, null);
- b.add(parserLine, OLDSCOPE);
+ scopePop(b);
b.add(parserLine, CONTINUE);
// jump here on break
b.set(size, JS.N(b.size - size));
- b.add(parserLine, POP); // N
- b.add(parserLine, POP); // KEYS
- b.add(parserLine, POP); // OBJ
-
+ b.add(parserLine, POP);
} else {
if (hadVar) pushBackToken(VAR, null); // yeah, this actually matters
- b.add(parserLine, NEWSCOPE); // grab a fresh scope
+ scopePush(b); // grab a fresh scope
parseStatement(b, null); // initializer
JSFunction e2 = // we need to put the incrementor before the test
if (peekToken() != SEMI)
startExpr(e2, -1);
else
- e2.add(parserLine, JSFunction.LITERAL, Boolean.TRUE); // handle the for(foo;;foo) case
+ e2.add(parserLine, JSFunction.LITERAL, JS.T); // handle the for(foo;;foo) case
consume(SEMI);
if (label != null) b.add(parserLine, LABEL, label);
b.add(parserLine, LOOP);
b.add(parserLine, CONTINUE); // if we fall out the bottom, CONTINUE
b.set(size2 - 1, JS.N(b.size - size2 + 1)); // end of the loop
- b.add(parserLine, OLDSCOPE); // get our scope back
+ scopePop(b); // get our scope back
}
break;
}
case LC: { // blocks are statements too
pushBackToken();
- b.add(parserLine, NEWSCOPE);
+ scopePush(b);
parseBlock(b, label);
- b.add(parserLine, OLDSCOPE);
+ scopePop(b);
break;
}
import java.util.*;
import java.io.*;
+// FEATURE: Update for new api
+
/** A JS interface to a Java '.properties' file; very crude */
public class PropertyFile extends JS {
- private final Properties p = new Properties();
+ /*private final Properties p = new Properties();
private final Hash cache = new Hash(10, 3);
private File f;
Object ret = p.get(toString(key));
if (ret != null) return ret;
return new Minion(escape(toString(key)));
- }
+ }*/
}
if (name.equals("SOAP-ENV:Fault")) fault = true;
// add a generic struct; we'll change this if our type is different
- objects.addElement(new JS());
+ objects.addElement(new JS.O());
for(int i=0; i<keys.length; i++) {
String key = keys[i];
if (key.endsWith("ype")) {
if (value.endsWith("boolean")) {
objects.removeElementAt(objects.size() - 1);
- objects.addElement(Boolean.FALSE);
+ objects.addElement(JS.B(true));
} else if (value.endsWith("int")) {
objects.removeElementAt(objects.size() - 1);
- objects.addElement(new Integer(0));
+ objects.addElement(JS.N(0));
} else if (value.endsWith("double")) {
objects.removeElementAt(objects.size() - 1);
- objects.addElement(new Double(0.0));
+ objects.addElement(JS.N(0.0));
} else if (value.endsWith("string")) {
objects.removeElementAt(objects.size() - 1);
- objects.addElement("");
+ objects.addElement(JS.S(""));
} else if (value.endsWith("base64")) {
objects.removeElementAt(objects.size() - 1);
objects.addElement(new byte[] { });
}
// remove ourselves
- Object me = objects.elementAt(objects.size() - 1);
+ JS me = (JS)objects.elementAt(objects.size() - 1);
// find our parent
- Object parent = objects.size() > 1 ? objects.elementAt(objects.size() - 2) : null;
+ JS parent = objects.size() > 1 ? (JS)objects.elementAt(objects.size() - 2) : (JS)null;
// we want to fold stuff back into the fault object
if (objects.size() < 2) return;
} else if (parent != null && parent instanceof JS) {
objects.removeElementAt(objects.size() - 1);
try {
- ((JS)parent).put(name, me);
+ ((JS)parent).put(JS.S(name), me);
} catch (JSExn e) {
throw new Error("this should never happen");
}
Enumeration e = j.keys();
while(e.hasMoreElements()) {
Object key = e.nextElement();
- appendObject((String)key, j.get(key), sb);
+ appendObject((String)key, j.get((JS)key), sb);
}
sb.append("</" + name + ">\r\n");
if (args.length() > 0) {
Enumeration e = ((JS)args.elementAt(0)).keys();
while(e.hasMoreElements()) {
- Object key = e.nextElement();
- appendObject((String)key, ((JS)args.elementAt(0)).get(key), content);
+ JS key = (JS)e.nextElement();
+ appendObject(((JSString)key).coerceToString(), ((JS)args.elementAt(0)).get(key), content);
}
}
content.append(" </" + method + "></SOAP-ENV:Body></SOAP-ENV:Envelope>\r\n");
public static final int GRAMMAR = 78; // the grammar-definition operator (::=)
public static final int ADD_TRAP = 79; // the add-trap operator (++=)
public static final int DEL_TRAP = 80; // the del-trap operator (--=)
+ public static final int CASCADE = 81; // cascade expression - arg==true for write cascade
public static final int MAX_TOKEN = DEL_TRAP;
"HOOK", "COLON", "INC", "DEC", "DOT", "FUNCTION", "IF",
"ELSE", "SWITCH", "CASE", "DEFAULT", "WHILE", "DO", "FOR",
"VAR", "WITH", "CATCH", "FINALLY", "RESERVED", "GRAMMAR",
- "ADD_TRAP", "DEL_TRAP"
+ "ADD_TRAP", "DEL_TRAP", "CASCADE"
};
}
* linked list stack, with the most recently placed trap at the head
* of the list.
*/
-class Trap {
+final class Trap {
- JS trapee = null; ///< the box on which this trap was placed
- Object name = null; ///< the property that the trap was placed on
+ final JS target; ///< the box on which this trap was placed
+ final JS key; ///< the property that the trap was placed on
- JSFunction f = null; ///< the function for this trap
- Trap next = null; ///< the next trap down the trap stack
+ final JSFunction f; ///< the function for this trap
+ Trap next; ///< the next trap down the trap stack
- Trap(JS b, String n, JSFunction f, Trap nx) {
- trapee = b; name = n; this.f = f; this.next = nx;
- }
-
- static final JSFunction putInvoker = new JSFunction("putInvoker", 0, null);
- static final JSFunction getInvoker = new JSFunction("getInvoker", 0, null);
-
- static {
- putInvoker.add(1, ByteCodes.PUT, null);
- putInvoker.add(2, Tokens.RETURN, null);
- getInvoker.add(1, ByteCodes.GET, null);
- getInvoker.add(2, Tokens.RETURN, null);
+ Trap(JS b, JS n, JSFunction f, Trap nx) {
+ target = b; key = n; this.f = f; this.next = nx;
}
- void invoke(Object value) throws JSExn {
- Interpreter i = new Interpreter(putInvoker, false, null);
- i.stack.push(trapee);
- i.stack.push(name);
- i.stack.push(value);
- i.resume();
- }
-
- Object invoke() throws JSExn {
- Interpreter i = new Interpreter(getInvoker, false, null);
- i.stack.push(trapee);
- 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);
- }
- }
+ boolean isReadTrap() { return f.numFormalArgs == 0; }
+ boolean isWriteTrap() { return f.numFormalArgs != 0; }
+ Trap readTrap() { Trap t = this; while(t!=null && t.isWriteTrap()) t = t.next; return t; }
+ Trap writeTrap() { Trap t = this; while(t!=null && t.isReadTrap()) t = t.next; return t; }
+ Trap nextReadTrap() { return next == null ? null : next.readTrap(); }
+ Trap nextWriteTrap() { return next == null ? null : next.writeTrap(); }
}
-
}
public XMLRPC(String url, String method, XMLRPC httpSource) {
this.http = httpSource.http; this.url = url; this.method = method; }
- public Object get(Object name) {
- return new XMLRPC(url, (method.equals("") ? "" : method + ".") + name.toString(), this); }
+ public JS get(JS name) throws JSExn {
+ return new XMLRPC(url, (method.equals("") ? "" : method + ".") + JS.toString(name), this); }
/** this holds character content as we read it in -- since there is only one per instance, we don't support mixed content */
content.reset();
//#switch(c.getLocalName())
case "fault": fault = true;
- case "struct": objects.setElementAt(new JS(), objects.size() - 1);
+ case "struct": objects.setElementAt(new JS.O(), objects.size() - 1);
case "array": objects.setElementAt(null, objects.size() - 1);
case "value": objects.addElement("");
//#end
"the server sent a <dateTime.iso8601> tag which was malformed: " + s);
}
case "member":
- Object memberValue = objects.elementAt(objects.size() - 1);
- String memberName = (String)objects.elementAt(objects.size() - 2);
+ JS memberValue = (JS)objects.elementAt(objects.size() - 1);
+ JS memberName = (JS)objects.elementAt(objects.size() - 2);
JS struct = (JS)objects.elementAt(objects.size() - 3);
try {
struct.put(memberName, memberValue);
for(i=objects.size() - 1; objects.elementAt(i) != null; i--);
JSArray arr = new JSArray();
try {
- for(int j = i + 1; j<objects.size(); j++) arr.put(new Integer(j - i - 1), objects.elementAt(j));
+ for(int j = i + 1; j<objects.size(); j++) arr.put(JS.N(j - i - 1), (JS)objects.elementAt(j));
} catch (JSExn e) {
throw new Error("this should never happen");
}
} else if (o instanceof JSArray) {
if (tracker.get(o) != null) throw new JSExn("attempted to send multi-ref data structure via XML-RPC");
- tracker.put(o, Boolean.TRUE);
+ tracker.put(o, JS.B(true));
sb.append(" <value><array><data>\n");
JSArray a = (JSArray)o;
for(int i=0; i<a.length(); i++) appendObject(a.elementAt(i), sb);
} else if (o instanceof JS) {
if (tracker.get(o) != null) throw new JSExn("attempted to send multi-ref data structure via XML-RPC");
- tracker.put(o, Boolean.TRUE);
+ tracker.put(o, JS.B(true));
JS j = (JS)o;
sb.append(" <value><struct>\n");
Enumeration e = j.keys();
while(e.hasMoreElements()) {
Object key = e.nextElement();
sb.append(" <member><name>" + key + "</name>\n");
- appendObject(j.get(key), sb);
+ appendObject(j.get((JS)key), sb);
sb.append(" </member>\n");
}
sb.append(" </struct></value>\n");
// Call Sequence //////////////////////////////////////////////////////////////////////////
/* FIXME this has been disabled to make XMLRPC usable without Scheduler
- public final Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+ public final Object call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
JSArray args = new JSArray();
for(int i=0; i<nargs; i++) args.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
return call(args);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
try {
new Helper().parse(br);
- final Object result = fault ? new JSExn(objects.elementAt(0)) : objects.size() == 0 ? null : objects.elementAt(0);
+ if (fault) throw new JSExn((JS)objects.elementAt(0));
+ final JS result = (objects.size() == 0 ? (JS)null : ((JS)objects.elementAt(0)));
return (new Task() { public void perform() throws JSExn { callback.unpause(result); }});
} finally {
tracker.clear();
}
} catch (final JSExn e) {
final Exception e2 = e;
- return (new Task() { public void perform() throws JSExn { callback.unpause(e2); }});
+ return (new Task() { public void perform() throws JSExn { callback.unpause((JSExn)e2); }});
} catch (final IOException e) {
final Exception e2 = e;
- return (new Task() { public void perform() throws JSExn { callback.unpause(new JSExn(e2)); }});
+ return (new Task() { public void perform() throws JSExn { callback.unpause(new JSExn(e2.getMessage())); }});
} catch (final XML.Exn e) {
final Exception e2 = e;
- return (new Task() { public void perform() throws JSExn { callback.unpause(new JSExn(e2)); }});
+ return (new Task() { public void perform() throws JSExn { callback.unpause(new JSExn(e2.getMessage())); }});
}
}
}