2003/06/05 00:12:17
authormegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:00:45 +0000 (07:00 +0000)
committermegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:00:45 +0000 (07:00 +0000)
darcs-hash:20040130070045-2ba56-bc5514d5eb76cbc958938f3b8975243110481c98.gz

src/org/xwt/js/ForthBlock.java
src/org/xwt/js/OpCodes.java [new file with mode: 0644]
src/org/xwt/js/Parser.java

index 6946258..43607be 100644 (file)
@@ -5,318 +5,287 @@ import org.xwt.util.*;
 import java.io.*;
 
 /** a block of Forth bytecode */
-class ForthBlock implements Tokens {
+class ForthBlock implements OpCodes, Tokens {
 
-    /*
-     *  Each instruction is an opcode and an optional literal literal.
-     */
+    int line;
+    String sourceName;
+    int[] op = new int[10];
+    Object[] arg = new Object[10];
+    int size = 0;
 
-       // opcodes:
-       public static final byte arithmetic = -1;  //                               -- arithmetic operators from parser
-       public static final byte LITERAL = -2;     // < String | Number | null >    -- push a literal onto the stack
-       public static final byte ARRAY = -3;       // < size >                      -- create a new array of size <size>
-       public static final byte OBJECT = -4;      //                               -- push an empty object onto the stack
-       public static final byte FUNCTION = -5;    // < bytecode_block >            -- push a new instance of a function with the given bytecode
-       public static final byte DECLARE = -6;     // < name >                      -- declare <name> in the current scope
-       public static final byte THIS = -7;        //                               -- push the topmost non-transparent scope onto the stack
-       public static final byte GET = -8;         //                               -- get stack[0] from stack[1]
-       public static final byte GET_PRESERVE = -80;         //                               -- get stack[0] from stack[1]
-       public static final byte PUT = -9;         //                               -- put stack[1] to key stack[0] on stack[2]; leaves object on the stack
-       public static final byte JT = -13;         // < relative_address >          -- pop the stack; if true, jump to <relative_address>
-       public static final byte JF = -21;         // < relative_address >          -- pop the stack; if false, jump to <relative_address>
-       public static final byte JMP = -22;        // < relative_address >          -- jump to <relative_address>
-        static public final byte POP = -14;        //                               -- discard the top element on the stack
+    public ForthBlock(int line, String sourceName) { this.line = line; this.sourceName = sourceName; }
+    public ForthBlock(int line, String sourceName, int op_, Object arg_) { this(line, sourceName); add(op_, arg_); }
 
-       public static final byte CALL = -15;       // < numargs >                   -- call stack[0] with the topmost <numargs> values as arguments
-
-       public static final byte PUSHKEYS = -19;    //                               -- ??
-       public static final byte EXPR = -20;       //                               -- transitional
-       public static final byte SWAP = -23;       //                               -- transitional
-       public static final byte SCOPE = -30;       //                               -- transitional
-       public static final byte LOOP = -40;       //                               -- transitional
-       public static final byte DUP = -50;       //                               -- transitional
-       public static final byte LABEL = -60;       //                               -- transitional
-
-       int line;
-       String sourceName;
-       int[] op = new int[10];
-       Object[] arg = new Object[10];
-       int size = 0;
-
-       public ForthBlock(int line, String sourceName) { this.line = line; this.sourceName = sourceName; }
-       public ForthBlock(int line, String sourceName, int op_, Object arg_) { this(line, sourceName); add(op_, arg_); }
-
-       public int size() { return size; }
-       public void set(int pos, int op_, Object arg_) { op[pos] = op_; arg[pos] = arg_; }
-       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;
+    public int size() { return size; }
+    public void set(int pos, int op_, Object arg_) { op[pos] = op_; arg[pos] = arg_; }
+    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;
+    }
        
-       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), t)); break;
+    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 OpCodes.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), t)); 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.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);
+           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;
                    }
-                   break;
+                   if (result == Boolean.FALSE) break;
+                   t2 = new Stack();
+                   t2.push(Boolean.FALSE);
                }
+               break;
+           }
 
-               case PUT: {
-                   Object val = t.pop();
-                   Object key = t.pop();
-                   ((JS)t.peek()).put(key, val);
-                   t.push(val);
-                   break;
-               }
+           case PUT: {
+               Object val = t.pop();
+               Object key = t.pop();
+               ((JS)t.peek()).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_PRESERVE: {
-                   Object v = t.pop();
-                   Object o = t.peek();
-                   t.push(v);
-                   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 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 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 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);
-                               }
+           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 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;
-               }
+                       }
+                   });
+               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.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.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;
+               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;
+                   // 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]);
-                   } }
+               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;
                }
-           if (t.size() != 1) {
-               throw new EvaluatorException(line, sourceName, "eval() terminated with " + t.size() + " elements on the stack; one expected");
+
+               default: throw new Error("unknown opcode " + op[i]);
+               } }
            }
-           return t.pop();
+       if (t.size() != 1) {
+           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);
-           }
+    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;
+    }
 
     static class Stack {
        public Object[] os = new Object[256];
diff --git a/src/org/xwt/js/OpCodes.java b/src/org/xwt/js/OpCodes.java
new file mode 100644 (file)
index 0000000..a2a24e7
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.xwt.js;
+
+/** each instruction is an opcode and an optional literal literal. */
+public interface OpCodes {
+
+    /** 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 FUNCTION = -5;      
+
+    /** pop a string off the stack and declare it in the current scope */
+    public static final byte DECLARE = -6;       
+
+    /** push a reference to the current scope onto the stack */
+    public static final byte THIS = -7;
+
+    /** pop two elements off the stack; push stack[-1].get(stack[top]) */
+    public static final byte GET = -8;           
+
+    /** push stack[-1].get(stack[top]) */
+    public static final byte GET_PRESERVE = -80; 
+
+    /** pop two elements off the stack; stack[-2].put(stack[-1], stack[top]); push stack[top] */
+    public static final byte PUT = -9;           
+
+    /** literal is a relative address; pop stacktop and jump if the value is true */
+    public static final byte JT = -13;           
+
+    /** literal is a relative address; pop stacktop and jump if the value is false */
+    public static final byte JF = -21;           
+
+    /** literal is a relative address; jump to it */
+    public static final byte JMP = -22;          
+
+    /** discard the top stack element */
+    static public final byte POP = -14;          
+
+    /** pop two elements; call stack[-1](stack[top]) where stacktop is a JS.Array */
+    public static final byte CALL = -15;         
+
+    /** pop an element; push a JS.Array containing the keys of the popped element */
+    public static final byte PUSHKEYS = -19;     
+
+    /** FIXME: execute the ForthBlock pointed to by the literal */
+    public static final byte EXPR = -20;         
+
+    /** swap the top two elements on the stack */
+    public static final byte SWAP = -23;         
+
+    /** execute the ForthBlock pointed to by the literal in a fresh scope with parentScope==THIS */
+    public static final byte SCOPE = -30;        
+
+    /** push a copy of the top stack element */
+    public static final byte DUP = -50;          
+
+    /** declare a label */
+    public static final byte LABEL = -60;        
+
+    /** 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 = -40;         
+
+}
index 0a8fa22..67cb0a1 100644 (file)
@@ -5,7 +5,7 @@ import org.xwt.util.*;
 import java.io.*;
 
 /** parses a stream of lexed tokens into ForthBlock's */
-public class Parser extends Lexer {
+public class Parser extends Lexer implements OpCodes {
 
     // Constructors //////////////////////////////////////////////////////
 
@@ -91,7 +91,7 @@ public class Parser extends Lexer {
        case VAR: {
            if (prefix != null) { pushBackToken(); return prefix; }
            ForthBlock b = new ForthBlock(curLine, sourceName);
-           b.add(b.THIS);
+           b.add(OpCodes.THIS);
            while(true) {
                consume(NAME);
                String name = string;
@@ -107,7 +107,7 @@ public class Parser extends Lexer {
                if (peekToken() != COMMA) break;
                consume(COMMA);
            }
-           return b;
+           return parseSingleForthBlock(b, minPrecedence);
        }
 
        case IN: pushBackToken(); return prefix;
@@ -121,10 +121,10 @@ public class Parser extends Lexer {
            b.add(b.JF, new Integer(3));
            b.add(b.EXPR, parseStatement(false));
            b.add(b.JMP, new Integer(2));
-           if (peekToken() != ELSE) return b.add(b.LITERAL, null);
+           if (peekToken() != ELSE) return parseSingleForthBlock(b.add(b.LITERAL, null), minPrecedence);
            consume(ELSE);
            b.add(b.EXPR, parseStatement(false));
-           return b;
+           return parseSingleForthBlock(b, minPrecedence);
        }
 
            // FIXME: ugly hack!!
@@ -147,7 +147,7 @@ public class Parser extends Lexer {
                b.add(b.PUT);
                b.add(b.SWAP);
                b.add(b.POP);
-               return b;
+               return parseSingleForthBlock(b, minPrecedence);
            }
 
        case INC: case DEC:
@@ -155,19 +155,19 @@ public class Parser extends Lexer {
                // prefix
                ForthBlock b = (ForthBlock)parseMaximalForthBlock(null, precedence[tok]);
                b.set(b.size() - 1, tok, new Boolean(true));
-               return b;
+               return parseSingleForthBlock(b, minPrecedence);
            } else {
                // postfix
                ForthBlock b = (ForthBlock)prefix;
                b.set(b.size() - 1, tok, new Boolean(false));
-               return b;
+               return parseSingleForthBlock(b, minPrecedence);
            }
 
        case LP:
            if (prefix == null) {  // grouping
                ForthBlock b = new ForthBlock(curLine, sourceName, ForthBlock.EXPR, parseMaximalForthBlock());
                consume(RP);
-               return b;
+               return parseSingleForthBlock(b, minPrecedence);
 
            } else {  // invocation
                ForthBlock b = new ForthBlock(curLine, sourceName);
@@ -181,7 +181,7 @@ public class Parser extends Lexer {
                }
                consume(RP);
                b.add(b.CALL, new Integer(i));
-               return b;
+               return parseSingleForthBlock(b, minPrecedence);
            }
 
        case BANG: case BITNOT: case INSTANCEOF: case TYPEOF: {
@@ -189,13 +189,13 @@ public class Parser extends Lexer {
            ForthBlock b = new ForthBlock(curLine, sourceName);
            b.add(b.EXPR, parseMaximalForthBlock(null, precedence[tok]));
            b.add(tok);
-           return b;
+           return parseSingleForthBlock(b, minPrecedence);
        }
 
        case SUB:
            if (prefix == null && peekToken() == NUMBER) {
                getToken();
-               return new ForthBlock(curLine, sourceName, ForthBlock.LITERAL, new Double(number.doubleValue() * -1));
+               return parseSingleForthBlock(new ForthBlock(curLine, sourceName, ForthBlock.LITERAL, new Double(number.doubleValue() * -1)), minPrecedence);
            } // else fall through
         case BITOR: case BITXOR: case BITAND: case SHEQ: case SHNE: case LSH:
        case RSH: case URSH: case ADD: case MUL: case DIV: case MOD:
@@ -205,7 +205,7 @@ public class Parser extends Lexer {
            b.add(b.EXPR, prefix);
            b.add(b.EXPR, parseMaximalForthBlock(null, precedence[tok]));
            b.add(tok);
-           return b;
+           return parseSingleForthBlock(b, minPrecedence);
        }
 
            // includes short-circuit logic
@@ -217,7 +217,7 @@ public class Parser extends Lexer {
            b.add(tok == AND ? b.JF : b.JT, new Integer(3));
            b.add(b.POP);
            b.add(b.EXPR, parseMaximalForthBlock(null, precedence[tok]));
-           return b;
+           return parseSingleForthBlock(b, minPrecedence);
        }
 
        case WITH: throw new ParserException("XWT does not allow the WITH keyword");
@@ -225,21 +225,21 @@ public class Parser extends Lexer {
 
        case NUMBER:
            if (prefix != null) { pushBackToken(); return prefix; }
-           return new ForthBlock(curLine, sourceName, ForthBlock.LITERAL, number);
+           return parseSingleForthBlock(new ForthBlock(curLine, sourceName, ForthBlock.LITERAL, number), minPrecedence);
 
        case STRING:
            if (prefix != null) { pushBackToken(); return prefix; }
-           return new ForthBlock(curLine, sourceName, ForthBlock.LITERAL, string);
+           return parseSingleForthBlock(new ForthBlock(curLine, sourceName, ForthBlock.LITERAL, string), minPrecedence);
 
        case NULL: case TRUE: case FALSE: case NOP:
            if (prefix != null) { pushBackToken(); return prefix; }
-           return new ForthBlock(curLine, sourceName, ForthBlock.LITERAL, (tok == NULL || tok == NOP) ? null : new Boolean(tok == TRUE));
+           return parseSingleForthBlock(new ForthBlock(curLine, sourceName, ForthBlock.LITERAL, (tok == NULL || tok == NOP) ? null : new Boolean(tok == TRUE)), minPrecedence);
 
        case COMMA: pushBackToken(); return prefix;
 
-       case THIS:
+       case Tokens.THIS:
            if (prefix != null) { pushBackToken(); return prefix; } 
-           return new ForthBlock(curLine, sourceName, ForthBlock.THIS, null);
+           return parseSingleForthBlock(new ForthBlock(curLine, sourceName, OpCodes.THIS, null), minPrecedence);
 
        case NAME: {
            if (prefix != null) { pushBackToken(); return prefix; }
@@ -247,18 +247,18 @@ public class Parser extends Lexer {
            ForthBlock b = new ForthBlock(curLine, sourceName);
            if (peekToken() == ASSIGN) {
                consume(ASSIGN);
-               b.add(ForthBlock.THIS);
+               b.add(OpCodes.THIS);
                b.add(ForthBlock.LITERAL, name);
                b.add(ForthBlock.EXPR, parseMaximalForthBlock(null, minPrecedence));
                b.add(ForthBlock.PUT);
                b.add(ForthBlock.SWAP);
                b.add(ForthBlock.POP);
-               return b;
+               return parseSingleForthBlock(b, minPrecedence);
            } else {
-               b.add(ForthBlock.THIS);
+               b.add(OpCodes.THIS);
                b.add(ForthBlock.LITERAL, name);
                b.add(ForthBlock.GET);
-               return parseMaximalForthBlock(b, minPrecedence);
+               return parseSingleForthBlock(parseMaximalForthBlock(b, minPrecedence), minPrecedence);
            }
        }
 
@@ -279,7 +279,7 @@ public class Parser extends Lexer {
                b.add(b.LITERAL, target);
                b.add(b.GET);
            }
-           return b;
+           return parseSingleForthBlock(b, minPrecedence);
        }
 
        case LB: {
@@ -289,13 +289,13 @@ public class Parser extends Lexer {
                int i = 0;
                while(true) {
                    ForthBlock e = parseMaximalForthBlock();
-                   if (e == null && peekToken() == RB) { consume(RB); return b; }
+                   if (e == null && peekToken() == RB) { consume(RB); return parseSingleForthBlock(b, minPrecedence); }
                    b.add(b.LITERAL, new Integer(i++));
                    if (e == null) b.add(b.LITERAL, null);
                    else b.add(b.EXPR, e);
                    b.add(b.PUT);
                    b.add(b.POP);
-                   if (peekToken() == RB) { consume(RB); return b; }
+                   if (peekToken() == RB) { consume(RB); return parseSingleForthBlock(b, minPrecedence); }
                    consume(COMMA);
                }
            } else {
@@ -311,7 +311,7 @@ public class Parser extends Lexer {
                } else {
                    b.add(b.GET);
                }
-               return b;
+               return parseSingleForthBlock(b, minPrecedence);
            }
        }
 
@@ -319,7 +319,7 @@ public class Parser extends Lexer {
            if (prefix != null) { pushBackToken(); return prefix; }
            ForthBlock b = new ForthBlock(curLine, sourceName);
            b.add(b.OBJECT, null);
-           if (peekToken() == RC) { consume(RC); return b; }
+           if (peekToken() == RC) { consume(RC); return parseSingleForthBlock(b, minPrecedence); }
            while(true) {
                if (peekToken() != NAME && peekToken() != STRING) throw new Error("expected NAME or STRING");
                getToken();
@@ -328,9 +328,9 @@ public class Parser extends Lexer {
                b.add(b.EXPR, parseMaximalForthBlock());
                b.add(b.PUT);
                b.add(b.POP);
-               if (peekToken() == RC) { consume(RC); return b; }
+               if (peekToken() == RC) { consume(RC); return parseSingleForthBlock(b, minPrecedence); }
                consume(COMMA);
-               if (peekToken() == RC) { consume(RC); return b; }
+               if (peekToken() == RC) { consume(RC); return parseSingleForthBlock(b, minPrecedence); }
            }
        }
            
@@ -342,15 +342,15 @@ public class Parser extends Lexer {
            b.add(b.JMP, new Integer(2));
            consume(COLON);
            b.add(b.EXPR, parseMaximalForthBlock());
-           return b;
+           return parseSingleForthBlock(b, minPrecedence);
        }
            
-       case FUNCTION: {
+       case Tokens.FUNCTION: {
            if (prefix != null) { pushBackToken(); return prefix; }
            consume(LP);
            ForthBlock b = new ForthBlock(curLine, sourceName);
            int numArgs = 0;
-           b.add(b.THIS);
+           b.add(OpCodes.THIS);
            b.add(b.SWAP);
            b.add(b.LITERAL, "arguments");
            b.add(b.LITERAL, "arguments");
@@ -377,7 +377,7 @@ public class Parser extends Lexer {
                    b.add(b.POP);
 
                    // put it to the current scope
-                   b.add(b.THIS);
+                   b.add(OpCodes.THIS);
                    b.add(b.SWAP);
                    b.add(b.LITERAL, string);
                    b.add(b.SWAP);
@@ -395,7 +395,7 @@ public class Parser extends Lexer {
            // pop off the arguments array
            b.add(b.POP);
            parseStatement(true, b);
-           return new ForthBlock(curLine, sourceName, b.FUNCTION, b);
+           return parseSingleForthBlock(new ForthBlock(curLine, sourceName, OpCodes.FUNCTION, b), minPrecedence);
        }
            
        case WHILE: {
@@ -414,7 +414,7 @@ public class Parser extends Lexer {
 
            // if we fall out of the end, definately continue
            loop.add(CONTINUE);
-           return r;
+           return parseSingleForthBlock(r, minPrecedence);
        }
 
        case SWITCH: {
@@ -446,7 +446,7 @@ public class Parser extends Lexer {
                if (peekToken() == RC) {
                    consume(RC);
                    r.add(BREAK);
-                   return r;
+                   return parseSingleForthBlock(r, minPrecedence);
                }
            }
        }
@@ -467,7 +467,7 @@ public class Parser extends Lexer {
            loop.add(Lexer.CONTINUE);
            consume(RP);
            consume(SEMI);
-           return r;
+           return parseSingleForthBlock(r, minPrecedence);
        }
 
        case TRY: {
@@ -486,7 +486,7 @@ public class Parser extends Lexer {
            }
            if (tok == FINALLY) getToken();
 
-           return tryBlock;
+           return parseSingleForthBlock(tryBlock, minPrecedence);
        }
 
        case FOR: {
@@ -523,7 +523,7 @@ public class Parser extends Lexer {
                b2.add(b.PUT);
                b2.add(b.EXPR, parseStatement(false));
                b2.add(b.LITERAL, null);
-               return b;
+               return parseSingleForthBlock(b, minPrecedence);
                
            } else {
                ForthBlock b2 = new ForthBlock(curLine, sourceName);
@@ -553,7 +553,7 @@ public class Parser extends Lexer {
                b3.add(b.JT, new Integer(2));
                b3.add(BREAK);
                parseStatement(false, b3);
-               return b;
+               return parseSingleForthBlock(b, minPrecedence);
            }
        }