2003/05/26 10:38:33
authormegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:00:33 +0000 (07:00 +0000)
committermegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:00:33 +0000 (07:00 +0000)
darcs-hash:20040130070033-2ba56-fce2537a9388c844ea135a2483d59a3eaed8001c.gz

src/org/xwt/Box.java
src/org/xwt/Resources.java
src/org/xwt/SpecialBoxProperty.java
src/org/xwt/Static.java
src/org/xwt/XWT.java
src/org/xwt/js/JS.java
src/org/xwt/js/Parser.java

index 2ab32b9..a18d888 100644 (file)
@@ -1099,14 +1099,17 @@ public final class Box extends JS.Scope {
             // check if box being moved is currently target of a redirect
             for(Box cur = newnode.getParent(); cur != null; cur = cur.getParent())
                 if (cur.redirect == newnode) {
-                    if (Log.on) Log.log(this, "attempt to move a box that is the target of a redirect at "+ JS.getCurrentFunctionSourceName());
+                    if (Log.on) Log.log(this, "attempt to move a box that is the target of a redirect at "+
+                                       JS.getCurrentFunctionSourceName());
                     return;
                 }
 
             // check for recursive ancestor violation
             for(Box cur = this; cur != null; cur = cur.getParent())
                 if (cur == newnode) {
-                    if (Log.on) Log.log(this, "attempt to make a node a parent of its own ancestor at " + JS.getCurrentFunctionSourceName());
+                    if (Log.on) Log.log(this, "attempt to make a node a parent of its own ancestor at " +
+                                       JS.getCurrentFunctionSourceName());
+                    if (Log.on) Log.log(this, "box == " + this + "  ancestor == " + newnode);
                     return;
                 }
 
@@ -1205,6 +1208,7 @@ public final class Box extends JS.Scope {
     public void put(Object name_, Object value, boolean ignoretraps, RootProxy rp) {
        if (name_ instanceof Number) { put(((Number)name_).intValue(), value); return; }
        String name = (String)name_;
+       if (name == null) return;  // FIXME, shouldn't be necessary
         if (name.startsWith("xwt_")) {
             if (Log.on) Log.log(this, "attempt to set reserved property " + name + " at " + JS.getFileAndLine());
             return;
index c0240ca..a152bf8 100644 (file)
@@ -48,7 +48,9 @@ public class Resources {
     /** Load a directory as if it were an archive */
     public static synchronized void loadDirectory(File dir) throws IOException { loadDirectory(dir, ""); }
     private static synchronized void loadDirectory(File dir, String prefix) throws IOException {
-        new Static(prefix.replace(File.separatorChar, '.'));
+       String n = prefix.replace(File.separatorChar, '.');
+       if (n.endsWith(".")) n = n.substring(0, n.length() - 1);
+        new Static(n);
         String[] subfiles = dir.list();
         for(int i=0; i<subfiles.length; i++) {
             if (subfiles[i].equals("CVS") || !validResourceName(subfiles[i])) continue;
index bc9b9d4..c2b52b5 100644 (file)
@@ -81,10 +81,15 @@ class SpecialBoxProperty {
                     String s = value == null ? null : value.toString();
                     if (value == null) newcolor = 0x00000000;
                     else if (s.length() > 0 && s.charAt(0) == '#')
-                        newcolor = 0xFF000000 |
-                            (Integer.parseInt(s.substring(1, 3), 16) << 16) |
-                            (Integer.parseInt(s.substring(3, 5), 16) << 8) |
-                            Integer.parseInt(s.substring(5, 7), 16);
+                       try {
+                           newcolor = 0xFF000000 |
+                               (Integer.parseInt(s.substring(1, 3), 16) << 16) |
+                               (Integer.parseInt(s.substring(3, 5), 16) << 8) |
+                               Integer.parseInt(s.substring(5, 7), 16);
+                       } catch (NumberFormatException e) {
+                           Log.log(this, "invalid color " + s);
+                           return;
+                       }
                     else if (s.equals("black")) newcolor = black;
                     else if (s.equals("blue")) newcolor = blue;
                     else if (s.equals("green")) newcolor = green;
@@ -214,7 +219,8 @@ class SpecialBoxProperty {
 
         specialBoxProperties.put("static", new SpecialBoxProperty() {
                 public Object get(Box b) {
-                    String cfsn = JS.getCurrentFunction().getSourceName();
+                   JS.Function cf = JS.getCurrentFunction();
+                    String cfsn = cf.getSourceName();
                     for(int i=0; i<cfsn.length() - 1; i++)
                         if (cfsn.charAt(i) == '.' && (cfsn.charAt(i+1) == '_' || Character.isDigit(cfsn.charAt(i+1)))) {
                             cfsn = cfsn.substring(0, i);
index 29e1e5e..c5ebc06 100644 (file)
@@ -41,7 +41,8 @@ public class Static extends JS.Scope {
     /** creates a new static representing a package */
     public Static(String resourcename) { this(resourcename, true); }
 
-    public Object get(String name) {
+    public Object get(Object name_) {
+       String name = name_.toString();
         if (!ispackage) return super.get(name);
         return getStatic(resourcename + (resourcename.length() == 0 ? "" : ".") + name);
     }
index dd6edc2..2f28e32 100644 (file)
@@ -79,6 +79,18 @@ public final class XWT extends JS.Obj {
                if (args.length() < 2) return args.elementAt(0);
                return new Double(Math.max(((Number)args.elementAt(0)).doubleValue(),
                                           ((Number)args.elementAt(1)).doubleValue())); } };
+           else if ("cos".equals(name)) return new JS.Function() { public Object _call(JS.Array args) {
+               return new Double(Math.cos(((Number)args.elementAt(0)).doubleValue())); } };
+           else if ("sin".equals(name)) return new JS.Function() { public Object _call(JS.Array args) {
+               return new Double(Math.sin(((Number)args.elementAt(0)).doubleValue())); } };
+           else if ("tan".equals(name)) return new JS.Function() { public Object _call(JS.Array args) {
+               return new Double(Math.tan(((Number)args.elementAt(0)).doubleValue())); } };
+           else if ("acos".equals(name)) return new JS.Function() { public Object _call(JS.Array args) {
+               return new Double(Math.acos(((Number)args.elementAt(0)).doubleValue())); } };
+           else if ("asin".equals(name)) return new JS.Function() { public Object _call(JS.Array args) {
+               return new Double(Math.asin(((Number)args.elementAt(0)).doubleValue())); } };
+           else if ("atan".equals(name)) return new JS.Function() { public Object _call(JS.Array args) {
+               return new Double(Math.atan(((Number)args.elementAt(0)).doubleValue())); } };
            return null;
        }});
 
@@ -114,7 +126,7 @@ public final class XWT extends JS.Obj {
        put("println", new JS.Function() { public Object _call(JS.Array args) throws JS.Exn {
            if (args.length() != 1) return null;
            if (Log.on) Log.log(this, JS.getFileAndLine() + " " +
-                               (args.elementAt(0) == null ? "null" : args.elementAt(0).toString()));
+                               (args.elementAt(0) == null ? "**null**" : args.elementAt(0).toString()));
            return null;
        }});
 
@@ -137,11 +149,6 @@ public final class XWT extends JS.Obj {
            return ret;
        }});
 
-       put("theme", new JS.Function() { public Object _call(JS.Array args) throws JS.Exn {
-           Log.log(XWT.class, "xwt.theme() not implemented...");
-           return null;
-       }});
-
        put("xmlrpc", new JS.Function() { public Object _call(JS.Array args) throws JS.Exn {
            if (args.length() != 1 || args.elementAt(0) == null) return null;
            return new XMLRPC(args.elementAt(0).toString(), "");
@@ -272,65 +279,63 @@ public final class XWT extends JS.Obj {
     }});
 
     put("loadArchive", new JS.Function() { public Object _call(JS.Array args) throws JS.Exn {
-                if (!ThreadMessage.suspendThread()) return null;
-
-                try {
-                    if (args == null || args.length() < 1 || args.elementAt(0) == null) return null;
-                    URL u = new URL(args.elementAt(0).toString());
-                    
-                    JS.Function callback = null;
-                    if (args.length() == 2 && args.elementAt(1) != null && args.elementAt(1) instanceof JS.Function)
-                       callback = (JS.Function)args.elementAt(1);
-                    
-                    if (!u.getFile().endsWith(".xwar")) {
-                        if (Log.on) Log.log(this, "Error: archive names must end with .xwar: " + u.getFile());
-                        throw new JS.Exn("Error: archive names must end with .xwar: " + u.getFile());
-                    }
-                    
-                    if (u.getProtocol().equals("http")) {
-                        HTTP http = new HTTP(u.toString());
-                        if (Main.originAddr == null) {
-                            try {
-                                Main.originAddr = InetAddress.getByName(u.getHost());
-                            } catch (UnknownHostException e) {
-                                if (Log.on) Log.log(this, "couldn't resolve " + u.getHost() + "; proceeding without permissions");
-                                Main.originAddr = InetAddress.getByName("0.0.0.0");
-                            }
-                        } else {
-                            Main.originAddr = InetAddress.getByName("0.0.0.0");
-                        }
-                        HTTP.HTTPInputStream in = http.GET();
-                        Resources.loadArchive(in, in.getContentLength(), callback);
-
-                    } else if (u.getProtocol().equals("file")) {
-                        if (Main.originAddr != null) {
-                            if (Log.on) Log.log(this, "scripts downloaded from the network may not load xwars from the local filesystem");
-                            throw new JS.Exn("scripts downloaded from the network may not load xwars from the local filesystem");
-                        }
-                        Resources.loadArchive(new FileInputStream(u.getFile()), (int)new File(u.getFile()).length(), callback);
-                        
-                    } else {
-                        if (Log.on) Log.log(this, "unknown protocol \"" + u.getProtocol() + "\"");
-                        throw new JS.Exn("unknown protocol \"" + u.getProtocol() + "\"");
-                    }
-                    
-                } catch (MalformedURLException me) {
-                    if (Log.on) Log.log(this, "Malformed URL: " + args.elementAt(0));
-                    if (Log.on) Log.log(this, me);
-                    throw new JS.Exn(me.toString());
-                    
-                } catch (IOException ioe) {
-                    if (Log.on) Log.log(this, "IOException while loading archive:");
-                    if (Log.on) Log.log(this, ioe);
-                    throw new JS.Exn(ioe.toString());
-
-                } finally {
-                    ThreadMessage.resumeThread();
-
-                }
-                return null;
-            }
-        });
+       if (!ThreadMessage.suspendThread()) return null;
+       try {
+           if (args == null || args.length() < 1 || args.elementAt(0) == null) return null;
+           URL u = new URL(args.elementAt(0).toString());
+           
+           JS.Function callback = null;
+           if (args.length() == 2 && args.elementAt(1) != null && args.elementAt(1) instanceof JS.Function)
+               callback = (JS.Function)args.elementAt(1);
+           
+           if (!u.getFile().endsWith(".xwar")) {
+               if (Log.on) Log.log(this, "Error: archive names must end with .xwar: " + u.getFile());
+               throw new JS.Exn("Error: archive names must end with .xwar: " + u.getFile());
+           }
+           
+           if (u.getProtocol().equals("http")) {
+               HTTP http = new HTTP(u.toString());
+               if (Main.originAddr == null) {
+                   try {
+                       Main.originAddr = InetAddress.getByName(u.getHost());
+                   } catch (UnknownHostException e) {
+                       if (Log.on) Log.log(this, "couldn't resolve " + u.getHost() + "; proceeding without permissions");
+                       Main.originAddr = InetAddress.getByName("0.0.0.0");
+                   }
+               } else {
+                   Main.originAddr = InetAddress.getByName("0.0.0.0");
+               }
+               HTTP.HTTPInputStream in = http.GET();
+               Resources.loadArchive(in, in.getContentLength(), callback);
+               
+           } else if (u.getProtocol().equals("file")) {
+               if (Main.originAddr != null) {
+                   if (Log.on) Log.log(this, "scripts downloaded from the network may not load xwars from the local filesystem");
+                   throw new JS.Exn("scripts downloaded from the network may not load xwars from the local filesystem");
+               }
+               Resources.loadArchive(new FileInputStream(u.getFile()), (int)new File(u.getFile()).length(), callback);
+               
+           } else {
+               if (Log.on) Log.log(this, "unknown protocol \"" + u.getProtocol() + "\"");
+               throw new JS.Exn("unknown protocol \"" + u.getProtocol() + "\"");
+           }
+           
+       } catch (MalformedURLException me) {
+           if (Log.on) Log.log(this, "Malformed URL: " + args.elementAt(0));
+           if (Log.on) Log.log(this, me);
+           throw new JS.Exn(me.toString());
+           
+       } catch (IOException ioe) {
+           if (Log.on) Log.log(this, "IOException while loading archive:");
+           if (Log.on) Log.log(this, ioe);
+           throw new JS.Exn(ioe.toString());
+           
+       } finally {
+           ThreadMessage.resumeThread();
+           
+       }
+       return null;
+    }});
 
     put("prefetchImage", new JS.Function() { public Object _call(JS.Array args) throws JS.Exn {
        if (args == null || args.length() < 1 || args.elementAt(0) == null) return null;
index 7c2cc19..f2946ea 100644 (file)
@@ -72,19 +72,27 @@ public abstract class JS {
            for(int i=0; i<s.length(); i++) if (s.charAt(i) < '0' || s.charAt(i) > '9') return Integer.MIN_VALUE;
            return Integer.parseInt(s);
        }
-       public Object get(Object key) {
+       public Object get(Object key) throws JS.Exn {
            // FIXME: HACK!
            if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction;
+           if (key.equals("trapee")) return org.xwt.Trap.currentTrapee();
            if (key.equals("length")) return new Long(vec.size());
            int i = intVal(key);
            if (i == Integer.MIN_VALUE) return super.get(key);
-           return vec.elementAt(i);
+           try {
+               return vec.elementAt(i);
+           } catch (ArrayIndexOutOfBoundsException e) {
+               throw new JS.Exn(e.getMessage());
+           }
        }
        public void put(Object key, Object val) {
            if (key.equals("length")) vec.setSize(Parser.toNumber(val).intValue());
            int i = intVal(key);
            if (i == Integer.MIN_VALUE) super.put(key, val);
-           else vec.setElementAt(val, i);
+           else {
+               if (i >= vec.size()) vec.setSize(i+1);
+               vec.setElementAt(val, i);
+           }
        }
        public Object[] keys() {
            Object[] sup = super.keys();
@@ -108,24 +116,36 @@ public abstract class JS {
     }
 
     public static class Script extends Function {
-       Parser.Expr e = null;
-       private Script(Parser.Expr e) { this.e = e; }
+       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);
+           Function saved = (Function)currentFunction.get(Thread.currentThread());
+           currentFunction.put(Thread.currentThread(), this);
            try {
-               e.eval(rootScope);
+               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);
+           } finally {
+               if (saved == null) currentFunction.remove(Thread.currentThread());
+               else currentFunction.put(Thread.currentThread(), saved);
            }
            return null;
        }
        public static Script parse(Reader r, String sourceName, int line) throws IOException {
            Parser p = new Parser(r, sourceName, line);
-           p.pushBackToken(Lexer.LC);  // FIXME: hack
            try {
-               return new Script(p.parseBlock(true));
+               Vector exprs = new Vector();
+               while(true) {
+                   Parser.Expr ret = p.parseBlock(false);
+                   if (ret == null || (ret.code == Parser.LC && ret.left == null)) break;
+                   exprs.addElement(ret);
+               }
+               return new Script(exprs);
            } catch (Exception e) {
                if (Log.on) Log.log(Parser.class, e);
                return null;
index f70a13d..ba343cf 100644 (file)
@@ -36,8 +36,8 @@ public class Parser extends Lexer {
        isRightAssociative[ASSIGN] = true;
        precedence[HOOK] = 2;
        precedence[COMMA] = 3;
-       precedence[GT] = precedence[GE] = 4;
-       precedence[OR] = precedence[AND] = 5;
+       precedence[OR] = precedence[AND] = 4;
+       precedence[GT] = precedence[GE] = 5;
        precedence[BITOR] = 6;
        precedence[BITXOR] = 7;
        precedence[BITAND] = 8;
@@ -61,6 +61,7 @@ public class Parser extends Lexer {
     public Expr parseBlock(boolean requireBraces) throws IOException {
        Expr ret = null;
        int tok = peekToken();
+       if (tok == -1) return null;
        boolean braced = tok == LC;
        if (requireBraces && !braced) throw new ParserException("expected {");
        if (braced) getToken();
@@ -75,6 +76,11 @@ public class Parser extends Lexer {
            case LC: smt = parseBlock(true); break;
            case THROW: case RETURN: case ASSERT:
                getToken();
+               if (peekToken() == SEMI) {
+                   getToken();
+                   smt = new Expr(curLine, tok);
+                   break;
+               }
                smt = new Expr(curLine, tok, parseMaximalExpr());
                if (getToken() != SEMI) throw new ParserException("expected ;");
                break;
@@ -122,11 +128,13 @@ public class Parser extends Lexer {
            default:
                smt = parseMaximalExpr();
                if (smt == null) {
-                   if (head == null) throw new ParserException("empty statement list; next token is " + codeToString[peekToken()]);
+                   if (head == null) return null;
                    break OUTER;
                }
+               if (peekToken() == SEMI) getToken();
                break;
            }
+           if (!braced) return smt;
            if (head == null) head = tail = smt; else tail = (tail.next = smt);
        }
        return new Expr(curLine, LC, head);
@@ -339,7 +347,7 @@ public class Parser extends Lexer {
            if (prefix != null) { pushBackToken(); return prefix; }
            while(true) {
                if (getToken() != NAME) throw new ParserException("variable declarations must start with a variable name");
-               Expr name = new Expr(curLine, NAME, string);
+               String name = string;
                Expr initVal = null;
                tok = peekToken();
                Expr e = null;
@@ -347,9 +355,13 @@ public class Parser extends Lexer {
                    getToken();
                    initVal = parseMaximalExpr();
                    tok = peekToken();
-                   e = new Expr(curLine, ASSIGN, name, initVal);
+                   e = new Expr(curLine, VAR,
+                                new Expr(curLine, name),
+                                new Expr(curLine, ASSIGN,
+                                         new Expr(curLine, name),
+                                         initVal));
                } else {
-                   e = new Expr(curLine, NAME, name);
+                   e = new Expr(curLine, VAR, new Expr(curLine, name));
                }
                if (head == null) head = tail = e; else tail = tail.next = e;
                if (tok != COMMA) break;
@@ -392,9 +404,7 @@ public class Parser extends Lexer {
            if (tok == IF && peekToken() == ELSE) {
                getToken();
                return new Expr(curLine, tok, parenExpr, new Expr(curLine, ELSE, firstBlock, parseBlock(false)));
-           } else if (tok == IF) {
-               return new Expr(curLine, tok, parenExpr, new Expr(curLine, ELSE, firstBlock, new Expr(curLine, LC)));
-           } else if (tok == WHILE) {
+           } else {
                return new Expr(curLine, tok, parenExpr, firstBlock);
            }
        }
@@ -404,20 +414,25 @@ public class Parser extends Lexer {
        case FOR:
            if (prefix != null) { pushBackToken(); return prefix; }
            if (getToken() != LP) throw new ParserException("expected left paren");
-           e1 = parseMaximalExpr(null, -1);
+           e1 = parseMaximalExpr();
            if (peekToken() == IN) {
                getToken();
-               e2 = parseMaximalExpr(null, -1);
+               e2 = parseMaximalExpr();
                if (getToken() != RP) throw new ParserException("expected right paren");
-               return new Expr(curLine, FOR, new Expr(curLine, IN, e1, e2), parseBlock(false));
+               return new Expr(curLine, FOR, new Expr(curLine, IN, e1.left, e2), parseBlock(false));
                
            } else {
-               if (getToken() != SEMI) throw new ParserException("expected ;");
-               e2 = parseMaximalExpr();
-               if (getToken() != SEMI) throw new ParserException("expected ;");
-               e3 = parseMaximalExpr();
-               if (getToken() != RP) throw new ParserException("expected right paren");
-               return new Expr(curLine, LC, e1, new Expr(curLine, WHILE, e2, new Expr(curLine, LC, parseBlock(false), e3)));
+               Expr initExpr = e1;
+               expect(SEMI); getToken();
+               Expr whileExpr = parseMaximalExpr();
+               expect(SEMI); getToken();
+               Expr incExpr = parseMaximalExpr();
+               expect(RP); getToken();
+               Expr body = parseBlock(false);
+               body.next = incExpr;
+               Expr loop = new Expr(curLine, WHILE, whileExpr, body);
+               if (initExpr == null) initExpr = loop; else initExpr.next = loop;
+               return new Expr(curLine, LC, initExpr);
            }
            
        case DO: {
@@ -518,13 +533,24 @@ public class Parser extends Lexer {
            case Lexer.RSH: return new Long(toLong(left.eval(s)) >> toLong(right.eval(s)));
            case Lexer.URSH: return new Long(toLong(left.eval(s)) >>> toLong(right.eval(s)));
 
+               // FIXME: these need to work on strings
            case Lexer.LT: return toDouble(left.eval(s)) < toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
            case Lexer.LE: return toDouble(left.eval(s)) <= toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
            case Lexer.GT: return toDouble(left.eval(s)) > toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
            case Lexer.GE: return toDouble(left.eval(s)) >= toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
 
-           case Lexer.OR: return new Boolean(toBoolean(left.eval(s)) || toBoolean(right.eval(s)));
-           case Lexer.AND: return new Boolean(toBoolean(left.eval(s)) && toBoolean(right.eval(s)));
+           case Lexer.OR: {
+               boolean b1 = toBoolean(left.eval(s));
+               if (b1) return Boolean.TRUE;
+               return new Boolean(b1 || toBoolean(right.eval(s)));
+           }
+
+           case Lexer.AND: {
+               boolean b1 = toBoolean(left.eval(s));
+               if (!b1) return Boolean.FALSE;
+               return new Boolean(b1 && toBoolean(right.eval(s)));
+           }
+
            case Lexer.BANG: return new Boolean(!toBoolean(left.eval(s)));
 
            case Lexer.EQ:
@@ -532,7 +558,14 @@ public class Parser extends Lexer {
                // FIXME: should use Javascript coercion-equality rules
                Object l = left.eval(s);
                Object r = right.eval(s);
-               boolean ret = (l == null && r == null) || (l != null && l.equals(r));
+               boolean ret;
+               if (l == null) { Object t = r; r = l; l = t; }
+               if (l == null && r == null) ret = true;
+               else if (l instanceof Boolean) ret = new Boolean(toBoolean(r)).equals(l);
+               else if (l instanceof Number) ret = toNumber(r).doubleValue() == toNumber(l).doubleValue();
+               else if (l instanceof String) ret = r != null && l.equals(r.toString());
+               else ret = l.equals(r);
+               
                return new Boolean(code == Lexer.EQ ? ret : !ret);
            }
 
@@ -549,7 +582,7 @@ public class Parser extends Lexer {
                    } else if (o instanceof Boolean) {
                        throw new EvaluatorException("can't set properties on a Boolean");
                    } else {
-                       JS target = (JS)left.left.eval(s);
+                       JS target = (JS)o;
                        if (target == null) throw new JS.Exn(new EvaluatorException("attempted to put to the null value"));
                        target.put(left.right.eval(s), v);
                        return v;
@@ -581,10 +614,10 @@ public class Parser extends Lexer {
            case Lexer.THROW: throw new JS.Exn(left.eval(s));
            case Lexer.NAME: return s.get(string);
            case Lexer.THIS: return s.isTransparent() ? s : this.eval(s.getParentScope());
-
            case Lexer.DOT: {
                final Object o = left.eval(s);
-               Object v = ((right.code == Lexer.NAME) || (right.code == Lexer.STRING)) ? right.string : right.eval(s);
+               if (o == null) throw new EvaluatorException("tried to get a property from the null value");
+               Object v = (right.code == Lexer.STRING) ? right.string : right.eval(s);
                if (o instanceof String) {
                    if (v.equals("length")) return new Integer(((String)o).length());
                    else if (v.equals("substring")) return new JS.Function() {
@@ -595,17 +628,35 @@ public class Parser extends Lexer {
                                else throw new EvaluatorException("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(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 EvaluatorException("Not Implemented: properties on String objects");
                } else if (o instanceof Boolean) {
                    throw new EvaluatorException("Not Implemented: properties on Boolean objects");
                } else if (o instanceof Number) {
-                   throw new EvaluatorException("Not Implemented: properties on Number objects");
+                   Log.log(this, "Not Implemented: properties on Number objects");
+                   return null;
+                   //throw new EvaluatorException("Not Implemented: properties on Number objects");
                } else if (o instanceof JS) {
                    return ((JS)o).get(v);
                }
@@ -652,7 +703,6 @@ public class Parser extends Lexer {
                            JS.Scope scope = new JS.Scope(s) {
                                    // FIXME
                                    public String getSourceName() { return sourceName; }
-                                   public boolean isTransparent() { return true; }
                                    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;
@@ -661,7 +711,10 @@ public class Parser extends Lexer {
                                    }
                                };
                            int i = 0;
-                           for(Expr e = left; e != null; e = e.next) scope.put(e.string, args.get(new Integer(i++)));
+                           for(Expr e = left; e != null; e = e.next) {
+                               scope.declare(e.string);
+                               scope.put(e.string, args.get(new Integer(i++)));
+                           }
                            try {
                                return right.eval(scope);
                            } catch (ReturnException r) {
@@ -680,7 +733,8 @@ public class Parser extends Lexer {
                Object[] keys = ((JS)left.right.eval(s)).keys();
                for(int i=0; i<keys.length; i++) {
                    JS.Scope scope = new JS.Scope(s);
-                   scope.put(left.left.string, keys[i]);
+                   scope.declare(left.string);
+                   scope.put(left.string, keys[i]);
                    try {
                        right.eval(scope);
                    } catch (ContinueException c) {
@@ -713,28 +767,37 @@ public class Parser extends Lexer {
            }
            
            case Lexer.LC: // block
-               for(Expr e = left; e != null; e = e.next) e.eval(s);
+               JS.Scope scope = new JS.Scope(s);
+               for(Expr e = left; e != null; e = e.next) e.eval(scope);
                return null;
            
            case Lexer.RC: {   // Object ctor
                JS.Obj ret = new JS.Obj();
-               for(Expr e = left; e != null; e = e.next)
-                   ret.put(e.left.string, e.right.eval(s));
+               for(Expr e = left; e != null; e = e.next) {
+                   Object key = e.left.string;
+                   Object val = e.right.eval(s);
+                   ret.put(key, val);
+               }
                return ret;
            }
            
            case Lexer.VAR:
-               for(Expr e = left; e != null; e = e.next)
-                   if (e.code == Lexer.NAME) {
-                       s.declare(e.string);
-                   } else {
-                       s.declare(e.left.string);
-                       e.eval(s);
-                   }
+               for(Expr e = this.left; e != null; e = e.next) {
+                   s.declare(e.left.string);
+                   if (e.right != null) e.right.eval(s);
+               }
                return null;
 
            case Lexer.HOOK: return toBoolean(left.eval(s)) ? right.left.eval(s) : right.right.eval(s);
-           case Lexer.IF: return toBoolean(left.eval(s)) ? right.left.eval(s) : right.right.eval(s);
+           case Lexer.IF:
+               if (right.code == ELSE) {
+                   if (toBoolean(left.eval(s))) right.left.eval(s);
+                   else right.right.eval(s);
+                   return null;
+               } else {
+                   if (toBoolean(left.eval(s))) right.eval(s);
+                   return null;
+               }
            case Lexer.BREAK: throw new BreakException(string);
            case Lexer.CONTINUE: throw new ContinueException(string);
            case Lexer.RETURN: throw new ReturnException(left == null ? null : left.eval(s));
@@ -743,7 +806,8 @@ public class Parser extends Lexer {
            case Lexer.DO:
                try {
                    boolean first = true;
-                   while((first && code == Lexer.DO) || toBoolean(left.eval(s))) {
+                   Object bogus = null;
+                   for(;(first && code == Lexer.DO) || toBoolean(left.eval(s)); bogus = (right.next == null ? null : right.next.eval(s))) {
                        first = false;
                        try { right.eval(s);
                        } catch (ContinueException c) {
@@ -788,7 +852,7 @@ public class Parser extends Lexer {
        public static Number toNumber(Object o) {
            if (o == null) return new Long(0);
            if (o instanceof Number) return ((Number)o);
-           if (o instanceof String) return new Double((String)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();
            // FIXME