2003/11/03 00:08:26
authormegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:40:45 +0000 (07:40 +0000)
committermegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:40:45 +0000 (07:40 +0000)
darcs-hash:20040130074045-2ba56-e68dd1eb1c63ce9e3f6cecfe5addb9f4d15c9b9e.gz

src/org/xwt/Box.java.pp
src/org/xwt/Main.java
src/org/xwt/Picture.java
src/org/xwt/Platform.java
src/org/xwt/Res.java
src/org/xwt/Scheduler.java
src/org/xwt/Surface.java
src/org/xwt/Template.java
src/org/xwt/Trap.java
src/org/xwt/XWT.java
src/org/xwt/js/CompiledFunctionImpl.java

index c58eebc..7f8f46e 100644 (file)
@@ -112,8 +112,7 @@ public final class Box extends JS.Scope {
     public LENGTH maxwidth = MAX_LENGTH;
     public LENGTH maxheight = MAX_LENGTH;
     private String text = null;
-    private Res font = null;
-    private int fontsize = 10;
+    private Font font = null;
     private LENGTH textwidth = 0;
     private LENGTH textheight = 0;
 
@@ -454,15 +453,15 @@ public final class Box extends JS.Scope {
                 for(int y = globaly; y < clipy + cliph; y += image.getHeight())
                     buf.drawPicture(image, x, y, clipx, clipy, clipx + clipw, clipy + cliph);
 
-       if (text != null && !text.equals(""))
-            Glyph.rasterizeGlyphs(font, fontsize, text, buf, textcolor, globalx, globaly, clipx, clipy, clipw + clipx, clipy + cliph,
-                                  new Callback() { public Object call(Object arg) {
-                                      Box.this.dirty();
-                                      Box b = Box.this;
-                                      MARK_FOR_REFLOW_b;
-                                      b.dirty();
-                                      return null;
-                                  }});
+       if (text != null && !text.equals("") && font != null) // FIXME
+            font.rasterizeGlyphs(text, buf, textcolor,
+                                 globalx, globaly, clipx, clipy, clipw + clipx, clipy + cliph,
+                                 new Scheduler.Task() { public void perform() {
+                                     Box.this.dirty();
+                                     Box b = Box.this;
+                                     MARK_FOR_REFLOW_b;
+                                     b.dirty();
+                                 }});
 
         if (path != null) {
             if (rtransform == null) rpath = null;
@@ -518,7 +517,7 @@ public final class Box extends JS.Scope {
      *  INVARIANT: after completion, getChild(min(i, numChildren())) == newnode
      *  WARNING: O(n) runtime, unless i == numChildren()
      */
-    public void put(int i, Object value) {
+    public void put(int i, Object value, TailCall tail) {
         if (i < 0) return;
         Trap t = value == null ? (Trap)get("childremoved", Trap.class) : (Trap)get("childadded", Trap.class);
             
@@ -528,17 +527,18 @@ public final class Box extends JS.Scope {
         }
 
         if (redirect == null) {
-            if (t != null) t.perform(value);
+            if (t != null) t.perform(value, tail);
             else if (Log.on) Log.logJS(this, "attempt to add/remove children to/from a node with a null redirect");
         } else if (redirect != this) {
             Box b = value == null ? (Box)redirect.get(i) : (Box)value;
-            redirect.put(i, value);
-            if (t != null) t.perform(value);
+            // FIXME: what if both of them want a tailcall?
+            redirect.put(i, value, tail);
+            if (t != null) t.perform(value, tail);
         } else if (value == null) {
             if (i >= 0 && i < numChildren()) {
                 Box b = getChild(i);
                 b.remove();
-                if (t != null) t.perform(b);
+                if (t != null) t.perform(b, tail);
             }
         } else {
             Box newnode = (Box)value;
@@ -600,12 +600,12 @@ public final class Box extends JS.Scope {
             MARK_FOR_REFLOW_this;
             
             newnode.dirty();
-            if (t != null) t.perform(b);
+            if (t != null) t.perform(b, tail);
         }
     }
     
     public Object get(Object key, Object key2) { return super.get(key, key2); }
-    public void put(Object key, Object key2, Object val) { super.put(key, key2, val); }
+    public void put2(Object key, Object key2, Object val) { super.put2(key, key2, val); }
 
     public Object get_(Object name) { return super.get(name); }
     public Object get(Object name) { return get(name, false); }
@@ -618,7 +618,7 @@ public final class Box extends JS.Scope {
 
         // See if we're triggering a trap
         Trap t = ignoretraps ? (Trap)null : (Trap)get(name, Trap.class);
-        if (t != null) return t.perform();
+        if (t != null) { t.perform(); return null; }
 
         // Check for a special handler
         SpecialBoxProperty gph = (SpecialBoxProperty)SpecialBoxProperty.specialBoxProperties.get(name);
@@ -647,17 +647,15 @@ public final class Box extends JS.Scope {
      *  @param rp if this put is being performed via a root proxy, rp is the root proxy.
      */
     public void put_(Object name, Object value) { super.put(name, value); }
-    public void put(Object name, Object value) { put(name, value, false); }
-    public void put(Object name_, Object value, boolean ignoretraps) {
-        if (name_ instanceof Number) { put(((Number)name_).intValue(), value); return; }
+    public void put(Object name, Object value) { put(name, value, null, false); }  // FIXME: correct?
+    public void put(Object name, Object value, TailCall tail) { put(name, value, tail, false); }
+    public void put(Object name_, Object value, TailCall tail, boolean ignoretraps) {
+        if (name_ instanceof Number) { put(((Number)name_).intValue(), value, tail); }
         if (!(name_ instanceof String)) { super.put(name_,value); return; }
         String name = name_.toString();
         if (!ignoretraps) {
             Trap t = (Trap)get(name, Trap.class);
-            if (t != null) {
-                t.perform(value);
-                return;
-            }
+            if (t != null) { t.perform(value, tail); return; }
         }
 
         SpecialBoxProperty gph = (SpecialBoxProperty)SpecialBoxProperty.specialBoxProperties.get(name);
@@ -729,7 +727,9 @@ public final class Box extends JS.Scope {
         // note that JavaScript box[0] will invoke put(int i), not put(String s)
         if (oldparent != null) {
             Trap t = (Trap)oldparent.get("childremoved", Trap.class);
-            if (t != null) t.perform(this);
+
+            // FIXME!!
+            //if (t != null) t.perform(this, tail);
         }
     }
 
@@ -808,16 +808,14 @@ public final class Box extends JS.Scope {
 
     public void recompute_font() {
         if (text == null) { textwidth = textheight = 0; return; }
-        if (font == null) { /* FIXME */ }
-        long widthheight = Glyph.rasterizeGlyphs(font, fontsize, text, null, textcolor, 0, 0, 0, 0, 0, 0,
-                                                 new Callback() { public Object call(Object arg) {
-                                                     Box b = Box.this;
-                                                     recompute_font();
-                                                     MARK_FOR_REFLOW_b;
-                                                     b.dirty();
-                                                     return null;
-                                                 } });
-        if (widthheight == -1) return;
+        if (font == null) { /* FIXME */ return; }
+        long widthheight = font.rasterizeGlyphs(text, null, textcolor, 0, 0, 0, 0, 0, 0,
+                                                new Scheduler.Task() { public void perform() {
+                                                    Box b = Box.this;
+                                                    recompute_font();
+                                                    MARK_FOR_REFLOW_b;
+                                                    b.dirty();
+                                                } });
         textwidth = (int)((widthheight & 0xffff0000) >> 16);
         textheight = (int)(widthheight & 0x0000ffff);
         MARK_FOR_REFLOW_this;
@@ -897,15 +895,18 @@ public final class Box extends JS.Scope {
             specialBoxProperties.put("fill", new ColorBoxProperty() {
                     public void put(final Box b, final Object value) {
                         if (value == null || !(value instanceof Res)) super.put(b, value);
-                        else Picture.fromRes((Res)value, new Callback() { public Object call(Object pic) {
-                            if (pic == b.image) return null;
-                            b.image = (Picture)pic;
-                            b.minwidth = b.image.getWidth();
-                            b.minheight = b.image.getHeight();
-                            MARK_FOR_REFLOW_b;
-                            b.dirty();
-                            return null;
-                        } });
+                        else {
+                            Callback callback = new Callback() { public Object call(Object pic) {
+                                b.image = (Picture)pic;
+                                b.minwidth = b.image.getWidth();
+                                b.minheight = b.image.getHeight();
+                                MARK_FOR_REFLOW_b;
+                                b.dirty();
+                                return null;
+                            } };
+                            Picture pic = Picture.fromRes((Res)value, callback);
+                            if (pic != null) callback.call(pic);
+                        }
                     }
                     public int getColor(Box b) { return b.fillcolor; }
                     public void putColor(Box b, int argb) { b.fillcolor = argb; }
@@ -1011,7 +1012,8 @@ public final class Box extends JS.Scope {
                     public Object get(Box b) { return b.font; }
                     public void put(Box b, Object value) {
                         // FIXME: translate value into a resource if it is a string
-                        b.font = value == null ? null : (Res)value;
+                        b.font = value == null ? null :
+                            Font.getFont((Res)value, b.font == null ? 10 : b.font.pointsize); // FIXME
                         MARK_FOR_REFLOW_b;
                         b.flags |= FONT_CHANGED_FLAG;
                         b.dirty();
@@ -1021,8 +1023,9 @@ public final class Box extends JS.Scope {
             specialBoxProperties.put("fontsize", new SpecialBoxProperty() {
                     public Object get(Box b) { return b.font; }
                     public void put(Box b, Object value) {
-                        if (b.fontsize == stoi(value)) return;
-                        b.fontsize = stoi(value);
+                        if (b.font != null && b.font.pointsize == stoi(value)) return;
+                        b.font = value == null ? null :
+                            Font.getFont(b.font == null ? null : b.font.res, stoi(value)); // FIXME
                         MARK_FOR_REFLOW_b;
                         b.flags |= FONT_CHANGED_FLAG;
                         b.dirty();
index 3c79fd8..bc26f96 100644 (file)
@@ -91,16 +91,13 @@ public class Main {
         Picture.fromRes((Res)Main.builtin.get("org/xwt/builtin/scar.png"), new Callback() {
                 public Object call(Object arg) {
                     scarImage = (Picture)arg;
-                    Scheduler.add(new Scheduler.Task() {
-                            public Object call(Object args) {
-                                Template.getTemplate(((Res)final_rr.get(initialTemplate))).apply(new Box(), null, xwt);
-                                return null;
-                            }
-                        });
+                    Scheduler.add(new Scheduler.Task() { public void perform() {
+                        Template.getTemplate(((Res)final_rr.get(initialTemplate))).apply(new Box(), null, xwt);
+                    } });
                     return null;
                 } });
 
-        new Thread() { public void run() { Scheduler.run(); } }.start();
+        new Thread() { public void run() { Scheduler.singleton.run(); } }.start();
         Platform.running();
     }
 
index a4cc602..38f9c0a 100644 (file)
@@ -33,28 +33,32 @@ public abstract class Picture {
     private static Cache cache = new Cache();
     private static GIF gif = new GIF();
     
+    // FIXME: return a Picture that gets filled in later
     /** turns a resource into a Picture.Source and passes it to the callback */
-    public static void fromRes(final Res r, final Callback callback) {
+    public static Picture fromRes(final Res r, final Callback callback) {
         Picture ret = (Picture)cache.get(r);
-        if (ret != null) {
-            callback.call(ret);
-            return;
-        }
-
+        if (ret != null) return ret;
         try {
-            // FIXME: put self in background
-            PushbackInputStream pbis = new PushbackInputStream(r.getInputStream());
-            int c = pbis.read();
-            pbis.unread(c);
-            if (c == 'G') ret = gif.fromInputStream(pbis, r.getDescriptiveName());
-            else if (c == 137) ret = new PNG().fromInputStream(pbis, r.getDescriptiveName());
-            else if (c == 0xff) ret = Platform.decodeJPEG(pbis, r.getDescriptiveName());
-            else throw new JS.Exn("couldn't figure out image type from first byte");
-            cache.put(r, ret);
-            ret.res = r;
-            callback.call(ret);
+            Platform.inputStreamToByteArray(r.getInputStream(), new Callback() { public Object call(Object o) {
+                try {
+                    Picture ret = null;
+                    byte[] b = (byte[])o;
+                    InputStream pbis = new ByteArrayInputStream(b);
+                    if ((b[0] & 0xff) == 'G') ret = gif.fromInputStream(pbis, r.getDescriptiveName());
+                    else if ((b[0] & 0xff) == 137) ret = new PNG().fromInputStream(pbis, r.getDescriptiveName());
+                    else if ((b[0] & 0xff) == 0xff) ret = Platform.decodeJPEG(pbis, r.getDescriptiveName());
+                    else throw new JS.Exn("couldn't figure out image type from first byte");
+                    ret.res = r;
+                    cache.put(r, ret);
+                    callback.call(ret);
+                } catch (Exception e) {
+                    Log.log(Picture.class, e);
+                }
+                return null;
+            }});
         } catch (Exception e) {
             Log.log(Picture.class, e);
         }
+        return null;
     }
 }
index 5b1f0ac..f8937ca 100644 (file)
@@ -317,6 +317,25 @@ public class Platform {
 
         return cachedProxyInfo;
     }
+
+    /** returns a Scheduler instance; used to implement platform-specific schedulers */
+    protected Scheduler _getScheduler() { return new Scheduler(); }
+    public static Scheduler getScheduler() { return platform._getScheduler(); }
+    
+    /** read an input stream into a byte array and invoke callback when ready */
+    protected void _inputStreamToByteArray(final InputStream is, final Callback c) {
+        new java.lang.Thread() {
+            public void run() {
+                try {
+                    final byte[] b = InputStreamToByteArray.convert(is);
+                    Scheduler.add(new Scheduler.Task() { public void perform() { c.call(b); }});
+                } catch (IOException e) {
+                    Log.log(Platform.class, e);
+                }
+            }
+        }.start();    
+    }
+    public static void inputStreamToByteArray(InputStream is, Callback c) { platform._inputStreamToByteArray(is, c); }
     
     public static void running() { platform._running(); }
     public void _running() { new Semaphore().block(); }
index 196bb2c..ea503b5 100644 (file)
@@ -219,13 +219,11 @@ public abstract class Res extends JS {
                     public int read(byte[] b, int off, int len) throws IOException {
                         int ret = super.read(b, off, len);
                         if (ret != 1) bytesDownloaded += ret;
-                        Scheduler.add(new Scheduler.Task() { public Object call(Object arg) {
+                        Scheduler.add(new Scheduler.Task() { public void perform() {
                             JS.Array args = new JS.Array();
                             args.addElement(new Integer(bytesDownloaded));
                             args.addElement(new Integer(is instanceof KnownLength ? ((KnownLength)is).getLength() : 0));
-                            // FIXME
-                            // new JS.Thread(callback, callbackScope).resume();
-                            return null;
+                            new JS.Thread(callback, null, args).resume();
                         } });
                         return ret;
                     }
index 95fd6bf..2cb7892 100644 (file)
@@ -5,24 +5,22 @@ import java.util.*;
 import org.xwt.js.*;
 import org.xwt.util.*;
 
-// FEATURE: reimplement Watcher
 /** Implements cooperative multitasking */
 public class Scheduler {
 
-    private static Scheduler singleton = new Scheduler();
-    public static void run() { singleton.do_run(); }
+    public static final Scheduler singleton = Platform.getScheduler();
     protected Scheduler() { }
 
-    public static abstract class Task implements Callback { public abstract Object call(Object o); }
-
-    private static Queue runnable = new Queue(50);
+    public static abstract class Task { public abstract void perform(); }
 
+    protected static Queue runnable = new Queue(50);
     public static void add(Task t) { singleton.runnable.append(t); }
-    public void do_run() {
+    public void run() {
         while(true) {
             Task t = (Task)runnable.remove(true);
             try {
-                t.call(null);
+                t.perform();
+                // FIXME: be smarter about this
                 for(int i=0; i<Surface.allSurfaces.size(); i++)
                     ((Surface)Surface.allSurfaces.elementAt(i)).render();
             } catch (Exception e) {
index 9f48b2c..887534b 100644 (file)
@@ -255,10 +255,9 @@ public abstract class Surface extends PixelBuffer {
     protected final void Maximized(boolean b) { maximized = b; new SimpleMessage("Maximized", b ? Boolean.TRUE : Boolean.FALSE, root); }
     protected final void Focused(boolean b) { new SimpleMessage("Focused", b ? Boolean.TRUE : Boolean.FALSE, root); }
     public static void Refresh() {
-        Scheduler.add(new Scheduler.Task() { public Object call(Object arg) {
+        Scheduler.add(new Scheduler.Task() { public void perform() {
                 for(int i=0; i<allSurfaces.size(); i++)
                     ((Surface)allSurfaces.elementAt(i)).render();
-                return null;
         }}); }
 
     public final void setMaximized(boolean b) { if (b != maximized) _setMaximized(maximized = b); }
index 1b2d9b6..d91c828 100644 (file)
@@ -121,7 +121,9 @@ public class Template {
         for (int i=0; children != null && i<children.size(); i++) {
             Box kid = new Box();
             ((Template)children.elementAt(i)).apply(kid, callback, xwt, pis);
-            b.put(b.numChildren(), kid);
+
+            // FIXME: can't actually pass a null tailcall here
+            b.put(b.numChildren(), kid, null);
         }
 
         if (script != null) new JS.Thread(script, pis).resume();
index 2f436c4..f982ffa 100644 (file)
@@ -16,6 +16,19 @@ public class Trap {
 
     // Static Data //////////////////////////////////////////////////////////////
 
+    private static JS.CompiledFunction cascadeHelper = null;
+    private static String cascadeHelperText =
+        "return function(q) { var ret = arguments.doTrap;" +
+        "if (ret != false && !arguments.didCascade) arguments.cascade = q; };";
+    static {
+        try {
+            cascadeHelper = JS.parse("cascadeHelper", 1, new StringReader(cascadeHelperText));
+            cascadeHelper = (JS.CompiledFunction)new JS.Thread(cascadeHelper).resume();
+        } catch (Exception e) {
+            Log.log(Trap.class, e);
+        }
+    }
+
     /** List of properties that cannot be trapped */
     private static final Hash PROHIBITED = new Hash(120, 3);
 
@@ -63,12 +76,7 @@ public class Trap {
             if (t.f == f) return;
         
         // actually place the trap
-        Trap t = new Trap();
-        t.next = (Trap)trapee.get(name, Trap.class);
-        trapee.put(name, Trap.class, t);
-        t.trapee = trapee;
-        t.name = name;
-        t.f = f;
+        trapee.put2(name, Trap.class, new Trap(trapee, name.toString(), f, (Trap)trapee.get(name, Trap.class)));
     }
 
 
@@ -80,66 +88,43 @@ public class Trap {
      */
     static void delTrap(Box trapee, Object name, JS.CompiledFunction f) {
         Trap t = (Trap)trapee.get(name, Trap.class);
-        if (t.f == f) {
-            trapee.put(name, Trap.class, t.next);
-            return;
-        }
+        if (t.f == f) { trapee.put2(name, Trap.class, t.next); return; }
         for(; t.next != null; t = t.next)
-            if (t.next.f == f) {
-                t.next = t.next.next;
-                return;
-            }
+            if (t.next.f == f) { t.next = t.next.next; return; }
         Log.logJS("warning: tried to remove a trap that had not been placed");
     }
 
 
     // Instance Methods //////////////////////////////////////////////////////////////////////////
 
-    private Trap() { }
+    private Trap(Box b, String n, JS.CompiledFunction f, Trap nx)
+    { trapee = b; name = n; this.f = f; this.next = nx; }
+
+    // Read Traps //////////////////////////////////////////////////////////////////////
 
     public Object perform() {
-        try {
-            if (f.getNumFormalArgs() > 0) return cascade();
-            JS.Thread.current().setTailCall(f, new TrapArgs(this));
-            return null;
-        } catch (Exception e) {
-            Log.log(this, "Exception thrown from within trap: " + e);
-            return null;
-        }
+        if (f.getNumFormalArgs() > 0) return cascade();
+        else return new JS.TailCall().set(f, new TrapArgs(this));
     }
 
-    private static JS.CompiledFunction cascadeHelper = null;
-    private static String cascadeHelperText =
-        "return function(q) { var ret = arguments.doTrap;" +
-        "if (ret != false && !arguments.didCascade) arguments.cascade = q; };";
-    static {
-        try {
-            cascadeHelper = JS.parse("cascadeHelper", 1, new StringReader(cascadeHelperText));
-            cascadeHelper = (JS.CompiledFunction)new JS.Thread(cascadeHelper).resume();
-        } catch (Exception e) {
-            Log.log(Trap.class, e);
-        }
+    public Object cascade() {
+        if (next != null) return next.perform();
+        else return trapee.get(name, true);
     }
 
-    public void perform(Object val) {
-        try {
-            if (f.getNumFormalArgs() == 0) cascade(val);
-            else JS.Thread.current().setTailCall(cascadeHelper, new TrapArgs(this, val));
-        } catch (Exception e) {
-            Log.log(this, "Exception thrown from within trap: " + e);
-            e.printStackTrace();
-        }
+    // Write Traps //////////////////////////////////////////////////////////////////////
+
+    public void perform(Object val, JS.TailCall tail) {
+        if (f.getNumFormalArgs() == 0) cascade(val, tail);
+        else tail.set(cascadeHelper, new TrapArgs(this, val));
     }
     
-    public Object cascade() {
-        if (next != null) { next.perform(); return null; }
-        return trapee.get(name, true);
+    public void cascade(Object val, JS.TailCall tail) {
+        if (next != null) next.perform(val, tail);
+        else trapee.put(name, val, tail, true);
     }
 
-    public void cascade(Object val) {
-        if (next != null) next.perform(val);
-        trapee.put(name, val, true);
-    }
+    // Args ///////////////////////////////////////////////////////////////////////////
 
     private static class TrapArgs extends JS.Array {
         private Trap t;
@@ -147,16 +132,16 @@ public class Trap {
         public TrapArgs(Trap t) { this.t = t; }
         public TrapArgs(Trap t, Object value) { this.t = t; addElement(value); }
         
-        public void put(Object key, Object val) {
-            if (key.equals("cascade")) { cascadeHappened = true; t.cascade(val); }
-            else super.put(key, val);
+        public void put(Object key, Object val, JS.TailCall tail) {
+            if (key.equals("cascade")) { cascadeHappened = true; t.cascade(val, tail); }
+            else super.put(key, val, (JS.TailCall)tail);
         }
 
         public Object get(Object key) {
             // common case
             if(!(key instanceof String)) return super.get(key);
             if (key.equals("trapee")) return t.trapee;
-            if (key.equals("doTrap")) { JS.Thread.current().setTailCall(t.f, this); return null; }
+            if (key.equals("doTrap")) return new JS.TailCall().set(t.f, this);
             if (key.equals("didCascade")) return cascadeHappened ? Boolean.TRUE : Boolean.FALSE;
             if (key.equals("trapname")) return t.name;
             if (key.equals("cascade")) return t.cascade();
index c5f7a13..99b158f 100644 (file)
@@ -52,9 +52,8 @@ public final class XWT extends JS.Obj {
 
     public void put(Object name, final Object value) {
         if (name.equals("thread") && value != null && (value instanceof JS.Callable || value instanceof JS.CompiledFunction)) {
-            Scheduler.add(new Scheduler.Task() { public Object call(Object arg) {
+            Scheduler.add(new Scheduler.Task() { public void perform() {
                 new JS.Thread((CompiledFunction)value).resume();
-                return null;
             } });
         } else if (name.equals("clipboard")) Platform.setClipBoard(value.toString());
         else if (name.equals("frame")) Platform.createSurface((Box)value, true, true);
@@ -238,13 +237,9 @@ public final class XWT extends JS.Obj {
     public static void sleep(final int i) {
         final JS.Thread jsthread = JS.Thread.current();
         final long currentTime = System.currentTimeMillis();
-        final Scheduler.Task task = new Scheduler.Task() { public Object call(Object arg) {
-            if (System.currentTimeMillis() - currentTime < i) {
-                Scheduler.add(this);
-            } else {
-                jsthread.resume();
-            }
-            return null;
+        final Scheduler.Task task = new Scheduler.Task() { public void perform() {
+            if (System.currentTimeMillis() - currentTime < i) Scheduler.add(this);
+            else jsthread.resume();
         } };
         jsthread.pause();
         Scheduler.add(task);
index 2c69e1d..90f0322 100644 (file)
@@ -102,17 +102,6 @@ class CompiledFunctionImpl extends JS.Obj implements ByteCodes, Tokens {
         try {
             if (cx.paused) return null;
             cx.bind();
-            if (cx.tailCallFunction != null) {
-                cx.stack.pop();  // discard actual return value
-                cx.pc -= 2;
-                cx.stack.push(new CallMarker(cx));
-                cx.stack.push(cx.tailCallArgs);
-                cx.currentCompiledFunction = cx.tailCallFunction;
-                cx.tailCallFunction = null;
-                cx.tailCallArgs = null;
-                cx.scope = new FunctionScope("unknown", cx.currentCompiledFunction.parentScope);
-                cx.pc = 0;
-            }
             if (cx.currentCompiledFunction == null) return cx.stack.pop();
             if (cx.pc >= ((CompiledFunctionImpl)cx.currentCompiledFunction).size) return cx.stack.pop();
             String label = null;
@@ -245,8 +234,18 @@ class CompiledFunctionImpl extends JS.Obj implements ByteCodes, Tokens {
                     throw je("tried to put a value to the " + key + " property on a " + target.getClass().getName());
                 if (key == null)
                     throw je("tried to assign \"" + (val==null?"(null)":val.toString()) + "\" to the null key");
-                ((JS)target).put(key, val);
+                // FIXME too many allocations here
+                TailCall tail = new TailCall();
+                ((JS)target).put(key, val, tail);
                 cx.stack.push(val);
+                if (tail.func != null) {
+                    cx.stack.push(new CallMarker(cx));
+                    cx.stack.push(tail.args);
+                    cx.currentCompiledFunction = tail.func;
+                    cx.scope = new CompiledFunctionImpl.FunctionScope("unknown", tail.func.parentScope);
+                    cx.pc = -1;
+                    break;
+                }
                 break;
             }
 
@@ -268,6 +267,14 @@ class CompiledFunctionImpl extends JS.Obj implements ByteCodes, Tokens {
                     ret = Internal.getFromPrimitive(o,v);
                 else if (o instanceof JS) {
                     ret = ((JS)o).get(v);
+                    if (ret instanceof JS.TailCall) {
+                        cx.stack.push(new CallMarker(cx));
+                        cx.stack.push(((JS.TailCall)ret).args);
+                        cx.currentCompiledFunction = ((JS.TailCall)ret).func;
+                        cx.scope = new CompiledFunctionImpl.FunctionScope("unknown", ((JS.TailCall)ret).func.parentScope);
+                        cx.pc = -1;
+                        break;
+                    }
                 } else 
                     throw je("tried to get property " + v + " from a " + o.getClass().getName());
                 cx.stack.push(ret);
@@ -298,6 +305,14 @@ class CompiledFunctionImpl extends JS.Obj implements ByteCodes, Tokens {
                             break;
                         } else {
                             o = ((JS)o).get(method);
+                            if (o instanceof JS.TailCall) {
+                                cx.stack.push(new CallMarker(cx));
+                                cx.stack.push(((JS.TailCall)o).args);
+                                cx.currentCompiledFunction = ((JS.TailCall)o).func;
+                                cx.scope = new CompiledFunctionImpl.FunctionScope("unknown", ((JS.TailCall)o).func.parentScope);
+                                cx.pc = -1;
+                                break;
+                            }
                         }
                     } else {
                         throw new JS.Exn("Tried to call a method on an object that isn't a JS object: " + o);
@@ -314,6 +329,14 @@ class CompiledFunctionImpl extends JS.Obj implements ByteCodes, Tokens {
                     
                 } else {
                     ret = ((JS.Callable)o).call(arguments);
+                    if (ret instanceof JS.TailCall) {
+                        cx.stack.push(new CallMarker(cx));
+                        cx.stack.push(((JS.TailCall)ret).args);
+                        cx.currentCompiledFunction = ((JS.TailCall)ret).func;
+                        cx.scope = new CompiledFunctionImpl.FunctionScope("unknown", ((JS.TailCall)ret).func.parentScope);
+                        cx.pc = -1;
+                        break;
+                    }
                 }
                 cx.stack.push(ret);
                 break;