2003/06/08 23:48:03
[org.ibex.core.git] / src / org / xwt / js / JS.java
index f2946ea..ea117c4 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,6 +69,7 @@ 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; } 
@@ -59,8 +78,11 @@ public abstract class JS {
        public Object getObject() { return js; } 
     } 
 
+    /** A JavaScript Array */
     public static class Array extends Obj {
        private Vec vec = new Vec();
+       public Array() { }
+       public Array(int size) { vec.setSize(size); }
        private static int intVal(Object o) {
            if (o instanceof Number) {
                int intVal = ((Number)o).intValue();
@@ -86,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 {
@@ -102,38 +124,42 @@ public abstract class JS {
            ret[vec.size()] = "length";
            return ret;
        }
+       public void setSize(int i) { vec.setSize(i); }
        public int length() { return vec.size(); }
        public Object elementAt(int i) { return vec.elementAt(i); }
        public void addElement(Object o) { vec.addElement(o); }
        public void setElementAt(Object o, int i) { vec.setElementAt(o, i); }
     }
 
-    public static Hashtable currentFunction = new Hashtable();
+    /** Anything that is callable */
     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);
+       public int getLine() throws JS.Exn { return -1; }
+       public abstract Object _call(JS.Array args) throws JS.Exn, ByteCodeBlock.ControlTransferException;
+       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);
            return null;
        }
        public static Script parse(Reader r, String sourceName, int line) throws IOException {
@@ -141,8 +167,8 @@ public abstract class JS {
            try {
                Vector exprs = new Vector();
                while(true) {
-                   Parser.Expr ret = p.parseBlock(false);
-                   if (ret == null || (ret.code == Parser.LC && ret.left == null)) break;
+                   ByteCodeBlock ret = p.parseStatement();
+                   if (ret == null) break;
                    exprs.addElement(ret);
                }
                return new Script(exprs);
@@ -174,7 +200,10 @@ 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);
+       }
     }