2003/06/09 03:13:18
[org.ibex.core.git] / src / org / xwt / js / JS.java
index 0628891..b0d54e5 100644 (file)
@@ -1,29 +1,46 @@
-// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL] 
+// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] 
 
 package org.xwt.js; 
 import org.xwt.util.*; 
 import java.io.*;
 import java.util.*;
 
-/** The public API for the JS engine */
-// FEATURE: try using mutable, recycled 'Num' objects
+/**
+ *  The public API for the JS engine; JS itself is actually a class
+ *  implementing the minimal amount of functionality for an Object
+ *  which can be manipulated by JavaScript code.
+ */
 public abstract class JS { 
 
+
     // Static Methods //////////////////////////////////////////////////////////////////////
 
-    public static Function getCurrentFunction() {
-       return (Function)currentFunction.get(Thread.currentThread());
-    }
-    public static String getCurrentFunctionSourceName() {
-       return getCurrentFunctionSourceName(Thread.currentThread());
-    }
+    private static Hashtable currentFunction = new Hashtable();
+    public static Function getCurrentFunction() { return (Function)currentFunction.get(Thread.currentThread()); }
+    public static String getCurrentFunctionSourceName() { return getCurrentFunctionSourceName(Thread.currentThread()); }
+    public static String getFileAndLine() { return getCurrentFunctionSourceName() + ":" + getCurrentFunction().getLine(); }
     public static String getCurrentFunctionSourceName(Thread t) {
        Function f = (Function)currentFunction.get(t);
        if (f == null) return "null";
        return f.getSourceName();
     }
-    public static String getFileAndLine() {
-       return "unknown:??";
+
+    public static boolean toBoolean(Object o) {
+       if (o == null) return false;
+       if (o instanceof Boolean) return ((Boolean)o).booleanValue();
+       if (o instanceof Number) return o.equals(new Integer(0));
+       return true;
+    }
+
+    public static long toLong(Object o) { return toNumber(o).longValue(); }
+    public static double toDouble(Object o) { return toNumber(o).doubleValue(); }
+    public static Number toNumber(Object o) {
+       if (o == null) return new Long(0);
+       if (o instanceof Number) return ((Number)o);
+       if (o instanceof String) try { return new Double((String)o); } catch (NumberFormatException e) { return new Double(0); }
+       if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? new Long(1) : new Long(0);
+       if (o instanceof JS) return ((JS)o).coerceToNumber();
+       throw new Error("toNumber() got object of type " + o.getClass().getName() + " which we don't know how to handle");
     }
 
 
@@ -40,6 +57,7 @@ public abstract class JS {
 
     // Subclasses /////////////////////////////////////////////////////////////////////////
 
+    /** A slightly more featureful version of JS */
     public static class Obj extends JS {
        private Hash entries = new Hash();
        private boolean sealed = false;
@@ -51,14 +69,16 @@ public abstract class JS {
        public Object[] keys() { return(entries.keys()); }
     }
 
+    /** An exception which can be thrown and caught by JavaScripts */
     public static class Exn extends RuntimeException { 
        private Object js = null; 
        public Exn(Object js) { this.js = js; } 
-       public String toString() { return "JS.Exn: " + js.toString(); }
+       public String toString() { return "JS.Exn: " + js; }
        public String getMessage() { return toString(); }
        public Object getObject() { return js; } 
     } 
 
+    /** A JavaScript Array */
     public static class Array extends Obj {
        private Vec vec = new Vec();
        public Array() { }
@@ -88,7 +108,7 @@ public abstract class JS {
            }
        }
        public void put(Object key, Object val) {
-           if (key.equals("length")) vec.setSize(Parser.toNumber(val).intValue());
+           if (key.equals("length")) vec.setSize(toNumber(val).intValue());
            int i = intVal(key);
            if (i == Integer.MIN_VALUE) super.put(key, val);
            else {
@@ -111,32 +131,51 @@ public abstract class JS {
        public void setElementAt(Object o, int i) { vec.setElementAt(o, i); }
     }
 
-    public static Hashtable currentFunction = new Hashtable();
-    public static abstract class Function extends Obj {
-       public abstract Object _call(JS.Array args) throws JS.Exn;
-       public String getSourceName() throws JS.Exn { return "unknown"; }
-       public final Object call(JS.Array args) throws JS.Exn { return _call(args); }
-    }
-
-    public static class Script extends Function {
-       Vector e = null;
-       private Script(Vector e) { this.e = e; }
-       public String getSourceName() throws JS.Exn { return ((Parser.Expr)e.elementAt(0)).sourceName; }
-       public Object _call(JS.Array args) throws JS.Exn {
-           Scope rootScope = (Scope)args.elementAt(0);
+    /** Anything that is callable */
+    public static class Function extends Obj {
+       ByteCodeBlock bytecodes;
+       int line;
+       String sourceName;
+       Scope parentScope;
+       public Function() { this(-1, "unknown", null, null); }
+       public Function(int line, String sourceName, ByteCodeBlock bytecodes, Scope parentScope) {
+           this.sourceName = sourceName;
+           this.line = line;
+           this.bytecodes = bytecodes;
+           this.parentScope = parentScope;
+       }
+       public String getSourceName() throws JS.Exn { return sourceName; }
+       public int getLine() throws JS.Exn { return line; }
+       public Object _call(JS.Array args) throws JS.Exn, ByteCodeBlock.ControlTransferException {
+           if (bytecodes == null) throw new Error("tried to call() a JS.Function with bytecodes == null");
+           Vec stack = new Vec();
+           stack.push(args);
+           return bytecodes.eval(new FunctionScope(sourceName, parentScope), stack);
+       }
+       public final Object call(JS.Array args) throws JS.Exn {
            Function saved = (Function)currentFunction.get(Thread.currentThread());
            currentFunction.put(Thread.currentThread(), this);
            try {
-               for(int i=0; i<e.size(); i++)
-                   ((Parser.Expr)e.elementAt(i)).eval(rootScope);
-           } catch (Parser.ReturnException e) {
-               // ignore
-           } catch (Parser.ControlTransferException e) {
-               throw new JS.Exn("block threw a ControlTransferException: " + e);
+               return _call(args);
+           } catch (ByteCodeBlock.ReturnException e) {  // ignore
+               return e.retval;
+           } catch (ByteCodeBlock.ControlTransferException e) {
+               throw new RuntimeException(getSourceName() + ":" + getLine() +
+                                          " error, ControlTransferException tried to leave a function");
            } finally {
                if (saved == null) currentFunction.remove(Thread.currentThread());
                else currentFunction.put(Thread.currentThread(), saved);
            }
+       }
+    }
+
+    public static class Script extends Function {
+       Vector e = null;
+       private Script(Vector e) { this.e = e; }
+       public String getSourceName() throws JS.Exn { return ((ByteCodeBlock)e.elementAt(0)).getSourceName(); }
+       public Object _call(JS.Array args) throws JS.Exn, ByteCodeBlock.ControlTransferException {
+           Scope rootScope = (Scope)args.elementAt(0);
+           for(int i=0; i<e.size(); i++) ((ByteCodeBlock)e.elementAt(i)).eval(rootScope, new Vec());
            return null;
        }
        public static Script parse(Reader r, String sourceName, int line) throws IOException {
@@ -144,7 +183,7 @@ public abstract class JS {
            try {
                Vector exprs = new Vector();
                while(true) {
-                   Parser.Expr ret = p.parseBlock(false);
+                   ByteCodeBlock ret = p.parseStatement();
                    if (ret == null) break;
                    exprs.addElement(ret);
                }
@@ -177,10 +216,22 @@ public abstract class JS {
            else super.put(key, val == null ? NULL : val);
        }
        public Object[] keys() { throw new Error("you can't enumerate the properties of a Scope"); }
-       public void declare(String s) { if (isTransparent()) getParentScope().declare(s); else super.put(s, NULL);}
+       public void declare(String s) {
+           if (isTransparent()) getParentScope().declare(s);
+           else super.put(s, NULL);
+       }
     } 
  
-
+    private class FunctionScope extends JS.Scope {
+       String sourceName;
+       public FunctionScope(String sourceName, Scope parentScope) { super(parentScope); this.sourceName = sourceName; }
+       public String getSourceName() { return sourceName; }
+       public Object get(Object key) throws JS.Exn {
+           if (key.equals("trapee")) return org.xwt.Trap.currentTrapee();
+           else if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction;
+           return super.get(key);
+       }
+    }
 }