       return id;
     },
 
-    _fixupList: function fixupList(repl, list) {
+    _fixupList: function _fixupList(repl, list) {
       for(var i = 0; i < list.length; ++i) {
         if(list[i] instanceof Array) {
           this._fixupList(repl, list[i]);
         } else if(typeof list[i] === 'object') {
-          list[i] = this._lookupObject(repl, list[i].objid);
+          var obj = list[i];
+          if(obj.funcall) {
+            var parts = obj.funcall;
+            this._fixupList(repl, parts);
+            var [thisobj, func] = this._parseFunc(parts[0]);
+            list[i] = func.apply(thisobj, parts.slice(1));
+          } else if(obj.objid) {
+            list[i] = this._lookupObject(repl, obj.objid);
+          } else {
+            throw new Error('Unknown object type: ' + obj.toSource());
+          }
         }
       }
     },
 
-    handleInput: function handleInput(repl, input) {
+    _parseFunc: function(func) {
+      var thisobj = null;
+
+      if(typeof func === 'string') {
+        func = window[func];
+      } else if(func instanceof Array) {
+        if(func.length === 1 && typeof func[0] !== 'string') {
+          func = func[0];
+        } else {
+          [thisobj, func] = this._parsePropDescriptor(func);
+          func = thisobj[func];
+        }
+      }
+
+      return [thisobj,func];
+    },
+
+    _encodeReturn: function(value, array_as_mv) {
+      var ret;
+
+      if(value === null) {
+        ret = ['special', 'null'];
+      } else if(value === true) {
+        ret = ['special', 'true'];
+      } else if(value === false) {
+        ret = ['special', 'false'];
+      } else if(value === undefined) {
+        ret = ['special', 'undefined'];
+      } else if(typeof value === 'number') {
+        if(isNaN(value)) {
+          ret = ['special', 'NaN'];
+        } else if(value === Infinity) {
+          ret = ['special', 'Infinity'];
+        } else if(value === -Infinity) {
+          ret = ['special', '-Infinity'];
+        } else {
+          ret = ['atom', value];
+        }
+      } else if(typeof value === 'string') {
+        ret = ['atom', value];
+      } else if(array_as_mv && value instanceof Array) {
+        ret = ['array', value.map(this._encodeReturn, this)];
+      } else {
+        ret = ['objid', this._findOrAllocateObject(repl, value)];
+      }
+
+      return ret;
+    },
+
+    _handleInputLine: function _handleInputLine(repl, line) {
+      var ret;
+      var array_as_mv = false;
+
       try {
-        var parts = eval(input);
+        if(line[0] === '*') {
+          array_as_mv = true;
+          line = line.substring(1);
+        }
+        var parts = eval(line);
         this._fixupList(repl, parts);
-        var value = parts[0].apply(parts[1], parts.slice(2));
-        if(value === null || value === true || value === false ||
-          value === undefined || typeof value === 'object' |
-          typeof value === 'function')
-        {
-          var ret = ['objid', this._findOrAllocateObject(repl, value) ];
-        } else {
-          var ret = ['atom', value ];
-        }
+        var [thisobj, func] = this._parseFunc(parts[0]);
+        ret = this._encodeReturn(
+          func.apply(thisobj, parts.slice(1)),
+          array_as_mv);
       } catch(x) {
-        var ret = ['error', x.toString() ];
+        ret = ['error', x.toString() ];
       }
 
       var JSON = Components.classes['@mozilla.org/dom/json;1'].createInstance(Components.interfaces.nsIJSON);
       repl.print(JSON.encode(ret));
-      repl.popInteractor();
       repl._prompt();
+    },
+
+    handleInput: function handleInput(repl, chunk) {
+      this._input += chunk;
+      var match, line;
+      while(match = this._input.match(/.*\\n/)) {
+        line = match[0];
+
+        if(line === 'EXIT\\n') {
+          repl.popInteractor();
+          repl._prompt();
+          return;
+        }
+
+        this._input = this._input.substring(line.length);
+        this._handleInputLine(repl, line);
+      }
     }
   });
 })

         while pstate
         if (espresso--pstate-is-toplevel-defun pstate)
         do (goto-char (espresso--pitem-h-begin it))
         and return t)))

(defun espresso--beginning-of-defun-flat ()
  "Internal helper for espresso--beginning-of-defun"

(defun espresso--which-func-joiner (parts)
  (mapconcat #'identity parts "."))

(defun espresso--imenu-to-flat (items prefix symbols)
  (loop for item in items
        if (imenu--subalist-p item)
        do (espresso--imenu-to-flat
            (cdr item) (concat prefix (car item) ".")
            symbols)
        else
        do (let* ((name (concat prefix (car item)))
                  (name2 name)
                  (ctr 0))

             (while (gethash name2 symbols)
               (setq name2 (format "%s<%d>" name (incf ctr))))

             (puthash name2 (cdr item) symbols))))

(defun espresso--get-all-known-symbols ()
  "Get a hash table of all relevant Javascript symbols across all
espresso-mode buffers. Each key is the name of a symbol (possibly
disambiguated with <N>, where N > 1), and each value is a marker
giving the location of that symbol."
  (loop with symbols = (make-hash-table :test 'equal)
        with imenu-use-markers = t
        for buffer being the buffers
        for imenu-index = (with-current-buffer buffer
                            (when (eq major-mode 'espresso-mode)
                              (espresso--imenu-create-index)))
        do (espresso--imenu-to-flat imenu-index "" symbols)
        finally return symbols))

(defvar espresso--symbol-history nil
  "History of entered Javascript symbols")

(defun espresso--read-symbol (symbols-table prompt &optional initial-input)
  "Read a symbol from SYMBOLS-TABLE, which is a hash table like
the one from `espresso--get-all-known-symbols'. Return value is a
cons of (SYMBOL-NAME . LOCATION), where SYMBOL-NAME is a string
and LOCATION is a marker.

Prompt is PROMPT.

If INITIAL-INPUT is not nil, use it as the initial input"

  (unless ido-mode
    (ido-mode t)
    (ido-mode nil))

  (let ((choice (ido-completing-read
                 prompt
                 (loop for key being the hash-keys of symbols-table
                       collect key)
                 nil t initial-input 'espresso--symbol-history)))
    (cons choice (gethash choice symbols-table))))

(defun espresso--guess-symbol-at-point ()
  (let ((bounds (bounds-of-thing-at-point 'symbol)))
    (when bounds
      (save-excursion
        (goto-char (car bounds))
        (when (eq (char-before) ?.)
          (backward-char)
          (setf (car bounds) (point))))
      (buffer-substring (car bounds) (cdr bounds)))))

(defun espresso-find-symbol (&optional arg)
  "Jump to a Javascript symbol we read in from the user. With
prefix argument, restrict symbols to those from the current
buffer. Pushes a mark onto the tag ring just like `find-tag'."

  (interactive "P")
  (let (symbols marker)
    (if (not arg)
        (setq symbols (espresso--get-all-known-symbols))
      (setq symbols (make-hash-table :test 'equal))
      (espresso--imenu-to-flat (espresso--imenu-create-index)
                               "" symbols))

    (setq marker (cdr (espresso--read-symbol
                       symbols "Jump to: "
                       (espresso--guess-symbol-at-point))))

    (ring-insert find-tag-marker-ring (point-marker))
    (switch-to-buffer (marker-buffer marker))
    (push-mark)
    (goto-char marker)))

;;; MozRepl integration

(put 'espresso-moz-bad-rpc 'error-conditions '(error timeout))
(put 'espresso-moz-bad-rpc 'error-message "Mozilla RPC Error")
