--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+/**
+ * Constants for the various JavaScript ByteCode operations.
+ *
+ * Each instruction is an opcode and an optional literal literal;
+ * some Tokens are also valid; see Tokens.java
+ */
+interface ByteCodes {
+
+ /** push the literal onto the stack */
+ public static final byte LITERAL = -2;
+
+ /** push a new array onto the stack with length equal to the literal */
+ public static final byte ARRAY = -3;
+
+ /** push an empty object onto the stack */
+ public static final byte OBJECT = -4;
+
+ /** create a new instance; literal is a reference to the corresponding ForthBlock */
+ public static final byte NEWFUNCTION = -5;
+
+ /** 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;
+
+ /** push a reference to the current scope onto the stack */
+ public static final byte TOPSCOPE = -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) */
+ public static final byte GET = -8;
+
+ /** push stack[-1].get(stack[top]) */
+ public static final byte GET_PRESERVE = -9;
+
+ /** pop two elements off the stack; stack[-2].put(stack[-1], stack[top]); push stack[top] */
+ public static final byte PUT = -10;
+
+ /** literal is a relative address; pop stacktop and jump if the value is true */
+ public static final byte JT = -11;
+
+ /** literal is a relative address; pop stacktop and jump if the value is false */
+ public static final byte JF = -12;
+
+ /** literal is a relative address; jump to it */
+ public static final byte JMP = -13;
+
+ /** discard the top stack element */
+ static public final byte POP = -14;
+
+ /** pop element; call stack[top](stack[-n], stack[-n+1]...) where n is the number of args to the function */
+ public static final byte CALL = -15;
+
+ /** pop an element; push a JS.JSArray containing the keys of the popped element */
+ public static final byte PUSHKEYS = -16;
+
+ /** push the top element down so that (arg) elements are on top of it; all other elements retain ordering */
+ public static final byte SWAP = -17;
+
+ /** execute the bytecode block pointed to by the literal in a fresh scope with parentScope==THIS */
+ public static final byte NEWSCOPE = -18;
+
+ /** execute the bytecode block pointed to by the literal in a fresh scope with parentScope==THIS */
+ public static final byte OLDSCOPE = -19;
+
+ /** push a copy of the top stack element */
+ public static final byte DUP = -20;
+
+ /** a NOP; confers a label upon the following instruction */
+ public static final byte LABEL = -21;
+
+ /** execute the ForthBlock pointed to by the literal until BREAK encountered; push TRUE onto the stack for the first iteration
+ * and FALSE for all subsequent iterations */
+ public static final byte LOOP = -22;
+
+ /** similar effect a a GET followed by a CALL */
+ public static final byte CALLMETHOD = -23;
+
+ /** finish a finally block and carry out whatever instruction initiated the finally block */
+ public static final byte FINALLY_DONE = -24;
+
+ /** finish a finally block and carry out whatever instruction initiated the finally block */
+ public static final byte MAKE_GRAMMAR = -25;
+
+ public static final String[] bytecodeToString = new String[] {
+ "", "", "LITERAL", "ARRAY", "OBJECT", "NEWFUNCTION", "DECLARE", "TOPSCOPE",
+ "GET", "GET_PRESERVE", "PUT", "JT", "JF", "JMP", "POP", "CALL", "PUSHKEYS",
+ "SWAP", "NEWSCOPE", "OLDSCOPE", "DUP", "LABEL", "LOOP", "CALLMETHOD",
+ "FINALLY_DONE", "MAKE_GRAMMAR"
+ };
+}
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+import org.ibex.util.*;
+import java.util.*;
+import java.io.*;
+
+// FEATURE: support for move
+// FEATURE: support for bytestreams
+// FEATURE: cache directories so we can do equality checking on them?
+// FEATURE: autoconvert "true" to true and "0.3" to 0.3 on readback
+
+/**
+ * A crude mechanism for using a filesystem as object storage.
+ *
+ * This object represents a directory; writing a string, number, or
+ * boolean to any of its properties will create a file with the
+ * (encoded) property name as its filename and the "stringified"
+ * value as its contents.
+ *
+ * Writing 'null' to one of this object's properties will
+ * [recursively if necessary] delete the corresponding directory
+ * entry.
+ *
+ * Writing any other object to one of this object's properties will
+ * create a new Directory object and copy the other object's keys()
+ * into the new Directory. This means that assigning one directory
+ * to a property of another directory will <i>copy</i> the directory,
+ * not move it. There is currently no way to move directories.
+ *
+ * If an object is written to a property that already has an entry,
+ * the old one is deleted (equivalent to writing 'null') first.
+ *
+ * WARNING: when instantiating a Directory object with a file
+ * argument that points to a non-directory File, this class will
+ * delete that file and create a directory!
+ */
+public class Directory extends JS {
+
+ File f;
+
+ /**
+ * 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()));
+ if (!f.isDirectory()) destroy(f);
+ f.mkdirs();
+ }
+
+ private static void destroy(File f) throws IOException {
+ if (!f.exists()) return;
+ if (f.isDirectory()) {
+ String[] entries = f.list();
+ for(int i=0; i<entries.length; i++) destroy(new File(f.getAbsolutePath() + File.separatorChar + entries[i]));
+ }
+ f.delete();
+ }
+
+ public void put(Object key0, Object 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) {
+ Directory d2 = new Directory(f2);
+ Enumeration e = ((JS)val).keys();
+ while(e.hasMoreElements()) {
+ String k = (String)e.nextElement();
+ Object v = ((JS)val).get(k);
+ d2.put(k, v);
+ }
+ } else {
+ OutputStream out = new FileOutputStream(f2);
+ Writer w = new OutputStreamWriter(out);
+ w.write(toString(val));
+ w.flush();
+ out.close();
+ }
+ } catch (IOException ioe) {
+ throw new JSExn.IO(ioe);
+ }
+ }
+
+ public Object get(Object key0) throws JSExn {
+ try {
+ if (key0 == null) return null;
+ String key = toString(key0);
+ File f2 = new File(f.getAbsolutePath() + File.separatorChar + FileNameEncoder.encode(key));
+ if (!f2.exists()) return null;
+ if (f2.isDirectory()) return new Directory(f2);
+ char[] chars = new char[((int)f2.length()) * 2];
+ int numchars = 0;
+ 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);
+ numchars += numread;
+ }
+ } catch (IOException ioe) {
+ throw new JSExn.IO(ioe);
+ }
+ }
+
+ public Enumeration keys() {
+ final String[] elements = f.list();
+ return new Enumeration() {
+ int i = 0;
+ public boolean hasMoreElements() { return i < elements.length; }
+ public Object nextElement() { return FileNameEncoder.decode(elements[i++]); }
+ };
+ }
+}
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+import org.ibex.util.*;
+import java.util.*;
+
+/** 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()); }
+ private static Hashtable threadToInterpreter = new Hashtable();
+
+
+ // Instance members and methods //////////////////////////////////////////////////////////////////////
+
+ 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
+ int pc = 0; ///< the program counter
+
+ Interpreter(JSFunction f, boolean pauseable, JSArray args) {
+ stack.push(new Interpreter.CallMarker(this)); // the "root function returned" marker -- f==null
+ this.f = f;
+ this.pausecount = pauseable ? 0 : -1;
+ this.scope = new JSScope(f.parentScope);
+ stack.push(args);
+ }
+
+ /** this is the only synchronization point we need in order to be threadsafe */
+ synchronized Object resume() throws JSExn {
+ Thread t = Thread.currentThread();
+ Interpreter old = (Interpreter)threadToInterpreter.get(t);
+ threadToInterpreter.put(t, this);
+ try {
+ return run();
+ } finally {
+ if (old == null) threadToInterpreter.remove(t);
+ else threadToInterpreter.put(t, old);
+ }
+ }
+
+ static int getLine() {
+ Interpreter c = Interpreter.current();
+ return c == null || c.f == null || c.pc < 0 || c.pc >= c.f.size ? -1 : c.f.line[c.pc];
+ }
+
+ static String getSourceName() {
+ Interpreter c = Interpreter.current();
+ return c == null || c.f == null ? null : c.f.sourceName;
+ }
+
+ private static JSExn je(String s) { return new JSExn(getSourceName() + ":" + getLine() + " " + s); }
+
+ // FIXME: double check the trap logic
+ private Object 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) {
+ FinallyData fd = (FinallyData) stack.pop();
+ if(fd == null) continue OUTER; // NOP
+ if(fd.exn != null) throw fd.exn;
+ op = fd.op;
+ 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 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 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 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 LABEL: 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");
+ 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);
+ 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);
+ break;
+
+ case BREAK:
+ case CONTINUE:
+ while(stack.size() > 0) {
+ Object 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
+ stack.push(new FinallyData(op, arg));
+ scope = ((TryMarker)o).scope;
+ pc = ((TryMarker)o).finallyLoc - 1;
+ continue OUTER;
+ }
+ 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;
+ scope = ((LoopMarker)o).scope;
+ if (op == CONTINUE) { stack.push(o); stack.push(Boolean.FALSE); }
+ pc = op == BREAK ? endOfLoop - 1 : loopInstructionLocation;
+ continue OUTER;
+ }
+ }
+ }
+ throw new Error("CONTINUE/BREAK invoked but couldn't find LoopMarker at " +
+ getSourceName() + ":" + getLine());
+
+ case TRY: {
+ int[] jmps = (int[]) arg;
+ // jmps[0] is how far away the catch block is, jmps[1] is how far away the finally block is
+ // each can be < 0 if the specified block does not exist
+ stack.push(new TryMarker(jmps[0] < 0 ? -1 : pc + jmps[0], jmps[1] < 0 ? -1 : pc + jmps[1], this));
+ break;
+ }
+
+ case RETURN: {
+ Object retval = stack.pop();
+ while(stack.size() > 0) {
+ Object o = stack.pop();
+ if (o instanceof TryMarker) {
+ if(((TryMarker)o).finallyLoc < 0) continue;
+ stack.push(retval);
+ stack.push(new FinallyData(RETURN));
+ scope = ((TryMarker)o).scope;
+ 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;
+ continue OUTER;
+ }
+ }
+ }
+ scope = ((CallMarker)o).scope;
+ pc = ((CallMarker)o).pc - 1;
+ f = (JSFunction)((CallMarker)o).f;
+ stack.push(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); }
+ } else {
+ t = ((JS)target).getTrap(key);
+ }
+ while (t != null && t.f.numFormalArgs == 0) t = t.next; // find the first write trap
+ }
+ 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;
+ }
+ ((JS)target).put(key, val);
+ if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
+ stack.push(val);
+ break;
+ }
+
+ case GET:
+ case GET_PRESERVE: {
+ Object o, v;
+ if (op == GET) {
+ v = arg == null ? stack.pop() : arg;
+ o = stack.pop();
+ } else {
+ v = stack.pop();
+ o = stack.peek();
+ stack.push(v);
+ }
+ 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);
+ if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
+ stack.push(ret);
+ break;
+ }
+ throw je("tried to get property " + v + " from a " + o.getClass().getName());
+ }
+
+ case CALL: case CALLMETHOD: {
+ int numArgs = JS.toInt(arg);
+ Object method = null;
+ Object ret = null;
+ Object 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");
+ } 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]);
+ stack.push(new CallMarker(this));
+ stack.push(arguments);
+ f = (JSFunction)object;
+ scope = new JSScope(f.parentScope);
+ pc = -1;
+ break;
+
+ } else if (object instanceof JS) {
+ 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);
+
+ /* FIXME
+ case MAKE_GRAMMAR: {
+ final Grammar r = (Grammar)arg;
+ final JSScope final_scope = scope;
+ Grammar r2 = new Grammar() {
+ public int match(String s, int start, Hash v, JSScope scope) throws JSExn {
+ return r.match(s, start, v, final_scope);
+ }
+ public int matchAndWrite(String s, int start, Hash v, JSScope scope, String key) throws JSExn {
+ return r.matchAndWrite(s, start, v, final_scope, key);
+ }
+ public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+ Hash v = new Hash();
+ r.matchAndWrite((String)a0, 0, v, final_scope, "foo");
+ return v.get("foo");
+ }
+ };
+ Object obj = stack.pop();
+ if (obj != null && obj instanceof Grammar) r2 = new Grammar.Alternative((Grammar)obj, r2);
+ stack.push(r2);
+ break;
+ }
+ */
+ case ADD_TRAP: case DEL_TRAP: {
+ Object val = stack.pop();
+ Object key = stack.pop();
+ Object obj = stack.peek();
+ // A trap addition/removal
+ JS js = obj instanceof JSScope ? ((JSScope)obj).top() : (JS) obj;
+ 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();
+ 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)));
+ } else {
+ Object[] args = new Object[count];
+ while(--count >= 0) args[count] = stack.pop();
+ if(args[0] instanceof String) {
+ StringBuffer sb = new StringBuffer(64);
+ for(int i=0;i<args.length;i++) sb.append(JS.toString(args[i]));
+ stack.push(sb.toString());
+ } else {
+ int numStrings = 0;
+ for(int i=0;i<args.length;i++) if(args[i] instanceof String) numStrings++;
+ if(numStrings == 0) {
+ double d = 0.0;
+ for(int i=0;i<args.length;i++) d += JS.toDouble(args[i]);
+ stack.push(JS.N(d));
+ } else {
+ int i=0;
+ StringBuffer sb = new StringBuffer(64);
+ if(!(args[0] instanceof String || args[1] instanceof String)) {
+ double d=0.0;
+ do {
+ d += JS.toDouble(args[i++]);
+ } while(!(args[i] instanceof String));
+ sb.append(JS.toString(JS.N(d)));
+ }
+ while(i < args.length) sb.append(JS.toString(args[i++]));
+ stack.push(sb.toString());
+ }
+ }
+ }
+ break;
+ }
+
+ default: {
+ Object right = stack.pop();
+ Object left = stack.pop();
+ switch(op) {
+
+ case BITOR: stack.push(JS.N(JS.toLong(left) | JS.toLong(right))); break;
+ case BITXOR: stack.push(JS.N(JS.toLong(left) ^ JS.toLong(right))); break;
+ case BITAND: stack.push(JS.N(JS.toLong(left) & JS.toLong(right))); break;
+
+ case SUB: stack.push(JS.N(JS.toDouble(left) - JS.toDouble(right))); break;
+ case MUL: stack.push(JS.N(JS.toDouble(left) * JS.toDouble(right))); break;
+ case DIV: stack.push(JS.N(JS.toDouble(left) / JS.toDouble(right))); break;
+ case MOD: stack.push(JS.N(JS.toDouble(left) % JS.toDouble(right))); break;
+
+ case LSH: 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;
+ }
+
+ 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);
+ stack.push(JS.B(op == EQ ? ret : !ret)); break;
+ }
+
+ default: throw new Error("unknown opcode " + op);
+ } }
+ }
+
+ } 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;
+ } // end try/catch
+ } // end for
+ }
+
+
+
+ // 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; }
+ }
+
+ public static class CatchMarker { }
+ private static CatchMarker catchMarker = new CatchMarker();
+
+ public static class LoopMarker {
+ public int location;
+ public String label;
+ 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;
+ public TryMarker(int catchLoc, int finallyLoc, Interpreter cx) {
+ this.catchLoc = catchLoc;
+ this.finallyLoc = finallyLoc;
+ this.scope = cx.scope;
+ this.f = cx.f;
+ }
+ }
+ public static class FinallyData {
+ public int op;
+ public Object arg;
+ 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
+ }
+
+
+ // 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);
+ }
+ //#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));
+ }
+ 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
+ }
+ 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);
+ }
+ };
+ }
+ return null;
+ }
+
+ private static class Stub extends JS {
+ private Object 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 {
+ return ((JS)obj).callMethod(method, a0, a1, a2, rest, nargs);
+ }
+ }
+}
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+import org.ibex.util.*;
+import java.io.*;
+import java.util.*;
+
+/** The minimum set of functionality required for objects which are manipulated by JavaScript */
+public class JS extends org.ibex.util.BalancedTree {
+
+ 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 {
+ throw new JSExn("you cannot call this object (class=" + this.getClass().getName() +")");
+ }
+
+ JS _unclone() { return this; }
+ public static class Cloneable extends JS {
+ public Object jsclone() throws JSExn {
+ return new Clone(this);
+ }
+ }
+
+ 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 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 {
+ return clonee.call(a0, a1, a2, rest, nargs);
+ }
+ }
+
+ // Static Interpreter Control Methods ///////////////////////////////////////////////////////////////
+
+ /** log a message with the current JavaScript sourceName/line */
+ public static void log(Object message) { info(message); }
+ public static void debug(Object message) { Log.debug(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
+ public static void info(Object message) { Log.info(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
+ public static void warn(Object message) { Log.warn(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
+ public static void error(Object message) { Log.error(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
+
+ public static class NotPauseableException extends Exception { NotPauseableException() { } }
+
+ /** returns a callback which will restart the context; expects a value to be pushed onto the stack when unpaused */
+ public static UnpauseCallback pause() throws NotPauseableException {
+ Interpreter i = Interpreter.current();
+ if (i.pausecount == -1) throw new NotPauseableException();
+ i.pausecount++;
+ return new JS.UnpauseCallback(i);
+ }
+
+ 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();
+ }
+ }
+
+
+
+ // 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;
+ 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(); }
+
+ /** 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");
+ }
+
+ /** coerce an object to a String */
+ public static String toString(Object o) {
+ 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();
+ }
+ 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.");
+ }
+
+ public String coerceToString() {
+ throw new RuntimeException("can't coerce "+this+" [" + getClass().getName() + "] to type String.");
+ }
+
+ // 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;
+ int idx = i + smallIntCache.length / 2;
+ if (idx < smallIntCache.length && idx > 0) {
+ ret = smallIntCache[idx];
+ if (ret != null) return ret;
+ }
+ else ret = largeIntCache[Math.abs(idx % largeIntCache.length)];
+ if (ret == null || ret.intValue() != i) {
+ ret = new Integer(i);
+ if (idx < smallIntCache.length && idx > 0) smallIntCache[idx] = ret;
+ else largeIntCache[Math.abs(idx % largeIntCache.length)] = ret;
+ }
+ return ret;
+ }
+
+ private static Enumeration emptyEnumeration = new Enumeration() {
+ public boolean hasMoreElements() { return false; }
+ public Object nextElement() { throw new NoSuchElementException(); }
+ };
+
+ private Hash entries = null;
+
+ public static JS fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
+ return JSFunction._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);
+ }
+
+
+ // 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 {
+ Trap t = getTrap(key);
+ if (t != null) t.invoke(value);
+ else put(key, value);
+ }
+
+ /** performs a get, triggering traps if present; traps are run in an unpauseable interpreter */
+ public Object getAndTriggerTraps(Object 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);
+ }
+
+ /** 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);
+ }
+
+ /** adds a trap, avoiding duplicates */
+ protected final void addTrap(Object name, JSFunction f) throws JSExn {
+ if (f.numFormalArgs > 1) throw new JSExn("traps must take either one argument (write) or no arguments (read)");
+ boolean isRead = f.numFormalArgs == 0;
+ if (!isTrappable(name, isRead)) throw new JSExn("not allowed "+(isRead?"read":"write")+" trap on property: "+name);
+ for(Trap t = getTrap(name); t != null; t = t.next) if (t.f == f) return;
+ putTrap(name, new Trap(this, name.toString(), f, (Trap)getTrap(name)));
+ }
+
+ /** deletes a trap, if present */
+ protected final void delTrap(Object name, JSFunction f) {
+ Trap t = (Trap)getTrap(name);
+ if (t == null) return;
+ if (t.f == f) { putTrap(t.name, t.next); return; }
+ for(; t.next != null; t = t.next) if (t.next.f == f) { t.next = t.next.next; return; }
+ }
+
+
+}
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+import org.ibex.util.*;
+import java.util.*;
+
+/** A JavaScript JSArray */
+public class JSArray extends JS {
+ private static final Object NULL = new Object();
+
+ public JSArray() { }
+ public JSArray(int size) { setSize(size); }
+
+ private static int intVal(Object o) {
+ if (o instanceof Number) {
+ int intVal = ((Number)o).intValue();
+ if (intVal == ((Number)o).doubleValue()) return intVal;
+ return Integer.MIN_VALUE;
+ }
+ if (!(o instanceof String)) return Integer.MIN_VALUE;
+ String s = (String)o;
+ for(int i=0; i<s.length(); i++) if (s.charAt(i) < '0' || s.charAt(i) > '9') return Integer.MIN_VALUE;
+ return Integer.parseInt(s);
+ }
+
+ public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+ //#switch(method)
+ case "pop": {
+ int oldSize = size();
+ if(oldSize == 0) return null;
+ return removeElementAt(oldSize-1);
+ }
+ case "reverse": return reverse();
+ case "toString": return join(",");
+ case "shift":
+ if(length() == 0) return null;
+ return removeElementAt(0);
+ case "join":
+ return join(nargs == 0 ? "," : JS.toString(a0));
+ case "sort":
+ return sort(nargs < 1 ? null : a0);
+ case "slice":
+ int start = toInt(nargs < 1 ? null : a0);
+ int end = nargs < 2 ? length() : toInt(a1);
+ return slice(start, end);
+ case "push": {
+ int oldSize = size();
+ for(int i=0; i<nargs; i++) insertElementAt(i==0?a0:i==1?a1:i==2?a2:rest[i-3],oldSize+i);
+ return N(oldSize + nargs);
+ }
+ case "unshift":
+ for(int i=0; i<nargs; i++) insertElementAt(i==0?a0:i==1?a1:i==2?a2:rest[i-3],i);
+ return N(size());
+ case "splice":
+ JSArray array = new JSArray();
+ for(int i=0; i<nargs; i++) array.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
+ return splice(array);
+ //#end
+ return super.callMethod(method, a0, a1, a2, rest, nargs);
+ }
+
+ public Object get(Object key) throws JSExn {
+ int i = intVal(key);
+ if (i != Integer.MIN_VALUE) {
+ if (i < 0 || i >= size()) return null;
+ return elementAt(i);
+ }
+ //#switch(key)
+ case "pop": return METHOD;
+ case "reverse": return METHOD;
+ case "toString": return METHOD;
+ case "shift": return METHOD;
+ case "join": return METHOD;
+ case "sort": return METHOD;
+ case "slice": return METHOD;
+ case "push": return METHOD;
+ case "unshift": return METHOD;
+ case "splice": return METHOD;
+ case "length": return N(size());
+ //#end
+ 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 {
+ int oldSize = size();
+ if(i < oldSize) {
+ setElementAt(val,i);
+ } else {
+ if(i > oldSize) setSize(i);
+ insertElementAt(val,i);
+ }
+ }
+ }
+
+ 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 final void setSize(int newSize) {
+ // FEATURE: This could be done a lot more efficiently in BalancedTree
+ int oldSize = size();
+ for(int i=oldSize;i<newSize;i++) insertElementAt(null,i);
+ for(int i=oldSize-1;i>=newSize;i--) removeElementAt(i);
+ }
+
+ public final int length() { return size(); }
+ public final Object elementAt(int i) {
+ if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
+ Object o = getNode(i);
+ return o == NULL ? null : o;
+ }
+ public final void addElement(Object o) {
+ insertNode(size(),o==null ? NULL : o);
+ }
+ public final void setElementAt(Object o, int i) {
+ if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
+ replaceNode(i,o==null ? NULL : o);
+ }
+ public final void insertElementAt(Object o, int i) {
+ if(i < 0 || i > size()) throw new ArrayIndexOutOfBoundsException(i);
+ insertNode(i,o==null ? NULL : o);
+ }
+ public final Object removeElementAt(int i) {
+ if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
+ Object o = deleteNode(i);
+ return o == NULL ? null : o;
+ }
+
+ public final int size() { return treeSize(); }
+ public String typeName() { return "array"; }
+
+ private Object join(String sep) {
+ int length = size();
+ if(length == 0) return "";
+ StringBuffer sb = new StringBuffer(64);
+ int i=0;
+ while(true) {
+ Object o = elementAt(i);
+ if(o != null) sb.append(JS.toString(o));
+ if(++i == length) break;
+ sb.append(sep);
+ }
+ return sb.toString();
+ }
+
+ // FEATURE: Implement this more efficiently
+ private Object reverse() {
+ int size = size();
+ if(size < 2) return this;
+ Vec vec = toVec();
+ clear();
+ for(int i=size-1,j=0;i>=0;i--,j++) insertElementAt(vec.elementAt(i),j);
+ return this;
+ }
+
+ private Object slice(int start, int end) {
+ int length = length();
+ if(start < 0) start = length+start;
+ if(end < 0) end = length+end;
+ if(start < 0) start = 0;
+ if(end < 0) end = 0;
+ if(start > length) start = length;
+ if(end > length) end = length;
+ JSArray a = new JSArray(end-start);
+ for(int i=0;i<end-start;i++)
+ a.setElementAt(elementAt(start+i),i);
+ 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));
+ }
+ };
+ private Object sort(Object 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());
+ }
+ }
+ });
+ } else {
+ vec.sort(defaultSort);
+ }
+ setFromVec(vec);
+ return this;
+ }
+
+ private Object splice(JSArray args) {
+ int oldLength = length();
+ int start = JS.toInt(args.length() < 1 ? null : args.elementAt(0));
+ int deleteCount = JS.toInt(args.length() < 2 ? null : args.elementAt(1));
+ int newCount = args.length() - 2;
+ if(newCount < 0) newCount = 0;
+ if(start < 0) start = oldLength+start;
+ if(start < 0) start = 0;
+ if(start > oldLength) start = oldLength;
+ if(deleteCount < 0) deleteCount = 0;
+ if(deleteCount > oldLength-start) deleteCount = oldLength-start;
+ int newLength = oldLength - deleteCount + newCount;
+ int lengthChange = newLength - oldLength;
+ JSArray ret = new JSArray(deleteCount);
+ for(int i=0;i<deleteCount;i++)
+ ret.setElementAt(elementAt(start+i),i);
+ if(lengthChange > 0) {
+ setSize(newLength);
+ for(int i=newLength-1;i>=start+newCount;i--)
+ setElementAt(elementAt(i-lengthChange),i);
+ } else if(lengthChange < 0) {
+ for(int i=start+newCount;i<newLength;i++)
+ setElementAt(elementAt(i-lengthChange),i);
+ setSize(newLength);
+ }
+ for(int i=0;i<newCount;i++)
+ setElementAt(args.elementAt(i+2),start+i);
+ return ret;
+ }
+
+ protected Vec toVec() {
+ int count = size();
+ Vec vec = new Vec();
+ vec.setSize(count);
+ for(int i=0;i<count;i++) {
+ Object o = getNode(i);
+ vec.setElementAt(o == NULL ? null : o,i);
+ }
+ return vec;
+ }
+
+ protected void setFromVec(Vec vec) {
+ int count = vec.size();
+ clear();
+ for(int i=0;i<count;i++) {
+ Object o = vec.elementAt(i);
+ insertNode(i,o==null ? NULL : o);
+ }
+ }
+
+ public String toString() { return JS.toString(join(",")); }
+}
--- /dev/null
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * The contents of this file are subject to the Netscape Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation. Portions created by Netscape are
+ * Copyright (C) 1997-1999 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU Public License (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of those above.
+ * If you wish to allow use of your version of this file only
+ * under the terms of the GPL and not to allow others to use your
+ * version of this file under the NPL, indicate your decision by
+ * deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete
+ * the provisions above, a recipient may use your version of this
+ * file under either the NPL or the GPL.
+ */
+
+package org.ibex.js;
+
+import java.text.DateFormat;
+
+/**
+ * This class implements the Date native object.
+ * See ECMA 15.9.
+ * @author Mike McCabe
+ * @author Adam Megacz (many modifications
+ */
+public class JSDate extends JS {
+
+ public JSDate() {
+ 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();
+ }
+ }
+
+ public String toString() { return date_format(date, FORMATSPEC_FULL); }
+
+ public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] 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);
+ case "valueOf": return N(this.date);
+ case "getTime": return N(this.date);
+ case "getYear": return N(getYear(date));
+ case "getFullYear": return N(YearFromTime(LocalTime(date)));
+ case "getUTCFullYear": return N(YearFromTime(date));
+ case "getMonth": return N(MonthFromTime(LocalTime(date)));
+ case "getUTCMonth": return N(MonthFromTime(date));
+ case "getDate": return N(DateFromTime(LocalTime(date)));
+ case "getUTCDate": return N(DateFromTime(date));
+ case "getDay": return N(WeekDay(LocalTime(date)));
+ case "getUTCDay": return N(WeekDay(date));
+ case "getHours": return N(HourFromTime(LocalTime(date)));
+ case "getUTCHours": return N(HourFromTime(date));
+ case "getMinutes": return N(MinFromTime(LocalTime(date)));
+ case "getUTCMinutes": return N(MinFromTime(date));
+ case "getSeconds": return N(SecFromTime(LocalTime(date)));
+ case "getUTCSeconds": return N(SecFromTime(date));
+ case "getMilliseconds": return N(msFromTime(LocalTime(date)));
+ case "getUTCMilliseconds": return N(msFromTime(date));
+ case "getTimezoneOffset": return N(getTimezoneOffset(date));
+ //#end
+ return super.callMethod(method, a0, a1, a2, rest, nargs);
+ }
+ case 1: {
+ //#switch(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];
+ for(int i=0; i<nargs; i++) args[i] = i==0 ? a0 : i==1 ? a1 : i==2 ? a2 : rest[i-3];
+ //#switch(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));
+ case "setUTCSeconds": return N(this.makeTime(args, 2, false));
+ case "setMinutes": return N(this.makeTime(args, 3, true));
+ case "setUTCMinutes": return N(this.makeTime(args, 3, false));
+ case "setHours": return N(this.makeTime(args, 4, true));
+ case "setUTCHours": return N(this.makeTime(args, 4, false));
+ case "setDate": return N(this.makeDate(args, 1, true));
+ case "setUTCDate": return N(this.makeDate(args, 1, false));
+ case "setMonth": return N(this.makeDate(args, 2, true));
+ case "setUTCMonth": return N(this.makeDate(args, 2, false));
+ case "setFullYear": return N(this.makeDate(args, 3, true));
+ case "setUTCFullYear": return N(this.makeDate(args, 3, false));
+ //#end
+ }
+ }
+ return super.callMethod(method, a0, a1, a2, rest, nargs);
+ }
+
+ public Object get(Object key) throws JSExn {
+ //#switch(key)
+ case "toString": return METHOD;
+ case "toTimeString": return METHOD;
+ case "toDateString": return METHOD;
+ case "toLocaleString": return METHOD;
+ case "toLocaleTimeString": return METHOD;
+ case "toLocaleDateString": return METHOD;
+ case "toUTCString": return METHOD;
+ case "valueOf": return METHOD;
+ case "getTime": return METHOD;
+ case "getYear": return METHOD;
+ case "getFullYear": return METHOD;
+ case "getUTCFullYear": return METHOD;
+ case "getMonth": return METHOD;
+ case "getUTCMonth": return METHOD;
+ case "getDate": return METHOD;
+ case "getUTCDate": return METHOD;
+ case "getDay": return METHOD;
+ case "getUTCDay": return METHOD;
+ case "getHours": return METHOD;
+ case "getUTCHours": return METHOD;
+ case "getMinutes": return METHOD;
+ case "getUTCMinutes": return METHOD;
+ case "getSeconds": return METHOD;
+ case "getUTCSeconds": return METHOD;
+ case "getMilliseconds": return METHOD;
+ case "getUTCMilliseconds": return METHOD;
+ case "getTimezoneOffset": return METHOD;
+ case "setTime": return METHOD;
+ case "setYear": return METHOD;
+ case "setMilliseconds": return METHOD;
+ case "setUTCMilliseconds": return METHOD;
+ case "setSeconds": return METHOD;
+ case "setUTCSeconds": return METHOD;
+ case "setMinutes": return METHOD;
+ case "setUTCMinutes": return METHOD;
+ case "setHours": return METHOD;
+ case "setUTCHours": return METHOD;
+ case "setDate": return METHOD;
+ case "setUTCDate": return METHOD;
+ case "setMonth": return METHOD;
+ case "setUTCMonth": return METHOD;
+ case "setFullYear": return METHOD;
+ case "setUTCFullYear": return METHOD;
+ //#end
+ return super.get(key);
+ }
+
+ /* ECMA helper functions */
+
+ private static final double HalfTimeDomain = 8.64e15;
+ private static final double HoursPerDay = 24.0;
+ private static final double MinutesPerHour = 60.0;
+ private static final double SecondsPerMinute = 60.0;
+ private static final double msPerSecond = 1000.0;
+ private static final double MinutesPerDay = (HoursPerDay * MinutesPerHour);
+ private static final double SecondsPerDay = (MinutesPerDay * SecondsPerMinute);
+ private static final double SecondsPerHour = (MinutesPerHour * SecondsPerMinute);
+ private static final double msPerDay = (SecondsPerDay * msPerSecond);
+ private static final double msPerHour = (SecondsPerHour * msPerSecond);
+ private static final double msPerMinute = (SecondsPerMinute * msPerSecond);
+
+ private static double Day(double t) {
+ return java.lang.Math.floor(t / msPerDay);
+ }
+
+ private static double TimeWithinDay(double t) {
+ double result;
+ result = t % msPerDay;
+ if (result < 0)
+ result += msPerDay;
+ return result;
+ }
+
+ private static int DaysInYear(int y) {
+ if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))
+ return 366;
+ else
+ return 365;
+ }
+
+
+ /* math here has to be f.p, because we need
+ * floor((1968 - 1969) / 4) == -1
+ */
+ private static double DayFromYear(double y) {
+ return ((365 * ((y)-1970) + java.lang.Math.floor(((y)-1969)/4.0)
+ - java.lang.Math.floor(((y)-1901)/100.0) + java.lang.Math.floor(((y)-1601)/400.0)));
+ }
+
+ private static double TimeFromYear(double y) {
+ return DayFromYear(y) * msPerDay;
+ }
+
+ private static int YearFromTime(double t) {
+ int lo = (int) java.lang.Math.floor((t / msPerDay) / 366) + 1970;
+ int hi = (int) java.lang.Math.floor((t / msPerDay) / 365) + 1970;
+ int mid;
+
+ /* above doesn't work for negative dates... */
+ if (hi < lo) {
+ int temp = lo;
+ lo = hi;
+ hi = temp;
+ }
+
+ /* Use a simple binary search algorithm to find the right
+ year. This seems like brute force... but the computation
+ of hi and lo years above lands within one year of the
+ correct answer for years within a thousand years of
+ 1970; the loop below only requires six iterations
+ for year 270000. */
+ while (hi > lo) {
+ mid = (hi + lo) / 2;
+ if (TimeFromYear(mid) > t) {
+ hi = mid - 1;
+ } else {
+ if (TimeFromYear(mid) <= t) {
+ int temp = mid + 1;
+ if (TimeFromYear(temp) > t) {
+ return mid;
+ }
+ lo = mid + 1;
+ }
+ }
+ }
+ return lo;
+ }
+
+ private static boolean InLeapYear(double t) {
+ return DaysInYear(YearFromTime(t)) == 366;
+ }
+
+ private static int DayWithinYear(double t) {
+ int year = YearFromTime(t);
+ return (int) (Day(t) - DayFromYear(year));
+ }
+ /*
+ * The following array contains the day of year for the first day of
+ * each month, where index 0 is January, and day 0 is January 1.
+ */
+
+ private static double DayFromMonth(int m, boolean leap) {
+ int day = m * 30;
+
+ if (m >= 7) { day += m / 2 - 1; }
+ else if (m >= 2) { day += (m - 1) / 2 - 1; }
+ else { day += m; }
+
+ if (leap && m >= 2) { ++day; }
+
+ return day;
+ }
+
+ private static int MonthFromTime(double t) {
+ int d, step;
+
+ d = DayWithinYear(t);
+
+ if (d < (step = 31))
+ return 0;
+
+ // Originally coded as step += (InLeapYear(t) ? 29 : 28);
+ // but some jits always returned 28!
+ if (InLeapYear(t))
+ step += 29;
+ else
+ step += 28;
+
+ if (d < step)
+ return 1;
+ if (d < (step += 31))
+ return 2;
+ if (d < (step += 30))
+ return 3;
+ if (d < (step += 31))
+ return 4;
+ if (d < (step += 30))
+ return 5;
+ if (d < (step += 31))
+ return 6;
+ if (d < (step += 31))
+ return 7;
+ if (d < (step += 30))
+ return 8;
+ if (d < (step += 31))
+ return 9;
+ if (d < (step += 30))
+ return 10;
+ return 11;
+ }
+
+ private static int DateFromTime(double t) {
+ int d, step, next;
+
+ d = DayWithinYear(t);
+ if (d <= (next = 30))
+ return d + 1;
+ step = next;
+
+ // Originally coded as next += (InLeapYear(t) ? 29 : 28);
+ // but some jits always returned 28!
+ if (InLeapYear(t))
+ next += 29;
+ else
+ next += 28;
+
+ if (d <= next)
+ return d - step;
+ step = next;
+ if (d <= (next += 31))
+ return d - step;
+ step = next;
+ if (d <= (next += 30))
+ return d - step;
+ step = next;
+ if (d <= (next += 31))
+ return d - step;
+ step = next;
+ if (d <= (next += 30))
+ return d - step;
+ step = next;
+ if (d <= (next += 31))
+ return d - step;
+ step = next;
+ if (d <= (next += 31))
+ return d - step;
+ step = next;
+ if (d <= (next += 30))
+ return d - step;
+ step = next;
+ if (d <= (next += 31))
+ return d - step;
+ step = next;
+ if (d <= (next += 30))
+ return d - step;
+ step = next;
+
+ return d - step;
+ }
+
+ private static int WeekDay(double t) {
+ double result;
+ result = Day(t) + 4;
+ result = result % 7;
+ if (result < 0)
+ result += 7;
+ return (int) result;
+ }
+
+ private static double Now() {
+ return (double) System.currentTimeMillis();
+ }
+
+ /* Should be possible to determine the need for this dynamically
+ * if we go with the workaround... I'm not using it now, because I
+ * can't think of any clean way to make toLocaleString() and the
+ * time zone (comment) in toString match the generated string
+ * values. Currently it's wrong-but-consistent in all but the
+ * most recent betas of the JRE - seems to work in 1.1.7.
+ */
+ private final static boolean TZO_WORKAROUND = false;
+ private static double DaylightSavingTA(double t) {
+ if (!TZO_WORKAROUND) {
+ java.util.Date date = new java.util.Date((long) t);
+ if (thisTimeZone.inDaylightTime(date))
+ return msPerHour;
+ else
+ return 0;
+ } else {
+ /* Use getOffset if inDaylightTime() is broken, because it
+ * seems to work acceptably. We don't switch over to it
+ * entirely, because it requires (expensive) exploded date arguments,
+ * and the api makes it impossible to handle dst
+ * changeovers cleanly.
+ */
+
+ // Hardcode the assumption that the changeover always
+ // happens at 2:00 AM:
+ t += LocalTZA + (HourFromTime(t) <= 2 ? msPerHour : 0);
+
+ int year = YearFromTime(t);
+ double offset = thisTimeZone.getOffset(year > 0 ? 1 : 0,
+ year,
+ MonthFromTime(t),
+ DateFromTime(t),
+ WeekDay(t),
+ (int)TimeWithinDay(t));
+
+ if ((offset - LocalTZA) != 0)
+ return msPerHour;
+ else
+ return 0;
+ // return offset - LocalTZA;
+ }
+ }
+
+ private static double LocalTime(double t) {
+ return t + LocalTZA + DaylightSavingTA(t);
+ }
+
+ public static double internalUTC(double t) {
+ return t - LocalTZA - DaylightSavingTA(t - LocalTZA);
+ }
+
+ private static int HourFromTime(double t) {
+ double result;
+ result = java.lang.Math.floor(t / msPerHour) % HoursPerDay;
+ if (result < 0)
+ result += HoursPerDay;
+ return (int) result;
+ }
+
+ private static int MinFromTime(double t) {
+ double result;
+ result = java.lang.Math.floor(t / msPerMinute) % MinutesPerHour;
+ if (result < 0)
+ result += MinutesPerHour;
+ return (int) result;
+ }
+
+ private static int SecFromTime(double t) {
+ double result;
+ result = java.lang.Math.floor(t / msPerSecond) % SecondsPerMinute;
+ if (result < 0)
+ result += SecondsPerMinute;
+ return (int) result;
+ }
+
+ private static int msFromTime(double t) {
+ double result;
+ result = t % msPerSecond;
+ if (result < 0)
+ result += msPerSecond;
+ return (int) result;
+ }
+
+ private static double MakeTime(double hour, double min,
+ double sec, double ms)
+ {
+ return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec)
+ * msPerSecond + ms;
+ }
+
+ private static double MakeDay(double year, double month, double date) {
+ double result;
+ boolean leap;
+ double yearday;
+ double monthday;
+
+ year += java.lang.Math.floor(month / 12);
+
+ month = month % 12;
+ if (month < 0)
+ month += 12;
+
+ leap = (DaysInYear((int) year) == 366);
+
+ yearday = java.lang.Math.floor(TimeFromYear(year) / msPerDay);
+ monthday = DayFromMonth((int) month, leap);
+
+ result = yearday
+ + monthday
+ + date - 1;
+ return result;
+ }
+
+ private static double MakeDate(double day, double time) {
+ return day * msPerDay + time;
+ }
+
+ private static double TimeClip(double d) {
+ if (d != d ||
+ d == Double.POSITIVE_INFINITY ||
+ d == Double.NEGATIVE_INFINITY ||
+ java.lang.Math.abs(d) > HalfTimeDomain)
+ {
+ return Double.NaN;
+ }
+ if (d > 0.0)
+ return java.lang.Math.floor(d + 0.);
+ else
+ return java.lang.Math.ceil(d + 0.);
+ }
+
+ /* end of ECMA helper functions */
+
+ /* find UTC time from given date... no 1900 correction! */
+ public static double date_msecFromDate(double year, double mon,
+ double mday, double hour,
+ double min, double sec,
+ double msec)
+ {
+ double day;
+ double time;
+ double result;
+
+ day = MakeDay(year, mon, mday);
+ time = MakeTime(hour, min, sec, msec);
+ result = MakeDate(day, time);
+ return result;
+ }
+
+
+ private static final int MAXARGS = 7;
+ private static double jsStaticJSFunction_UTC(Object[] args) {
+ double array[] = new double[MAXARGS];
+ int loop;
+ double d;
+
+ for (loop = 0; loop < MAXARGS; loop++) {
+ if (loop < args.length) {
+ d = _toNumber(args[loop]);
+ if (d != d || Double.isInfinite(d)) {
+ return Double.NaN;
+ }
+ array[loop] = toDouble(args[loop]);
+ } else {
+ array[loop] = 0;
+ }
+ }
+
+ /* adjust 2-digit years into the 20th century */
+ if (array[0] >= 0 && array[0] <= 99)
+ array[0] += 1900;
+
+ /* if we got a 0 for 'date' (which is out of range)
+ * pretend it's a 1. (So Date.UTC(1972, 5) works) */
+ if (array[2] < 1)
+ array[2] = 1;
+
+ d = date_msecFromDate(array[0], array[1], array[2],
+ array[3], array[4], array[5], array[6]);
+ d = TimeClip(d);
+ return d;
+ // return N(d);
+ }
+
+ /*
+ * Use ported code from jsdate.c rather than the locale-specific
+ * date-parsing code from Java, to keep js and rhino consistent.
+ * Is this the right strategy?
+ */
+
+ /* for use by date_parse */
+
+ /* replace this with byte arrays? Cheaper? */
+ private static String wtb[] = {
+ "am", "pm",
+ "monday", "tuesday", "wednesday", "thursday", "friday",
+ "saturday", "sunday",
+ "january", "february", "march", "april", "may", "june",
+ "july", "august", "september", "october", "november", "december",
+ "gmt", "ut", "utc", "est", "edt", "cst", "cdt",
+ "mst", "mdt", "pst", "pdt"
+ /* time zone table needs to be expanded */
+ };
+
+ private static int ttb[] = {
+ -1, -2, 0, 0, 0, 0, 0, 0, 0, /* AM/PM */
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 10000 + 0, 10000 + 0, 10000 + 0, /* UT/UTC */
+ 10000 + 5 * 60, 10000 + 4 * 60, /* EDT */
+ 10000 + 6 * 60, 10000 + 5 * 60,
+ 10000 + 7 * 60, 10000 + 6 * 60,
+ 10000 + 8 * 60, 10000 + 7 * 60
+ };
+
+ /* helper for date_parse */
+ private static boolean date_regionMatches(String s1, int s1off,
+ String s2, int s2off,
+ int count)
+ {
+ boolean result = false;
+ /* return true if matches, otherwise, false */
+ int s1len = s1.length();
+ int s2len = s2.length();
+
+ while (count > 0 && s1off < s1len && s2off < s2len) {
+ if (Character.toLowerCase(s1.charAt(s1off)) !=
+ Character.toLowerCase(s2.charAt(s2off)))
+ break;
+ s1off++;
+ s2off++;
+ count--;
+ }
+
+ if (count == 0) {
+ result = true;
+ }
+ return result;
+ }
+
+ private static double date_parseString(String s) {
+ double msec;
+
+ int year = -1;
+ int mon = -1;
+ int mday = -1;
+ int hour = -1;
+ int min = -1;
+ int sec = -1;
+ char c = 0;
+ char si = 0;
+ int i = 0;
+ int n = -1;
+ double tzoffset = -1;
+ char prevc = 0;
+ int limit = 0;
+ boolean seenplusminus = false;
+
+ if (s == null) // ??? Will s be null?
+ return Double.NaN;
+ limit = s.length();
+ while (i < limit) {
+ c = s.charAt(i);
+ i++;
+ if (c <= ' ' || c == ',' || c == '-') {
+ if (i < limit) {
+ si = s.charAt(i);
+ if (c == '-' && '0' <= si && si <= '9') {
+ prevc = c;
+ }
+ }
+ continue;
+ }
+ if (c == '(') { /* comments) */
+ int depth = 1;
+ while (i < limit) {
+ c = s.charAt(i);
+ i++;
+ if (c == '(')
+ depth++;
+ else if (c == ')')
+ if (--depth <= 0)
+ break;
+ }
+ continue;
+ }
+ if ('0' <= c && c <= '9') {
+ n = c - '0';
+ while (i < limit && '0' <= (c = s.charAt(i)) && c <= '9') {
+ n = n * 10 + c - '0';
+ i++;
+ }
+
+ /* allow TZA before the year, so
+ * 'Wed Nov 05 21:49:11 GMT-0800 1997'
+ * works */
+
+ /* uses of seenplusminus allow : in TZA, so Java
+ * no-timezone style of GMT+4:30 works
+ */
+ if ((prevc == '+' || prevc == '-')/* && year>=0 */) {
+ /* make ':' case below change tzoffset */
+ seenplusminus = true;
+
+ /* offset */
+ if (n < 24)
+ n = n * 60; /* EG. "GMT-3" */
+ else
+ n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
+ if (prevc == '+') /* plus means east of GMT */
+ n = -n;
+ if (tzoffset != 0 && tzoffset != -1)
+ return Double.NaN;
+ tzoffset = n;
+ } else if (n >= 70 ||
+ (prevc == '/' && mon >= 0 && mday >= 0 && year < 0)) {
+ if (year >= 0)
+ return Double.NaN;
+ else if (c <= ' ' || c == ',' || c == '/' || i >= limit)
+ year = n < 100 ? n + 1900 : n;
+ else
+ return Double.NaN;
+ } else if (c == ':') {
+ if (hour < 0)
+ hour = /*byte*/ n;
+ else if (min < 0)
+ min = /*byte*/ n;
+ else
+ return Double.NaN;
+ } else if (c == '/') {
+ if (mon < 0)
+ mon = /*byte*/ n-1;
+ else if (mday < 0)
+ mday = /*byte*/ n;
+ else
+ return Double.NaN;
+ } else if (i < limit && c != ',' && c > ' ' && c != '-') {
+ return Double.NaN;
+ } else if (seenplusminus && n < 60) { /* handle GMT-3:30 */
+ if (tzoffset < 0)
+ tzoffset -= n;
+ else
+ tzoffset += n;
+ } else if (hour >= 0 && min < 0) {
+ min = /*byte*/ n;
+ } else if (min >= 0 && sec < 0) {
+ sec = /*byte*/ n;
+ } else if (mday < 0) {
+ mday = /*byte*/ n;
+ } else {
+ return Double.NaN;
+ }
+ prevc = 0;
+ } else if (c == '/' || c == ':' || c == '+' || c == '-') {
+ prevc = c;
+ } else {
+ int st = i - 1;
+ int k;
+ while (i < limit) {
+ c = s.charAt(i);
+ if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')))
+ break;
+ i++;
+ }
+ if (i <= st + 1)
+ return Double.NaN;
+ for (k = wtb.length; --k >= 0;)
+ if (date_regionMatches(wtb[k], 0, s, st, i-st)) {
+ int action = ttb[k];
+ if (action != 0) {
+ if (action < 0) {
+ /*
+ * AM/PM. Count 12:30 AM as 00:30, 12:30 PM as
+ * 12:30, instead of blindly adding 12 if PM.
+ */
+ if (hour > 12 || hour < 0) {
+ return Double.NaN;
+ } else {
+ if (action == -1 && hour == 12) { // am
+ hour = 0;
+ } else if (action == -2 && hour != 12) {// pm
+ hour += 12;
+ }
+ }
+ } else if (action <= 13) { /* month! */
+ if (mon < 0) {
+ mon = /*byte*/ (action - 2);
+ } else {
+ return Double.NaN;
+ }
+ } else {
+ tzoffset = action - 10000;
+ }
+ }
+ break;
+ }
+ if (k < 0)
+ return Double.NaN;
+ prevc = 0;
+ }
+ }
+ if (year < 0 || mon < 0 || mday < 0)
+ return Double.NaN;
+ if (sec < 0)
+ sec = 0;
+ if (min < 0)
+ min = 0;
+ if (hour < 0)
+ hour = 0;
+ if (tzoffset == -1) { /* no time zone specified, have to use local */
+ double time;
+ time = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
+ return internalUTC(time);
+ }
+
+ msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
+ msec += tzoffset * msPerMinute;
+ return msec;
+ }
+
+ private static double jsStaticJSFunction_parse(String s) {
+ return date_parseString(s);
+ }
+
+ private static final int FORMATSPEC_FULL = 0;
+ private static final int FORMATSPEC_DATE = 1;
+ private static final int FORMATSPEC_TIME = 2;
+
+ private static String date_format(double t, int format) {
+ if (t != t)
+ return NaN_date_str;
+
+ StringBuffer result = new StringBuffer(60);
+ double local = LocalTime(t);
+
+ /* offset from GMT in minutes. The offset includes daylight savings,
+ if it applies. */
+ int minutes = (int) java.lang.Math.floor((LocalTZA + DaylightSavingTA(t))
+ / msPerMinute);
+ /* map 510 minutes to 0830 hours */
+ int offset = (minutes / 60) * 100 + minutes % 60;
+
+ String dateStr = Integer.toString(DateFromTime(local));
+ String hourStr = Integer.toString(HourFromTime(local));
+ String minStr = Integer.toString(MinFromTime(local));
+ String secStr = Integer.toString(SecFromTime(local));
+ String offsetStr = Integer.toString(offset > 0 ? offset : -offset);
+ int year = YearFromTime(local);
+ String yearStr = Integer.toString(year > 0 ? year : -year);
+
+ /* Tue Oct 31 09:41:40 GMT-0800 (PST) 2000 */
+ /* Tue Oct 31 2000 */
+ /* 09:41:40 GMT-0800 (PST) */
+
+ if (format != FORMATSPEC_TIME) {
+ result.append(days[WeekDay(local)]);
+ result.append(' ');
+ result.append(months[MonthFromTime(local)]);
+ if (dateStr.length() == 1)
+ result.append(" 0");
+ else
+ result.append(' ');
+ result.append(dateStr);
+ result.append(' ');
+ }
+
+ if (format != FORMATSPEC_DATE) {
+ if (hourStr.length() == 1)
+ result.append('0');
+ result.append(hourStr);
+ if (minStr.length() == 1)
+ result.append(":0");
+ else
+ result.append(':');
+ result.append(minStr);
+ if (secStr.length() == 1)
+ result.append(":0");
+ else
+ result.append(':');
+ result.append(secStr);
+ if (offset > 0)
+ result.append(" GMT+");
+ else
+ result.append(" GMT-");
+ for (int i = offsetStr.length(); i < 4; i++)
+ result.append('0');
+ result.append(offsetStr);
+
+ if (timeZoneFormatter == null)
+ timeZoneFormatter = new java.text.SimpleDateFormat("zzz");
+
+ if (timeZoneFormatter != null) {
+ result.append(" (");
+ java.util.Date date = new java.util.Date((long) t);
+ result.append(timeZoneFormatter.format(date));
+ result.append(')');
+ }
+ if (format != FORMATSPEC_TIME)
+ result.append(' ');
+ }
+
+ if (format != FORMATSPEC_TIME) {
+ if (year < 0)
+ result.append('-');
+ for (int i = yearStr.length(); i < 4; i++)
+ result.append('0');
+ result.append(yearStr);
+ }
+
+ 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 toDouble(double d) { return d; }
+
+ public JSDate(Object a0, Object a1, Object a2, Object[] rest, int nargs) {
+
+ JSDate obj = this;
+ switch (nargs) {
+ case 0: {
+ obj.date = Now();
+ return;
+ }
+ 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
+ date = _toNumber(a0);
+ } else {
+ // it's a string; parse it.
+ String str = (String) a0;
+ date = date_parseString(str);
+ }
+ obj.date = TimeClip(date);
+ return;
+ }
+ default: {
+ // multiple arguments; year, month, day etc.
+ double array[] = new double[MAXARGS];
+ array[0] = toDouble(a0);
+ array[1] = toDouble(a1);
+ if (nargs >= 2) array[2] = toDouble(a2);
+ for(int i=0; i<nargs; i++) {
+ double d = _toNumber(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
+ if (d != d || Double.isInfinite(d)) {
+ obj.date = Double.NaN;
+ return;
+ }
+ array[i] = d;
+ }
+
+ /* adjust 2-digit years into the 20th century */
+ if (array[0] >= 0 && array[0] <= 99)
+ array[0] += 1900;
+
+ /* if we got a 0 for 'date' (which is out of range)
+ * pretend it's a 1 */
+ if (array[2] < 1)
+ array[2] = 1;
+
+ double day = MakeDay(array[0], array[1], array[2]);
+ double time = MakeTime(array[3], array[4], array[5], array[6]);
+ time = MakeDate(day, time);
+ time = internalUTC(time);
+ obj.date = TimeClip(time);
+
+ return;
+ }
+ }
+ }
+
+ /* constants for toString, toUTCString */
+ private static String NaN_date_str = "Invalid Date";
+
+ private static String[] days = {
+ "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
+ };
+
+ private static String[] months = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ private static String toLocale_helper(double t,
+ java.text.DateFormat formatter)
+ {
+ if (t != t)
+ return NaN_date_str;
+
+ java.util.Date tempdate = new java.util.Date((long) t);
+ return formatter.format(tempdate);
+ }
+
+ private static String toLocaleString(double date) {
+ if (localeDateTimeFormatter == null)
+ localeDateTimeFormatter =
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
+
+ return toLocale_helper(date, localeDateTimeFormatter);
+ }
+
+ private static String toLocaleTimeString(double date) {
+ if (localeTimeFormatter == null)
+ localeTimeFormatter = DateFormat.getTimeInstance(DateFormat.LONG);
+
+ return toLocale_helper(date, localeTimeFormatter);
+ }
+
+ private static String toLocaleDateString(double date) {
+ if (localeDateFormatter == null)
+ localeDateFormatter = DateFormat.getDateInstance(DateFormat.LONG);
+
+ return toLocale_helper(date, localeDateFormatter);
+ }
+
+ private static String toUTCString(double date) {
+ StringBuffer result = new StringBuffer(60);
+
+ String dateStr = Integer.toString(DateFromTime(date));
+ String hourStr = Integer.toString(HourFromTime(date));
+ String minStr = Integer.toString(MinFromTime(date));
+ String secStr = Integer.toString(SecFromTime(date));
+ int year = YearFromTime(date);
+ String yearStr = Integer.toString(year > 0 ? year : -year);
+
+ result.append(days[WeekDay(date)]);
+ result.append(", ");
+ if (dateStr.length() == 1)
+ result.append('0');
+ result.append(dateStr);
+ result.append(' ');
+ result.append(months[MonthFromTime(date)]);
+ if (year < 0)
+ result.append(" -");
+ else
+ result.append(' ');
+ int i;
+ for (i = yearStr.length(); i < 4; i++)
+ result.append('0');
+ result.append(yearStr);
+
+ if (hourStr.length() == 1)
+ result.append(" 0");
+ else
+ result.append(' ');
+ result.append(hourStr);
+ if (minStr.length() == 1)
+ result.append(":0");
+ else
+ result.append(':');
+ result.append(minStr);
+ if (secStr.length() == 1)
+ result.append(":0");
+ else
+ result.append(':');
+ result.append(secStr);
+
+ result.append(" GMT");
+ return result.toString();
+ }
+
+ private static double getYear(double date) {
+ int result = YearFromTime(LocalTime(date));
+ result -= 1900;
+ return result;
+ }
+
+ private static double getTimezoneOffset(double date) {
+ return (date - LocalTime(date)) / msPerMinute;
+ }
+
+ public double setTime(double time) {
+ this.date = TimeClip(time);
+ return this.date;
+ }
+
+ private double makeTime(Object[] args, int maxargs, boolean local) {
+ int i;
+ double conv[] = new double[4];
+ double hour, min, sec, msec;
+ double lorutime; /* Local or UTC version of date */
+
+ double time;
+ double result;
+
+ double date = this.date;
+
+ /* just return NaN if the date is already NaN */
+ if (date != date)
+ return date;
+
+ /* Satisfy the ECMA rule that if a function is called with
+ * fewer arguments than the specified formal arguments, the
+ * remaining arguments are set to undefined. Seems like all
+ * the Date.setWhatever functions in ECMA are only varargs
+ * beyond the first argument; this should be set to undefined
+ * if it's not given. This means that "d = new Date();
+ * d.setMilliseconds()" returns NaN. Blech.
+ */
+ if (args.length == 0)
+ args = new Object[] { null };
+
+ for (i = 0; i < args.length && i < maxargs; i++) {
+ conv[i] = _toNumber(args[i]);
+
+ // limit checks that happen in MakeTime in ECMA.
+ if (conv[i] != conv[i] || Double.isInfinite(conv[i])) {
+ this.date = Double.NaN;
+ return this.date;
+ }
+ conv[i] = toDouble(conv[i]);
+ }
+
+ if (local)
+ lorutime = LocalTime(date);
+ else
+ lorutime = date;
+
+ i = 0;
+ int stop = args.length;
+
+ if (maxargs >= 4 && i < stop)
+ hour = conv[i++];
+ else
+ hour = HourFromTime(lorutime);
+
+ if (maxargs >= 3 && i < stop)
+ min = conv[i++];
+ else
+ min = MinFromTime(lorutime);
+
+ if (maxargs >= 2 && i < stop)
+ sec = conv[i++];
+ else
+ sec = SecFromTime(lorutime);
+
+ if (maxargs >= 1 && i < stop)
+ msec = conv[i++];
+ else
+ msec = msFromTime(lorutime);
+
+ time = MakeTime(hour, min, sec, msec);
+ result = MakeDate(Day(lorutime), time);
+
+ if (local)
+ result = internalUTC(result);
+ date = TimeClip(result);
+
+ this.date = date;
+ return date;
+ }
+
+ private double setHours(Object[] args) {
+ return makeTime(args, 4, true);
+ }
+
+ private double setUTCHours(Object[] args) {
+ return makeTime(args, 4, false);
+ }
+
+ private double makeDate(Object[] args, int maxargs, boolean local) {
+ int i;
+ double conv[] = new double[3];
+ double year, month, day;
+ double lorutime; /* local or UTC version of date */
+ double result;
+
+ double date = this.date;
+
+ /* See arg padding comment in makeTime.*/
+ if (args.length == 0)
+ args = new Object[] { null };
+
+ for (i = 0; i < args.length && i < maxargs; i++) {
+ conv[i] = _toNumber(args[i]);
+
+ // limit checks that happen in MakeDate in ECMA.
+ if (conv[i] != conv[i] || Double.isInfinite(conv[i])) {
+ this.date = Double.NaN;
+ return this.date;
+ }
+ conv[i] = toDouble(conv[i]);
+ }
+
+ /* return NaN if date is NaN and we're not setting the year,
+ * If we are, use 0 as the time. */
+ if (date != date) {
+ if (args.length < 3) {
+ return Double.NaN;
+ } else {
+ lorutime = 0;
+ }
+ } else {
+ if (local)
+ lorutime = LocalTime(date);
+ else
+ lorutime = date;
+ }
+
+ i = 0;
+ int stop = args.length;
+
+ if (maxargs >= 3 && i < stop)
+ year = conv[i++];
+ else
+ year = YearFromTime(lorutime);
+
+ if (maxargs >= 2 && i < stop)
+ month = conv[i++];
+ else
+ month = MonthFromTime(lorutime);
+
+ if (maxargs >= 1 && i < stop)
+ day = conv[i++];
+ else
+ day = DateFromTime(lorutime);
+
+ day = MakeDay(year, month, day); /* day within year */
+ result = MakeDate(day, TimeWithinDay(lorutime));
+
+ if (local)
+ result = internalUTC(result);
+
+ date = TimeClip(result);
+
+ this.date = date;
+ return date;
+ }
+
+ private double setYear(double year) {
+ double day, result;
+ if (year != year || Double.isInfinite(year)) {
+ this.date = Double.NaN;
+ return this.date;
+ }
+
+ if (this.date != this.date) {
+ this.date = 0;
+ } else {
+ this.date = LocalTime(this.date);
+ }
+
+ if (year >= 0 && year <= 99)
+ year += 1900;
+
+ day = MakeDay(year, MonthFromTime(this.date), DateFromTime(this.date));
+ result = MakeDate(day, TimeWithinDay(this.date));
+ result = internalUTC(result);
+
+ this.date = TimeClip(result);
+ return this.date;
+ }
+
+
+ // private static final int
+ // Id_toGMTString = Id_toUTCString; // Alias, see Ecma B.2.6
+// #/string_id_map#
+
+ /* cached values */
+ private static java.util.TimeZone thisTimeZone;
+ private static double LocalTZA;
+ private static java.text.DateFormat timeZoneFormatter;
+ private static java.text.DateFormat localeDateTimeFormatter;
+ private static java.text.DateFormat localeDateFormatter;
+ private static java.text.DateFormat localeTimeFormatter;
+
+ private double date;
+
+ public long getRawTime() { return (long)this.date; }
+}
+
+
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+import org.ibex.util.*;
+import java.io.*;
+
+/** 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 + "\"");
+ }
+ }
+ }
+ public void printStackTrace() { printStackTrace(System.err); }
+ public void printStackTrace(PrintWriter pw) {
+ for(int i=0; i<backtrace.size(); i++) pw.println(" at " + (String) backtrace.elementAt(i));
+ super.printStackTrace(pw);
+ }
+ public void printStackTrace(PrintStream ps) {
+ 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 getMessage() { return toString(); }
+ public Object getObject() { return js; }
+ public void addBacktrace(String line) { backtrace.addElement(line); }
+
+
+ public static class IO extends JSExn {
+ public IO(java.io.IOException ioe) {
+ super("ibex.io: " + ioe.toString());
+ JS.warn(ioe);
+ }
+ }
+}
+
+/** should only be used for failed coercions */
+class JSRuntimeExn extends RuntimeException {
+ private Object js = null;
+ public JSRuntimeExn(Object js) { this.js = js; }
+ public String toString() { return "JSRuntimeExn: " + js; }
+ public String getMessage() { return toString(); }
+ public Object getObject() { return js; }
+}
+
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+import java.io.*;
+import org.ibex.util.*;
+
+/** A JavaScript function, compiled into bytecode */
+class JSFunction extends JS implements ByteCodes, Tokens, Task {
+
+
+ // Fields and Accessors ///////////////////////////////////////////////
+
+ int numFormalArgs = 0; ///< the number of formal arguments
+
+ String sourceName; ///< the source code file that this block was drawn from
+ private int firstLine = -1; ///< the first line of this script
+
+ int[] line = new int[10]; ///< the line numbers
+ int[] op = new int[10]; ///< the instructions
+ Object[] arg = new Object[10]; ///< the arguments to the instructions
+ int size = 0; ///< the number of instruction/argument pairs
+
+ JSScope parentScope; ///< the default scope to use as a parent scope when executing this
+
+
+ // 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());
+ 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
+ // NOTE: Neither *this* function nor the new function should be modified after this call
+ ret.op = this.op;
+ ret.arg = this.arg;
+ ret.line = this.line;
+ ret.size = this.size;
+ 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);
+ return cx.resume();
+ }
+
+ public JSScope getParentScope() { return parentScope; }
+
+ // Adding and Altering Bytecodes ///////////////////////////////////////////////////
+
+ JSFunction(String sourceName, int firstLine, JSScope parentScope) {
+ this.sourceName = sourceName;
+ this.firstLine = firstLine;
+ this.parentScope = parentScope;
+ }
+
+ int get(int pos) { return op[pos]; }
+ Object getArg(int pos) { return arg[pos]; }
+ void set(int pos, int op_, Object arg_) { op[pos] = op_; arg[pos] = arg_; }
+ void set(int pos, Object arg_) { arg[pos] = arg_; }
+ int pop() { size--; arg[size] = null; return op[size]; }
+ void paste(JSFunction other) { for(int i=0; i<other.size; i++) add(other.line[i], other.op[i], other.arg[i]); }
+ JSFunction add(int line, int op_) { return add(line, op_, null); }
+ JSFunction add(int line, int op_, Object arg_) {
+ if (size == op.length - 1) {
+ int[] line2 = new int[op.length * 2]; System.arraycopy(this.line, 0, line2, 0, op.length); this.line = line2;
+ Object[] arg2 = new Object[op.length * 2]; System.arraycopy(arg, 0, arg2, 0, arg.length); arg = arg2;
+ int[] op2 = new int[op.length * 2]; System.arraycopy(op, 0, op2, 0, op.length); op = op2;
+ }
+ this.line[size] = line;
+ op[size] = op_;
+ arg[size] = arg_;
+ size++;
+ return this;
+ }
+
+
+ // Debugging //////////////////////////////////////////////////////////////////////
+
+ public String toString() { return "JSFunction [" + sourceName + ":" + firstLine + "]"; }
+
+ public String dump() {
+ 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(") :");
+ 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]);
+ 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]));
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+
+}
+
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL ]
+
+package org.ibex.js;
+
+/** The JavaScript Math object */
+public class JSMath extends JS {
+
+ 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 {
+ switch(nargs) {
+ case 0: {
+ //#switch(method)
+ case "random": return new Double(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)));
+ //#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)));
+ //#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)
+ case "E": return E;
+ case "LN10": return LN10;
+ case "LN2": return LN2;
+ case "LOG10E": return LOG10E;
+ case "LOG2E": return LOG2E;
+ case "PI": return PI;
+ case "SQRT1_2": return SQRT1_2;
+ case "SQRT2": return SQRT2;
+ case "ceil": return METHOD;
+ case "floor": return METHOD;
+ case "round": return METHOD;
+ case "min": return METHOD;
+ case "max": return METHOD;
+ case "pow": return METHOD;
+ case "atan2": return METHOD;
+ case "abs": return METHOD;
+ case "sin": return METHOD;
+ case "cos": return METHOD;
+ case "tan": return METHOD;
+ case "asin": return METHOD;
+ case "acos": return METHOD;
+ case "atan": return METHOD;
+ case "sqrt": return METHOD;
+ case "exp": return METHOD;
+ case "log": return METHOD;
+ case "random": return METHOD;
+ //#end
+ return super.get(key);
+ }
+}
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+import org.ibex.util.*;
+import java.io.*;
+import java.util.*;
+import java.lang.reflect.*;
+
+/** Automatic JS-ification via Reflection (not for use in the core) */
+public class JSReflection extends JS {
+
+ public static Object 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 Object[]) {
+ // FIXME: get element type here
+ }
+ throw new JSExn("Reflection object tried to return a " + o.getClass().getName());
+ }
+
+ 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"); }
+ }
+
+ // FIXME public static class Hash { }
+ // FIXME public Enumeration keys() throws JSExn { }
+
+ public Object get(Object key) throws JSExn {
+ String k = toString(key);
+ try {
+ Field f = this.getClass().getField(k);
+ return wrap(f.get(this));
+ } 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;
+ } catch (SecurityException nfe) { }
+ return null;
+ }
+
+ public void put(Object key, Object 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 {
+ String k = toString(method);
+ try {
+ Method[] methods = this.getClass().getMethods();
+ for(int j=0; j<methods.length; j++) {
+ if (methods[j].getName().equals(k) && methods[j].getParameterTypes().length == nargs) {
+ Object[] args = new Object[nargs];
+ for(int i = 0; i<args.length; i++) {
+ if (i==0) args[i] = a0;
+ else if (i==1) args[i] = a1;
+ else if (i==2) args[i] = a2;
+ else args[i] = rest[i-3];
+ }
+ return wrap(methods[j].invoke(this, args));
+ }
+ }
+ } catch (IllegalAccessException nfe) {
+ } catch (InvocationTargetException it) {
+ Throwable ite = it.getTargetException();
+ if (ite instanceof JSExn) throw ((JSExn)ite);
+ JS.warn(ite);
+ throw new JSExn("unhandled reflected exception: " + ite.toString());
+ } catch (SecurityException nfe) { }
+ throw new JSExn("called a reflection method with the wrong number of arguments");
+ }
+}
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+import gnu.regexp.*;
+
+/** A JavaScript regular expression object */
+public class JSRegexp extends JS {
+ private boolean global;
+ private RE re;
+ private int lastIndex;
+
+ public JSRegexp(Object arg0, Object arg1) throws JSExn {
+ if(arg0 instanceof JSRegexp) {
+ JSRegexp r = (JSRegexp) arg0;
+ this.global = r.global;
+ this.re = r.re;
+ this.lastIndex = r.lastIndex;
+ } else {
+ String pattern = (String)arg0;
+ String sFlags = null;
+ int flags = 0;
+ if(arg1 != null) sFlags = (String)arg1;
+ if(sFlags == null) sFlags = "";
+ for(int i=0;i<sFlags.length();i++) {
+ switch(sFlags.charAt(i)) {
+ case 'i': flags |= RE.REG_ICASE; break;
+ case 'm': flags |= RE.REG_MULTILINE; break;
+ case 'g': global = true; break;
+ default: throw new JSExn("Invalid flag in regexp \"" + sFlags.charAt(i) + "\"");
+ }
+ }
+ re = newRE(pattern,flags);
+ put("source", pattern);
+ put("global", B(global));
+ put("ignoreCase", B(flags & RE.REG_ICASE));
+ put("multiline", B(flags & RE.REG_MULTILINE));
+ }
+ }
+
+ public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+ switch(nargs) {
+ case 1: {
+ //#switch(method)
+ case "exec": {
+ String s = (String)a0;
+ int start = global ? lastIndex : 0;
+ if(start < 0 || start >= s.length()) { lastIndex = 0; return null; }
+ REMatch match = re.getMatch(s,start);
+ if(global) lastIndex = match == null ? s.length() : match.getEndIndex();
+ return match == null ? null : matchToExecResult(match,re,s);
+ }
+ case "test": {
+ String s = (String)a0;
+ if (!global) return B(re.getMatch(s) != null);
+ int start = global ? lastIndex : 0;
+ if(start < 0 || start >= s.length()) { lastIndex = 0; return null; }
+ REMatch match = re.getMatch(s,start);
+ lastIndex = match != null ? s.length() : match.getEndIndex();
+ return B(match != null);
+ }
+ case "toString": return toString(a0);
+ case "stringMatch": return stringMatch(a0,a1);
+ case "stringSearch": return stringSearch(a0,a1);
+ //#end
+ break;
+ }
+ case 2: {
+ //#switch(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)
+ case "exec": return METHOD;
+ case "test": return METHOD;
+ case "toString": return METHOD;
+ case "lastIndex": return N(lastIndex);
+ //#end
+ return super.get(key);
+ }
+
+ public void put(Object key, Object value) throws JSExn {
+ if(key.equals("lastIndex")) lastIndex = JS.toNumber(value).intValue();
+ super.put(key,value);
+ }
+
+ private static Object matchToExecResult(REMatch match, RE re, String s) {
+ try {
+ JS ret = new JS();
+ ret.put("index", N(match.getStartIndex()));
+ ret.put("input",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));
+ 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");
+ }
+ }
+
+ public static Object stringMatch(Object o, Object arg0) throws JSExn {
+ String s = o.toString();
+ RE re;
+ JSRegexp regexp = null;
+ if(arg0 instanceof JSRegexp) {
+ regexp = (JSRegexp) arg0;
+ re = regexp.re;
+ } else {
+ re = newRE(arg0.toString(),0);
+ }
+
+ if(regexp == null) {
+ REMatch match = re.getMatch(s);
+ return matchToExecResult(match,re,s);
+ }
+ if(!regexp.global) return regexp.callMethod("exec", s, null, null, null, 1);
+
+ JSArray ret = new JSArray();
+ REMatch[] matches = re.getAllMatches(s);
+ for(int i=0;i<matches.length;i++) ret.addElement(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();
+ RE re = arg0 instanceof JSRegexp ? ((JSRegexp)arg0).re : newRE(arg0.toString(),0);
+ 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();
+ RE re;
+ JSFunction replaceFunc = null;
+ String replaceString = null;
+ JSRegexp regexp = null;
+ if(arg0 instanceof JSRegexp) {
+ regexp = (JSRegexp) arg0;
+ re = regexp.re;
+ } else {
+ re = newRE(arg0.toString(),0);
+ }
+ if(arg1 instanceof JSFunction)
+ replaceFunc = (JSFunction) arg1;
+ else
+ replaceString = JS.toString(arg1.toString());
+ REMatch[] matches;
+ if(regexp != null && regexp.global) {
+ matches = re.getAllMatches(s);
+ if(regexp != null) {
+ if(matches.length > 0)
+ regexp.lastIndex = matches[matches.length-1].getEndIndex();
+ else
+ regexp.lastIndex = s.length();
+ }
+ } else {
+ REMatch match = re.getMatch(s);
+ if(match != null)
+ matches = new REMatch[]{ match };
+ else
+ matches = new REMatch[0];
+ }
+
+ StringBuffer sb = new StringBuffer(s.length());
+ int pos = 0;
+ char[] sa = s.toCharArray();
+ for(int i=0;i<matches.length;i++) {
+ REMatch match = matches[i];
+ sb.append(sa,pos,match.getStartIndex()-pos);
+ pos = match.getEndIndex();
+ 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;
+ 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;
+ }
+ switch(numArgs) {
+ case 3:
+ a1 = N(match.getStartIndex());
+ a2 = s;
+ break;
+ case 4:
+ a2 = N(match.getStartIndex());
+ rest[0] = s;
+ break;
+ default:
+ rest[rest.length - 2] = N(match.getStartIndex());
+ rest[rest.length - 1] = s;
+ }
+
+ // note: can't perform pausing operations in here
+ sb.append((String)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();
+ }
+
+ private static String mySubstitute(REMatch match, String s, String source) {
+ StringBuffer sb = new StringBuffer();
+ int i,n;
+ char c,c2;
+ for(i=0;i<s.length()-1;i++) {
+ c = s.charAt(i);
+ if(c != '$') {
+ sb.append(c);
+ continue;
+ }
+ i++;
+ c = s.charAt(i);
+ switch(c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if(i < s.length()-1 && (c2 = s.charAt(i+1)) >= '0' && c2 <= '9') {
+ n = (c - '0') * 10 + (c2 - '0');
+ i++;
+ } else {
+ n = c - '0';
+ }
+ if(n > 0)
+ sb.append(match.toString(n));
+ break;
+ case '$':
+ sb.append('$'); break;
+ case '&':
+ sb.append(match.toString()); break;
+ case '`':
+ sb.append(source.substring(0,match.getStartIndex())); break;
+ case '\'':
+ sb.append(source.substring(match.getEndIndex())); break;
+ default:
+ sb.append('$');
+ sb.append(c);
+ }
+ }
+ if(i < s.length()) sb.append(s.charAt(i));
+ return sb.toString();
+ }
+
+
+ public static Object stringSplit(String s, Object arg0, Object arg1, int nargs) {
+ int limit = nargs < 2 ? Integer.MAX_VALUE : JS.toInt(arg1);
+ if(limit < 0) limit = Integer.MAX_VALUE;
+ if(limit == 0) return new JSArray();
+
+ RE re = null;
+ JSRegexp regexp = null;
+ String sep = null;
+ JSArray ret = new JSArray();
+ int p = 0;
+
+ if(arg0 instanceof JSRegexp) {
+ regexp = (JSRegexp) arg0;
+ re = regexp.re;
+ } else {
+ sep = arg0.toString();
+ }
+
+ // special case this for speed. additionally, the code below doesn't properly handle
+ // zero length strings
+ if(sep != null && sep.length()==0) {
+ int len = s.length();
+ for(int i=0;i<len;i++)
+ ret.addElement(s.substring(i,i+1));
+ return ret;
+ }
+
+ OUTER: while(p < s.length()) {
+ if(re != null) {
+ 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()));
+ p = zeroLength ? p + 1 : m.getEndIndex();
+ if(!zeroLength) {
+ for(int i=1;i<=re.getNumSubs();i++) {
+ ret.addElement(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));
+ p = x + sep.length();
+ }
+ if(ret.length() == limit) break;
+ }
+ if(p < s.length() && ret.length() != limit)
+ ret.addElement(s.substring(p));
+ return ret;
+ }
+
+ public static RE newRE(String pattern, int flags) throws JSExn {
+ try {
+ return new RE(pattern,flags,RESyntax.RE_SYNTAX_PERL5);
+ } catch(REException e) {
+ throw new JSExn(e.toString());
+ }
+ }
+
+ public String typeName() { return "regexp"; }
+}
--- /dev/null
+// 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 {
+
+ private JSScope parentScope;
+
+ private static final Object NULL_PLACEHOLDER = new Object();
+
+ public JSScope(JSScope parentScope) { this.parentScope = parentScope; }
+ public void declare(String 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);
+ 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 {
+ if (parentScope != null && !has(key)) parentScope.put(key, val);
+ else super.put(key, val == null ? NULL_PLACEHOLDER : val);
+ }
+
+ public JSScope top() {
+ JSScope s = this;
+ while(s.parentScope != null) s = s.parentScope;
+ return s;
+ }
+
+ public static class Global extends JSScope {
+ private final static Double NaN = new Double(Double.NaN);
+ private final static Double POSITIVE_INFINITY = new Double(Double.POSITIVE_INFINITY);
+
+ public Global() { super(null); }
+ public Object get(Object key) throws JSExn {
+ //#switch(key)
+ case "NaN": return 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;
+ //#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);
+ }
+
+ private Object parseInt(Object arg, Object r) {
+ int radix = JS.toInt(r);
+ String s = (String)arg;
+ int start = 0;
+ int length = s.length();
+ int sign = 1;
+ long n = 0;
+ if(radix != 0 && (radix < 2 || radix > 36)) return NaN;
+ while(start < length && Character.isWhitespace(s.charAt(start))) start++;
+ if((length >= start+1) && (s.charAt(start) == '+' || s.charAt(start) == '-')) {
+ sign = s.charAt(start) == '+' ? 1 : -1;
+ start++;
+ }
+ if(radix == 0 && length >= start+1 && s.charAt(start) == '0') {
+ start++;
+ if(length >= start+1 && (s.charAt(start) == 'x' || s.charAt(start) == 'X')) {
+ start++;
+ radix = 16;
+ } else {
+ radix = 8;
+ if(length == start || Character.digit(s.charAt(start+1),8)==-1) return JS.ZERO;
+ }
+ }
+ if(radix == 0) radix = 10;
+ if(length == start || Character.digit(s.charAt(start),radix) == -1) return NaN;
+ // try the fast way first
+ try {
+ String s2 = start == 0 ? s : s.substring(start);
+ return JS.N(sign*Integer.parseInt(s2,radix));
+ } catch(NumberFormatException e) { }
+ // fall through to a slower but emca-compliant method
+ for(int i=start;i<length;i++) {
+ int digit = Character.digit(s.charAt(i),radix);
+ if(digit < 0) break;
+ n = n*radix + digit;
+ if(n < 0) return 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;
+ int start = 0;
+ int length = s.length();
+ while(start < length && Character.isWhitespace(s.charAt(0))) start++;
+ int end = length;
+ // as long as the string has no trailing garbage,this is fast, its slow with
+ // trailing garbage
+ while(start < end) {
+ try {
+ return JS.N(s.substring(start,length));
+ } catch(NumberFormatException e) { }
+ end--;
+ }
+ return NaN;
+ }
+ }
+}
+
--- /dev/null
+// Derived from org.mozilla.javascript.TokenStream [NPL]
+
+/**
+ * The contents of this file are subject to the Netscape Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.
+ *
+ * Contributor(s): Roger Lawrence, Mike McCabe
+ */
+
+package org.ibex.js;
+import java.io.*;
+
+/** Lexes a stream of characters into a stream of Tokens */
+class Lexer implements Tokens {
+
+ /** for debugging */
+ public static void main(String[] s) throws IOException {
+ Lexer l = new Lexer(new InputStreamReader(System.in), "stdin", 0);
+ int tok = 0;
+ while((tok = l.getToken()) != -1) System.out.println(codeToString[tok]);
+ }
+
+ /** the token that was just parsed */
+ protected int op;
+
+ /** the most recently parsed token, <i>regardless of pushbacks</i> */
+ protected int mostRecentlyReadToken;
+
+ /** if the token just parsed was a NUMBER, this is the numeric value */
+ protected Number number = null;
+
+ /** if the token just parsed was a NAME or STRING, this is the string value */
+ protected String string = null;
+
+ /** the line number of the most recently <i>lexed</i> token */
+ protected int line = 0;
+
+ /** the line number of the most recently <i>parsed</i> token */
+ protected int parserLine = 0;
+
+ /** the column number of the current token */
+ protected int col = 0;
+
+ /** the name of the source code file being lexed */
+ protected String sourceName;
+
+ private SmartReader in;
+ public Lexer(Reader r, String sourceName, int line) throws IOException {
+ this.sourceName = sourceName;
+ this.line = line;
+ this.parserLine = line;
+ in = new SmartReader(r);
+ }
+
+
+ // Predicates ///////////////////////////////////////////////////////////////////////
+
+ private static boolean isAlpha(int c) { return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); }
+ private static boolean isDigit(int c) { return (c >= '0' && c <= '9'); }
+ private static int xDigitToInt(int c) {
+ if ('0' <= c && c <= '9') return c - '0';
+ else if ('a' <= c && c <= 'f') return c - ('a' - 10);
+ else if ('A' <= c && c <= 'F') return c - ('A' - 10);
+ else return -1;
+ }
+
+
+ // Token Subtype Handlers /////////////////////////////////////////////////////////
+
+ private int getKeyword(String name) throws IOException {
+ //#switch(name)
+ case "if": return IF;
+ case "lt": return LT;
+ case "gt": return GT;
+ case "in": return IN;
+ case "do": return DO;
+ case "and": return AND;
+ case "or": return OR;
+ case "for": return FOR;
+ case "int": return RESERVED;
+ case "new": return RESERVED;
+ case "try": return TRY;
+ case "var": return VAR;
+ case "byte": return RESERVED;
+ case "case": return CASE;
+ case "char": return RESERVED;
+ case "else": return ELSE;
+ case "enum": return RESERVED;
+ case "goto": return RESERVED;
+ case "long": return RESERVED;
+ case "null": return NULL;
+ case "true": return TRUE;
+ case "with": return RESERVED;
+ case "void": return RESERVED;
+ case "class": return RESERVED;
+ case "break": return BREAK;
+ case "while": return WHILE;
+ case "false": return FALSE;
+ case "const": return RESERVED;
+ case "final": return RESERVED;
+ case "super": return RESERVED;
+ case "throw": return THROW;
+ case "catch": return CATCH;
+ case "class": return RESERVED;
+ case "delete": return RESERVED;
+ case "return": return RETURN;
+ case "throws": return RESERVED;
+ case "double": return RESERVED;
+ case "assert": return ASSERT;
+ case "public": return RESERVED;
+ case "switch": return SWITCH;
+ case "typeof": return TYPEOF;
+ case "package": return RESERVED;
+ case "default": return DEFAULT;
+ case "finally": return FINALLY;
+ case "boolean": return RESERVED;
+ case "private": return RESERVED;
+ case "extends": return RESERVED;
+ case "abstract": return RESERVED;
+ case "continue": return CONTINUE;
+ case "debugger": return RESERVED;
+ case "function": return FUNCTION;
+ case "volatile": return RESERVED;
+ case "interface": return RESERVED;
+ case "protected": return RESERVED;
+ case "transient": return RESERVED;
+ case "implements": return RESERVED;
+ case "instanceof": return RESERVED;
+ case "synchronized": return RESERVED;
+ //#end
+ return -1;
+ }
+
+ private int getIdentifier(int c) throws IOException {
+ in.startString();
+ while (Character.isJavaIdentifierPart((char)(c = in.read())));
+ in.unread();
+ String str = in.getString();
+ int result = getKeyword(str);
+ if (result == RESERVED) throw new LexerException("The reserved word \"" + str + "\" is not permitted in Ibex scripts");
+ if (result != -1) return result;
+ this.string = str.intern();
+ return NAME;
+ }
+
+ private int getNumber(int c) throws IOException {
+ int base = 10;
+ in.startString();
+ double dval = Double.NaN;
+ long longval = 0;
+ boolean isInteger = true;
+
+ // figure out what base we're using
+ if (c == '0') {
+ if (Character.toLowerCase((char)(c = in.read())) == 'x') { base = 16; in.startString(); }
+ else if (isDigit(c)) base = 8;
+ }
+
+ while (0 <= xDigitToInt(c) && !(base < 16 && isAlpha(c))) c = in.read();
+ if (base == 10 && (c == '.' || c == 'e' || c == 'E')) {
+ isInteger = false;
+ if (c == '.') do { c = in.read(); } while (isDigit(c));
+ if (c == 'e' || c == 'E') {
+ c = in.read();
+ if (c == '+' || c == '-') c = in.read();
+ if (!isDigit(c)) throw new LexerException("float listeral did not have an exponent value");
+ do { c = in.read(); } while (isDigit(c));
+ }
+ }
+ in.unread();
+
+ String numString = in.getString();
+ if (base == 10 && !isInteger) {
+ try { dval = (Double.valueOf(numString)).doubleValue(); }
+ catch (NumberFormatException ex) { throw new LexerException("invalid numeric literal: \"" + numString + "\""); }
+ } else {
+ if (isInteger) {
+ longval = Long.parseLong(numString, base);
+ dval = (double)longval;
+ } else {
+ dval = Double.parseDouble(numString);
+ longval = (long) dval;
+ if (longval == dval) isInteger = true;
+ }
+ }
+
+ if (!isInteger) this.number = JS.N(dval);
+ else this.number = JS.N(longval);
+ return NUMBER;
+ }
+
+ private int getString(int c) throws IOException {
+ StringBuffer stringBuf = null;
+ int quoteChar = c;
+ c = in.read();
+ in.startString(); // start after the first "
+ while(c != quoteChar) {
+ if (c == '\n' || c == -1) throw new LexerException("unterminated string literal");
+ if (c == '\\') {
+ if (stringBuf == null) {
+ in.unread(); // Don't include the backslash
+ stringBuf = new StringBuffer(in.getString());
+ in.read();
+ }
+ switch (c = in.read()) {
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = '\u000B'; break;
+ case '\\': c = '\\'; break;
+ case 'u': {
+ int v = 0;
+ for(int i=0; i<4; i++) {
+ int ci = in.read();
+ if (!((ci >= '0' && ci <= '9') || (ci >= 'a' && ci <= 'f') || (ci >= 'A' && ci <= 'F')))
+ throw new LexerException("illegal character '" + ((char)c) + "' in \\u unicode escape sequence");
+ v = (v << 8) | Integer.parseInt(ci + "", 16);
+ }
+ c = (char)v;
+ break;
+ }
+ default:
+ // just use the character that was escaped
+ break;
+ }
+ }
+ if (stringBuf != null) stringBuf.append((char) c);
+ c = in.read();
+ }
+ if (stringBuf != null) this.string = stringBuf.toString().intern();
+ else {
+ in.unread(); // miss the trailing "
+ this.string = in.getString().intern();
+ in.read();
+ }
+ return STRING;
+ }
+
+ private int _getToken() throws IOException {
+ int c;
+ do { c = in.read(); } while (c == '\u0020' || c == '\u0009' || c == '\u000C' || c == '\u000B' || c == '\n' );
+ if (c == -1) return -1;
+ if (c == '\\' || Character.isJavaIdentifierStart((char)c)) return getIdentifier(c);
+ if (isDigit(c) || (c == '.' && isDigit(in.peek()))) return getNumber(c);
+ if (c == '"' || c == '\'') return getString(c);
+ switch (c) {
+ case ';': return SEMI;
+ case '[': return LB;
+ case ']': return RB;
+ case '{': return LC;
+ case '}': return RC;
+ case '(': return LP;
+ case ')': return RP;
+ case ',': return COMMA;
+ case '?': return HOOK;
+ case ':': return !in.match(':') ? COLON : in.match('=') ? GRAMMAR : le(":: is not a valid token");
+ case '.': return DOT;
+ case '|': return in.match('|') ? OR : (in.match('=') ? ASSIGN_BITOR : BITOR);
+ case '^': return in.match('=') ? ASSIGN_BITXOR : BITXOR;
+ case '&': return in.match('&') ? AND : in.match('=') ? ASSIGN_BITAND : BITAND;
+ case '=': return !in.match('=') ? ASSIGN : in.match('=') ? SHEQ : EQ;
+ case '!': return !in.match('=') ? BANG : in.match('=') ? SHNE : NE;
+ case '%': return in.match('=') ? ASSIGN_MOD : MOD;
+ case '~': return BITNOT;
+ case '+': return in.match('=') ? ASSIGN_ADD : in.match('+') ? (in.match('=') ? ADD_TRAP : INC) : ADD;
+ case '-': return in.match('=') ? ASSIGN_SUB: in.match('-') ? (in.match('=') ? DEL_TRAP : DEC) : SUB;
+ case '*': return in.match('=') ? ASSIGN_MUL : MUL;
+ case '<': return !in.match('<') ? (in.match('=') ? LE : LT) : in.match('=') ? ASSIGN_LSH : LSH;
+ case '>': return !in.match('>') ? (in.match('=') ? GE : GT) :
+ in.match('>') ? (in.match('=') ? ASSIGN_URSH : URSH) : (in.match('=') ? ASSIGN_RSH : RSH);
+ case '/':
+ if (in.match('=')) return ASSIGN_DIV;
+ if (in.match('/')) { while ((c = in.read()) != -1 && c != '\n'); in.unread(); return getToken(); }
+ if (!in.match('*')) return DIV;
+ while ((c = in.read()) != -1 && !(c == '*' && in.match('/'))) {
+ if (c == '\n' || c != '/' || !in.match('*')) continue;
+ if (in.match('/')) return getToken();
+ throw new LexerException("nested comments are not permitted");
+ }
+ if (c == -1) throw new LexerException("unterminated comment");
+ return getToken(); // `goto retry'
+ default: throw new LexerException("illegal character: \'" + ((char)c) + "\'");
+ }
+ }
+
+ private int le(String s) throws LexerException { if (true) throw new LexerException(s); return 0; }
+
+ // SmartReader ////////////////////////////////////////////////////////////////
+
+ /** a Reader that tracks line numbers and can push back tokens */
+ private class SmartReader {
+ PushbackReader reader = null;
+ int lastread = -1;
+
+ public SmartReader(Reader r) { reader = new PushbackReader(r); }
+ public void unread() throws IOException { unread((char)lastread); }
+ public void unread(char c) throws IOException {
+ reader.unread(c);
+ if(c == '\n') col = -1;
+ else col--;
+ if (accumulator != null) accumulator.setLength(accumulator.length() - 1);
+ }
+ public boolean match(char c) throws IOException { if (peek() == c) { reader.read(); return true; } else return false; }
+ public int peek() throws IOException {
+ int peeked = reader.read();
+ if (peeked != -1) reader.unread((char)peeked);
+ return peeked;
+ }
+ public int read() throws IOException {
+ lastread = reader.read();
+ if (accumulator != null) accumulator.append((char)lastread);
+ if (lastread != '\n' && lastread != '\r') col++;
+ if (lastread == '\n') {
+ // col is -1 if we just unread a newline, this is sort of ugly
+ if (col != -1) parserLine = ++line;
+ col = 0;
+ }
+ return lastread;
+ }
+
+ // FEATURE: could be much more efficient
+ StringBuffer accumulator = null;
+ public void startString() {
+ accumulator = new StringBuffer();
+ accumulator.append((char)lastread);
+ }
+ public String getString() throws IOException {
+ String ret = accumulator.toString().intern();
+ accumulator = null;
+ return ret;
+ }
+ }
+
+
+ // Token PushBack code ////////////////////////////////////////////////////////////
+
+ private int pushBackDepth = 0;
+ private int[] pushBackInts = new int[10];
+ private Object[] pushBackObjects = new Object[10];
+
+ /** push back a token */
+ public final void pushBackToken(int op, Object obj) {
+ if (pushBackDepth >= pushBackInts.length - 1) {
+ int[] newInts = new int[pushBackInts.length * 2];
+ System.arraycopy(pushBackInts, 0, newInts, 0, pushBackInts.length);
+ pushBackInts = newInts;
+ Object[] newObjects = new Object[pushBackObjects.length * 2];
+ System.arraycopy(pushBackObjects, 0, newObjects, 0, pushBackObjects.length);
+ pushBackObjects = newObjects;
+ }
+ pushBackInts[pushBackDepth] = op;
+ pushBackObjects[pushBackDepth] = obj;
+ pushBackDepth++;
+ }
+
+ /** push back the most recently read token */
+ public final void pushBackToken() { pushBackToken(op, number != null ? (Object)number : (Object)string); }
+
+ /** read a token but leave it in the stream */
+ public final int peekToken() throws IOException {
+ int ret = getToken();
+ pushBackToken();
+ return ret;
+ }
+
+ /** read a token */
+ public final int getToken() throws IOException {
+ number = null;
+ string = null;
+ if (pushBackDepth == 0) {
+ mostRecentlyReadToken = op;
+ return op = _getToken();
+ }
+ pushBackDepth--;
+ op = pushBackInts[pushBackDepth];
+ if (pushBackObjects[pushBackDepth] != null) {
+ number = pushBackObjects[pushBackDepth] instanceof Number ? (Number)pushBackObjects[pushBackDepth] : null;
+ string = pushBackObjects[pushBackDepth] instanceof String ? (String)pushBackObjects[pushBackDepth] : null;
+ }
+ return op;
+ }
+
+ class LexerException extends IOException {
+ public LexerException(String s) { super(sourceName + ":" + line + "," + col + ": " + s); }
+ }
+}
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+import org.ibex.util.*;
+import java.io.*;
+
+/**
+ * Parses a stream of lexed tokens into a tree of JSFunction's.
+ *
+ * There are three kinds of things we parse: blocks, statements, and
+ * expressions.
+ *
+ * - Expressions are a special type of statement that evaluates to a
+ * value (for example, "break" is not an expression, * but "3+2"
+ * is). Some tokens sequences start expressions (for * example,
+ * literal numbers) and others continue an expression which * has
+ * already been begun (for example, '+'). Finally, some *
+ * expressions are valid targets for an assignment operation; after
+ * * each of these expressions, continueExprAfterAssignable() is
+ * called * to check for an assignment operation.
+ *
+ * - A statement ends with a semicolon and does not return a value.
+ *
+ * - A block is a single statement or a sequence of statements
+ * surrounded by curly braces.
+ *
+ * Each parsing method saves the parserLine before doing its actual
+ * work and restores it afterwards. This ensures that parsing a
+ * subexpression does not modify the line number until a token
+ * *after* the subexpression has been consumed by the parent
+ * expression.
+ *
+ * Technically it would be a better design for this class to build an
+ * intermediate parse tree and use that to emit bytecode. Here's the
+ * tradeoff:
+ *
+ * Advantages of building a parse tree:
+ * - easier to apply optimizations
+ * - would let us handle more sophisticated languages than JavaScript
+ *
+ * Advantages of leaving out the parse tree
+ * - faster compilation
+ * - less load on the garbage collector
+ * - much simpler code, easier to understand
+ * - less error-prone
+ *
+ * Fortunately JS is such a simple language that we can get away with
+ * the half-assed approach and still produce a working, complete
+ * compiler.
+ *
+ * The bytecode language emitted doesn't really cause any appreciable
+ * semantic loss, and is itself a parseable language very similar to
+ * Forth or a postfix variant of LISP. This means that the bytecode
+ * can be transformed into a parse tree, which can be manipulated.
+ * So if we ever want to add an optimizer, it could easily be done by
+ * producing a parse tree from the bytecode, optimizing that tree,
+ * and then re-emitting the bytecode. The parse tree node class
+ * would also be much simpler since the bytecode language has so few
+ * operators.
+ *
+ * Actually, the above paragraph is slightly inaccurate -- there are
+ * places where we push a value and then perform an arbitrary number
+ * of operations using it before popping it; this doesn't parse well.
+ * But these cases are clearly marked and easy to change if we do
+ * need to move to a parse tree format.
+ */
+class Parser extends Lexer implements ByteCodes {
+
+
+ // Constructors //////////////////////////////////////////////////////
+
+ public Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); }
+
+ /** for debugging */
+ public static void main(String[] s) throws IOException {
+ JS block = JS.fromReader("stdin", 0, new InputStreamReader(System.in));
+ if (block == null) return;
+ System.out.println(block);
+ }
+
+
+ // Statics ////////////////////////////////////////////////////////////
+
+ static byte[] precedence = new byte[MAX_TOKEN + 1];
+ static boolean[] isRightAssociative = new boolean[MAX_TOKEN + 1];
+ // Use this as the precedence when we want anything up to the comma
+ private final static int NO_COMMA = 2;
+ static {
+ isRightAssociative[ASSIGN] =
+ isRightAssociative[ASSIGN_BITOR] =
+ isRightAssociative[ASSIGN_BITXOR] =
+ isRightAssociative[ASSIGN_BITAND] =
+ isRightAssociative[ASSIGN_LSH] =
+ isRightAssociative[ASSIGN_RSH] =
+ isRightAssociative[ASSIGN_URSH] =
+ isRightAssociative[ASSIGN_ADD] =
+ isRightAssociative[ASSIGN_SUB] =
+ isRightAssociative[ASSIGN_MUL] =
+ isRightAssociative[ASSIGN_DIV] =
+ isRightAssociative[ASSIGN_MOD] =
+ isRightAssociative[ADD_TRAP] =
+ isRightAssociative[DEL_TRAP] =
+ true;
+
+ precedence[COMMA] = 1;
+ // 2 is intentionally left unassigned. we use minPrecedence==2 for comma separated lists
+ precedence[ASSIGN] =
+ precedence[ASSIGN_BITOR] =
+ precedence[ASSIGN_BITXOR] =
+ precedence[ASSIGN_BITAND] =
+ precedence[ASSIGN_LSH] =
+ precedence[ASSIGN_RSH] =
+ precedence[ASSIGN_URSH] =
+ precedence[ASSIGN_ADD] =
+ precedence[ASSIGN_SUB] =
+ precedence[ASSIGN_MUL] =
+ precedence[ASSIGN_DIV] =
+ precedence[ADD_TRAP] =
+ precedence[DEL_TRAP] =
+ precedence[ASSIGN_MOD] = 3;
+ precedence[HOOK] = 4;
+ precedence[OR] = 5;
+ precedence[AND] = 6;
+ precedence[BITOR] = 7;
+ precedence[BITXOR] = 8;
+ precedence[BITAND] = 9;
+ precedence[EQ] = precedence[NE] = precedence[SHEQ] = precedence[SHNE] = 10;
+ precedence[LT] = precedence[LE] = precedence[GT] = precedence[GE] = 11;
+ precedence[LSH] = precedence[RSH] = precedence[URSH] = 12;
+ precedence[ADD] = precedence[SUB] = 12;
+ precedence[MUL] = precedence[DIV] = precedence[MOD] = 13;
+ precedence[BITNOT] = precedence[BANG] = precedence[TYPEOF] = 14;
+ precedence[DOT] = precedence[LB] = precedence[LP] = precedence[INC] = precedence[DEC] = 15;
+ }
+
+
+ // Parsing Logic /////////////////////////////////////////////////////////
+
+ /** gets a token and throws an exception if it is not <tt>code</tt> */
+ private void consume(int code) throws IOException {
+ if (getToken() != code) {
+ if(code == NAME) switch(op) {
+ case RETURN: case TYPEOF: case BREAK: case CONTINUE: case TRY: case THROW:
+ case ASSERT: case NULL: case TRUE: case FALSE: case IN: case IF: case ELSE:
+ case SWITCH: case CASE: case DEFAULT: case WHILE: case VAR: case WITH:
+ case CATCH: case FINALLY:
+ throw pe("Bad variable name; '" + codeToString[op].toLowerCase() + "' is a javascript keyword");
+ }
+ throw pe("expected " + codeToString[code] + ", got " + (op == -1 ? "EOF" : codeToString[op]));
+ }
+ }
+
+ /**
+ * Parse the largest possible expression containing no operators
+ * of precedence below <tt>minPrecedence</tt> and append the
+ * bytecodes for that expression to <tt>appendTo</tt>; the
+ * appended bytecodes MUST grow the stack by exactly one element.
+ */
+ private void startExpr(JSFunction appendTo, int minPrecedence) throws IOException {
+ int saveParserLine = parserLine;
+ _startExpr(appendTo, minPrecedence);
+ parserLine = saveParserLine;
+ }
+ private void _startExpr(JSFunction appendTo, int minPrecedence) throws IOException {
+ int tok = getToken();
+ JSFunction b = appendTo;
+
+ switch (tok) {
+ 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 NULL: b.add(parserLine, LITERAL, null); break;
+ case TRUE: case FALSE: b.add(parserLine, LITERAL, JS.B(tok == TRUE)); 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);
+ break;
+ }
+
+ case LB: {
+ b.add(parserLine, ARRAY, JS.ZERO); // push an array onto the stack
+ int size0 = b.size;
+ int i = 0;
+ if (peekToken() != RB)
+ while(true) { // iterate over the initialization values
+ b.add(parserLine, LITERAL, JS.N(i++)); // push the index in the array to place it into
+ if (peekToken() == COMMA || peekToken() == RB)
+ b.add(parserLine, LITERAL, null); // for stuff like [1,,2,]
+ else
+ startExpr(b, NO_COMMA); // push the value onto the stack
+ b.add(parserLine, PUT); // put it into the array
+ b.add(parserLine, POP); // discard the value remaining on the stack
+ if (peekToken() == RB) break;
+ consume(COMMA);
+ }
+ b.set(size0 - 1, JS.N(i)); // back at the ARRAY instruction, write the size of the array
+ consume(RB);
+ break;
+ }
+ case SUB: { // negative literal (like "3 * -1")
+ consume(NUMBER);
+ b.add(parserLine, LITERAL, JS.N(number.doubleValue() * -1));
+ break;
+ }
+ case LP: { // grouping (not calling)
+ startExpr(b, -1);
+ consume(RP);
+ break;
+ }
+ case INC: case DEC: { // prefix (not postfix)
+ startExpr(b, precedence[tok]);
+ int prev = b.size - 1;
+ 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
+ throw pe("prefixed increment/decrement can only be performed on a valid assignment target");
+ 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);
+ break;
+ }
+ case BANG: case BITNOT: case TYPEOF: {
+ startExpr(b, precedence[tok]);
+ b.add(parserLine, tok);
+ break;
+ }
+ case LC: { // object constructor
+ b.add(parserLine, OBJECT, null); // put an object on the stack
+ if (peekToken() != RC)
+ while(true) {
+ if (peekToken() != NAME && peekToken() != STRING)
+ throw pe("expected NAME or STRING");
+ getToken();
+ b.add(parserLine, LITERAL, string); // grab the key
+ consume(COLON);
+ startExpr(b, NO_COMMA); // grab the value
+ b.add(parserLine, PUT); // put the value into the object
+ b.add(parserLine, POP); // discard the remaining value
+ if (peekToken() == RC) break;
+ consume(COMMA);
+ if (peekToken() == RC) break; // we permit {,,} -- I'm not sure if ECMA does
+ }
+ consume(RC);
+ break;
+ }
+ case NAME: {
+ b.add(parserLine, TOPSCOPE);
+ b.add(parserLine, LITERAL, string);
+ continueExprAfterAssignable(b,minPrecedence);
+ break;
+ }
+ case FUNCTION: {
+ consume(LP);
+ int numArgs = 0;
+ JSFunction b2 = new JSFunction(sourceName, parserLine, null);
+ 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);
+
+ while(peekToken() != RP) { // run through the list of argument names
+ numArgs++;
+ if (peekToken() == NAME) {
+ consume(NAME); // a named argument
+ String varName = string;
+
+ 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
+ }
+ if (peekToken() == RP) break;
+ consume(COMMA);
+ }
+ consume(RP);
+
+ 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
+
+ b2.add(parserLine, LITERAL, null); // in case we "fall out the bottom", return NULL
+ b2.add(parserLine, RETURN);
+
+ break;
+ }
+ default: throw pe("expected expression, found " + codeToString[tok] + ", which cannot start an expression");
+ }
+
+ // attempt to continue the expression
+ continueExpr(b, minPrecedence);
+ }
+ /*
+ private Grammar parseGrammar(Grammar g) throws IOException {
+ int tok = getToken();
+ if (g != null)
+ switch(tok) {
+ case BITOR: return new Grammar.Alternative(g, parseGrammar(null));
+ case ADD: return parseGrammar(new Grammar.Repetition(g, 1, Integer.MAX_VALUE));
+ case MUL: return parseGrammar(new Grammar.Repetition(g, 0, Integer.MAX_VALUE));
+ case HOOK: return parseGrammar(new Grammar.Repetition(g, 0, 1));
+ }
+ Grammar g0 = null;
+ switch(tok) {
+ //case NUMBER: g0 = new Grammar.Literal(number); break;
+ case NAME: g0 = new Grammar.Reference(string); break;
+ case STRING:
+ g0 = new Grammar.Literal(string);
+ if (peekToken() == DOT) {
+ String old = string;
+ consume(DOT);
+ consume(DOT);
+ consume(STRING);
+ if (old.length() != 1 || string.length() != 1) throw pe("literal ranges must be single-char strings");
+ g0 = new Grammar.Range(old.charAt(0), string.charAt(0));
+ }
+ break;
+ case LP: g0 = parseGrammar(null); consume(RP); break;
+ default: pushBackToken(); return g;
+ }
+ if (g == null) return parseGrammar(g0);
+ return parseGrammar(new Grammar.Juxtaposition(g, g0));
+ }
+ */
+ /**
+ * Assuming that a complete assignable (lvalue) has just been
+ * parsed and the object and key are on the stack,
+ * <tt>continueExprAfterAssignable</tt> will attempt to parse an
+ * 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 {
+ int saveParserLine = parserLine;
+ _continueExprAfterAssignable(b,minPrecedence);
+ parserLine = saveParserLine;
+ }
+ private void _continueExprAfterAssignable(JSFunction b,int minPrecedence) 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])))
+ // force the default case
+ tok = -1;
+ switch(tok) {
+ /*
+ case GRAMMAR: {
+ b.add(parserLine, GET_PRESERVE);
+ Grammar g = parseGrammar(null);
+ if (peekToken() == LC) {
+ g.action = new JSFunction(sourceName, parserLine, null);
+ parseBlock((JSFunction)g.action);
+ ((JSFunction)g.action).add(parserLine, LITERAL, null); // in case we "fall out the bottom", return NULL
+ ((JSFunction)g.action).add(parserLine, RETURN);
+ }
+ b.add(parserLine, MAKE_GRAMMAR, g);
+ b.add(parserLine, PUT);
+ break;
+ }
+ */
+ 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);
+
+ 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);
+ } else {
+ 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
+ break;
+ }
+ case ASSIGN: {
+ startExpr(b, precedence[tok]);
+ b.add(parserLine, PUT);
+ b.add(parserLine, SWAP);
+ b.add(parserLine, POP);
+ 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));
+ break;
+ }
+ default: {
+ pushBackToken();
+ 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);
+ return;
+ }
+ }
+ }
+
+
+ /**
+ * Assuming that a complete expression has just been parsed,
+ * <tt>continueExpr</tt> will attempt to extend this expression by
+ * parsing additional tokens and appending additional bytecodes.
+ *
+ * No operators with precedence less than <tt>minPrecedence</tt>
+ * will be parsed.
+ *
+ * If any bytecodes are appended, they will not alter the stack
+ * depth.
+ */
+ private void continueExpr(JSFunction b, int minPrecedence) throws IOException {
+ int saveParserLine = parserLine;
+ _continueExpr(b, minPrecedence);
+ parserLine = saveParserLine;
+ }
+ private void _continueExpr(JSFunction b, int minPrecedence) throws IOException {
+ if (b == null) throw new Error("got null b; this should never happen");
+ int tok = getToken();
+ if (tok == -1) return;
+ if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok]))) {
+ pushBackToken();
+ return;
+ }
+
+ switch (tok) {
+ case LP: { // invocation (not grouping)
+ int n = parseArgs(b, 1);
+ b.add(parserLine, CALL, JS.N(n));
+ break;
+ }
+ case BITOR: case BITXOR: case BITAND: case SHEQ: case SHNE: case LSH:
+ case RSH: case URSH: case MUL: case DIV: case MOD:
+ case GT: case GE: case EQ: case NE: case LT: case LE: case SUB: {
+ startExpr(b, precedence[tok]);
+ b.add(parserLine, tok);
+ break;
+ }
+ case ADD: {
+ int count=1;
+ int nextTok;
+ do {
+ startExpr(b,precedence[tok]);
+ count++;
+ nextTok = getToken();
+ } while(nextTok == tok);
+ pushBackToken();
+ b.add(parserLine, tok, JS.N(count));
+ break;
+ }
+ case OR: case AND: {
+ b.add(parserLine, tok == AND ? JSFunction.JF : JSFunction.JT, JS.ZERO); // test to see if we can short-circuit
+ int size = b.size;
+ startExpr(b, precedence[tok]); // otherwise check the second value
+ b.add(parserLine, JMP, JS.N(2)); // leave the second value on the stack and jump to the end
+ b.add(parserLine, LITERAL, tok == AND ?
+ JS.B(false) : JS.B(true)); // target of the short-circuit jump is here
+ b.set(size - 1, JS.N(b.size - size)); // write the target of the short-circuit jump
+ break;
+ }
+ case DOT: {
+ // support foo..bar syntax for foo[""].bar
+ if (peekToken() == DOT) {
+ string = "";
+ } else {
+ consume(NAME);
+ }
+ b.add(parserLine, LITERAL, string);
+ continueExprAfterAssignable(b,minPrecedence);
+ break;
+ }
+ case LB: { // subscripting (not array constructor)
+ startExpr(b, -1);
+ consume(RB);
+ continueExprAfterAssignable(b,minPrecedence);
+ break;
+ }
+ case HOOK: {
+ b.add(parserLine, JF, JS.ZERO); // jump to the if-false expression
+ int size = b.size;
+ startExpr(b, minPrecedence); // write the if-true expression
+ b.add(parserLine, JMP, JS.ZERO); // if true, jump *over* the if-false expression
+ b.set(size - 1, JS.N(b.size - size + 1)); // now we know where the target of the jump is
+ consume(COLON);
+ size = b.size;
+ startExpr(b, minPrecedence); // write the if-false expression
+ b.set(size - 1, JS.N(b.size - size + 1)); // this is the end; jump to here
+ break;
+ }
+ case COMMA: {
+ // pop the result of the previous expression, it is ignored
+ b.add(parserLine,POP);
+ startExpr(b,-1);
+ break;
+ }
+ default: {
+ pushBackToken();
+ return;
+ }
+ }
+
+ continueExpr(b, minPrecedence); // try to continue the expression
+ }
+
+ // 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 {
+ 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);
+ }
+ consume(RP);
+ return i;
+ }
+
+ /** Parse a block of statements which must be surrounded by LC..RC. */
+ void parseBlock(JSFunction b) throws IOException { parseBlock(b, null); }
+ void parseBlock(JSFunction b, String label) throws IOException {
+ int saveParserLine = parserLine;
+ _parseBlock(b, label);
+ parserLine = saveParserLine;
+ }
+ void _parseBlock(JSFunction b, String label) throws IOException {
+ if (peekToken() == -1) return;
+ else if (peekToken() != LC) parseStatement(b, null);
+ else {
+ consume(LC);
+ while(peekToken() != RC && peekToken() != -1) parseStatement(b, null);
+ consume(RC);
+ }
+ }
+
+ /** Parse a single statement, consuming the RC or SEMI which terminates it. */
+ void parseStatement(JSFunction b, String label) throws IOException {
+ int saveParserLine = parserLine;
+ _parseStatement(b, label);
+ parserLine = saveParserLine;
+ }
+ void _parseStatement(JSFunction b, String label) throws IOException {
+ int tok = peekToken();
+ if (tok == -1) return;
+ switch(tok = getToken()) {
+
+ case THROW: case ASSERT: case RETURN: {
+ if (tok == RETURN && peekToken() == SEMI)
+ b.add(parserLine, LITERAL, null);
+ else
+ startExpr(b, -1);
+ b.add(parserLine, tok);
+ consume(SEMI);
+ break;
+ }
+ case BREAK: case CONTINUE: {
+ if (peekToken() == NAME) consume(NAME);
+ b.add(parserLine, tok, string);
+ consume(SEMI);
+ break;
+ }
+ case VAR: {
+ b.add(parserLine, TOPSCOPE); // push the current scope
+ while(true) {
+ consume(NAME);
+ b.add(parserLine, DECLARE, string); // declare it
+ 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, 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;
+ }
+ case IF: {
+ consume(LP);
+ startExpr(b, -1);
+ consume(RP);
+
+ b.add(parserLine, JF, JS.ZERO); // if false, jump to the else-block
+ int size = b.size;
+ parseStatement(b, null);
+
+ if (peekToken() == ELSE) {
+ consume(ELSE);
+ b.add(parserLine, JMP, JS.ZERO); // if we took the true-block, jump over the else-block
+ b.set(size - 1, JS.N(b.size - size + 1));
+ size = b.size;
+ parseStatement(b, null);
+ }
+ b.set(size - 1, JS.N(b.size - size + 1)); // regardless of which branch we took, b[size] needs to point here
+ break;
+ }
+ case WHILE: {
+ consume(LP);
+ if (label != null) b.add(parserLine, LABEL, label);
+ b.add(parserLine, LOOP);
+ int size = b.size;
+ b.add(parserLine, POP); // discard the first-iteration indicator
+ startExpr(b, -1);
+ b.add(parserLine, JT, JS.N(2)); // if the while() clause is true, jump over the BREAK
+ b.add(parserLine, BREAK);
+ consume(RP);
+ parseStatement(b, null);
+ b.add(parserLine, CONTINUE); // if we fall out of the end, definately continue
+ b.set(size - 1, JS.N(b.size - size + 1)); // end of the loop
+ break;
+ }
+ case SWITCH: {
+ consume(LP);
+ if (label != null) b.add(parserLine, LABEL, label);
+ b.add(parserLine, LOOP);
+ int size0 = b.size;
+ startExpr(b, -1);
+ consume(RP);
+ consume(LC);
+ while(true)
+ if (peekToken() == CASE) { // we compile CASE statements like a bunch of if..else's
+ consume(CASE);
+ b.add(parserLine, DUP); // duplicate the switch() value; we'll consume one copy
+ startExpr(b, -1);
+ consume(COLON);
+ b.add(parserLine, EQ); // check if we should do this case-block
+ b.add(parserLine, JF, JS.ZERO); // if not, jump to the next one
+ int size = b.size;
+ while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null);
+ b.set(size - 1, JS.N(1 + b.size - size));
+ } else if (peekToken() == DEFAULT) {
+ consume(DEFAULT);
+ consume(COLON);
+ while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null);
+ } else if (peekToken() == RC) {
+ consume(RC);
+ b.add(parserLine, BREAK); // break out of the loop if we 'fall through'
+ break;
+ } else {
+ throw pe("expected CASE, DEFAULT, or RC; got " + codeToString[peekToken()]);
+ }
+ b.set(size0 - 1, JS.N(b.size - size0 + 1)); // end of the loop
+ break;
+ }
+
+ case DO: {
+ if (label != null) b.add(parserLine, LABEL, label);
+ b.add(parserLine, LOOP);
+ int size = b.size;
+ parseStatement(b, null);
+ consume(WHILE);
+ consume(LP);
+ startExpr(b, -1);
+ b.add(parserLine, JT, JS.N(2)); // check the while() clause; jump over the BREAK if true
+ b.add(parserLine, BREAK);
+ b.add(parserLine, CONTINUE);
+ consume(RP);
+ consume(SEMI);
+ b.set(size - 1, JS.N(b.size - size + 1)); // end of the loop; write this location to the LOOP instruction
+ break;
+ }
+
+ case TRY: {
+ b.add(parserLine, TRY); // try bytecode causes a TryMarker to be pushed
+ int tryInsn = b.size - 1;
+ // parse the expression to be TRYed
+ parseStatement(b, null);
+ // pop the try marker. this is pushed when the TRY bytecode is executed
+ b.add(parserLine, POP);
+ // jump forward to the end of the catch block, start of the finally block
+ b.add(parserLine, JMP);
+ int successJMPInsn = b.size - 1;
+
+ if (peekToken() != CATCH && peekToken() != FINALLY)
+ throw pe("try without catch or finally");
+
+ int catchJMPDistance = -1;
+ if (peekToken() == CATCH) {
+ Vec catchEnds = new Vec();
+ boolean catchAll = false;
+
+ catchJMPDistance = b.size - tryInsn;
+
+ while(peekToken() == CATCH && !catchAll) {
+ String exceptionVar;
+ getToken();
+ consume(LP);
+ consume(NAME);
+ exceptionVar = string;
+ int[] writebacks = new int[] { -1, -1, -1 };
+ if (peekToken() != RP) {
+ // extended Ibex catch block: catch(e faultCode "foo.bar.baz")
+ consume(NAME);
+ b.add(parserLine, DUP);
+ b.add(parserLine, LITERAL, string);
+ b.add(parserLine, GET);
+ b.add(parserLine, DUP);
+ b.add(parserLine, LITERAL, null);
+ b.add(parserLine, EQ);
+ b.add(parserLine, JT);
+ writebacks[0] = b.size - 1;
+ if (peekToken() == STRING) {
+ consume(STRING);
+ b.add(parserLine, DUP);
+ b.add(parserLine, LITERAL, string);
+ b.add(parserLine, LT);
+ b.add(parserLine, JT);
+ writebacks[1] = b.size - 1;
+ b.add(parserLine, DUP);
+ b.add(parserLine, LITERAL, string + "/"); // (slash is ASCII after dot)
+ b.add(parserLine, GE);
+ b.add(parserLine, JT);
+ writebacks[2] = b.size - 1;
+ } else {
+ consume(NUMBER);
+ b.add(parserLine, DUP);
+ b.add(parserLine, LITERAL, number);
+ b.add(parserLine, EQ);
+ b.add(parserLine, JF);
+ writebacks[1] = b.size - 1;
+ }
+ b.add(parserLine, POP); // pop the element thats on the stack from the compare
+ } else {
+ catchAll = true;
+ }
+ 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);
+ b.add(parserLine, POP);
+ parseBlock(b, null);
+ b.add(parserLine, OLDSCOPE);
+
+ b.add(parserLine, JMP);
+ catchEnds.addElement(new Integer(b.size-1));
+
+ for(int i=0; i<3; i++) if (writebacks[i] != -1) b.set(writebacks[i], JS.N(b.size-writebacks[i]));
+ b.add(parserLine, POP); // pop the element thats on the stack from the compare
+ }
+
+ if(!catchAll)
+ b.add(parserLine, THROW);
+
+ for(int i=0;i<catchEnds.size();i++) {
+ int n = ((Integer)catchEnds.elementAt(i)).intValue();
+ b.set(n, JS.N(b.size-n));
+ }
+
+ // pop the try and catch markers
+ b.add(parserLine,POP);
+ b.add(parserLine,POP);
+ }
+
+ // jump here if no exception was thrown
+ b.set(successJMPInsn, JS.N(b.size - successJMPInsn));
+
+ int finallyJMPDistance = -1;
+ if (peekToken() == FINALLY) {
+ b.add(parserLine, LITERAL, null); // null FinallyData
+ finallyJMPDistance = b.size - tryInsn;
+ consume(FINALLY);
+ parseStatement(b, null);
+ b.add(parserLine,FINALLY_DONE);
+ }
+
+ // setup the TRY arguments
+ b.set(tryInsn, new int[] { catchJMPDistance, finallyJMPDistance });
+
+ break;
+ }
+
+ case FOR: {
+ consume(LP);
+
+ tok = getToken();
+ boolean hadVar = false; // if it's a for..in, we ignore the VAR
+ if (tok == VAR) { hadVar = true; tok = getToken(); }
+ String varName = string;
+ boolean forIn = peekToken() == IN; // determine if this is a for..in loop or not
+ pushBackToken(tok, varName);
+
+ if (forIn) {
+ consume(NAME);
+ consume(IN);
+ startExpr(b,-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, 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);
+
+ b.add(parserLine, NEWSCOPE);
+ if(hadVar) {
+ b.add(parserLine, DECLARE, varName);
+ b.add(parserLine, POP);
+ }
+
+ // 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));
+
+ parseStatement(b, null);
+
+ b.add(parserLine, OLDSCOPE);
+ 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
+
+ } else {
+ if (hadVar) pushBackToken(VAR, null); // yeah, this actually matters
+ b.add(parserLine, NEWSCOPE); // grab a fresh scope
+
+ parseStatement(b, null); // initializer
+ JSFunction e2 = // we need to put the incrementor before the test
+ new JSFunction(sourceName, parserLine, null); // so we save the test here
+ if (peekToken() != SEMI)
+ startExpr(e2, -1);
+ else
+ e2.add(parserLine, JSFunction.LITERAL, Boolean.TRUE); // handle the for(foo;;foo) case
+ consume(SEMI);
+ if (label != null) b.add(parserLine, LABEL, label);
+ b.add(parserLine, LOOP);
+ int size2 = b.size;
+
+ b.add(parserLine, JT, JS.ZERO); // if we're on the first iteration, jump over the incrementor
+ int size = b.size;
+ if (peekToken() != RP) { // do the increment thing
+ startExpr(b, -1);
+ b.add(parserLine, POP);
+ }
+ b.set(size - 1, JS.N(b.size - size + 1));
+ consume(RP);
+
+ b.paste(e2); // ok, *now* test if we're done yet
+ b.add(parserLine, JT, JS.N(2)); // break out if we don't meet the test
+ b.add(parserLine, BREAK);
+ parseStatement(b, null);
+ 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
+ }
+ break;
+ }
+
+ case NAME: { // either a label or an identifier; this is the one place we're not LL(1)
+ String possiblyTheLabel = string;
+ if (peekToken() == COLON) { // label
+ consume(COLON);
+ parseStatement(b, possiblyTheLabel);
+ break;
+ } else { // expression
+ pushBackToken(NAME, possiblyTheLabel);
+ startExpr(b, -1);
+ b.add(parserLine, POP);
+ if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
+ break;
+ }
+ }
+
+ case SEMI: return; // yep, the null statement is valid
+
+ case LC: { // blocks are statements too
+ pushBackToken();
+ b.add(parserLine, NEWSCOPE);
+ parseBlock(b, label);
+ b.add(parserLine, OLDSCOPE);
+ break;
+ }
+
+ default: { // hope that it's an expression
+ pushBackToken();
+ startExpr(b, -1);
+ b.add(parserLine, POP);
+ if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
+ break;
+ }
+ }
+ }
+
+
+ // ParserException //////////////////////////////////////////////////////////////////////
+ private IOException pe(String s) { return new IOException(sourceName + ":" + line + " " + s); }
+
+}
+
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+import org.ibex.util.*;
+import java.util.*;
+import java.io.*;
+
+/** A JS interface to a Java '.properties' file; very crude */
+public class PropertyFile extends JS {
+
+ private final Properties p = new Properties();
+ private final Hash cache = new Hash(10, 3);
+ private File f;
+
+ private class Minion extends JS {
+ private final String prefix;
+ Minion(String prefix) { this.prefix = prefix; }
+ public Number coerceToNumber() { return N(coerceToString()); }
+ public Boolean coerceToBoolean() { return B(coerceToString().equals("true")); }
+ public String coerceToString() { return (String)p.get(prefix.substring(0, prefix.length() - 1)); }
+ public Enumeration keys() throws JSExn { throw new JSExn("PropertyFile.keys() not supported"); }
+ public Object get(Object key) throws JSExn {
+ if (toString(key).equals("")) return coerceToString();
+ Object ret = p.get(prefix + escape(toString(key)));
+ if (ret != null) return ret;
+ return new Minion(prefix + escape(toString(key)) + ".");
+ }
+ public void put(Object key, Object val) throws JSExn {
+ try {
+ p.put(prefix + (prefix.equals("") ? "" : ".") + escape(toString(key)), toString(val));
+ File fnew = new File(f.getName() + ".new");
+ FileOutputStream fo = new FileOutputStream(fnew);
+ p.save(fo, "");
+ fo.close();
+ fnew.renameTo(f);
+ f = fnew;
+ } catch (IOException e) { throw new JSExn(e); }
+ }
+ }
+
+ public static String escape(String s) {
+ return s.replaceAll("\\\\", "\\\\\\\\").replaceAll("\\.", "\\\\.").replaceAll("=","\\\\="); }
+ public PropertyFile(File f) throws IOException { this.f = f; this.p.load(new FileInputStream(f)); }
+ public void put(Object key, Object val) throws JSExn { new Minion("").put(key, val); }
+ public Enumeration keys() throws JSExn { return new Minion("").keys(); }
+ public Object get(Object key) throws JSExn {
+ Object ret = p.get(toString(key));
+ if (ret != null) return ret;
+ return new Minion(escape(toString(key)));
+ }
+}
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+import java.io.*;
+import java.util.zip.*;
+import org.ibex.util.*;
+import org.ibex.plat.*;
+import org.ibex.net.*;
+
+/**
+ * Essentiall an InputStream "factory". You can repeatedly ask a
+ * Stream for an InputStream, and each InputStream you get back will
+ * be totally independent of the others (ie separate stream position
+ * and state) although they draw from the same data source.
+ */
+public abstract class Stream extends JS.Cloneable {
+
+ // Public Interface //////////////////////////////////////////////////////////////////////////////
+
+ public static InputStream getInputStream(Object js) throws IOException { return ((Stream)((JS)js).unclone()).getInputStream();}
+ public static class NotCacheableException extends Exception { }
+
+ // streams are "sealed" by default to prevent accidental object leakage
+ public void put(Object key, Object val) { }
+ private Cache getCache = new Cache(100);
+ protected Object _get(Object key) { return null; }
+ public final Object get(Object key) {
+ Object ret = getCache.get(key);
+ if (ret == null) getCache.put(key, ret = _get(key));
+ return ret;
+ }
+
+ // Private Interface //////////////////////////////////////////////////////////////////////////////
+
+ public abstract InputStream getInputStream() throws IOException;
+ protected String getCacheKey() throws NotCacheableException { throw new NotCacheableException(); }
+
+ /** HTTP or HTTPS resource */
+ public static class HTTP extends Stream {
+ private String 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 InputStream getInputStream() throws IOException { return new org.ibex.net.HTTP(url).GET(); }
+ }
+
+ /** byte arrays */
+ public static class ByteArray extends Stream {
+ private byte[] bytes;
+ private String cacheKey;
+ public ByteArray(byte[] bytes, String cacheKey) { this.bytes = bytes; this.cacheKey = cacheKey; }
+ public String getCacheKey() throws NotCacheableException {
+ if (cacheKey == null) throw new NotCacheableException(); return cacheKey; }
+ public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(bytes); }
+ }
+
+ /** a file */
+ public static class File extends Stream {
+ private String path;
+ public File(String path) { this.path = 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); }
+ }
+
+ /** "unwrap" a Zip archive */
+ public static class Zip extends Stream {
+ private Stream parent;
+ private String path;
+ public Zip(Stream parent) { this(parent, null); }
+ public Zip(Stream parent, String path) {
+ while(path != null && path.startsWith("/")) path = path.substring(1);
+ this.parent = parent;
+ this.path = path;
+ }
+ public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "!zip:"; }
+ public Object _get(Object key) { return new Zip(parent, path==null?(String)key:path+'/'+(String)key); }
+ public InputStream getInputStream() throws IOException {
+ InputStream pis = parent.getInputStream();
+ ZipInputStream zis = new ZipInputStream(pis);
+ ZipEntry ze = zis.getNextEntry();
+ while(ze != null && !ze.getName().equals(path)) ze = zis.getNextEntry();
+ if (ze == null) throw new IOException("requested file (" + path + ") not found in archive");
+ return new KnownLength.KnownLengthInputStream(zis, (int)ze.getSize());
+ }
+ }
+
+ /** "unwrap" a Cab archive */
+ public static class Cab extends Stream {
+ private Stream parent;
+ private String path;
+ public Cab(Stream parent) { this(parent, null); }
+ public Cab(Stream parent, String path) { this.parent = parent; this.path = path; }
+ public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "!cab:"; }
+ public Object _get(Object key) { return new Cab(parent, path==null?(String)key:path+'/'+(String)key); }
+ public InputStream getInputStream() throws IOException { return new MSPack(parent.getInputStream()).getInputStream(path); }
+ }
+
+ /** the Builtin resource */
+ public static class Builtin extends Stream {
+ public String getCacheKey() throws NotCacheableException { throw new NotCacheableException(); }
+ public InputStream getInputStream() throws IOException { return Platform.getBuiltinInputStream(); }
+ }
+
+ /** shadow resource which replaces the graft */
+ public static class ProgressWatcher extends Stream {
+ final Stream watchee;
+ JS callback;
+ public ProgressWatcher(Stream watchee, JS callback) { this.watchee = watchee; this.callback = callback; }
+ public String getCacheKey() throws NotCacheableException { return watchee.getCacheKey(); }
+ public InputStream getInputStream() throws IOException {
+ final InputStream is = watchee.getInputStream();
+ return new FilterInputStream(is) {
+ int bytesDownloaded = 0;
+ public int read() throws IOException {
+ int ret = super.read();
+ if (ret != -1) bytesDownloaded++;
+ return ret;
+ }
+ public int read(byte[] b, int off, int len) throws IOException {
+ int ret = super.read(b, off, len);
+ if (ret != 1) bytesDownloaded += ret;
+ Scheduler.add(new Task() { public void perform() throws IOException, JSExn {
+ callback.call(N(bytesDownloaded),
+ N(is instanceof KnownLength ? ((KnownLength)is).getLength() : 0), null, null, 2);
+ } });
+ return ret;
+ }
+ };
+ }
+ }
+
+ /** subclass from this if you want a CachedInputStream for each path */
+ public static class CachedStream extends Stream {
+ private Stream parent;
+ private boolean disk = false;
+ private String key;
+ public String getCacheKey() throws NotCacheableException { return key; }
+ CachedInputStream cis = null;
+ public CachedStream(Stream p, String s, boolean d) throws NotCacheableException {
+ this.parent = p; this.disk = d; this.key = p.getCacheKey();
+ }
+ public InputStream getInputStream() throws IOException {
+ if (cis != null) return cis.getInputStream();
+ if (!disk) {
+ cis = new CachedInputStream(parent.getInputStream());
+ } else {
+ java.io.File f = org.ibex.core.LocalStorage.Cache.getCacheFileForKey(key);
+ if (f.exists()) return new FileInputStream(f);
+ cis = new CachedInputStream(parent.getInputStream(), f);
+ }
+ return cis.getInputStream();
+ }
+ }
+}
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+/** this class contains a <tt>public static final int</tt> for each valid token */
+interface Tokens {
+
+ // Token Constants //////////////////////////////////////////////////////////
+
+ // arithmetic operations; also valid as bytecodes
+ public static final int BITOR = 0; // |
+ public static final int ASSIGN_BITOR = 1; // |=
+ public static final int BITXOR = 2; // ^
+ public static final int ASSIGN_BITXOR = 3; // ^=
+ public static final int BITAND = 4; // &
+ public static final int ASSIGN_BITAND = 5; // &=
+ public static final int LSH = 6; // <<
+ public static final int ASSIGN_LSH = 7; // <<=
+ public static final int RSH = 8; // >>
+ public static final int ASSIGN_RSH = 9; // >>=
+ public static final int URSH = 10; // >>>
+ public static final int ASSIGN_URSH = 11; // >>>=
+ public static final int ADD = 12; // +
+ public static final int ASSIGN_ADD = 13; // +=
+ public static final int SUB = 14; // -
+ public static final int ASSIGN_SUB = 15; // -=
+ public static final int MUL = 16; // *
+ public static final int ASSIGN_MUL = 17; // *=
+ public static final int DIV = 18; // /
+ public static final int ASSIGN_DIV = 19; // /=
+ public static final int MOD = 20; // %
+ public static final int ASSIGN_MOD = 21; // %=
+ public static final int BITNOT = 22; // ~
+ public static final int ASSIGN_BITNOT = 23; // ~=
+
+ // logical operations; also valid as bytecodes
+ public static final int OR = 24; // ||
+ public static final int AND = 25; // &&
+ public static final int BANG = 26; // !
+
+ // equality operations; also valid as bytecodes
+ public static final int EQ = 27; // ==
+ public static final int NE = 28; // !=
+ public static final int LT = 29; // <
+ public static final int LE = 30; // <=
+ public static final int GT = 31; // >
+ public static final int GE = 32; // >=
+ public static final int SHEQ = 33; // ===
+ public static final int SHNE = 34; // !==
+
+ // other permissible bytecode tokens
+ public static final int RETURN = 35; // return
+ public static final int TYPEOF = 36; // typeof
+ public static final int BREAK = 37; // break keyword
+ public static final int CONTINUE = 38; // continue keyword
+ public static final int TRY = 39; // try
+ public static final int THROW = 40; // throw
+ public static final int ASSERT = 41; // assert keyword
+
+ public static final int NAME = 42; // *** identifiers ***
+ public static final int NUMBER = 43; // *** numeric literals ***
+ public static final int STRING = 44; // *** string literals ***
+ public static final int NULL = 45; // null
+ public static final int THIS = 46; // this
+ public static final int FALSE = 47; // false
+ public static final int TRUE = 48; // true
+ public static final int IN = 49; // in
+
+ public static final int SEMI = 50; // ;
+ public static final int LB = 51; // [
+ public static final int RB = 52; // ]
+ public static final int LC = 53; // {
+ public static final int RC = 54; // }
+ public static final int LP = 55; // (
+ public static final int RP = 56; // )
+ public static final int COMMA = 57; // ,
+ public static final int ASSIGN = 58; // =
+ public static final int HOOK = 59; // ?
+ public static final int COLON = 60; // :
+ public static final int INC = 61; // ++
+ public static final int DEC = 62; // --
+ public static final int DOT = 63; // .
+ public static final int FUNCTION = 64; // function
+ public static final int IF = 65; // if keyword
+ public static final int ELSE = 66; // else keyword
+ public static final int SWITCH = 67; // switch keyword
+ public static final int CASE = 68; // case keyword
+ public static final int DEFAULT = 69; // default keyword
+ public static final int WHILE = 70; // while keyword
+ public static final int DO = 71; // do keyword
+ public static final int FOR = 72; // for keyword
+ public static final int VAR = 73; // var keyword
+ public static final int WITH = 74; // with keyword
+ public static final int CATCH = 75; // catch keyword
+ public static final int FINALLY = 76; // finally keyword
+ public static final int RESERVED = 77; // reserved keyword
+ 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 MAX_TOKEN = DEL_TRAP;
+
+ public final static String[] codeToString = new String[] {
+ "BITOR", "ASSIGN_BITOR", "BITXOR", "ASSIGN_BITXOR", "BITAND",
+ "ASSIGN_BITAND", "LSH", "ASSIGN_LSH", "RSH", "ASSIGN_RSH",
+ "URSH", "ASSIGN_URSH", "ADD", "ASSIGN_ADD", "SUB",
+ "ASSIGN_SUB", "MUL", "ASSIGN_MUL", "DIV", "ASSIGN_DIV", "MOD",
+ "ASSIGN_MOD", "BITNOT", "ASSIGN_BITNOT", "OR", "AND", "BANG",
+ "EQ", "NE", "LT", "LE", "GT", "GE", "SHEQ", "SHNE", "RETURN",
+ "TYPEOF", "BREAK", "CONTINUE", "TRY", "THROW", "ASSERT", "NAME",
+ "NUMBER", "STRING", "NULL", "THIS", "FALSE", "TRUE", "IN",
+ "SEMI", "LB", "RB", "LC", "RC", "LP", "RP", "COMMA", "ASSIGN",
+ "HOOK", "COLON", "INC", "DEC", "DOT", "FUNCTION", "IF",
+ "ELSE", "SWITCH", "CASE", "DEFAULT", "WHILE", "DO", "FOR",
+ "VAR", "WITH", "CATCH", "FINALLY", "RESERVED", "GRAMMAR",
+ "ADD_TRAP", "DEL_TRAP"
+ };
+
+}
+
+
--- /dev/null
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.ibex.js;
+
+/**
+ * This class encapsulates a single trap placed on a given node. The
+ * traps for a given property name on a given box are maintained as a
+ * linked list stack, with the most recently placed trap at the head
+ * of the list.
+ */
+class Trap {
+
+ JS trapee = null; ///< the box on which this trap was placed
+ Object name = null; ///< 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
+
+ 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);
+ }
+
+ 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);
+ }
+ }
+}
+