2003/06/07 11:04:13
[org.ibex.core.git] / src / org / xwt / js / ForthBlock.java
index 0cc4ed2..4741d76 100644 (file)
@@ -18,309 +18,310 @@ class ForthBlock implements OpCodes, Tokens {
 
     public int size() { return size; }
     public void set(int pos, int op_, Object arg_) { op[pos] = op_; arg[pos] = arg_; }
+    public void paste(ForthBlock other) { for(int i=0; i<other.size; i++) add(other.op[i], other.arg[i]); }
     public ForthBlock add(int op_) { return add(op_, null); }
     public ForthBlock add(int op_, Object arg_) {
-       if (size == op.length - 1) {
-           int[] op2 = new int[op.length * 2]; System.arraycopy(op, 0, op2, 0, op.length); op = op2;
-           Object[] arg2 = new Object[op.length * 2]; System.arraycopy(arg, 0, arg2, 0, arg.length); arg = arg2;
-       }
-       op[size] = op_;
-       arg[size] = arg_;
-       size++;
-       return this;
+        if (size == op.length - 1) {
+            int[] op2 = new int[op.length * 2]; System.arraycopy(op, 0, op2, 0, op.length); op = op2;
+            Object[] arg2 = new Object[op.length * 2]; System.arraycopy(arg, 0, arg2, 0, arg.length); arg = arg2;
+        }
+        op[size] = op_;
+        arg[size] = arg_;
+        size++;
+        return this;
     }
-       
+        
     public Object eval(final JS.Scope s) throws ControlTransferException, JS.Exn { return eval(s, new Stack()); }
     public Object eval(final JS.Scope s, Stack t) throws ControlTransferException {
-       for(int i=0; i<size; i++)
-           switch(op[i]) {
-           case LABEL: break; // FIXME
-           case LITERAL: t.push(arg[i]); break;
-           case OBJECT: t.push(new JS.Obj()); break;
-           case ARRAY: t.push(new JS.Array(JS.toNumber(arg[i]).intValue())); break;
-           case DECLARE: s.declare((String)t.pop()); break;
-           case THIS: t.push(s); break;   // FIXME: transparents
-           case JT: if (JS.toBoolean(t.pop())) i += JS.toNumber(arg[i]).intValue() - 1; break;
-           case JF: if (!JS.toBoolean(t.pop())) i += JS.toNumber(arg[i]).intValue() - 1; break;
-           case JMP: i += JS.toNumber(arg[i]).intValue() - 1; break;
-           case POP: t.pop(); break;
-           case SWAP: t.swap(); break;
-           case DUP: t.push(t.peek()); break;
-           case NOP: break;
-           case EXPR: t.push(((ForthBlock)arg[i]).eval(s)); break;
-           case SCOPE: t.push(((ForthBlock)arg[i]).eval(new JS.Scope(s))); break;
+        for(int i=0; i<size; i++)
+            switch(op[i]) {
+            case LABEL: break; // FIXME
+            case LITERAL: t.push(arg[i]); break;
+            case OBJECT: t.push(new JS.Obj()); break;
+            case ARRAY: t.push(new JS.Array(JS.toNumber(arg[i]).intValue())); break;
+            case DECLARE: s.declare((String)t.pop()); break;
+            case THIS: t.push(s); break;   // FIXME: transparents
+            case JT: if (JS.toBoolean(t.pop())) i += JS.toNumber(arg[i]).intValue() - 1; break;
+            case JF: if (!JS.toBoolean(t.pop())) i += JS.toNumber(arg[i]).intValue() - 1; break;
+            case JMP: i += JS.toNumber(arg[i]).intValue() - 1; break;
+            case POP: t.pop(); break;
+            case SWAP: t.swap(); break;
+            case DUP: t.push(t.peek()); break;
+            case NOP: break;
+            case EXPR: t.push(((ForthBlock)arg[i]).eval(s)); break;
+            case SCOPE: t.push(((ForthBlock)arg[i]).eval(new JS.Scope(s))); break;
 
-           case ASSERT: if (!JS.toBoolean(t.pop())) throw new EvaluatorException(line, sourceName, "assertion failed"); break;
-           case RETURN: throw new ReturnException(t.pop());
-           case THROW: throw new JS.Exn(t.pop());
+            case ASSERT: if (!JS.toBoolean(t.pop())) throw new EvaluatorException(line, sourceName, "assertion failed"); break;
+            case RETURN: throw new ReturnException(t.pop());
+            case THROW: throw new JS.Exn(t.pop());
 
-           case TRY: break;
-           case INSTANCEOF: break;
-           case TYPEOF: break;
-           case PUSHKEYS: {
-               Object o = t.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);
-               t.push(a);
-               break;
-           }
+            case TRY: break;
+            case INSTANCEOF: break;
+            case TYPEOF: break;
+            case PUSHKEYS: {
+                Object o = t.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);
+                t.push(a);
+                break;
+            }
 
-           case Lexer.BITNOT: t.push(new Long(~JS.toLong(t.pop()))); break;
-           case Lexer.BANG: t.push(new Boolean(!JS.toBoolean(t.pop()))); break;
-                   
-           case Lexer.BREAK: {
-               // FIXME: make sure this can only appear in proper places
-               return Boolean.FALSE;
-           }
-           case Lexer.CONTINUE: {
-               // FIXME: make sure this can only appear in proper places
-               return Boolean.TRUE;
-           }
-           case LOOP: {
-               ForthBlock loop = (ForthBlock)arg[i];
-               Stack t2 = new Stack();
-               t2.push(Boolean.TRUE);
-               while (true) {
-                   Boolean result;
-                   try {
-                       result = (Boolean)loop.eval(new JS.Scope(s), t2);
-                   } catch (ContinueException c) {
-                       result = Boolean.TRUE;
-                   } catch (BreakException b) {
-                       result = Boolean.FALSE;
-                   }
-                   if (result == Boolean.FALSE) break;
-                   t2 = new Stack();
-                   t2.push(Boolean.FALSE);
-               }
-               break;
-           }
+            case Lexer.BITNOT: t.push(new Long(~JS.toLong(t.pop()))); break;
+            case Lexer.BANG: t.push(new Boolean(!JS.toBoolean(t.pop()))); break;
+                    
+            case Lexer.BREAK: {
+                // FIXME: make sure this can only appear in proper places
+                return Boolean.FALSE;
+            }
+            case Lexer.CONTINUE: {
+                // FIXME: make sure this can only appear in proper places
+                return Boolean.TRUE;
+            }
+            case LOOP: {
+                ForthBlock loop = (ForthBlock)arg[i];
+                Stack t2 = new Stack();
+                t2.push(Boolean.TRUE);
+                while (true) {
+                    Boolean result;
+                    try {
+                        result = (Boolean)loop.eval(new JS.Scope(s), t2);
+                    } catch (ContinueException c) {
+                        result = Boolean.TRUE;
+                    } catch (BreakException b) {
+                        result = Boolean.FALSE;
+                    }
+                    if (result == Boolean.FALSE) break;
+                    t2 = new Stack();
+                    t2.push(Boolean.FALSE);
+                }
+                break;
+            }
 
-           case PUT: {
-               Object val = t.pop();
-               Object key = t.pop();
-               JS target = (JS)t.peek();
-               if (target == null) throw new JS.Exn("tried to put a value to the " + key + " property on the null value");
-               target.put(key, val);
-               t.push(val);
-               break;
-           }
+            case PUT: {
+                Object val = t.pop();
+                Object key = t.pop();
+                JS target = (JS)t.peek();
+                if (target == null) throw new JS.Exn("tried to put a value to the " + key + " property on the null value");
+                target.put(key, val);
+                t.push(val);
+                break;
+            }
 
-           case GET: {
-               Object v = t.pop();
-               Object o = t.pop();
-               t.push(doGet(o, v));
-               break;
-           }
+            case GET: {
+                Object v = t.pop();
+                Object o = t.pop();
+                t.push(doGet(o, v));
+                break;
+            }
 
-           case GET_PRESERVE: {
-               Object v = t.pop();
-               Object o = t.peek();
-               t.push(v);
-               t.push(doGet(o, v));
-               break;
-           }
-                   
-           case CALL: {
-               JS.Array arguments = new JS.Array();
-               int numArgs = JS.toNumber(arg[i]).intValue();
-               arguments.setSize(numArgs);
-               for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(t.pop(), j);
-               JS.Function f = (JS.Function)t.pop();
-               if (f == null) throw new JS.Exn(new EvaluatorException(line, sourceName, "attempted to call null"));
-               t.push(f.call(arguments));
-               break;
-           }
+            case GET_PRESERVE: {
+                Object v = t.pop();
+                Object o = t.peek();
+                t.push(v);
+                t.push(doGet(o, v));
+                break;
+            }
+                    
+            case CALL: {
+                JS.Array arguments = new JS.Array();
+                int numArgs = JS.toNumber(arg[i]).intValue();
+                arguments.setSize(numArgs);
+                for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(t.pop(), j);
+                JS.Function f = (JS.Function)t.pop();
+                if (f == null) throw new JS.Exn(new EvaluatorException(line, sourceName, "attempted to call null"));
+                t.push(f.call(arguments));
+                break;
+            }
 
-           case OpCodes.FUNCTION: {
-               final ForthBlock myBytes = (ForthBlock)arg[i];
-               t.push(new JS.Function() {
-                       public String toString() { return sourceName + ":" + line; }
-                       public String getSourceName() throws JS.Exn { return sourceName; }
-                       public int getLine() throws JS.Exn { return line; }
-                       public Object _call(final JS.Array args) throws JS.Exn {
-                           Function save = JS.getCurrentFunction();
-                           JS.currentFunction.put(java.lang.Thread.currentThread(), this);
-                           JS.Scope scope = new JS.Scope(s) {
-                                   // FIXME
-                                   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);
-                                   }
-                               };
-                           Stack t0 = new Stack();
-                           t0.push(args);
-                           try {
-                               return myBytes.eval(scope, t0);
-                           } catch (ReturnException r) {
-                               return r.retval;
-                           } catch (ControlTransferException c) {
-                               throw new EvaluatorException(line, sourceName, "error, ControlTransferException tried to leave a function: " + c);
-                           } finally {
-                               if (save == null) JS.currentFunction.remove(java.lang.Thread.currentThread());
-                               else JS.currentFunction.put(java.lang.Thread.currentThread(), save);
-                           }
-                       }
-                   });
-               break;
-           }
+            case OpCodes.FUNCTION: {
+                final ForthBlock myBytes = (ForthBlock)arg[i];
+                t.push(new JS.Function() {
+                        public String toString() { return sourceName + ":" + line; }
+                        public String getSourceName() throws JS.Exn { return sourceName; }
+                        public int getLine() throws JS.Exn { return line; }
+                        public Object _call(final JS.Array args) throws JS.Exn {
+                            Function save = JS.getCurrentFunction();
+                            JS.currentFunction.put(java.lang.Thread.currentThread(), this);
+                            JS.Scope scope = new JS.Scope(s) {
+                                    // FIXME
+                                    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);
+                                    }
+                                };
+                            Stack t0 = new Stack();
+                            t0.push(args);
+                            try {
+                                return myBytes.eval(scope, t0);
+                            } catch (ReturnException r) {
+                                return r.retval;
+                            } catch (ControlTransferException c) {
+                                throw new EvaluatorException(line, sourceName, "error, ControlTransferException tried to leave a function: " + c);
+                            } finally {
+                                if (save == null) JS.currentFunction.remove(java.lang.Thread.currentThread());
+                                else JS.currentFunction.put(java.lang.Thread.currentThread(), save);
+                            }
+                        }
+                    });
+                break;
+            }
 
-           case Lexer.INC: case Lexer.DEC: {
-               boolean isPrefix = JS.toBoolean(arg[i]);
-               Object key = t.pop();
-               JS obj = (JS)t.pop();
-               Number num = JS.toNumber(obj.get(key));
-               Number val = new Double(op[i] == Lexer.INC ? num.doubleValue() + 1.0 : num.doubleValue() - 1.0);
-               obj.put(key, val);
-               t.push(isPrefix ? val : num);
-               break;
-           }
+            case Lexer.INC: case Lexer.DEC: {
+                boolean isPrefix = JS.toBoolean(arg[i]);
+                Object key = t.pop();
+                JS obj = (JS)t.pop();
+                Number num = JS.toNumber(obj.get(key));
+                Number val = new Double(op[i] == Lexer.INC ? num.doubleValue() + 1.0 : num.doubleValue() - 1.0);
+                obj.put(key, val);
+                t.push(isPrefix ? val : num);
+                break;
+            }
 
-           default: {
-               Object right = t.pop();
-               Object left = t.pop();
-               switch(op[i]) {
+            default: {
+                Object right = t.pop();
+                Object left = t.pop();
+                switch(op[i]) {
 
-               case Lexer.BITOR: t.push(new Long(JS.toLong(left) | JS.toLong(right))); break;
-               case Lexer.BITXOR: t.push(new Long(JS.toLong(left) ^ JS.toLong(right))); break;
-               case Lexer.BITAND: t.push(new Long(JS.toLong(left) & JS.toLong(right))); break;
+                case Lexer.BITOR: t.push(new Long(JS.toLong(left) | JS.toLong(right))); break;
+                case Lexer.BITXOR: t.push(new Long(JS.toLong(left) ^ JS.toLong(right))); break;
+                case Lexer.BITAND: t.push(new Long(JS.toLong(left) & JS.toLong(right))); break;
 
-               case Lexer.ADD: {
-                   Object l = left;
-                   Object r = right;
-                   if (l instanceof String || r instanceof String) {
-                       if (l == null) l = "null";
-                       if (r == null) r = "null";
-                       if (l instanceof Number && ((Number)l).doubleValue() == ((Number)l).longValue())
-                           l = new Long(((Number)l).longValue());
-                       if (r instanceof Number && ((Number)r).doubleValue() == ((Number)r).longValue())
-                           r = new Long(((Number)r).longValue());
-                       t.push(l.toString() + r.toString()); break;
-                   }
-                   t.push(new Double(JS.toDouble(l) + JS.toDouble(r))); break;
-               }
-                       
-               case Lexer.SUB: t.push(new Double(JS.toDouble(left) - JS.toDouble(right))); break;
-               case Lexer.MUL: t.push(new Double(JS.toDouble(left) * JS.toDouble(right))); break;
-               case Lexer.DIV: t.push(new Double(JS.toDouble(left) / JS.toDouble(right))); break;
-               case Lexer.MOD: t.push(new Double(JS.toDouble(left) % JS.toDouble(right))); break;
-                       
-               case Lexer.LSH: t.push(new Long(JS.toLong(left) << JS.toLong(right))); break;
-               case Lexer.RSH: t.push(new Long(JS.toLong(left) >> JS.toLong(right))); break;
-               case Lexer.URSH: t.push(new Long(JS.toLong(left) >>> JS.toLong(right))); break;
-                       
-                   // FIXME: these need to work on strings
-               case Lexer.LT: t.push(JS.toDouble(left) < JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
-               case Lexer.LE: t.push(JS.toDouble(left) <= JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
-               case Lexer.GT: t.push(JS.toDouble(left) > JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
-               case Lexer.GE: t.push(JS.toDouble(left) >= JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
-                   
-               case Lexer.EQ:
-               case Lexer.NE: {
-                   // FIXME: should use Javascript coercion-equality rules
-                   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 (l instanceof Boolean) ret = new Boolean(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);
-                   t.push(new Boolean(op[i] == Lexer.EQ ? ret : !ret)); break;
-               }
+                case Lexer.ADD: {
+                    Object l = left;
+                    Object r = right;
+                    if (l instanceof String || r instanceof String) {
+                        if (l == null) l = "null";
+                        if (r == null) r = "null";
+                        if (l instanceof Number && ((Number)l).doubleValue() == ((Number)l).longValue())
+                            l = new Long(((Number)l).longValue());
+                        if (r instanceof Number && ((Number)r).doubleValue() == ((Number)r).longValue())
+                            r = new Long(((Number)r).longValue());
+                        t.push(l.toString() + r.toString()); break;
+                    }
+                    t.push(new Double(JS.toDouble(l) + JS.toDouble(r))); break;
+                }
+                        
+                case Lexer.SUB: t.push(new Double(JS.toDouble(left) - JS.toDouble(right))); break;
+                case Lexer.MUL: t.push(new Double(JS.toDouble(left) * JS.toDouble(right))); break;
+                case Lexer.DIV: t.push(new Double(JS.toDouble(left) / JS.toDouble(right))); break;
+                case Lexer.MOD: t.push(new Double(JS.toDouble(left) % JS.toDouble(right))); break;
+                        
+                case Lexer.LSH: t.push(new Long(JS.toLong(left) << JS.toLong(right))); break;
+                case Lexer.RSH: t.push(new Long(JS.toLong(left) >> JS.toLong(right))); break;
+                case Lexer.URSH: t.push(new Long(JS.toLong(left) >>> JS.toLong(right))); break;
+                        
+                    // FIXME: these need to work on strings
+                case Lexer.LT: t.push(JS.toDouble(left) < JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
+                case Lexer.LE: t.push(JS.toDouble(left) <= JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
+                case Lexer.GT: t.push(JS.toDouble(left) > JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
+                case Lexer.GE: t.push(JS.toDouble(left) >= JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
+                    
+                case Lexer.EQ:
+                case Lexer.NE: {
+                    // FIXME: should use Javascript coercion-equality rules
+                    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 (l instanceof Boolean) ret = new Boolean(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);
+                    t.push(new Boolean(op[i] == Lexer.EQ ? ret : !ret)); break;
+                }
 
-               default: throw new Error("unknown opcode " + op[i]);
-               } }
-           }
-       if (t.size() != 1) {
-           for(int i=0; i<size; i++) {
-               System.out.println((op[i] >= 0 ? codeToString[op[i]] : "" + op[i]) + " [" + arg[i] + "]");
-           }
-           throw new EvaluatorException(line, sourceName, "eval() terminated with " + t.size() + " elements on the stack; one expected");
-       }
-       return t.pop();
+                default: throw new Error("unknown opcode " + op[i]);
+                } }
+            }
+        if (t.size() != 1) {
+            for(int i=0; i<size; i++) {
+                System.out.println((op[i] >= 0 ? codeToString[op[i]] : "" + op[i]) + " [" + arg[i] + "]");
+            }
+            throw new EvaluatorException(line, sourceName, "eval() terminated with " + t.size() + " elements on the stack; one expected");
+        }
+        return t.pop();
     }
 
     public Object doGet(final Object o, final Object v) {
-       if (o == null) {
-           throw new EvaluatorException(line, sourceName, "tried to get property \"" + v + "\" from the null value");
-       }
-       if (o instanceof String) {
-           if (v.equals("length")) return new Integer(((String)o).length());
-           else if (v.equals("substring")) return new JS.Function() {
-                   public Object _call(JS.Array args) {
-                       if (args.length() == 1) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue());
-                       else if (args.length() == 2) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue(),
-                                                                                 JS.toNumber(args.elementAt(1)).intValue());
-                       else throw new Error("String.substring() can only take one or two arguments");
-                   }
-               };
-           else if (v.equals("toLowerCase")) return new JS.Function() {
-                   public Object _call(JS.Array args) {
-                       return ((String)o).toLowerCase();
-                   } };
-           else if (v.equals("toUpperCase")) return new JS.Function() {
-                   public Object _call(JS.Array args) {
-                       return ((String)o).toString().toUpperCase();
-                   } };
-           else if (v.equals("charAt")) return new JS.Function() {
-                   public Object _call(JS.Array args) {
-                       return ((String)o).charAt(JS.toNumber(args.elementAt(0)).intValue()) + "";
-                   } };
-           else if (v.equals("lastIndexOf")) return new JS.Function() {
-                   public Object _call(JS.Array args) {
-                       if (args.length() != 1) return null;
-                       return new Integer(((String)o).lastIndexOf(args.elementAt(0).toString()));
-                   } };
-           else if (v.equals("indexOf")) return new JS.Function() {
-                   public Object _call(JS.Array args) {
-                       if (args.length() != 1) return null;
-                       return new Integer(((String)o).indexOf(args.elementAt(0).toString()));
-                   } };
-           throw new Error("Not Implemented: propery " + v + " on String objects");
-       } else if (o instanceof Boolean) {
-           throw new Error("Not Implemented: properties on Boolean objects");
-       } else if (o instanceof Number) {
-           Log.log(this, "Not Implemented: properties on Number objects");
-           return null;
-           //throw new Error("Not Implemented: properties on Number objects");
-       } else if (o instanceof JS) {
-           return ((JS)o).get(v);
-       }
-       return null;
+        if (o == null) {
+            throw new EvaluatorException(line, sourceName, "tried to get property \"" + v + "\" from the null value");
+        }
+        if (o instanceof String) {
+            if (v.equals("length")) return new Integer(((String)o).length());
+            else if (v.equals("substring")) return new JS.Function() {
+                    public Object _call(JS.Array args) {
+                        if (args.length() == 1) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue());
+                        else if (args.length() == 2) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue(),
+                                                                                  JS.toNumber(args.elementAt(1)).intValue());
+                        else throw new Error("String.substring() can only take one or two arguments");
+                    }
+                };
+            else if (v.equals("toLowerCase")) return new JS.Function() {
+                    public Object _call(JS.Array args) {
+                        return ((String)o).toLowerCase();
+                    } };
+            else if (v.equals("toUpperCase")) return new JS.Function() {
+                    public Object _call(JS.Array args) {
+                        return ((String)o).toString().toUpperCase();
+                    } };
+            else if (v.equals("charAt")) return new JS.Function() {
+                    public Object _call(JS.Array args) {
+                        return ((String)o).charAt(JS.toNumber(args.elementAt(0)).intValue()) + "";
+                    } };
+            else if (v.equals("lastIndexOf")) return new JS.Function() {
+                    public Object _call(JS.Array args) {
+                        if (args.length() != 1) return null;
+                        return new Integer(((String)o).lastIndexOf(args.elementAt(0).toString()));
+                    } };
+            else if (v.equals("indexOf")) return new JS.Function() {
+                    public Object _call(JS.Array args) {
+                        if (args.length() != 1) return null;
+                        return new Integer(((String)o).indexOf(args.elementAt(0).toString()));
+                    } };
+            throw new Error("Not Implemented: propery " + v + " on String objects");
+        } else if (o instanceof Boolean) {
+            throw new Error("Not Implemented: properties on Boolean objects");
+        } else if (o instanceof Number) {
+            Log.log(this, "Not Implemented: properties on Number objects");
+            return null;
+            //throw new Error("Not Implemented: properties on Number objects");
+        } else if (o instanceof JS) {
+            return ((JS)o).get(v);
+        }
+        return null;
     }
 
     static class Stack {
-       public Object[] os = new Object[256];
-       private int size = 0;
-       public void push(Object o) { os[size++] = o; }
-       public Object pop() { return os[--size]; }
-       public Object peek() { return os[size - 1]; }
-       public void swap() { Object temp = os[size - 1]; os[size - 1] = os[size - 2]; os[size - 2] = temp; }
-       public int size() { return size; }
+        public Object[] os = new Object[256];
+        private int size = 0;
+        public void push(Object o) { os[size++] = o; }
+        public Object pop() { return os[--size]; }
+        public Object peek() { return os[size - 1]; }
+        public void swap() { Object temp = os[size - 1]; os[size - 1] = os[size - 2]; os[size - 2] = temp; }
+        public int size() { return size; }
     }
 
     static class EvaluatorException extends RuntimeException {
-       public EvaluatorException(int line, String sourceName, String s) { super(sourceName + ":" + line + " " + s); }
+        public EvaluatorException(int line, String sourceName, String s) { super(sourceName + ":" + line + " " + s); }
     }
 
     static abstract class ControlTransferException extends Exception { }
     static class BreakException extends ControlTransferException {
-       public String label;
-       BreakException(String label) { this.label = label; }
+        public String label;
+        BreakException(String label) { this.label = label; }
     }
     static class ContinueException extends ControlTransferException {
-       public String label;
-       ContinueException(String label) { this.label = label; }
+        public String label;
+        ContinueException(String label) { this.label = label; }
     }
     static class ReturnException extends ControlTransferException {
-       public Object retval;
-       ReturnException(Object retval) { this.retval = retval; }
+        public Object retval;
+        ReturnException(Object retval) { this.retval = retval; }
     }
     
 }