2003/11/13 05:04:22
[org.ibex.core.git] / src / org / xwt / js / JSFunction.java
similarity index 71%
rename from src/org/xwt/js/Function.java
rename to src/org/xwt/js/JSFunction.java
index f31d0ff..1d068e9 100644 (file)
@@ -4,10 +4,15 @@ package org.xwt.js;
 import org.xwt.util.*;
 import java.io.*;
 
-/** a JavaScript function, compiled into bytecode */
-public class Function extends JS.Obj implements ByteCodes, Tokens {
-
-    public int getNumFormalArgs() { return numFormalArgs; }
+/** A JavaScript function, compiled into bytecode */
+public class JSFunction extends JSCallable implements ByteCodes, Tokens {
+
+    /** Note: code gets run in an <i>unpauseable</i> context. */
+    public Object call(JSArray args) {
+        Context cx = new JSContext(this, false);
+        cx.invoke(args);
+        return cx.stack.pop();
+    }
 
 
     // Fields and Accessors ///////////////////////////////////////////////
@@ -19,13 +24,13 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
     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
-    JS.Scope parentScope;          ///< the default scope to use as a parent scope when executing this
+    JSScope parentJSScope;          ///< the default scope to use as a parent scope when executing this
 
 
     // Constructors ////////////////////////////////////////////////////////
 
-    private Function cloneWithNewParentScope(JS.Scope s) {
-        Function ret = new Function(sourceName, firstLine, s);
+    public JSFunction cloneWithNewParentJSScope(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;
@@ -36,14 +41,14 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
         return ret;
     }
 
-    private Function(String sourceName, int firstLine, JS.Scope parentScope) {
+    private JSFunction(String sourceName, int firstLine, JSScope parentJSScope) {
         this.sourceName = sourceName;
         this.firstLine = firstLine;
-        this.parentScope = parentScope;
+        this.parentJSScope = parentJSScope;
     }
 
-    protected Function(String sourceName, int firstLine, Reader sourceCode, JS.Scope parentScope) throws IOException {
-        this(sourceName, firstLine, parentScope);
+    protected JSFunction(String sourceName, int firstLine, Reader sourceCode, JSScope parentJSScope) throws IOException {
+        this(sourceName, firstLine, parentJSScope);
         if (sourceCode == null) return;
         Parser p = new Parser(sourceCode, sourceName, firstLine);
         while(true) {
@@ -64,9 +69,9 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
     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(Function other) { for(int i=0; i<other.size; i++) add(other.line[i], other.op[i], other.arg[i]); }
-    Function add(int line, int op_) { return add(line, op_, null); }
-    Function add(int line, int op_, Object arg_) {
+    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;
@@ -83,14 +88,13 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
     // Invoking the Bytecode ///////////////////////////////////////////////////////
 
     /** returns false if the thread has been paused */
-    static Object eval(final Context cx) throws JS.Exn {
+    static Object eval(final JSContext cx) throws JS.Exn {
+        final initialPauseCount = cx.pausecount;
         OUTER: for(;; cx.pc++) {
         try {
             if (cx.f == null || cx.pc >= cx.f.size) return cx.stack.pop();
             int op = cx.f.op[cx.pc];
             Object arg = cx.f.arg[cx.pc];
-            Object returnedFromJava = null;
-            boolean checkReturnedFromJava = false;
             if(op == FINALLY_DONE) {
                 FinallyData fd = (FinallyData) cx.stack.pop();
                 if(fd == null) continue OUTER; // NOP
@@ -99,8 +103,8 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
             }
             switch(op) {
             case LITERAL: cx.stack.push(arg); break;
-            case OBJECT: cx.stack.push(new JS.Obj()); break;
-            case ARRAY: cx.stack.push(new JS.Array(JS.toNumber(arg).intValue())); break;
+            case OBJECT: cx.stack.push(new JSObj()); break;
+            case ARRAY: cx.stack.push(new JSArray(JS.toNumber(arg).intValue())); break;
             case DECLARE: cx.scope.declare((String)(arg==null ? cx.stack.peek() : arg)); if(arg != null) cx.stack.push(arg); break;
             case TOPSCOPE: cx.stack.push(cx.scope); break;
             case JT: if (JS.toBoolean(cx.stack.pop())) cx.pc += JS.toNumber(arg).intValue() - 1; break;
@@ -109,12 +113,12 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
             case POP: cx.stack.pop(); break;
             case SWAP: { Object o1 = cx.stack.pop(); Object o2 = cx.stack.pop(); cx.stack.push(o1); cx.stack.push(o2); break; }
             case DUP: cx.stack.push(cx.stack.peek()); break;
-            case NEWSCOPE: cx.scope = new JS.Scope(cx.scope); break;
-            case OLDSCOPE: cx.scope = cx.scope.getParentScope(); break;
+            case NEWSCOPE: cx.scope = new JSScope(cx.scope); break;
+            case OLDSCOPE: cx.scope = cx.scope.getParentJSScope(); break;
             case ASSERT: if (!JS.toBoolean(cx.stack.pop())) throw je("assertion failed"); break;
             case BITNOT: cx.stack.push(new Long(~JS.toLong(cx.stack.pop()))); break;
             case BANG: cx.stack.push(new Boolean(!JS.toBoolean(cx.stack.pop()))); break;
-            case NEWFUNCTION: cx.stack.push(((Function)arg).cloneWithNewParentScope(cx.scope)); break;
+            case NEWFUNCTION: cx.stack.push(((JSFunction)arg).cloneWithNewParentJSScope(cx.scope)); break;
             case LABEL: break;
 
             case TYPEOF: {
@@ -130,10 +134,9 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
 
             case PUSHKEYS: {
                 Object o = cx.stack.peek();
-                Object[] keys = ((JS)o).keys();
-                JS.Array a = new JS.Array();
-                a.setSize(keys.length);
-                for(int j=0; j<keys.length; j++) a.setElementAt(keys[j], j);
+                Enumeration e = ((JS)o).keys();
+                JSArray a = new JSArray();
+                while(e.hasMoreElements()) a.addElement(e.nextElement());
                 cx.stack.push(a);
                 break;
             }
@@ -190,9 +193,30 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
                         cx.pc = ((TryMarker)o).finallyLoc - 1;
                         continue OUTER;
                     } else if (o instanceof CallMarker) {
+                        if (cx.scope instanceof JSTrapScope) {
+                            JSTrapScope ts = (JSTrapScope)cx.scope;
+                            if (!ts.cascadeHappened) {
+                                ts.cascadeHappened = true;
+                                JSTrap t = ts.t.next;
+                                while (t != null && t.f.numFormalArgs == 0) t = t.next;
+                                if (t == null) {
+                                    ts.trappee.put(key, val);
+                                    if (cx.pausecount > initialPauseCount) return;   // we were paused
+                                } else {
+                                    cx.stack.push(o);
+                                    JSArray args = new JSArray();
+                                    args.addElement(ts.val);
+                                    cx.stack.push(ta);
+                                    cx.f = t.f;
+                                    cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, ts.val);
+                                    cx.pc = -1;
+                                    break;
+                                }
+                            }
+                        }
                         cx.scope = ((CallMarker)o).scope;
                         cx.pc = ((CallMarker)o).pc;
-                        cx.f = (Function)((CallMarker)o).f;
+                        cx.f = (JSFunction)((CallMarker)o).f;
                         cx.stack.push(retval);
                         continue OUTER;
                     }
@@ -210,9 +234,30 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
                     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");
-                returnedFromJava = ((JS)target).put(key, val);
-                if (returnedFromJava != null) checkReturnedFromJava = true;
-                else cx.stack.push(val);
+                JSTrap t = null;
+                if (o instanceof JSTrap.JSTrappable) {
+                    t = ((JSTrap.JSTrappable)o).getTrap(v);
+                    while (t != null && t.f.numFormalArgs == 0) t = t.next;
+                } else if (o instanceof JSTrap.JSTrapScope && key.equals("cascade")) {
+                    JSTrap.JSTrapScope ts = (JSTrap.JSTrapScope)o;
+                    t = ts.t.next;
+                    ts.cascadeHappened = true;
+                    while (t != null && t.f.numFormalArgs == 0) t = t.next;
+                    if (t == null) o = ts.t.trappee;
+                }
+                if (t != null) {
+                    cx.stack.push(new CallMarker(cx));
+                    JSArray args = new JSArray();
+                    args.addElement(val);
+                    cx.stack.push(ta);
+                    cx.f = t.f;
+                    cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, val);
+                    cx.pc = -1;
+                    break;
+                }
+                ((JS)target).put(key, val);
+                if (cx.pausecount > initialPauseCount) return;   // we were paused
+                cx.stack.push(val);
                 break;
             }
 
@@ -235,53 +280,87 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
                     cx.stack.push(ret);
                     break;
                 } else if (o instanceof JS) {
-                    returnedFromJava = ((JS)o).get(v);
-                    checkReturnedFromJava = true;
+                    JSTrap t = null;
+                    if (o instanceof JSTrap.JSTrappable) {
+                        t = ((JSTrap.JSTrappable)o).getTrap(v);
+                        while (t != null && t.f.numFormalArgs != 0) t = t.next;
+                    } else if (o instanceof JSTrap.JSTrapScope && key.equals("cascade")) {
+                        t = ((JSTrap.JSTrapScope)o).t.next;
+                        while (t != null && t.f.numFormalArgs != 0) t = t.next;
+                        if (t == null) o = ((JSTrap.JSTrapScope)o).t.trappee;
+                    }
+                    if (t != null) {
+                        cx.stack.push(new CallMarker(cx));
+                        JSArray args = new JSArray();
+                        cx.stack.push(ta);
+                        cx.f = t.f;
+                        cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, null);
+                        ((JSTrap.JSTrapScope)cx.scope).cascadeHappened = true;
+                        cx.pc = -1;
+                        break;
+                    }
+                    ret = ((JS)o).get(v);
+                    if (cx.pausecount > initialPauseCount) return;   // we were paused
+                    cx.stack.push(ret);
                     break;
                 }
                 throw je("tried to get property " + v + " from a " + o.getClass().getName());
             }
             
-            case CALL: case CALLMETHOD: case CALL_REVERSED: {
-                JS.Array arguments = new JS.Array();
+            case CALL: case CALLMETHOD: {
                 int numArgs = JS.toNumber(arg).intValue();
-                arguments.setSize(numArgs);
-                Object o = null;
-                if (op == CALL_REVERSED) o = cx.stack.pop();
-                for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(cx.stack.pop(), j);
-                if (op != CALL_REVERSED) o = cx.stack.pop();
+                Object o = cx.stack.pop();
                 if(o == null) throw je("attempted to call null");
                 Object ret;
-
+                Object method = null;
                 if(op == CALLMETHOD) {
-                    Object method = o;
+                    method = o;
+                    if (method == null) throw new JS.Exn("cannot call the null method");
                     o = cx.stack.pop();
                     if(o instanceof String || o instanceof Number || o instanceof Boolean) {
+                        JSArray arguments = new JSArray();
+                        for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(cx.stack.pop(), j);
                         ret = Internal.callMethodOnPrimitive(o,method,arguments);
                         cx.stack.push(ret);
                         cx.pc += 2;  // skip the GET and CALL
-                    } else if (o instanceof JS && ((JS)o).callMethod(method, arguments, true) == Boolean.TRUE) {
-                        returnedFromJava = ((JS)o).callMethod(method, arguments, false);
-                        checkReturnedFromJava = true;
-                        cx.pc += 2;  // skip the GET and CALL
+                        break;
+                    } else if (o instanceof JSCallable) {
+                        // fall through
                     } else {
                         // put the args back on the stack and let the GET followed by CALL happen
-                        for(int j=0; j<numArgs; j++) cx.stack.push(arguments.elementAt(j));
                         cx.stack.push(o);
                         cx.stack.push(method);
+                        break;
                     }
 
-                } else if (o instanceof Function) {
+                } else if (o instanceof JSFunction) {
+                    // FEATURE: use something similar to call0/call1/call2 here
+                    JSArray arguments = new JSArray();
+                    for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(cx.stack.pop(), j);
                     cx.stack.push(new CallMarker(cx));
                     cx.stack.push(arguments);
-                    cx.f = (Function)o;
-                    cx.scope = new Scope(cx.f.parentScope);
+                    cx.f = (JSFunction)o;
+                    cx.scope = new JSScope(cx.f.parentJSScope);
                     cx.pc = -1;
-                    
-                } else {
-                    returnedFromJava = ((JS.Callable)o).call(arguments);
-                    checkReturnedFromJava = true;
+                    break;
+                }
+
+                JSCallable c = ((JSCallable)o);
+                switch(numArgs) {
+                    case 0: ret = c.call0(method); break;
+                    case 1: ret = c.call1(method, cx.stack.pop()); break;
+                    case 2: ret = c.call2(method, cx.stack.pop(), cx.stack.pop()); break;
+                    default: {
+                        JSArray arguments = new JSArray();
+                        for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(cx.stack.pop(), j);
+                        ret = c.call(method, arguments);
+                        if (cx.pausecount > initialPauseCount) return;   // we were paused
+                        break;
+                    }
                 }
+                cx.stack.push(ret);
+                if (method != null) cx.pc += 2;  // skip the GET and CALL if this was a GETCALL
+
                 break;
             }
 
@@ -291,28 +370,18 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
                 throw new JS.Exn(o);
             }
 
-            case INC: case DEC: {
-                boolean isPrefix = JS.toBoolean(arg);
-                Object key = cx.stack.pop();
-                JS obj = (JS)cx.stack.pop();
-                Number num = JS.toNumber(obj.get(key));
-                Number val = new Double(op == INC ? num.doubleValue() + 1.0 : num.doubleValue() - 1.0);
-                obj.put(key, val);
-                cx.stack.push(isPrefix ? val : num);
-                break;
-            }
-            
             case ASSIGN_SUB: case ASSIGN_ADD: {
                 Object val = cx.stack.pop();
                 Object old = cx.stack.pop();
                 Object key = cx.stack.pop();
                 Object obj = cx.stack.peek();
-                if (val instanceof Function && obj instanceof JS.Scope) {
-                    JS.Scope parent = (JS.Scope)obj;
-                    while(parent.getParentScope() != null) parent = parent.getParentScope();
-                    if (parent instanceof org.xwt.Box) {
-                        org.xwt.Box b = (org.xwt.Box)parent;
-                        if (op == ASSIGN_ADD) b.addTrap(key, val); else b.delTrap(key, val);
+                if (val instanceof JSFunction && obj instanceof JSScope) {
+                    JSScope parent = (JSScope)obj;
+                    while(parent.getParentJSScope() != null) parent = parent.getParentJSScope();
+                    if (parent instanceof JSTrap.JSTrappable) {
+                        JSTrap.JSTrappable b = (JSTrap.JSTrappable)parent;
+                        if (op == ASSIGN_ADD) JSTrap.addTrap(b, key, (JSFunction)val);
+                        else JSTrap.delTrap(b, key, (JSFunction)val);
                         // skip over the "normal" implementation of +=/-=
                         cx.pc += ((Integer)arg).intValue() - 1;
                         break;
@@ -418,26 +487,6 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
                 } }
             }
 
-            // handle special directions returned from Java callouts
-            // ideally we would do this with exceptions, but they're *very* slow in gcj
-            if (checkReturnedFromJava) {
-                checkReturnedFromJava = false;
-                if (returnedFromJava == Context.pause) {
-                    cx.pc++;
-                    return Context.pause;
-                } else if (returnedFromJava instanceof TailCall) {
-                    cx.stack.push(new CallMarker(cx));
-                    cx.stack.push(((JS.TailCall)returnedFromJava).args);
-                    cx.f = ((JS.TailCall)returnedFromJava).func;
-                    cx.scope = new Scope(cx.f.parentScope);
-                    cx.pc = -1;
-                } else {
-                    cx.stack.push(returnedFromJava);
-                }
-                continue OUTER;
-            }
-
-
         } catch(JS.Exn e) {
             while(cx.stack.size() > 0) {
                 Object o = cx.stack.pop();
@@ -474,7 +523,7 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
 
     // Debugging //////////////////////////////////////////////////////////////////////
 
-    public String toString() { return "Function [" + sourceName + ":" + firstLine + "]"; }
+    public String toString() { return "JSFunction [" + sourceName + ":" + firstLine + "]"; }
     public String dump() {
         StringBuffer sb = new StringBuffer(1024);
         sb.append("\n" + sourceName + ": " + firstLine + "\n");
@@ -504,10 +553,10 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
 
     static class EvaluatorException extends RuntimeException { public EvaluatorException(String s) { super(s); } }
     static EvaluatorException ee(String s) {
-        throw new EvaluatorException(Context.getSourceName() + ":" + Context.getLine() + " " + s);
+        throw new EvaluatorException(JSContext.getSourceName() + ":" + JSContext.getLine() + " " + s);
     }
     static JS.Exn je(String s) {
-        throw new JS.Exn(Context.getSourceName() + ":" + Context.getLine() + " " + s);
+        throw new JS.Exn(JSContext.getSourceName() + ":" + JSContext.getLine() + " " + s);
     }
 
 
@@ -515,9 +564,9 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
 
     public static class CallMarker {
         int pc;
-        Scope scope;
-        Function f;
-        public CallMarker(Context cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
+        JSScope scope;
+        JSFunction f;
+        public CallMarker(JSContext cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
     }
     
     public static class CatchMarker { public CatchMarker() { } }
@@ -526,8 +575,8 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
     public static class LoopMarker {
         public int location;
         public String label;
-        public JS.Scope scope;
-        public LoopMarker(int location, String label, JS.Scope scope) {
+        public JSScope scope;
+        public LoopMarker(int location, String label, JSScope scope) {
             this.location = location;
             this.label = label;
             this.scope = scope;
@@ -536,8 +585,8 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
     public static class TryMarker {
         public int catchLoc;
         public int finallyLoc;
-        public JS.Scope scope;
-        public TryMarker(int catchLoc, int finallyLoc, JS.Scope scope) {
+        public JSScope scope;
+        public TryMarker(int catchLoc, int finallyLoc, JSScope scope) {
             this.catchLoc = catchLoc;
             this.finallyLoc = finallyLoc;
             this.scope = scope;