changeset 22477:07f3efb4e321

Merge with 4a83dc15e774bdf6f01e409982b81d6338296d69
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Mon, 07 Dec 2015 21:17:46 -0800
parents 50baaa7da8e8 (current diff) 4a83dc15e774 (diff)
children e4dd15f04c7d
files mx.truffle/mx_truffle.py truffle/com.oracle.truffle.api.vm/src/com/oracle/truffle/api/vm/PolyglotEngine.java truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceSection.java truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLHandler.java truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLServer.java truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/SLRootNode.java truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLServer.java truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/package.html truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/FrameDebugDescription.java truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServer.java truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServerContext.java
diffstat 25 files changed, 1621 insertions(+), 1155 deletions(-) [+]
line wrap: on
line diff
--- a/mx.truffle/mx_truffle.py	Fri Dec 04 14:32:24 2015 +0100
+++ b/mx.truffle/mx_truffle.py	Mon Dec 07 21:17:46 2015 -0800
@@ -53,7 +53,7 @@
 def sldebug(args):
     """run a simple command line debugger for the Simple Language"""
     vmArgs, slArgs = mx.extract_VM_args(args, useDoubleDash=True)
-    mx.run_java(vmArgs + ['-cp', mx.classpath("com.oracle.truffle.sl.tools"), "com.oracle.truffle.sl.tools.debug.SLREPLServer"] + slArgs)
+    mx.run_java(vmArgs + ['-cp', mx.classpath("com.oracle.truffle.sl.tools"), "com.oracle.truffle.sl.tools.debug.SLREPL"] + slArgs)
 
 def _truffle_gate_runner(args, tasks):
     with Task('Truffle UnitTests', tasks) as t:
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java	Mon Dec 07 21:17:46 2015 -0800
@@ -32,22 +32,23 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.util.Map;
+import java.util.Objects;
 
 import com.oracle.truffle.api.debug.Debugger;
 import com.oracle.truffle.api.debug.SuspendedEvent;
-import com.oracle.truffle.api.frame.FrameInstance;
 import com.oracle.truffle.api.frame.MaterializedFrame;
 import com.oracle.truffle.api.impl.Accessor;
 import com.oracle.truffle.api.impl.FindContextNode;
 import com.oracle.truffle.api.instrument.ASTProber;
 import com.oracle.truffle.api.instrument.Instrumenter;
+import com.oracle.truffle.api.instrument.KillException;
+import com.oracle.truffle.api.instrument.QuitException;
 import com.oracle.truffle.api.instrument.SyntaxTag;
 import com.oracle.truffle.api.instrument.Visualizer;
 import com.oracle.truffle.api.instrument.WrapperNode;
 import com.oracle.truffle.api.nodes.Node;
 import com.oracle.truffle.api.nodes.RootNode;
 import com.oracle.truffle.api.source.Source;
-import java.util.Objects;
 
 /**
  * An entry point for everyone who wants to implement a Truffle based language. By providing an
@@ -391,7 +392,7 @@
          * . The names of arguments are parameters for the resulting {#link CallTarget} that allow
          * the <code>source</code> to reference the actual parameters passed to
          * {@link CallTarget#call(java.lang.Object...)}.
-         * 
+         *
          * @param source the source to evaluate
          * @param argumentNames the names of {@link CallTarget#call(java.lang.Object...)} arguments
          *            that can be referenced from the source
@@ -470,21 +471,21 @@
             }
             try {
                 return target.call();
+            } catch (KillException | QuitException ex) {
+                throw ex;
             } catch (Throwable ex) {
                 throw new IOException(ex);
             }
         }
 
         @Override
-        protected Object evalInContext(Object vm, SuspendedEvent ev, String code, FrameInstance frame) throws IOException {
-            Node n = frame == null ? ev.getNode() : frame.getCallNode();
-            RootNode rootNode = n.getRootNode();
+        protected Object evalInContext(Object vm, SuspendedEvent ev, String code, Node node, MaterializedFrame frame) throws IOException {
+            RootNode rootNode = node.getRootNode();
             Class<? extends TruffleLanguage> languageType = findLanguage(rootNode);
-            Env env = findLanguage(vm, languageType);
-            TruffleLanguage<?> lang = findLanguage(env);
-            Source source = Source.fromText(code, "eval in context");
-            CallTarget target = lang.parse(source, n);
-            return target.call();
+            final Env env = findLanguage(vm, languageType);
+            final TruffleLanguage<?> lang = findLanguage(env);
+            final Source source = Source.fromText(code, "eval in context");
+            return lang.evalInContext(source, node, frame);
         }
 
         @Override
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/Debugger.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/Debugger.java	Mon Dec 07 21:17:46 2015 -0800
@@ -29,6 +29,7 @@
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
@@ -50,6 +51,7 @@
 import com.oracle.truffle.api.nodes.Node;
 import com.oracle.truffle.api.source.LineLocation;
 import com.oracle.truffle.api.source.Source;
+import com.oracle.truffle.api.source.SourceSection;
 
 /**
  * Represents debugging related state of a {@link com.oracle.truffle.api.vm.PolyglotEngine}.
@@ -678,6 +680,12 @@
          */
         private MaterializedFrame haltedFrame;
 
+        /**
+         * Subset of the Truffle stack corresponding to the current execution, not including the
+         * current frame.
+         */
+        private List<FrameInstance> contextStack;
+
         private DebugExecutionContext(Source executionSource, DebugExecutionContext previousContext) {
             this(executionSource, previousContext, -1);
         }
@@ -748,7 +756,55 @@
             // Clean up, just in cased the one-shot breakpoints got confused
             lineBreaks.disposeOneShots();
 
-            final int contextStackDepth = currentStackDepth() - contextStackBase;
+            // Includes the "caller" frame (not iterated)
+            final int contextStackDepth = (currentStackDepth() - contextStackBase) + 1;
+
+            final List<String> recentWarnings = new ArrayList<>(warnings);
+            warnings.clear();
+
+            final List<FrameInstance> frames = new ArrayList<>();
+            // Map the Truffle stack for this execution, ignore nested executions
+            // Ignore frames for which no CallNode is available.
+            // The top/current/0 frame is not produced by the iterator; reported separately
+            Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<FrameInstance>() {
+                int stackIndex = 1;
+                int frameIndex = 1;
+
+                @Override
+                public FrameInstance visitFrame(FrameInstance frameInstance) {
+                    if (stackIndex < contextStackDepth) {
+                        final Node callNode = frameInstance.getCallNode();
+                        if (callNode != null) {
+                            final SourceSection sourceSection = callNode.getEncapsulatingSourceSection();
+                            if (sourceSection != null && !sourceSection.getIdentifier().equals("<unknown>")) {
+                                frames.add(frameInstance);
+                                frameIndex++;
+                            } else if (TRACE) {
+                                if (callNode != null) {
+                                    contextTrace("HIDDEN frame added: " + callNode);
+                                } else {
+                                    contextTrace("HIDDEN frame added");
+                                }
+                                frames.add(frameInstance);
+                                frameIndex++;
+                            }
+                        } else if (TRACE) {
+                            if (callNode != null) {
+                                contextTrace("HIDDEN frame added: " + callNode);
+                            } else {
+                                contextTrace("HIDDEN frame added");
+                            }
+                            frames.add(frameInstance);
+                            frameIndex++;
+                        }
+                        stackIndex++;
+                        return null;
+                    }
+                    return frameInstance;
+                }
+            });
+            contextStack = Collections.unmodifiableList(frames);
+
             if (TRACE) {
                 final String reason = haltReason == null ? "" : haltReason + "";
                 final String where = before ? "BEFORE" : "AFTER";
@@ -757,12 +813,9 @@
                 // printStack(OUT);
             }
 
-            final List<String> recentWarnings = new ArrayList<>(warnings);
-            warnings.clear();
-
             try {
                 // Pass control to the debug client with current execution suspended
-                ACCESSOR.dispatchEvent(vm, new SuspendedEvent(Debugger.this, astNode, mFrame, recentWarnings, contextStackDepth));
+                ACCESSOR.dispatchEvent(vm, new SuspendedEvent(Debugger.this, haltedNode, haltedFrame, contextStack, recentWarnings));
                 // Debug client finished normally, execution resumes
                 // Presume that the client has set a new strategy (or default to Continue)
                 running = true;
@@ -840,8 +893,8 @@
         debugContext = debugContext.predecessor;
     }
 
-    Object evalInContext(SuspendedEvent ev, String code, FrameInstance frame) throws IOException {
-        return ACCESSOR.evalInContext(vm, ev, code, frame);
+    Object evalInContext(SuspendedEvent ev, String code, Node node, MaterializedFrame frame) throws IOException {
+        return ACCESSOR.evalInContext(vm, ev, code, node, frame);
     }
 
     @SuppressWarnings("rawtypes")
@@ -874,8 +927,8 @@
         }
 
         @Override
-        protected Object evalInContext(Object vm, SuspendedEvent ev, String code, FrameInstance frame) throws IOException {
-            return super.evalInContext(vm, ev, code, frame);
+        protected Object evalInContext(Object vm, SuspendedEvent ev, String code, Node node, MaterializedFrame frame) throws IOException {
+            return super.evalInContext(vm, ev, code, node, frame);
         }
     }
 
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/SuspendedEvent.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/SuspendedEvent.java	Mon Dec 07 21:17:46 2015 -0800
@@ -24,17 +24,17 @@
  */
 package com.oracle.truffle.api.debug;
 
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Collections;
+import java.util.List;
+
 import com.oracle.truffle.api.CompilerDirectives;
-import com.oracle.truffle.api.Truffle;
 import com.oracle.truffle.api.frame.FrameInstance;
-import com.oracle.truffle.api.frame.FrameInstanceVisitor;
+import com.oracle.truffle.api.frame.FrameInstance.FrameAccess;
 import com.oracle.truffle.api.frame.MaterializedFrame;
 import com.oracle.truffle.api.instrument.StandardSyntaxTag;
 import com.oracle.truffle.api.nodes.Node;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 
 /**
  * This event is delivered to all
@@ -48,36 +48,32 @@
  */
 @SuppressWarnings("javadoc")
 public final class SuspendedEvent {
-    private final List<String> recentWarnings;
-    private final MaterializedFrame mFrame;
-    private final Node astNode;
-    private final List<FrameInstance> frames;
-    private final Debugger debugger;
+
+    private static boolean TRACE = false;
+    private static final String TRACE_PREFIX = "EVENT: ";
+    private static final PrintStream OUT = System.out;
 
-    SuspendedEvent(Debugger prepares, Node astNode, MaterializedFrame mFrame, List<String> recentWarnings, final int stackDepth) {
-        this.debugger = prepares;
-        this.astNode = astNode;
-        this.mFrame = mFrame;
-        this.recentWarnings = recentWarnings;
+    private static void trace(String format, Object... args) {
+        if (TRACE) {
+            OUT.println(TRACE_PREFIX + String.format(format, args));
+        }
+    }
 
-        this.frames = new ArrayList<>();
-        // Map the Truffle stack for this execution, ignore nested executions
-        // The top (current) frame is not produced by the iterator.
-        frames.add(Truffle.getRuntime().getCurrentFrame());
-        Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<FrameInstance>() {
-            int frameCount = 1;
+    private final Debugger debugger;
+    private final Node haltedNode;
+    private final MaterializedFrame haltedFrame;
+    private final List<FrameInstance> stack;
+    private final List<String> warnings;
 
-            @Override
-            public FrameInstance visitFrame(FrameInstance frameInstance) {
-                if (frameCount < stackDepth) {
-                    frames.add(frameInstance);
-                    frameCount++;
-                    return null;
-                }
-                return frameInstance;
-            }
-        });
-
+    SuspendedEvent(Debugger debugger, Node haltedNode, MaterializedFrame haltedFrame, List<FrameInstance> stack, List<String> warnings) {
+        this.debugger = debugger;
+        this.haltedNode = haltedNode;
+        this.haltedFrame = haltedFrame;
+        this.stack = stack;
+        this.warnings = warnings;
+        if (TRACE) {
+            trace("Execution suspended at Node=" + haltedNode);
+        }
     }
 
     /**
@@ -93,26 +89,27 @@
     }
 
     public Node getNode() {
-        return astNode;
+        return haltedNode;
     }
 
     public MaterializedFrame getFrame() {
-        return mFrame;
+        return haltedFrame;
     }
 
     public List<String> getRecentWarnings() {
-        return Collections.unmodifiableList(recentWarnings);
+        return Collections.unmodifiableList(warnings);
     }
 
     /**
      * Gets the stack frames from the currently halted
-     * {@link com.oracle.truffle.api.vm.PolyglotEngine} execution.
+     * {@link com.oracle.truffle.api.vm.PolyglotEngine} execution, not counting the Node and Frame
+     * where halted.
      *
      * @return list of stack frames
      */
     @CompilerDirectives.TruffleBoundary
     public List<FrameInstance> getStack() {
-        return Collections.unmodifiableList(frames);
+        return stack;
     }
 
     /**
@@ -198,13 +195,20 @@
      * Evaluates given code snippet in the context of currently suspended execution.
      *
      * @param code the snippet to evaluate
-     * @param frame <code>null</code> in case the evaluation should happen in top most frame,
-     *            non-null value to specify a frame from those {@link #getStack() currently on
-     *            stack} to perform the evaluation in context of
+     * @param frameNumber specify a frame from those {@link #getStack() currently on stack} to
+     *            perform the evaluation in context of
      * @return the computed value
      * @throws IOException in case an evaluation goes wrong
      */
-    public Object eval(String code, FrameInstance frame) throws IOException {
-        return debugger.evalInContext(this, code, frame);
+    public Object eval(String code, Integer frameNumber) throws IOException {
+        if (frameNumber < 0 || frameNumber >= stack.size()) {
+            throw new IOException("invalid frame number");
+        }
+        if (frameNumber == 0) {
+            return debugger.evalInContext(this, code, haltedNode, haltedFrame);
+        }
+        final FrameInstance instance = stack.get(frameNumber - 1);
+        final MaterializedFrame frame = instance.getFrame(FrameAccess.MATERIALIZE, true).materialize();
+        return debugger.evalInContext(this, code, instance.getCallNode(), frame);
     }
 }
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/TagBreakpointFactory.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/TagBreakpointFactory.java	Mon Dec 07 21:17:46 2015 -0800
@@ -447,7 +447,5 @@
                 TagBreakpointImpl.this.nodeEnter(node, vFrame);
             }
         }
-
     }
-
 }
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java	Mon Dec 07 21:17:46 2015 -0800
@@ -39,7 +39,6 @@
 import com.oracle.truffle.api.TruffleLanguage.Env;
 import com.oracle.truffle.api.debug.Debugger;
 import com.oracle.truffle.api.debug.SuspendedEvent;
-import com.oracle.truffle.api.frame.FrameInstance;
 import com.oracle.truffle.api.frame.MaterializedFrame;
 import com.oracle.truffle.api.instrument.ASTProber;
 import com.oracle.truffle.api.instrument.Instrumenter;
@@ -158,8 +157,8 @@
         return API.eval(l, s, cache);
     }
 
-    protected Object evalInContext(Object vm, SuspendedEvent ev, String code, FrameInstance frame) throws IOException {
-        return API.evalInContext(vm, ev, code, frame);
+    protected Object evalInContext(Object vm, SuspendedEvent ev, String code, Node node, MaterializedFrame frame) throws IOException {
+        return API.evalInContext(vm, ev, code, node, frame);
     }
 
     protected Object importSymbol(Object vm, TruffleLanguage<?> queryingLang, String globalName) {
@@ -320,10 +319,6 @@
         return API.findContext(env);
     }
 
-    protected TruffleLanguage<?> findLanguage(Env env) {
-        return API.findLanguage(env);
-    }
-
     /** Applies all registered {@linkplain ASTProber probers} to the AST. */
     protected void probeAST(RootNode rootNode) {
         INSTRUMENT.probeAST(rootNode);
@@ -339,6 +334,10 @@
         return parse(truffleLanguage, code, context, argumentNames);
     }
 
+    protected TruffleLanguage<?> findLanguage(Env env) {
+        return API.findLanguage(env);
+    }
+
     protected CallTarget parse(TruffleLanguage<?> truffleLanguage, Source code, Node context, String... argumentNames) throws IOException {
         return API.parse(truffleLanguage, code, context, argumentNames);
     }
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultVisualizer.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultVisualizer.java	Mon Dec 07 21:17:46 2015 -0800
@@ -76,6 +76,9 @@
     }
 
     public String displayValue(Object value, int trim) {
+        if (value == null) {
+            return "<empty>";
+        }
         return trim(value.toString(), trim);
     }
 
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/LineLocation.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/LineLocation.java	Mon Dec 07 21:17:46 2015 -0800
@@ -45,7 +45,7 @@
 
     /**
      * Gets the 1-based number of a line in the source.
-     * 
+     *
      * @return value from 1 to infinity
      */
     public int getLineNumber() {
@@ -90,7 +90,15 @@
 
     @Override
     public int compareTo(LineLocation o) {
-        final int sourceResult = this.getSource().getPath().compareTo(o.getSource().getPath());
+        int sourceResult = 0;
+        final Source thisSource = this.getSource();
+        final String thisPath = thisSource.getPath();
+        if (thisPath == null) {
+            sourceResult = thisSource.getCode().compareTo(o.getSource().getCode());
+        } else {
+            final String thatPath = o.getSource().getPath();
+            sourceResult = thisPath.compareTo(thatPath);
+        }
         if (sourceResult != 0) {
             return sourceResult;
         }
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java	Mon Dec 07 21:17:46 2015 -0800
@@ -728,7 +728,7 @@
 
         @Override
         public String getPath() {
-            return description;
+            return null;
         }
 
         @Override
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPL.java	Mon Dec 07 21:17:46 2015 -0800
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * The Universal Permissive License (UPL), Version 1.0
+ *
+ * Subject to the condition set forth below, permission is hereby granted to any
+ * person obtaining a copy of this software, associated documentation and/or
+ * data (collectively the "Software"), free of charge and under any and all
+ * copyright rights in the Software, and any and all patent rights owned or
+ * freely licensable by each licensor hereunder covering either (i) the
+ * unmodified Software as contributed to or provided by such licensor, or (ii)
+ * the Larger Works (as defined below), to deal in both
+ *
+ * (a) the Software, and
+ *
+ * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
+ * one is included with the Software each a "Larger Work" to which the Software
+ * is contributed by such licensors),
+ *
+ * without restriction, including without limitation the rights to copy, create
+ * derivative works of, display, perform, and distribute the Software and make,
+ * use, sell, offer for sale, import, export, have made, and have sold the
+ * Software and the Larger Work(s), and to sublicense the foregoing rights on
+ * either these or other terms.
+ *
+ * This license is subject to the following condition:
+ *
+ * The above copyright notice and either this complete permission notice or at a
+ * minimum a reference to the UPL must be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.oracle.truffle.sl.tools.debug;
+
+import com.oracle.truffle.sl.nodes.instrument.SLDefaultVisualizer;
+import com.oracle.truffle.tools.debug.shell.server.REPLServer;
+
+// TODO (mlvdv) delete when the REPL is fully multi-language
+/**
+ * Start a single-language REPL for the Simple Language implementation.
+ */
+public final class SLREPL {
+
+    public static void main(String[] args) {
+
+        // Cheating for the prototype: start from SL, rather than from the client.
+        final REPLServer server = new REPLServer("application/x-sl", new SLDefaultVisualizer());
+        server.start();
+    }
+
+    private SLREPL() {
+    }
+}
--- a/truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLHandler.java	Fri Dec 04 14:32:24 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * The Universal Permissive License (UPL), Version 1.0
- *
- * Subject to the condition set forth below, permission is hereby granted to any
- * person obtaining a copy of this software, associated documentation and/or
- * data (collectively the "Software"), free of charge and under any and all
- * copyright rights in the Software, and any and all patent rights owned or
- * freely licensable by each licensor hereunder covering either (i) the
- * unmodified Software as contributed to or provided by such licensor, or (ii)
- * the Larger Works (as defined below), to deal in both
- *
- * (a) the Software, and
- *
- * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
- * one is included with the Software each a "Larger Work" to which the Software
- * is contributed by such licensors),
- *
- * without restriction, including without limitation the rights to copy, create
- * derivative works of, display, perform, and distribute the Software and make,
- * use, sell, offer for sale, import, export, have made, and have sold the
- * Software and the Larger Work(s), and to sublicense the foregoing rights on
- * either these or other terms.
- *
- * This license is subject to the following condition:
- *
- * The above copyright notice and either this complete permission notice or at a
- * minimum a reference to the UPL must be included in all copies or substantial
- * portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-package com.oracle.truffle.sl.tools.debug;
-
-import com.oracle.truffle.api.instrument.KillException;
-import com.oracle.truffle.api.instrument.QuitException;
-import com.oracle.truffle.api.source.Source;
-import com.oracle.truffle.api.vm.PolyglotEngine;
-import com.oracle.truffle.tools.debug.shell.REPLMessage;
-import com.oracle.truffle.tools.debug.shell.client.SimpleREPLClient;
-import com.oracle.truffle.tools.debug.shell.server.REPLHandler;
-import com.oracle.truffle.tools.debug.shell.server.REPLServerContext;
-import java.io.File;
-import java.util.ArrayList;
-
-/**
- * Instantiation of the "server handler" part of the "REPL*" debugger for the simple language.
- * <p>
- * These handlers implement debugging commands that require language-specific support.
- *
- * @see SimpleREPLClient
- */
-public abstract class SLREPLHandler extends REPLHandler {
-
-    protected SLREPLHandler(String op) {
-        super(op);
-    }
-
-    public static final SLREPLHandler INFO_HANDLER = new SLREPLHandler(REPLMessage.INFO) {
-
-        @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
-            final String topic = request.get(REPLMessage.TOPIC);
-
-            if (topic == null || topic.isEmpty()) {
-                final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.INFO);
-                return finishReplyFailed(message, "No info topic specified");
-            }
-
-            switch (topic) {
-
-                case REPLMessage.LANGUAGE:
-                    return createLanguageInfoReply();
-
-                default:
-                    final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.INFO);
-                    return finishReplyFailed(message, "No info about topic \"" + topic + "\"");
-            }
-        }
-    };
-
-    private static REPLMessage[] createLanguageInfoReply() {
-        final ArrayList<REPLMessage> langMessages = new ArrayList<>();
-
-        final REPLMessage msg1 = new REPLMessage(REPLMessage.OP, REPLMessage.INFO);
-        msg1.put(REPLMessage.TOPIC, REPLMessage.LANGUAGE);
-        msg1.put(REPLMessage.INFO_KEY, "Language");
-        msg1.put(REPLMessage.INFO_VALUE, "Simple");
-        msg1.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
-        langMessages.add(msg1);
-
-        return langMessages.toArray(new REPLMessage[0]);
-    }
-
-    public static final SLREPLHandler LOAD_RUN_SOURCE_HANDLER = new SLREPLHandler(REPLMessage.LOAD_RUN) {
-
-        @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
-            return loadHandler(request, serverContext, false);
-        }
-    };
-
-    public static final SLREPLHandler LOAD_STEP_SOURCE_HANDLER = new SLREPLHandler(REPLMessage.LOAD_STEP) {
-
-        @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
-            return loadHandler(request, serverContext, true);
-        }
-    };
-
-    // TODO (mlvdv) re-implement stepInto when vm support replaced
-    /**
-     * Runs a source, optionally stepping into a specified tag.
-     */
-    private static REPLMessage[] loadHandler(REPLMessage request, REPLServerContext serverContext, @SuppressWarnings("unused") boolean stepInto) {
-        final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.LOAD_RUN);
-        final String fileName = request.get(REPLMessage.SOURCE_NAME);
-        try {
-            final File file = new File(fileName);
-            if (!file.canRead()) {
-                return finishReplyFailed(reply, "can't find file \"" + fileName + "\"");
-            }
-            final PolyglotEngine vm = serverContext.engine();
-            vm.eval(Source.fromFileName(file.getPath()));
-            PolyglotEngine.Value main = vm.findGlobalSymbol("main");
-            if (main != null) {
-                main.execute();
-            }
-            final String path = file.getCanonicalPath();
-            reply.put(REPLMessage.FILE_PATH, path);
-            return finishReplySucceeded(reply, fileName + "  exited");
-        } catch (QuitException ex) {
-            throw ex;
-        } catch (KillException ex) {
-            return finishReplySucceeded(reply, fileName + " killed");
-        } catch (Exception ex) {
-            return finishReplyFailed(reply, "error loading file \"" + fileName + "\": " + ex.getMessage());
-        }
-    }
-}
--- a/truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLServer.java	Fri Dec 04 14:32:24 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,281 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * The Universal Permissive License (UPL), Version 1.0
- *
- * Subject to the condition set forth below, permission is hereby granted to any
- * person obtaining a copy of this software, associated documentation and/or
- * data (collectively the "Software"), free of charge and under any and all
- * copyright rights in the Software, and any and all patent rights owned or
- * freely licensable by each licensor hereunder covering either (i) the
- * unmodified Software as contributed to or provided by such licensor, or (ii)
- * the Larger Works (as defined below), to deal in both
- *
- * (a) the Software, and
- *
- * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
- * one is included with the Software each a "Larger Work" to which the Software
- * is contributed by such licensors),
- *
- * without restriction, including without limitation the rights to copy, create
- * derivative works of, display, perform, and distribute the Software and make,
- * use, sell, offer for sale, import, export, have made, and have sold the
- * Software and the Larger Work(s), and to sublicense the foregoing rights on
- * either these or other terms.
- *
- * This license is subject to the following condition:
- *
- * The above copyright notice and either this complete permission notice or at a
- * minimum a reference to the UPL must be included in all copies or substantial
- * portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-package com.oracle.truffle.sl.tools.debug;
-
-import com.oracle.truffle.api.debug.Breakpoint;
-import com.oracle.truffle.api.debug.Debugger;
-import com.oracle.truffle.api.debug.ExecutionEvent;
-import com.oracle.truffle.api.debug.SuspendedEvent;
-import com.oracle.truffle.api.instrument.QuitException;
-import com.oracle.truffle.api.instrument.Visualizer;
-import com.oracle.truffle.api.source.Source;
-import com.oracle.truffle.api.source.SourceSection;
-import com.oracle.truffle.api.vm.EventConsumer;
-import com.oracle.truffle.api.vm.PolyglotEngine;
-import com.oracle.truffle.api.vm.PolyglotEngine.Language;
-import com.oracle.truffle.sl.SLLanguage;
-import com.oracle.truffle.sl.nodes.instrument.SLDefaultVisualizer;
-import com.oracle.truffle.tools.debug.shell.REPLMessage;
-import com.oracle.truffle.tools.debug.shell.REPLServer;
-import com.oracle.truffle.tools.debug.shell.client.SimpleREPLClient;
-import com.oracle.truffle.tools.debug.shell.server.REPLHandler;
-import com.oracle.truffle.tools.debug.shell.server.REPLServerContext;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Instantiation of the "server" side of the "REPL*" debugger for the Simple language.
- * <p>
- * The SL parser is not equipped to parse program fragments, so any debugging functions that depend
- * on this are not supported, for example "eval" and breakpoint conditions.
- *
- * @see SimpleREPLClient
- */
-public final class SLREPLServer extends REPLServer {
-
-    // TODO (mlvdv) remove when there's a better way to express this dependency
-    @SuppressWarnings("unused") private static final Class<SLLanguage> DYNAMIC_DEPENDENCY = com.oracle.truffle.sl.SLLanguage.class;
-
-    public static void main(String[] args) {
-        // Cheating for the prototype: start from SL, rather than from the client.
-        final SLREPLServer server = new SLREPLServer();
-        final SimpleREPLClient client = new SimpleREPLClient(getShortName(server.language), server);
-
-        // Cheating for the prototype: allow server access to client for recursive debugging
-        server.setClient(client);
-
-        try {
-            client.start();
-        } catch (QuitException ex) {
-        }
-    }
-
-    private static String getShortName(Language language) {
-        return language.getName() + "(" + language.getVersion() + ")";
-    }
-
-    private final Language language;
-    private final PolyglotEngine vm;
-    private Debugger db;
-    private final String statusPrefix;
-    private final Map<String, REPLHandler> handlerMap = new HashMap<>();
-    private SLServerContext currentServerContext;
-    private SimpleREPLClient replClient = null;
-
-    private void add(REPLHandler fileHandler) {
-        handlerMap.put(fileHandler.getOp(), fileHandler);
-    }
-
-    public SLREPLServer() {
-        add(REPLHandler.BACKTRACE_HANDLER);
-        add(REPLHandler.BREAK_AT_LINE_HANDLER);
-        add(REPLHandler.BREAK_AT_LINE_ONCE_HANDLER);
-        add(REPLHandler.BREAK_AT_THROW_HANDLER);
-        add(REPLHandler.BREAK_AT_THROW_ONCE_HANDLER);
-        add(REPLHandler.BREAKPOINT_INFO_HANDLER);
-        add(REPLHandler.CLEAR_BREAK_HANDLER);
-        add(REPLHandler.CONTINUE_HANDLER);
-        add(REPLHandler.DELETE_HANDLER);
-        add(REPLHandler.DISABLE_BREAK_HANDLER);
-        add(REPLHandler.ENABLE_BREAK_HANDLER);
-        add(REPLHandler.FILE_HANDLER);
-        add(REPLHandler.FRAME_HANDLER);
-        add(SLREPLHandler.INFO_HANDLER);
-        add(REPLHandler.KILL_HANDLER);
-        add(SLREPLHandler.LOAD_RUN_SOURCE_HANDLER);
-        add(SLREPLHandler.LOAD_STEP_SOURCE_HANDLER);
-        add(REPLHandler.QUIT_HANDLER);
-        add(REPLHandler.STEP_INTO_HANDLER);
-        add(REPLHandler.STEP_OUT_HANDLER);
-        add(REPLHandler.STEP_OVER_HANDLER);
-        add(REPLHandler.TRUFFLE_HANDLER);
-        add(REPLHandler.TRUFFLE_NODE_HANDLER);
-
-        EventConsumer<SuspendedEvent> onHalted = new EventConsumer<SuspendedEvent>(SuspendedEvent.class) {
-            @Override
-            protected void on(SuspendedEvent ev) {
-                SLREPLServer.this.haltedAt(ev);
-            }
-        };
-        EventConsumer<ExecutionEvent> onExec = new EventConsumer<ExecutionEvent>(ExecutionEvent.class) {
-            @Override
-            protected void on(ExecutionEvent event) {
-                event.prepareStepInto();
-                db = event.getDebugger();
-            }
-        };
-
-        PolyglotEngine newVM = PolyglotEngine.newBuilder().onEvent(onHalted).onEvent(onExec).build();
-        this.language = newVM.getLanguages().get("application/x-sl");
-        assert language != null;
-
-        this.vm = newVM;
-        this.statusPrefix = getShortName(language) + " REPL:";
-    }
-
-    private void setClient(SimpleREPLClient replClient) {
-        this.replClient = replClient;
-    }
-
-    @Override
-    public REPLMessage start() {
-
-        this.currentServerContext = new SLServerContext(null, null);
-
-        // SL doesn't load modules (like other languages), so we just return a success
-        final REPLMessage reply = new REPLMessage();
-        reply.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
-        reply.put(REPLMessage.DISPLAY_MSG, getShortName(language) + " started");
-        return reply;
-    }
-
-    @Override
-    public REPLMessage[] receive(REPLMessage request) {
-        if (currentServerContext == null) {
-            final REPLMessage message = new REPLMessage();
-            message.put(REPLMessage.STATUS, REPLMessage.FAILED);
-            message.put(REPLMessage.DISPLAY_MSG, "server not started");
-            final REPLMessage[] reply = new REPLMessage[]{message};
-            return reply;
-        }
-        return currentServerContext.receive(request);
-    }
-
-    /**
-     * Execution context of a halted SL program.
-     */
-    public final class SLServerContext extends REPLServerContext {
-
-        private final SLServerContext predecessor;
-
-        public SLServerContext(SLServerContext predecessor, SuspendedEvent event) {
-            super(predecessor == null ? 0 : predecessor.getLevel() + 1, event);
-            this.predecessor = predecessor;
-        }
-
-        @Override
-        public REPLMessage[] receive(REPLMessage request) {
-            final String command = request.get(REPLMessage.OP);
-            final REPLHandler handler = handlerMap.get(command);
-
-            if (handler == null) {
-                final REPLMessage message = new REPLMessage();
-                message.put(REPLMessage.OP, command);
-                message.put(REPLMessage.STATUS, REPLMessage.FAILED);
-                message.put(REPLMessage.DISPLAY_MSG, statusPrefix + " op \"" + command + "\" not supported");
-                final REPLMessage[] reply = new REPLMessage[]{message};
-                return reply;
-            }
-            return handler.receive(request, currentServerContext);
-        }
-
-        @Override
-        public Language getLanguage() {
-            return language;
-        }
-
-        @Override
-        public Visualizer getVisualizer() {
-            return new SLDefaultVisualizer();
-        }
-
-        @Override
-        public PolyglotEngine engine() {
-            return vm;
-        }
-
-        @Override
-        protected Debugger db() {
-            return db;
-        }
-
-        @Override
-        public void registerBreakpoint(Breakpoint breakpoint) {
-            SLREPLServer.this.registerBreakpoint(breakpoint);
-        }
-
-        @Override
-        public Breakpoint findBreakpoint(int id) {
-            return SLREPLServer.this.findBreakpoint(id);
-        }
-
-        @Override
-        public int getBreakpointID(Breakpoint breakpoint) {
-            return SLREPLServer.this.getBreakpointID(breakpoint);
-        }
-
-    }
-
-    void haltedAt(SuspendedEvent event) {
-        // Create and push a new debug context where execution is halted
-        currentServerContext = new SLServerContext(currentServerContext, event);
-
-        // Message the client that execution is halted and is in a new debugging context
-        final REPLMessage message = new REPLMessage();
-        message.put(REPLMessage.OP, REPLMessage.STOPPED);
-        final SourceSection src = event.getNode().getSourceSection();
-        final Source source = src.getSource();
-        message.put(REPLMessage.SOURCE_NAME, source.getName());
-        message.put(REPLMessage.FILE_PATH, source.getPath());
-        message.put(REPLMessage.LINE_NUMBER, Integer.toString(src.getStartLine()));
-        message.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
-        message.put(REPLMessage.DEBUG_LEVEL, Integer.toString(currentServerContext.getLevel()));
-        List<String> warnings = event.getRecentWarnings();
-        if (!warnings.isEmpty()) {
-            final StringBuilder sb = new StringBuilder();
-            for (String warning : warnings) {
-                sb.append(warning + "\n");
-            }
-            message.put(REPLMessage.WARNINGS, sb.toString());
-        }
-        try {
-            // Cheat with synchrony: call client directly about entering a nested debugging
-            // context.
-            replClient.halted(message);
-        } finally {
-            // Returns when "continue" is called in the new debugging context
-
-            // Pop the debug context, and return so that the old context will continue
-            currentServerContext = currentServerContext.predecessor;
-        }
-    }
-}
--- a/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java	Mon Dec 07 21:17:46 2015 -0800
@@ -190,17 +190,6 @@
  * with the new version.
  * </ul>
  */
-
-/*
- *
- * <p> <b>Tools:</b><br> The use of some of Truffle's support for developer tools (based on the
- * Truffle {@linkplain Instrumenter Instrumentation Framework}) are demonstrated in this file, for
- * example: <ul> <li>a {@linkplain NodeExecCounter counter for node executions}, tabulated by node
- * type; and</li> <li>a simple {@linkplain CoverageTracker code coverage engine}.</li> </ul> In each
- * case, the tool is enabled if a corresponding local boolean variable in this file is set to {@code
- * true}. Results are printed at the end of the execution using each tool's <em>default
- * printer</em>.
- */
 @TruffleLanguage.Registration(name = "SL", version = "0.5", mimeType = "application/x-sl")
 public final class SLLanguage extends TruffleLanguage<SLContext> {
     public static final String builtinKind = "SL builtin";
@@ -228,11 +217,6 @@
         return context;
     }
 
-    /* Small tools that can be installed for demonstration */
-    // private static NodeExecCounter nodeExecCounter = null;
-    // private static NodeExecCounter statementExecCounter = null;
-    // private static CoverageTracker coverageTracker = null;
-
     /**
      * The main entry point. Use the mx command "mx sl" to run it with the correct class path setup.
      */
@@ -240,8 +224,6 @@
         PolyglotEngine vm = PolyglotEngine.newBuilder().build();
         assert vm.getLanguages().containsKey("application/x-sl");
 
-        setupToolDemos();
-
         int repeats = 1;
         if (args.length >= 2) {
             repeats = Integer.parseInt(args[1]);
@@ -261,7 +243,6 @@
         while (repeats-- > 0) {
             main.execute();
         }
-        reportToolDemos();
     }
 
     public static int parsingCount() {
@@ -525,7 +506,7 @@
 
     @Override
     protected Object evalInContext(Source source, Node node, MaterializedFrame mFrame) throws IOException {
-        throw new IllegalStateException("evalInContext not supported in this language");
+        throw new IllegalStateException("evalInContext not supported in SL");
     }
 
     public Node createFindContextNode0() {
@@ -535,38 +516,4 @@
     public SLContext findContext0(Node contextNode) {
         return findContext(contextNode);
     }
-
-    // TODO (mlvdv) remove the static hack when we no longer have the static demo variables
-    private static void setupToolDemos() {
-        // if (nodeExecCounts) {
-        // nodeExecCounter = new NodeExecCounter();
-        // nodeExecCounter.install();
-        // }
-        //
-        // if (statementCounts) {
-        // statementExecCounter = new NodeExecCounter(StandardSyntaxTag.STATEMENT);
-        // statementExecCounter.install();
-        // }
-        //
-        // if (coverage) {
-        // coverageTracker = new CoverageTracker();
-        // coverageTracker.install();
-        // }
-    }
-
-    private static void reportToolDemos() {
-        // if (nodeExecCounter != null) {
-        // nodeExecCounter.print(System.out);
-        // nodeExecCounter.dispose();
-        // }
-        // if (statementExecCounter != null) {
-        // statementExecCounter.print(System.out);
-        // statementExecCounter.dispose();
-        // }
-        // if (coverageTracker != null) {
-        // coverageTracker.print(System.out);
-        // coverageTracker.dispose();
-        // }
-    }
-
 }
--- a/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/instrument/SLDefaultVisualizer.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/instrument/SLDefaultVisualizer.java	Mon Dec 07 21:17:46 2015 -0800
@@ -94,7 +94,10 @@
 
     @Override
     public String displayValue(Object value, int trim) {
-        if (value == null || value == SLNull.SINGLETON) {
+        if (value == null) {
+            return "<empty>";
+        }
+        if (value == SLNull.SINGLETON) {
             return "null";
         }
         return trim(value.toString(), trim);
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLMessage.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLMessage.java	Mon Dec 07 21:17:46 2015 -0800
@@ -30,6 +30,8 @@
 import java.util.Set;
 import java.util.TreeMap;
 
+import com.oracle.truffle.tools.debug.shell.server.REPLServer;
+
 /**
  * A message for communication between a Read-Eval-Print-Loop server associated with a language
  * implementation and a possibly remote client.
@@ -53,6 +55,18 @@
     public static final String BREAKPOINT_IGNORE_COUNT = "breakpoint-ignore-count";
     public static final String BREAKPOINT_INFO = "breakpoint-info";
     public static final String BREAKPOINT_STATE = "breakpoint-state";
+    public static final String CALL = "call";
+    public static final String ARG0 = "call-argument-0";
+    public static final String ARG1 = "call-argument-1";
+    public static final String ARG2 = "call-argument-2";
+    public static final String ARG3 = "call-argument-3";
+    public static final String ARG4 = "call-argument-4";
+    public static final String ARG5 = "call-argument-5";
+    public static final String ARG6 = "call-argument-6";
+    public static final String ARG7 = "call-argument-7";
+    public static final String ARG8 = "call-argument-8";
+    public static final String ARG9 = "call-argument-9";
+    public static final String CALL_NAME = "call-name";
     public static final String CLEAR_BREAK = "clear-breakpoint";
     public static final String CODE = "code";
     public static final String CONTINUE = "continue";
@@ -70,14 +84,17 @@
     public static final String FRAME_INFO = "frame-info";
     public static final String FRAME_NUMBER = "frame-number";
     public static final String INFO = "info";
+    public static final String INFO_CURRENT_LANGUAGE = "info-current-language";
     public static final String INFO_KEY = "info-key";
+    public static final String INFO_SUPPORTED_LANGUAGES = "info-supported-languages";
     public static final String INFO_VALUE = "info-value";
     public static final String KILL = "kill";
-    public static final String LANGUAGE = "language";
+    public static final String LANG_NAME = "language-name";
+    public static final String LANG_VER = "language-version";
+    public static final String LANG_MIME = "language-MIME type";
     public static final String LINE_NUMBER = "line-number";
     public static final String LIST = "list";
-    public static final String LOAD_RUN = "load-run-source";
-    public static final String LOAD_STEP = "load-step-into-source";
+    public static final String LOAD_SOURCE = "load-source";
     public static final String METHOD_NAME = "method-name";
     public static final String OP = "op";
     public static final String OPTION = "option";
@@ -85,6 +102,10 @@
     public static final String REPEAT = "repeat";
     public static final String SET = "set";
     public static final String SET_BREAK_CONDITION = "set-breakpoint-condition";
+    public static final String SET_LANGUAGE = "set-language";
+    public static final String SLOT_ID = "slot-identifier";
+    public static final String SLOT_INDEX = "slot-index";
+    public static final String SLOT_VALUE = "slot-value";
     public static final String SOURCE_LINE_TEXT = "source-line-text";
     public static final String SOURCE_LOCATION = "source-location";
     public static final String SOURCE_NAME = "source-name";
@@ -99,6 +120,7 @@
     public static final String SUBTREE = "subtree";
     public static final String SUCCEEDED = "succeeded";
     public static final String TOPIC = "topic";
+    public static final String TRUE = "true";
     public static final String TRUFFLE = "truffle";
     public static final String TRUFFLE_AST = "truffle-ast";
     public static final String TRUFFLE_NODE = "truffle-node";
@@ -107,6 +129,8 @@
     public static final String UP = "up";
     public static final String VALUE = "value";
     public static final String WARNINGS = "warnings";
+    public static final String WELCOME_MESSAGE = "welcome-message";
+    public static final String[] ARG_NAMES = new String[]{ARG0, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8, ARG9};
     private final Map<String, String> map;
 
     /**
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLServer.java	Fri Dec 04 14:32:24 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package com.oracle.truffle.tools.debug.shell;
-
-import com.oracle.truffle.api.debug.Breakpoint;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-/**
- * The server side of a simple message-based protocol for a possibly remote language
- * Read-Eval-Print-Loop.
- */
-public abstract class REPLServer {
-
-    /**
-     * Starts up a server; status returned in a message.
-     */
-    public abstract REPLMessage start();
-
-    /**
-     * Ask the server to handle a request. Return a non-empty array of messages to simulate remote
-     * operation where the protocol has possibly multiple messages being returned asynchronously in
-     * response to each request.
-     */
-    public abstract REPLMessage[] receive(REPLMessage request);
-
-    private int breakpointCounter;
-    private Map<Breakpoint, Integer> breakpoints = new WeakHashMap<>();
-
-    protected final synchronized void registerBreakpoint(Breakpoint breakpoint) {
-        breakpoints.put(breakpoint, breakpointCounter++);
-    }
-
-    protected final synchronized Breakpoint findBreakpoint(int id) {
-        for (Map.Entry<Breakpoint, Integer> entrySet : breakpoints.entrySet()) {
-            if (id == entrySet.getValue()) {
-                return entrySet.getKey();
-            }
-        }
-        return null;
-    }
-
-    protected final synchronized int getBreakpointID(Breakpoint breakpoint) {
-        final Integer id = breakpoints.get(breakpoint);
-        return id == null ? -1 : id;
-    }
-
-}
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLClientContext.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLClientContext.java	Mon Dec 07 21:17:46 2015 -0800
@@ -24,14 +24,15 @@
  */
 package com.oracle.truffle.tools.debug.shell.client;
 
+import java.util.List;
+
 import com.oracle.truffle.api.source.Source;
-import com.oracle.truffle.tools.debug.shell.REPLServer;
-import java.util.List;
+import com.oracle.truffle.tools.debug.shell.server.REPLServer;
 
 /**
  * Client context for interaction with a program halted by the {@link REPLServer}.
  */
-public interface REPLClientContext {
+interface REPLClientContext {
 
     /**
      * The source code halted in this context.
@@ -96,4 +97,9 @@
      */
     void displayFailReply(String message);
 
+    /**
+     * Recompute the client's command line prompt.
+     */
+    void updatePrompt();
+
 }
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLRemoteCommand.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLRemoteCommand.java	Mon Dec 07 21:17:46 2015 -0800
@@ -26,6 +26,7 @@
 
 import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.tools.debug.shell.REPLMessage;
+
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
@@ -200,6 +201,71 @@
         }
     };
 
+    public static final REPLRemoteCommand CALL_CMD = new REPLRemoteCommand("call", null, "call a method/function") {
+
+        private final String[] help = {"call <name> <args>: calls a function by name"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (args.length == 1) {
+                context.displayFailReply("name to call not speciified");
+                return null;
+            }
+            final int maxArgs = REPLMessage.ARG_NAMES.length;
+            if (args.length > maxArgs + 2) {
+                context.displayFailReply("too many call arguments; no more than " + maxArgs + " supported");
+                return null;
+            }
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.CALL);
+            request.put(REPLMessage.CALL_NAME, args[1]);
+            for (int argIn = 2, argOut = 0; argIn < args.length; argIn++, argOut++) {
+                request.put(REPLMessage.ARG_NAMES[argOut], args[argIn]);
+            }
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            REPLMessage firstReply = replies[0];
+            if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
+                final String result = firstReply.get(REPLMessage.DISPLAY_MSG);
+                context.displayFailReply(result != null ? result : firstReply.toString());
+            } else {
+                context.displayReply(firstReply.get(REPLMessage.VALUE));
+            }
+        }
+    };
+
+    public static final REPLRemoteCommand CALL_STEP_INTO_CMD = new REPLRemoteCommand("call-step-into", "calls", "Call a method/function and step into") {
+
+        private final String[] help = {"call <name> <args>: calls function by name and step into"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            final REPLMessage request = CALL_CMD.createRequest(context, args);
+            if (request != null) {
+                request.put(REPLMessage.STEP_INTO, REPLMessage.TRUE);
+            }
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            CALL_CMD.processReply(context, replies);
+        }
+    };
+
     public static final REPLRemoteCommand CLEAR_BREAK_CMD = new REPLRemoteCommand("clear", null, "Clear a breakpoint") {
 
         private final String[] help = {"clear <n>: clear breakpoint number <n>"};
@@ -436,6 +502,59 @@
         }
     };
 
+    public static final REPLRemoteCommand EVAL_CMD = new REPLRemoteCommand("eval", null, "Evaluate a string, in context of the current frame if any") {
+
+        private int evalCounter = 0;
+
+        private final String[] help = {"eval <string>: evaluate <string> in context of the current frame if any"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (args.length > 1) {
+                final String code = args[1];
+                if (!code.isEmpty()) {
+                    // Create a fake entry in the file maps and cache, based on this unique name
+                    final String fakeFileName = "<eval" + ++evalCounter + ">";
+                    Source.fromNamedText(fakeFileName, code);
+                    final REPLMessage request = new REPLMessage();
+                    request.put(REPLMessage.OP, REPLMessage.EVAL);
+                    request.put(REPLMessage.CODE, code);
+                    request.put(REPLMessage.SOURCE_NAME, fakeFileName);
+                    if (context.level() > 0) {
+                        // Specify a requested execution context, if one exists; otherwise top level
+                        request.put(REPLMessage.FRAME_NUMBER, Integer.toString(context.getSelectedFrameNumber()));
+                    }
+                    return request;
+                }
+            }
+            return null;
+        }
+    };
+
+    public static final REPLRemoteCommand EVAL_STEP_INTO_CMD = new REPLRemoteCommand("eval-step-into", "evals", "Evaluate and step into a string, in context of the current frame if any") {
+
+        private final String[] help = {"eval-step-into <string>: evaluate <string> in context of the current frame if any, and step into"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            final REPLMessage request = EVAL_CMD.createRequest(context, args);
+            if (request != null) {
+                request.put(REPLMessage.STEP_INTO, REPLMessage.TRUE);
+            }
+            return request;
+        }
+    };
+
     public static final REPLRemoteCommand FRAME_CMD = new REPLRemoteCommand("frame", null, "Display a stack frame") {
 
         private final String[] help = {"frame : display currently selected frame", "frame <n> : display frame <n>"};
@@ -479,9 +598,11 @@
                 context.selectFrameNumber(frameNumber);
                 context.displayReply("Frame " + frameNumber + ":");
                 for (REPLMessage message : replies) {
-                    for (String line : message.get(REPLMessage.DISPLAY_MSG).split("\n")) {
-                        context.displayInfo(line);
-                    }
+                    final StringBuilder sb = new StringBuilder();
+                    sb.append("#" + message.get(REPLMessage.SLOT_INDEX) + ": ");
+                    sb.append(message.get(REPLMessage.SLOT_ID) + " = ");
+                    sb.append(message.get(REPLMessage.SLOT_VALUE));
+                    context.displayInfo(sb.toString());
                 }
             }
         }
@@ -510,7 +631,14 @@
         }
     };
 
-    public static final REPLRemoteCommand LOAD_RUN_CMD = new REPLRemoteCommand("load-run", "loadr", "Load and run a source") {
+    public static final REPLRemoteCommand LOAD_CMD = new REPLRemoteCommand("load", null, "Load source") {
+
+        private final String[] help = new String[]{"load : load currently selected file source", "load <file name> : load file <file name>"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
 
         @Override
         public REPLMessage createRequest(REPLClientContext context, String[] args) {
@@ -530,43 +658,66 @@
                 }
             }
             final REPLMessage request = new REPLMessage();
-            request.put(REPLMessage.OP, REPLMessage.LOAD_RUN);
+            request.put(REPLMessage.OP, REPLMessage.LOAD_SOURCE);
             request.put(REPLMessage.SOURCE_NAME, runSource.getPath());
             return request;
         }
     };
 
-    public static final REPLRemoteCommand LOAD_STEP_CMD = new REPLRemoteCommand("load-step", "loads", "Load and step into a source") {
+    public static final REPLRemoteCommand LOAD_STEP_INTO_CMD = new REPLRemoteCommand("load-step-into", "loads", "Load source and step in") {
+
+        private final String[] help = new String[]{"load : load currently selected file source and step in", "load <file name> : load file <file name> and step in"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
 
         @Override
         public REPLMessage createRequest(REPLClientContext context, String[] args) {
-            Source runSource = null;
-            if (args.length == 1) {
-                runSource = context.getSelectedSource();
-                if (runSource == null) {
-                    context.displayFailReply("No file selected");
-                    return null;
-                }
-            } else {
-                try {
-                    runSource = Source.fromFileName(args[1]);
-                } catch (IOException e) {
-                    context.displayFailReply("Can't find file: " + args[1]);
-                    return null;
-                }
+            final REPLMessage request = LOAD_CMD.createRequest(context, args);
+            if (request != null) {
+                request.put(REPLMessage.STEP_INTO, REPLMessage.TRUE);
             }
-            final REPLMessage request = new REPLMessage();
-            request.put(REPLMessage.OP, REPLMessage.LOAD_STEP);
-            request.put(REPLMessage.SOURCE_NAME, runSource.getPath());
             return request;
         }
     };
 
-    public static final REPLRemoteCommand STEP_INTO_CMD = new REPLRemoteCommand("step", "s", "(StepInto) next statement, going into functions.") {
+    public static final REPLRemoteCommand SET_LANG_CMD = new REPLRemoteCommand("language", "lang", "Set current language") {
+
+        private final String[] help = new String[]{"lang <language short name>:  set default language, \"info lang\" displays choices"};
 
         @Override
         public String[] getHelp() {
-            return new String[]{"step into:  step to next statement (into calls)", "step <n>: step to nth next statement (into calls)"};
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.SET_LANGUAGE);
+            if (args.length > 1) {
+                request.put(REPLMessage.LANG_NAME, args[1]);
+            }
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            context.updatePrompt();
+            super.processReply(context, replies);
+        }
+
+    };
+
+    public static final REPLRemoteCommand STEP_INTO_CMD = new REPLRemoteCommand("step", "s", "(StepInto) next statement, going into functions.") {
+
+        private final String[] help = new String[]{"step into:  step to next statement (into calls)", "step <n>: step to nth next statement (into calls)"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
         }
 
         @Override
@@ -625,9 +776,12 @@
 
     public static final REPLRemoteCommand STEP_OVER_CMD = new REPLRemoteCommand("next", "n", "(StepOver) execute next line of code, not into functions.") {
 
+        private final String[] help = new String[]{"next:  (StepOver) execute next line of code, not into functions.",
+                        "next <n>: (StepOver) execute to nth next statement (not counting into functions)"};
+
         @Override
         public String[] getHelp() {
-            return new String[]{"next:  (StepOver) execute next line of code, not into functions.", "next <n>: (StepOver) execute to nth next statement (not counting into functions)"};
+            return help;
         }
 
         @Override
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java	Mon Dec 07 21:17:46 2015 -0800
@@ -24,10 +24,12 @@
  */
 package com.oracle.truffle.tools.debug.shell.client;
 
+import com.oracle.truffle.api.instrument.QuitException;
 import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.tools.debug.shell.REPLClient;
 import com.oracle.truffle.tools.debug.shell.REPLMessage;
-import com.oracle.truffle.tools.debug.shell.REPLServer;
+import com.oracle.truffle.tools.debug.shell.server.REPLServer;
+
 import java.io.IOException;
 import java.io.PrintStream;
 import java.util.ArrayList;
@@ -39,6 +41,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.TreeSet;
+
 import jline.console.ConsoleReader;
 
 /**
@@ -79,6 +82,8 @@
  */
 public class SimpleREPLClient implements REPLClient {
 
+    // TODO (mlvdv) Temporarily in hybrid mode; will work either single language or multi (sort of)
+
     private static final String REPLY_PREFIX = "==> ";
     private static final String FAIL_PREFIX = "**> ";
     private static final String WARNING_PREFIX = "!!> ";
@@ -89,10 +94,8 @@
     static final String CODE_LINE_FORMAT = "    %3d  %s\n";
     static final String CODE_LINE_BREAK_FORMAT = "--> %3d  %s\n";
 
-    private static final String STACK_FRAME_FORMAT = "    %3d: at %s in %s    line =\"%s\"\n";
-    private static final String STACK_FRAME_SELECTED_FORMAT = "==> %3d: at %s in %s    line =\"%s\"\n";
-
-    private final String languageName;
+    private static final String STACK_FRAME_FORMAT = "    %3d: at %s in %s    %s\n";
+    private static final String STACK_FRAME_SELECTED_FORMAT = "==> %3d: at %s in %s    %s\n";
 
     // Top level commands
     private final Map<String, REPLCommand> commandMap = new HashMap<>();
@@ -105,13 +108,6 @@
     // Current local context
     ClientContextImpl clientContext;
 
-    // Cheating for the prototype; prototype startup now happens from the language server.
-    // So this isn't used.
-    public static void main(String[] args) {
-        final SimpleREPLClient repl = new SimpleREPLClient(null, null);
-        repl.start();
-    }
-
     private final ConsoleReader reader;
 
     private final PrintStream writer;
@@ -147,8 +143,7 @@
      */
     private Source selectedSource = null;
 
-    public SimpleREPLClient(String languageName, REPLServer replServer) {
-        this.languageName = languageName;
+    public SimpleREPLClient(REPLServer replServer) {
         this.replServer = replServer;
         this.writer = System.out;
         try {
@@ -162,6 +157,8 @@
         addCommand(REPLRemoteCommand.BREAK_AT_LINE_ONCE_CMD);
         addCommand(REPLRemoteCommand.BREAK_AT_THROW_CMD);
         addCommand(REPLRemoteCommand.BREAK_AT_THROW_ONCE_CMD);
+        addCommand(REPLRemoteCommand.CALL_CMD);
+        addCommand(REPLRemoteCommand.CALL_STEP_INTO_CMD);
         addCommand(REPLRemoteCommand.CLEAR_BREAK_CMD);
         addCommand(REPLRemoteCommand.CONDITION_BREAK_CMD);
         addCommand(REPLRemoteCommand.CONTINUE_CMD);
@@ -169,17 +166,19 @@
         addCommand(REPLRemoteCommand.DISABLE_CMD);
         addCommand(REPLRemoteCommand.DOWN_CMD);
         addCommand(REPLRemoteCommand.ENABLE_CMD);
-        addCommand(evalCommand);
+        addCommand(REPLRemoteCommand.EVAL_CMD);
+        addCommand(REPLRemoteCommand.EVAL_STEP_INTO_CMD);
         addCommand(fileCommand);
         addCommand(REPLRemoteCommand.FRAME_CMD);
         addCommand(helpCommand);
         addCommand(infoCommand);
         addCommand(REPLRemoteCommand.KILL_CMD);
         addCommand(listCommand);
-        addCommand(REPLRemoteCommand.LOAD_RUN_CMD);
-        addCommand(REPLRemoteCommand.LOAD_STEP_CMD);
+        addCommand(REPLRemoteCommand.LOAD_CMD);
+        addCommand(REPLRemoteCommand.LOAD_STEP_INTO_CMD);
         addCommand(quitCommand);
         addCommand(setCommand);
+        addCommand(REPLRemoteCommand.SET_LANG_CMD);
         addCommand(REPLRemoteCommand.STEP_INTO_CMD);
         addCommand(REPLRemoteCommand.STEP_OUT_CMD);
         addCommand(REPLRemoteCommand.STEP_OVER_CMD);
@@ -207,21 +206,24 @@
 
     public void start() {
 
-        REPLMessage startReply = replServer.start();
-
-        if (startReply.get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
-            clientContext.displayFailReply(startReply.get(REPLMessage.DISPLAY_MSG));
-            throw new RuntimeException("Can't start REPL server");
+        this.clientContext = new ClientContextImpl(null, null);
+        try {
+            showWelcome();
+            clientContext.startContextSession();
+        } catch (QuitException ex) {
+            clientContext.displayReply("Goodbye");
         }
+    }
 
-        this.clientContext = new ClientContextImpl(null, null);
-
-        try {
-            clientContext.startSession();
-        } finally {
-            clientContext.displayReply("Goodbye from " + languageName + "/REPL");
+    private void showWelcome() {
+        final REPLMessage request = new REPLMessage(REPLMessage.OP, REPLMessage.INFO);
+        request.put(REPLMessage.TOPIC, REPLMessage.WELCOME_MESSAGE);
+        final REPLMessage[] replies = clientContext.sendToServer(request);
+        if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
+            clientContext.displayReply("Welcome");
+        } else {
+            clientContext.displayReply(replies[0].get(REPLMessage.INFO_VALUE));
         }
-
     }
 
     public void addCommand(REPLCommand replCommand) {
@@ -263,18 +265,26 @@
             this.level = predecessor == null ? 0 : predecessor.level + 1;
 
             if (message != null) {
+                final String sourceName = message.get(REPLMessage.SOURCE_NAME);
                 try {
-                    this.haltedSource = Source.fromFileName(message.get(REPLMessage.SOURCE_NAME));
-                    selectedSource = this.haltedSource;
+                    this.haltedSource = Source.fromFileName(sourceName);
+                } catch (IOException ex) {
+                    final String code = message.get(REPLMessage.SOURCE_TEXT);
+                    if (code != null) {
+                        this.haltedSource = Source.fromText(code, sourceName);
+                    }
+                }
+                if (this.haltedSource != null) {
+                    selectedSource = haltedSource;
                     try {
                         haltedLineNumber = Integer.parseInt(message.get(REPLMessage.LINE_NUMBER));
                     } catch (NumberFormatException e) {
                         haltedLineNumber = 0;
                     }
-                } catch (IOException e1) {
+                } else {
                     this.haltedSource = null;
                     this.haltedLineNumber = 0;
-                    this.unknownSourceName = message.get(REPLMessage.SOURCE_NAME);
+                    this.unknownSourceName = sourceName;
                 }
             }
             updatePrompt();
@@ -289,20 +299,33 @@
             updatePrompt();
         }
 
-        private void updatePrompt() {
+        @Override
+        public void updatePrompt() {
+
+            String languageName = "???";
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.INFO);
+            request.put(REPLMessage.TOPIC, REPLMessage.INFO_CURRENT_LANGUAGE);
+            final REPLMessage[] replies = replServer.receive(request);
+            if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) {
+                languageName = replies[0].get(REPLMessage.LANG_NAME);
+            }
+            final String showLang = languageName == null ? "() " : "( " + languageName + " )";
             if (level == 0) {
                 // 0-level context; no executions halted.
                 if (selectedSource == null) {
-                    currentPrompt = languageName == null ? "() " : "( " + languageName + " ) ";
+                    currentPrompt = showLang + " ";
                 } else {
-                    currentPrompt = "(" + selectedSource.getShortName() + ") ";
+                    currentPrompt = "(" + selectedSource.getShortName() + ") " + showLang + " ";
                 }
             } else if (selectedSource != null && selectedSource != haltedSource) {
                 // User is focusing somewhere else than the current locn; show no line number.
                 final StringBuilder sb = new StringBuilder();
                 sb.append("(<" + Integer.toString(level) + "> ");
                 sb.append(selectedSource.getShortName());
-                sb.append(") ");
+                sb.append(")");
+                sb.append(showLang);
+                sb.append(" ");
                 currentPrompt = sb.toString();
             } else {
                 // Prompt reveals where currently halted.
@@ -312,7 +335,9 @@
                 if (haltedLineNumber > 0) {
                     sb.append(":" + Integer.toString(haltedLineNumber));
                 }
-                sb.append(") ");
+                sb.append(")");
+                sb.append(showLang);
+                sb.append(" ");
                 currentPrompt = sb.toString();
             }
 
@@ -443,7 +468,9 @@
                 for (REPLFrame frame : frameList) {
                     String sourceLineText = frame.sourceLineText();
                     if (sourceLineText == null) {
-                        sourceLineText = "<??>";
+                        sourceLineText = "";
+                    } else {
+                        sourceLineText = "line=\"" + sourceLineText + "\"";
                     }
                     if (frame.index() == selectedFrameNumber) {
                         writer.format(STACK_FRAME_SELECTED_FORMAT, frame.index(), frame.locationDescription(), frame.name(), sourceLineText);
@@ -476,7 +503,7 @@
             writer.println(TRACE_PREFIX + message);
         }
 
-        public void startSession() {
+        private void startContextSession() {
 
             while (true) {
                 try {
@@ -515,15 +542,19 @@
 
                     } else if (command instanceof REPLRemoteCommand) {
                         final REPLRemoteCommand remoteCommand = (REPLRemoteCommand) command;
-
                         final REPLMessage request = remoteCommand.createRequest(clientContext, args);
                         if (request == null) {
                             continue;
                         }
 
                         REPLMessage[] replies = sendToServer(request);
+                        remoteCommand.processReply(clientContext, replies);
 
-                        remoteCommand.processReply(clientContext, replies);
+                        final String path = replies[0].get(REPLMessage.FILE_PATH);
+                        if (path != null && !path.isEmpty()) {
+                            selectSource(path);
+                        }
+
                     } else {
                         assert false; // Should not happen.
                     }
@@ -651,7 +682,7 @@
         }
 
         try {
-            clientContext.startSession();
+            clientContext.startContextSession();
         } finally {
 
             // To continue execution, pop the context and return
@@ -698,33 +729,6 @@
         }
     };
 
-    private final REPLCommand evalCommand = new REPLRemoteCommand("eval", null, "Evaluate a string, in context of the current frame if any") {
-
-        private int evalCounter = 0;
-
-        @Override
-        public REPLMessage createRequest(REPLClientContext context, String[] args) {
-            if (args.length > 1) {
-                final String code = args[1];
-                if (!code.isEmpty()) {
-                    // Create a fake entry in the file maps and cache, based on this unique name
-                    final String fakeFileName = "<eval" + ++evalCounter + ">";
-                    Source.fromNamedText(fakeFileName, code);
-                    final REPLMessage request = new REPLMessage();
-                    request.put(REPLMessage.OP, REPLMessage.EVAL);
-                    request.put(REPLMessage.CODE, code);
-                    request.put(REPLMessage.SOURCE_NAME, fakeFileName);
-                    if (clientContext.level > 0) {
-                        // Specify a requested execution context, if one exists; otherwise top level
-                        request.put(REPLMessage.FRAME_NUMBER, Integer.toString(context.getSelectedFrameNumber()));
-                    }
-                    return request;
-                }
-            }
-            return null;
-        }
-    };
-
     private final REPLCommand fileCommand = new REPLRemoteCommand("file", null, "Set/display current file for viewing") {
 
         final String[] help = {"file:  display current file path", "file <filename>: Set file to be current file for viewing"};
@@ -922,9 +926,9 @@
         }
     };
 
-    private final REPLCommand infoLanguageCommand = new REPLRemoteCommand("language", "lang", "language and implementation details") {
+    private final REPLCommand infoLanguageCommand = new REPLRemoteCommand("languages", "lang", "languages supported") {
 
-        final String[] help = {"info language:  list details about the language implementation"};
+        final String[] help = {"info language:  list details about supported languages"};
 
         @Override
         public String[] getHelp() {
@@ -935,7 +939,7 @@
         public REPLMessage createRequest(REPLClientContext context, String[] args) {
             final REPLMessage request = new REPLMessage();
             request.put(REPLMessage.OP, REPLMessage.INFO);
-            request.put(REPLMessage.TOPIC, REPLMessage.LANGUAGE);
+            request.put(REPLMessage.TOPIC, REPLMessage.INFO_SUPPORTED_LANGUAGES);
             return request;
         }
 
@@ -944,12 +948,12 @@
             if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
                 clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG));
             } else {
-                clientContext.displayReply("Language info:");
+                clientContext.displayReply("Languages supported:");
                 for (REPLMessage message : replies) {
                     final StringBuilder sb = new StringBuilder();
-                    sb.append(message.get(REPLMessage.INFO_KEY));
-                    sb.append(": ");
-                    sb.append(message.get(REPLMessage.INFO_VALUE));
+                    sb.append(message.get(REPLMessage.LANG_NAME));
+                    sb.append(" ver. ");
+                    sb.append(message.get(REPLMessage.LANG_VER));
                     clientContext.displayInfo(sb.toString());
                 }
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/package-info.java	Mon Dec 07 21:17:46 2015 -0800
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ @ApiInfo(
+ group="To Review"
+ )
+ */
+
+/**
+ * This package contains <strong>REPL*</strong>: an experimental framework for
+ * building a <em>language-agnostic</em> command-line oriented debugger that:
+ * <ul>
+ * <li>works with every Truffle-implemented language "out of the box",
+ * i.e. requiring minimal additional support by language implementors;</li>
+ * <li>works simultaneously, without special configuration, for all Truffle language implementations
+ * available to it; and</li>
+ * <li>demonstrates Truffle language-interopability by debugging seamlessly
+ * across Truffle <em>cross-language</em> calls.</li>
+ * </ul>
+ * <p>
+ * <h4>Goals for building <strong>REPL*</strong></h4>
+ * <ol>
+ * <li>Exercise and test <em>in action</em> the built-in
+ * {@linkplain com.oracle.truffle.api.debug.Debugger} debugging support services
+ * provided as part of the Truffle API, which in turn relies on Truffle
+ * {@linkplain com.oracle.truffle.api.instrument.Instrumenter Instrumentation}.</li>
+ * <li>Emulates a client/server architecture to demonstrate that
+ * language-agnostic debugging can be implemented over wire protocols.  Wire
+ * protocol communication between client and server is <em>partially</em> emulated
+ * by passing messages expressed as textual key-value pairs. The emulation is
+ * <em>partial</em> because both run interleaved on a single JVM thread, with some
+ * sharing of resources.</li>
+ * <li>Provide a working debugger that is always available during development
+ * of new Truffle language implementations.</li>
+ * <li>Provide a working debugger with extra support for Truffle language
+ * development, in particular the ability to inspect the current structure
+ * of the Truffle AST around a halted location.</li>
+ * </ol>
+ * <h4>Command Set</h4>
+ * The Command Line Interface (CLI) for <strong>REPL*</strong> is based as
+ * much as possible on the CLI for the
+ * <a href="http://www.gnu.org/software/gdb/documentation/">GDB Debugger.</a>
+ * <h4>REPL* Functionality</h4>
+ * Basic navigation:
+ * <ul>
+ * <li>StepIn (n times)</li>
+ * <li>StepOut (n times)</li>
+ * <li>StepOver (n times)</li>
+ * </ul>
+ * Execution:
+ * <ul>
+ * <li>Load a file source</li>
+ * <li>Call a defined symbol</li>
+ * </ul>
+ * Stack:
+ * <ul>
+ * <li>List frames in current execution stack</li>
+ * <li>Select a frame</li>
+ * <li>Display selected frame contents</li>
+ * <li>Move frame selection up/down</li>
+ * </ul>
+ * Evaluate:
+ * <ul>
+ * <li>Evaluate a Language string in halted context</li>
+ * <li>Evaluate a Language string in selected frame</li>
+ * </ul>
+ * Breakpoints:
+ * <ul>
+ * <li>Set/create on a specified line</li>
+ * <li>Set/create on any throw (before exception created)</li>
+ * <li>Enable / Disable</li>
+ * <li>One-shot (once only)</li>
+ * <li>Get <em>Hit</em> count</li>
+ * <li>Set <em>Ignore</em> count</li>
+ * <li>Unset/dispose</li>
+ * <li>Get all breakpoints</li>
+ * <li>Find a breakpoint by UID</li>
+ * <li>Set/clear the condition on a breakpoint</li>
+ * </ul>
+ * Others:
+ * <ul>
+ * <li>Display halted location in source</li>
+ * <li>Nested execution
+ * <li>Help</li>
+ * <li>Info displays</li>
+ * <li>Set/display options</li>
+ * <li>Display Truffle AST structure</li>
+ * </ul>
+ */
+package com.oracle.truffle.tools.debug.shell;
+
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/package.html	Fri Dec 04 14:32:24 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-<!DOCTYPE html>
-<!--
-
-Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
-DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
-
-This code is free software; you can redistribute it and/or modify it
-under the terms of the GNU General Public License version 2 only, as
-published by the Free Software Foundation.  Oracle designates this
-particular file as subject to the "Classpath" exception as provided
-by Oracle in the LICENSE file that accompanied this code.
-
-This code is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-version 2 for more details (a copy is included in the LICENSE file that
-accompanied this code).
-
-You should have received a copy of the GNU General Public License version
-2 along with this work; if not, write to the Free Software Foundation,
-Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
-or visit www.oracle.com if you need additional information or have any
-questions.
--->
-<html>
-<body>
- This package contains an experimental framework for building simple command-line oriented
- debuggers that work with Truffle-implemented languages; it is used mainly for testing Truffle's
- built-in , which actually provides the
- debugging services.
- <p>
- Truffle debugging is made possible by the general purpose Instrumentation Framework built into
- the Truffle platform. Some online documentation for the Instrumentation Framework is available
- online:
- <q> <a href="https://wiki.openjdk.java.net/display/Graal/Instrumentation+API">https://wiki.
- openjdk.java.net/display/Graal/Instrumentation+API</a> </q>
- <p>
- Building one of these command line debuggers requires creating language-specific instances of:
- <ol>
- <li>DebugEngine, noting that this instance also
- depends on related services provided by the language implementation,</li>
- <li>{@link com.oracle.truffle.tools.debug.shell.REPLServer}, best accomplished by copying the
- implementation for Truffle's demonstration language "Simple" (a.k.a. "SL").</li>
- </ol>
-
- <strong>Disclaimer: </strong> although these command line debuggers are useful, they are not
- intended, and will not be maintained as, fully functioning debuggers. They should be considered
- valuable tools for the maintainers of the
- DebugEngine, as well as for Truffle language
- implementors for whom concurrent access to any kind debugging services can be quite helpful.
- <p>
- <strong>Note:</strong> Both the functionality and API for this package are under active
- development.
- <p>
-    </body>
-</html>
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/FrameDebugDescription.java	Fri Dec 04 14:32:24 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package com.oracle.truffle.tools.debug.shell.server;
-
-import com.oracle.truffle.api.frame.Frame;
-import com.oracle.truffle.api.frame.FrameInstance;
-import com.oracle.truffle.api.nodes.Node;
-
-public final class FrameDebugDescription {
-    private final int index;
-    private final Node node;
-    private final FrameInstance frameInstance;
-
-    FrameDebugDescription(int index, Node node, FrameInstance frameInstance) {
-        this.index = index;
-        this.node = node;
-        this.frameInstance = frameInstance;
-    }
-
-    /**
-     * Position in the current stack: {@code 0} at the top.
-     */
-    public int index() {
-        return index;
-    }
-
-    /**
-     * AST location.
-     */
-    public Node node() {
-        return node;
-    }
-
-    /**
-     * Access to the Truffle {@link Frame}.
-     */
-    public FrameInstance frameInstance() {
-        return frameInstance;
-    }
-}
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLHandler.java	Fri Dec 04 14:32:24 2015 +0100
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLHandler.java	Mon Dec 07 21:17:46 2015 -0800
@@ -24,11 +24,15 @@
  */
 package com.oracle.truffle.tools.debug.shell.server;
 
-import com.oracle.truffle.api.debug.Breakpoint;
-import com.oracle.truffle.api.frame.Frame;
-import com.oracle.truffle.api.frame.FrameDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
 import com.oracle.truffle.api.frame.FrameInstance;
+import com.oracle.truffle.api.frame.FrameInstance.FrameAccess;
 import com.oracle.truffle.api.frame.FrameSlot;
+import com.oracle.truffle.api.frame.MaterializedFrame;
 import com.oracle.truffle.api.instrument.ASTPrinter;
 import com.oracle.truffle.api.instrument.KillException;
 import com.oracle.truffle.api.instrument.QuitException;
@@ -37,10 +41,10 @@
 import com.oracle.truffle.api.nodes.Node;
 import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.api.vm.PolyglotEngine.Language;
 import com.oracle.truffle.tools.debug.shell.REPLMessage;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
+import com.oracle.truffle.tools.debug.shell.server.REPLServer.BreakpointInfo;
+import com.oracle.truffle.tools.debug.shell.server.REPLServer.Context;
 
 /**
  * Server-side REPL implementation of an {@linkplain REPLMessage "op"}.
@@ -61,30 +65,23 @@
     /**
      * Gets the "op" implemented by this handler.
      */
-    public final String getOp() {
+    final String getOp() {
         return op;
     }
 
     /**
      * Passes a request to this handler.
      */
-    public abstract REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext);
+    abstract REPLMessage[] receive(REPLMessage request, REPLServer replServer);
 
     /**
      * Creates skeleton for a reply message that identifies the operation currently being handled.
      */
-    protected final REPLMessage createReply() {
+    REPLMessage createReply() {
         return new REPLMessage(REPLMessage.OP, op);
     }
 
     /**
-     * Creates skeleton for a reply message that identifies a specified operation.
-     */
-    protected static final REPLMessage createReply(String opString) {
-        return new REPLMessage(REPLMessage.OP, opString);
-    }
-
-    /**
      * Completes a reply, reporting and explaining successful handling.
      */
     protected static final REPLMessage[] finishReplySucceeded(REPLMessage reply, String explanation) {
@@ -104,25 +101,32 @@
         return replies;
     }
 
-    static final REPLMessage createBreakpointInfoMessage(Breakpoint breakpoint, REPLServerContext serverContext) {
+    protected static final REPLMessage[] finishReplyFailed(REPLMessage reply, Exception ex) {
+        reply.put(REPLMessage.STATUS, REPLMessage.FAILED);
+        String message = ex.getMessage();
+        reply.put(REPLMessage.DISPLAY_MSG, message == null ? ex.getClass().getSimpleName() : message);
+        final REPLMessage[] replies = new REPLMessage[]{reply};
+        return replies;
+    }
+
+    protected static final REPLMessage createBreakpointInfoMessage(BreakpointInfo info) {
         final REPLMessage infoMessage = new REPLMessage(REPLMessage.OP, REPLMessage.BREAKPOINT_INFO);
-        infoMessage.put(REPLMessage.BREAKPOINT_ID, Integer.toString(serverContext.getBreakpointID(breakpoint)));
-        infoMessage.put(REPLMessage.BREAKPOINT_HIT_COUNT, Integer.toString(breakpoint.getHitCount()));
-        infoMessage.put(REPLMessage.BREAKPOINT_IGNORE_COUNT, Integer.toString(breakpoint.getIgnoreCount()));
-        infoMessage.put(REPLMessage.INFO_VALUE, breakpoint.getLocationDescription().toString());
-        if (breakpoint.getCondition() != null) {
-            infoMessage.put(REPLMessage.BREAKPOINT_CONDITION, breakpoint.getCondition().getCode());
+        infoMessage.put(REPLMessage.BREAKPOINT_ID, Integer.toString(info.getID()));
+        infoMessage.put(REPLMessage.BREAKPOINT_STATE, info.describeState());
+        infoMessage.put(REPLMessage.BREAKPOINT_HIT_COUNT, Integer.toString(info.getHitCount()));
+        infoMessage.put(REPLMessage.BREAKPOINT_IGNORE_COUNT, Integer.toString(info.getIgnoreCount()));
+        infoMessage.put(REPLMessage.INFO_VALUE, info.describeLocation());
+        if (info.getCondition() != null) {
+            infoMessage.put(REPLMessage.BREAKPOINT_CONDITION, info.getCondition());
         }
         infoMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
         return infoMessage;
     }
 
-    protected static REPLMessage createFrameInfoMessage(final REPLServerContext serverContext, FrameDebugDescription frame) {
-        final Visualizer visualizer = serverContext.getVisualizer();
+    protected static final REPLMessage createFrameInfoMessage(final REPLServer replServer, int number, Node node) {
+        final Visualizer visualizer = replServer.getVisualizer();
         final REPLMessage infoMessage = new REPLMessage(REPLMessage.OP, REPLMessage.FRAME_INFO);
-        infoMessage.put(REPLMessage.FRAME_NUMBER, Integer.toString(frame.index()));
-        final Node node = frame.node();
-
+        infoMessage.put(REPLMessage.FRAME_NUMBER, Integer.toString(number));
         infoMessage.put(REPLMessage.SOURCE_LOCATION, visualizer.displaySourceLocation(node));
         infoMessage.put(REPLMessage.METHOD_NAME, visualizer.displayMethodName(node));
 
@@ -131,7 +135,7 @@
             if (section == null) {
                 section = node.getEncapsulatingSourceSection();
             }
-            if (section != null) {
+            if (section != null && section.getSource() != null) {
                 infoMessage.put(REPLMessage.FILE_PATH, section.getSource().getPath());
                 infoMessage.put(REPLMessage.LINE_NUMBER, Integer.toString(section.getStartLine()));
                 infoMessage.put(REPLMessage.SOURCE_LINE_TEXT, section.getSource().getCode(section.getStartLine()));
@@ -144,23 +148,46 @@
     public static final REPLHandler BACKTRACE_HANDLER = new REPLHandler(REPLMessage.BACKTRACE) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
-            final REPLMessage reply = createReply();
-            final ArrayList<REPLMessage> frameMessages = new ArrayList<>();
-            for (FrameDebugDescription frame : serverContext.getStack()) {
-                frameMessages.add(createFrameInfoMessage(serverContext, frame));
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
+            final Visualizer visualizer = replServer.getVisualizer();
+            final ArrayList<REPLMessage> replies = new ArrayList<>();
+            final Context currentContext = replServer.getCurrentContext();
+            final List<FrameInstance> stack = currentContext.getStack();
+            replies.add(btMessage(0, currentContext.getNode(), visualizer));
+            for (int i = 1; i <= stack.size(); i++) {
+                replies.add(btMessage(i, stack.get(i - 1).getCallNode(), visualizer));
             }
-            if (frameMessages.size() > 0) {
-                return frameMessages.toArray(new REPLMessage[0]);
+            if (replies.size() > 0) {
+                return replies.toArray(new REPLMessage[0]);
             }
-            return finishReplyFailed(reply, "No stack");
+            return finishReplyFailed(new REPLMessage(REPLMessage.OP, REPLMessage.BACKTRACE), "No stack");
         }
     };
 
+    private static REPLMessage btMessage(int index, Node node, Visualizer visualizer) {
+        final REPLMessage btMessage = new REPLMessage(REPLMessage.OP, REPLMessage.BACKTRACE);
+        btMessage.put(REPLMessage.FRAME_NUMBER, Integer.toString(index));
+        if (node != null) {
+            btMessage.put(REPLMessage.SOURCE_LOCATION, visualizer.displaySourceLocation(node));
+            btMessage.put(REPLMessage.METHOD_NAME, visualizer.displayMethodName(node));
+            SourceSection section = node.getSourceSection();
+            if (section == null) {
+                section = node.getEncapsulatingSourceSection();
+            }
+            if (section != null && section.getSource() != null) {
+                btMessage.put(REPLMessage.FILE_PATH, section.getSource().getPath());
+                btMessage.put(REPLMessage.LINE_NUMBER, Integer.toString(section.getStartLine()));
+                btMessage.put(REPLMessage.SOURCE_LINE_TEXT, section.getSource().getCode(section.getStartLine()));
+            }
+            btMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
+        }
+        return btMessage;
+    }
+
     public static final REPLHandler BREAK_AT_LINE_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_LINE) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
             final String path = request.get(REPLMessage.FILE_PATH);
             final String fileName = request.get(REPLMessage.SOURCE_NAME);
@@ -169,12 +196,12 @@
             try {
                 source = Source.fromFileName(lookupFile, true);
             } catch (Exception ex) {
-                return finishReplyFailed(reply, ex.getMessage());
+                return finishReplyFailed(reply, ex);
             }
             if (source == null) {
                 return finishReplyFailed(reply, fileName + " not found");
             }
-            Integer lineNumber = request.getIntValue(REPLMessage.LINE_NUMBER);
+            final Integer lineNumber = request.getIntValue(REPLMessage.LINE_NUMBER);
             if (lineNumber == null) {
                 return finishReplyFailed(reply, "missing line number");
             }
@@ -182,16 +209,10 @@
             if (ignoreCount == null) {
                 ignoreCount = 0;
             }
-            Breakpoint breakpoint;
-            try {
-                breakpoint = serverContext.db().setLineBreakpoint(DEFAULT_IGNORE_COUNT, source.createLineLocation(lineNumber), false);
-                serverContext.registerBreakpoint(breakpoint);
-            } catch (Exception ex) {
-                return finishReplyFailed(reply, ex.getMessage());
-            }
+            final BreakpointInfo breakpointInfo = replServer.setLineBreakpoint(DEFAULT_IGNORE_COUNT, source.createLineLocation(lineNumber), false);
             reply.put(REPLMessage.SOURCE_NAME, fileName);
             reply.put(REPLMessage.FILE_PATH, source.getPath());
-            reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(serverContext.getBreakpointID(breakpoint)));
+            reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointInfo.getID()));
             reply.put(REPLMessage.LINE_NUMBER, Integer.toString(lineNumber));
             reply.put(REPLMessage.BREAKPOINT_IGNORE_COUNT, ignoreCount.toString());
             return finishReplySucceeded(reply, "Breakpoint set");
@@ -201,7 +222,7 @@
     public static final REPLHandler BREAK_AT_LINE_ONCE_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_LINE_ONCE) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
             final String path = request.get(REPLMessage.FILE_PATH);
             final String fileName = request.get(REPLMessage.SOURCE_NAME);
@@ -210,23 +231,19 @@
             try {
                 source = Source.fromFileName(lookupFile, true);
             } catch (Exception ex) {
-                return finishReplyFailed(reply, ex.getMessage());
+                return finishReplyFailed(reply, ex);
             }
             if (source == null) {
                 return finishReplyFailed(reply, fileName + " not found");
             }
-            Integer lineNumber = request.getIntValue(REPLMessage.LINE_NUMBER);
+            final Integer lineNumber = request.getIntValue(REPLMessage.LINE_NUMBER);
             if (lineNumber == null) {
                 return finishReplyFailed(reply, "missing line number");
             }
-            try {
-                Breakpoint b = serverContext.db().setLineBreakpoint(DEFAULT_IGNORE_COUNT, source.createLineLocation(lineNumber), true);
-                serverContext.registerBreakpoint(b);
-            } catch (Exception ex) {
-                return finishReplyFailed(reply, ex.getMessage());
-            }
+            final BreakpointInfo breakpointInfo = replServer.setLineBreakpoint(DEFAULT_IGNORE_COUNT, source.createLineLocation(lineNumber), true);
             reply.put(REPLMessage.SOURCE_NAME, fileName);
             reply.put(REPLMessage.FILE_PATH, source.getPath());
+            reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointInfo.getID()));
             reply.put(REPLMessage.LINE_NUMBER, Integer.toString(lineNumber));
             return finishReplySucceeded(reply, "One-shot line breakpoint set");
         }
@@ -235,14 +252,13 @@
     public static final REPLHandler BREAK_AT_THROW_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_THROW) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
             try {
-                Breakpoint b = serverContext.db().setTagBreakpoint(DEFAULT_IGNORE_COUNT, StandardSyntaxTag.THROW, false);
-                serverContext.registerBreakpoint(b);
+                replServer.setTagBreakpoint(DEFAULT_IGNORE_COUNT, StandardSyntaxTag.THROW, false);
                 return finishReplySucceeded(reply, "Breakpoint at any throw set");
             } catch (Exception ex) {
-                return finishReplyFailed(reply, ex.getMessage());
+                return finishReplyFailed(reply, ex);
             }
         }
     };
@@ -250,13 +266,13 @@
     public static final REPLHandler BREAK_AT_THROW_ONCE_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_THROW_ONCE) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
             try {
-                serverContext.db().setTagBreakpoint(DEFAULT_IGNORE_COUNT, StandardSyntaxTag.THROW, true);
+                replServer.setTagBreakpoint(DEFAULT_IGNORE_COUNT, StandardSyntaxTag.THROW, true);
                 return finishReplySucceeded(reply, "One-shot breakpoint at any throw set");
             } catch (Exception ex) {
-                return finishReplyFailed(reply, ex.getMessage());
+                return finishReplyFailed(reply, ex);
             }
         }
     };
@@ -264,11 +280,11 @@
     public static final REPLHandler BREAKPOINT_INFO_HANDLER = new REPLHandler(REPLMessage.BREAKPOINT_INFO) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
             final ArrayList<REPLMessage> infoMessages = new ArrayList<>();
-            for (Breakpoint breakpoint : serverContext.db().getBreakpoints()) {
-                infoMessages.add(createBreakpointInfoMessage(breakpoint, serverContext));
+            for (BreakpointInfo breakpointInfo : replServer.getBreakpoints()) {
+                infoMessages.add(createBreakpointInfoMessage(breakpointInfo));
             }
             if (infoMessages.size() > 0) {
                 return infoMessages.toArray(new REPLMessage[0]);
@@ -277,20 +293,52 @@
         }
     };
 
+    public static final REPLHandler CALL_HANDLER = new REPLHandler(REPLMessage.CALL) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
+            final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.CALL);
+            final String callName = request.get(REPLMessage.CALL_NAME);
+            if (callName == null) {
+                return finishReplyFailed(reply, "no name specified");
+            }
+            final ArrayList<String> argList = new ArrayList<>();
+            for (int argCount = 0; argCount < REPLMessage.ARG_NAMES.length; argCount++) {
+                final String arg = request.get(REPLMessage.ARG_NAMES[argCount]);
+                if (arg == null) {
+                    break;
+                }
+                argList.add(arg);
+            }
+            final boolean stepInto = REPLMessage.TRUE.equals(request.get(REPLMessage.STEP_INTO));
+            try {
+                final Object result = replServer.getCurrentContext().call(callName, stepInto, argList);
+                reply.put(REPLMessage.VALUE, result == null ? "<void>" : result.toString());
+            } catch (QuitException ex) {
+                throw ex;
+            } catch (KillException ex) {
+                return finishReplySucceeded(reply, callName + " killed");
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex);
+            }
+            return finishReplySucceeded(reply, callName + " returned");
+        }
+    };
+
     public static final REPLHandler CLEAR_BREAK_HANDLER = new REPLHandler(REPLMessage.CLEAR_BREAK) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
-            Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID);
+            final Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID);
             if (breakpointNumber == null) {
                 return finishReplyFailed(reply, "missing breakpoint number");
             }
-            final Breakpoint breakpoint = serverContext.findBreakpoint(breakpointNumber);
-            if (breakpoint == null) {
+            final BreakpointInfo breakpointInfo = replServer.findBreakpoint(breakpointNumber);
+            if (breakpointInfo == null) {
                 return finishReplyFailed(reply, "no breakpoint number " + breakpointNumber);
             }
-            breakpoint.dispose();
+            breakpointInfo.dispose();
             reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
             return finishReplySucceeded(reply, "Breakpoint " + breakpointNumber + " cleared");
         }
@@ -299,9 +347,9 @@
     public static final REPLHandler CONTINUE_HANDLER = new REPLHandler(REPLMessage.CONTINUE) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
-            serverContext.prepareContinue();
+            replServer.getCurrentContext().prepareContinue();
             return finishReplySucceeded(reply, "Continue mode entered");
         }
     };
@@ -309,34 +357,33 @@
     public static final REPLHandler DELETE_HANDLER = new REPLHandler(REPLMessage.DELETE_BREAK) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
-            int deleteCount = 0;
-            for (Breakpoint breakpoint : serverContext.db().getBreakpoints()) {
-                breakpoint.dispose();
-                deleteCount++;
-            }
-            if (deleteCount == 0) {
+            final Collection<BreakpointInfo> breakpoints = replServer.getBreakpoints();
+            if (breakpoints.isEmpty()) {
                 return finishReplyFailed(reply, "no breakpoints to delete");
             }
-            return finishReplySucceeded(reply, Integer.toString(deleteCount) + " breakpoints deleted");
+            for (BreakpointInfo breakpointInfo : breakpoints) {
+                breakpointInfo.dispose();
+            }
+            return finishReplySucceeded(reply, Integer.toString(breakpoints.size()) + " breakpoints deleted");
         }
     };
 
     public static final REPLHandler DISABLE_BREAK_HANDLER = new REPLHandler(REPLMessage.DISABLE_BREAK) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
             Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID);
             if (breakpointNumber == null) {
                 return finishReplyFailed(reply, "missing breakpoint number");
             }
-            final Breakpoint breakpoint = serverContext.findBreakpoint(breakpointNumber);
-            if (breakpoint == null) {
+            final BreakpointInfo breakpointInfo = replServer.findBreakpoint(breakpointNumber);
+            if (breakpointInfo == null) {
                 return finishReplyFailed(reply, "no breakpoint number " + breakpointNumber);
             }
-            breakpoint.setEnabled(false);
+            breakpointInfo.setEnabled(false);
             reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
             return finishReplySucceeded(reply, "Breakpoint " + breakpointNumber + " disabled");
         }
@@ -345,44 +392,68 @@
     public static final REPLHandler ENABLE_BREAK_HANDLER = new REPLHandler(REPLMessage.ENABLE_BREAK) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
             Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID);
             if (breakpointNumber == null) {
                 return finishReplyFailed(reply, "missing breakpoint number");
             }
-            final Breakpoint breakpoint = serverContext.findBreakpoint(breakpointNumber);
-            if (breakpoint == null) {
+            final BreakpointInfo breakpointInfo = replServer.findBreakpoint(breakpointNumber);
+            if (breakpointInfo == null) {
                 return finishReplyFailed(reply, "no breakpoint number " + breakpointNumber);
             }
-            breakpoint.setEnabled(true);
+            breakpointInfo.setEnabled(true);
             reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
             return finishReplySucceeded(reply, "Breakpoint " + breakpointNumber + " enabled");
         }
     };
+    public static final REPLHandler EVAL_HANDLER = new REPLHandler(REPLMessage.EVAL) {
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
+            final REPLMessage reply = createReply();
+            final String sourceName = request.get(REPLMessage.SOURCE_NAME);
+            reply.put(REPLMessage.SOURCE_NAME, sourceName);
+            final Context serverContext = replServer.getCurrentContext();
+            reply.put(REPLMessage.DEBUG_LEVEL, Integer.toString(serverContext.getLevel()));
 
+            final String source = request.get(REPLMessage.CODE);
+            final Visualizer visualizer = replServer.getVisualizer();
+            final Integer frameNumber = request.getIntValue(REPLMessage.FRAME_NUMBER);
+            final boolean stepInto = REPLMessage.TRUE.equals(request.get(REPLMessage.STEP_INTO));
+            try {
+                Object returnValue = serverContext.eval(source, frameNumber, stepInto);
+                return finishReplySucceeded(reply, visualizer.displayValue(returnValue, 0));
+            } catch (QuitException ex) {
+                throw ex;
+            } catch (KillException ex) {
+                return finishReplySucceeded(reply, "eval (" + sourceName + ") killed");
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex);
+            }
+        }
+    };
     public static final REPLHandler FILE_HANDLER = new REPLHandler(REPLMessage.FILE) {
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
             final String fileName = request.get(REPLMessage.SOURCE_NAME);
             if (fileName == null) {
                 return finishReplyFailed(reply, "no file specified");
             }
+            reply.put(REPLMessage.SOURCE_NAME, fileName);
             try {
                 Source source = Source.fromFileName(fileName);
                 if (source == null) {
-                    reply.put(REPLMessage.SOURCE_NAME, fileName);
-                    return finishReplyFailed(reply, " not found");
+                    return finishReplyFailed(reply, "file \"" + fileName + "\" not found");
                 } else {
-                    reply.put(REPLMessage.SOURCE_NAME, fileName);
                     reply.put(REPLMessage.FILE_PATH, source.getPath());
                     reply.put(REPLMessage.CODE, source.getCode());
                     return finishReplySucceeded(reply, "file found");
                 }
+            } catch (IOException ex) {
+                return finishReplyFailed(reply, "can't read file \"" + fileName + "\"");
             } catch (Exception ex) {
-                reply.put(REPLMessage.SOURCE_NAME, fileName);
-                return finishReplyFailed(reply, "file \"" + fileName + "\" not found");
+                return finishReplyFailed(reply, ex);
             }
         }
     };
@@ -395,70 +466,169 @@
     public static final REPLHandler FRAME_HANDLER = new REPLHandler(REPLMessage.FRAME) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
-            final REPLMessage reply = createReply();
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final Integer frameNumber = request.getIntValue(REPLMessage.FRAME_NUMBER);
             if (frameNumber == null) {
-                return finishReplyFailed(reply, "no frame number specified");
+                return finishReplyFailed(createReply(), "no frame number specified");
+            }
+            final Context currentContext = replServer.getCurrentContext();
+            final List<FrameInstance> stack = currentContext.getStack();
+            if (frameNumber < 0 || frameNumber > stack.size()) {
+                return finishReplyFailed(createReply(), "frame number " + frameNumber + " out of range");
             }
-            final List<FrameDebugDescription> stack = serverContext.getStack();
-            if (frameNumber < 0 || frameNumber >= stack.size()) {
-                return finishReplyFailed(reply, "frame number " + frameNumber + " out of range");
+            final Visualizer visualizer = replServer.getVisualizer();
+
+            MaterializedFrame frame;
+            Node node;
+            if (frameNumber == 0) {
+                frame = currentContext.getFrame();
+                node = currentContext.getNode();
+            } else {
+                final FrameInstance instance = stack.get(frameNumber - 1);
+                frame = instance.getFrame(FrameAccess.MATERIALIZE, true).materialize();
+                node = instance.getCallNode();
+            }
+            List<? extends FrameSlot> slots = frame.getFrameDescriptor().getSlots();
+            if (slots.size() == 0) {
+                final REPLMessage emptyFrameMessage = createFrameInfoMessage(replServer, frameNumber, node);
+                return finishReplySucceeded(emptyFrameMessage, "empty frame");
+            }
+            final ArrayList<REPLMessage> replies = new ArrayList<>();
+
+            for (FrameSlot slot : slots) {
+                final REPLMessage slotMessage = createFrameInfoMessage(replServer, frameNumber, node);
+                slotMessage.put(REPLMessage.SLOT_INDEX, Integer.toString(slot.getIndex()));
+                slotMessage.put(REPLMessage.SLOT_ID, visualizer.displayIdentifier(slot));
+                slotMessage.put(REPLMessage.SLOT_VALUE, visualizer.displayValue(frame.getValue(slot), 0));
+                slotMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
+                replies.add(slotMessage);
             }
-            final FrameDebugDescription frameDescription = stack.get(frameNumber);
-            final REPLMessage frameMessage = createFrameInfoMessage(serverContext, frameDescription);
-            final Frame frame = frameDescription.frameInstance().getFrame(FrameInstance.FrameAccess.READ_ONLY, true);
-            final Visualizer visualizer = serverContext.getVisualizer();
-            final FrameDescriptor frameDescriptor = frame.getFrameDescriptor();
-            try {
-                final StringBuilder sb = new StringBuilder();
-                for (FrameSlot slot : frameDescriptor.getSlots()) {
-                    sb.append(Integer.toString(slot.getIndex()) + ": " + visualizer.displayIdentifier(slot) + " = ");
-                    try {
-                        final Object value = frame.getValue(slot);
-                        sb.append(visualizer.displayValue(value, 0));
-                    } catch (Exception ex) {
-                        sb.append("???");
+            return replies.toArray(new REPLMessage[0]);
+        }
+    };
+
+    public static final REPLHandler INFO_HANDLER = new REPLHandler(REPLMessage.INFO) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
+            final String topic = request.get(REPLMessage.TOPIC);
+
+            if (topic == null || topic.isEmpty()) {
+                final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.INFO);
+                return finishReplyFailed(message, "No info topic specified");
+            }
+
+            switch (topic) {
+
+                case REPLMessage.INFO_SUPPORTED_LANGUAGES:
+                    final ArrayList<REPLMessage> langMessages = new ArrayList<>();
+
+                    for (Language language : replServer.getLanguages()) {
+                        final REPLMessage infoMessage = new REPLMessage(REPLMessage.OP, REPLMessage.INFO);
+                        infoMessage.put(REPLMessage.TOPIC, REPLMessage.INFO_SUPPORTED_LANGUAGES);
+                        infoMessage.put(REPLMessage.LANG_NAME, language.getName());
+                        infoMessage.put(REPLMessage.LANG_VER, language.getVersion());
+                        infoMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
+                        langMessages.add(infoMessage);
                     }
-                    sb.append("\n");
-                }
-                return finishReplySucceeded(frameMessage, sb.toString());
-            } catch (Exception ex) {
-                return finishReplyFailed(frameMessage, ex.toString());
+                    return langMessages.toArray(new REPLMessage[0]);
+
+                case REPLMessage.INFO_CURRENT_LANGUAGE:
+                    final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.INFO);
+                    reply.put(REPLMessage.TOPIC, REPLMessage.INFO_CURRENT_LANGUAGE);
+                    final String languageName = replServer.getCurrentContext().getLanguageName();
+                    reply.put(REPLMessage.LANG_NAME, languageName);
+                    return finishReplySucceeded(reply, languageName);
+
+                case REPLMessage.WELCOME_MESSAGE:
+                    final REPLMessage infoMessage = new REPLMessage(REPLMessage.OP, REPLMessage.INFO);
+                    infoMessage.put(REPLMessage.TOPIC, REPLMessage.WELCOME_MESSAGE);
+                    infoMessage.put(REPLMessage.INFO_VALUE, replServer.getWelcome());
+                    infoMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
+                    return finishReplySucceeded(infoMessage, "welcome");
+
+                default:
+                    final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.INFO);
+                    return finishReplyFailed(message, "No info about topic \"" + topic + "\"");
             }
         }
     };
-
     public static final REPLHandler KILL_HANDLER = new REPLHandler(REPLMessage.KILL) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
-            if (serverContext.getLevel() == 0) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
+            if (replServer.getCurrentContext().getLevel() == 0) {
                 return finishReplyFailed(createReply(), "nothing to kill");
             }
             throw new KillException();
         }
     };
 
+    public static final REPLHandler LOAD_HANDLER = new REPLHandler(REPLMessage.LOAD_SOURCE) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
+            final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.LOAD_SOURCE);
+            final String fileName = request.get(REPLMessage.SOURCE_NAME);
+            final boolean stepInto = REPLMessage.TRUE.equals(request.get(REPLMessage.STEP_INTO));
+            try {
+                final Source fileSource = Source.fromFileName(fileName);
+                replServer.getCurrentContext().eval(fileSource, stepInto);
+                reply.put(REPLMessage.FILE_PATH, fileName);
+                return finishReplySucceeded(reply, fileName + "  loaded");
+            } catch (QuitException ex) {
+                throw ex;
+            } catch (KillException ex) {
+                return finishReplySucceeded(reply, fileName + " killed");
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex);
+            }
+        }
+    };
+
     public static final REPLHandler QUIT_HANDLER = new REPLHandler(REPLMessage.QUIT) {
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             throw new QuitException();
         }
     };
 
+    public static final REPLHandler SET_LANGUAGE_HANDLER = new REPLHandler(REPLMessage.SET_LANGUAGE) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
+            final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.SET_LANGUAGE);
+            String languageName = request.get(REPLMessage.LANG_NAME);
+            if (languageName == null) {
+                final String oldLanguageName = replServer.getCurrentContext().getLanguageName();
+                reply.put(REPLMessage.LANG_NAME, reply.put(REPLMessage.LANG_NAME, oldLanguageName));
+                return finishReplySucceeded(reply, "Language set to " + oldLanguageName);
+            }
+            reply.put(REPLMessage.LANG_NAME, languageName);
+            try {
+                final String newLanguageName = replServer.getCurrentContext().setLanguage(languageName);
+                if (newLanguageName != null) {
+                    return finishReplySucceeded(reply, "Language set to " + newLanguageName);
+                }
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex);
+            }
+            return finishReplyFailed(reply, "Language \"" + languageName + "\" not supported");
+        }
+    };
+
     public static final REPLHandler SET_BREAK_CONDITION_HANDLER = new REPLHandler(REPLMessage.SET_BREAK_CONDITION) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.SET_BREAK_CONDITION);
             Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID);
             if (breakpointNumber == null) {
                 return finishReplyFailed(message, "missing breakpoint number");
             }
             message.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
-            final Breakpoint breakpoint = serverContext.findBreakpoint(breakpointNumber);
-            if (breakpoint == null) {
+            final BreakpointInfo breakpointInfo = replServer.findBreakpoint(breakpointNumber);
+            if (breakpointInfo == null) {
                 return finishReplyFailed(message, "no breakpoint number " + breakpointNumber);
             }
             final String expr = request.get(REPLMessage.BREAKPOINT_CONDITION);
@@ -466,11 +636,13 @@
                 return finishReplyFailed(message, "missing condition for " + breakpointNumber);
             }
             try {
-                breakpoint.setCondition(expr);
+                breakpointInfo.setCondition(expr);
             } catch (IOException ex) {
                 return finishReplyFailed(message, "invalid condition for " + breakpointNumber);
             } catch (UnsupportedOperationException ex) {
-                return finishReplyFailed(message, "conditions not unsupported by breakpoint " + breakpointNumber);
+                return finishReplyFailed(message, "conditions not supported by breakpoint " + breakpointNumber);
+            } catch (Exception ex) {
+                return finishReplyFailed(message, ex);
             }
             message.put(REPLMessage.BREAKPOINT_CONDITION, expr);
             return finishReplySucceeded(message, "Breakpoint " + breakpointNumber + " condition=\"" + expr + "\"");
@@ -480,13 +652,13 @@
     public static final REPLHandler STEP_INTO_HANDLER = new REPLHandler(REPLMessage.STEP_INTO) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
             Integer repeat = request.getIntValue(REPLMessage.REPEAT);
             if (repeat == null) {
                 repeat = 1;
             }
-            serverContext.prepareStepInto(repeat);
+            replServer.getCurrentContext().prepareStepInto(repeat);
             return finishReplySucceeded(reply, "StepInto <" + repeat + "> enabled");
         }
     };
@@ -494,8 +666,8 @@
     public static final REPLHandler STEP_OUT_HANDLER = new REPLHandler(REPLMessage.STEP_OUT) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
-            serverContext.prepareStepOut();
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
+            replServer.getCurrentContext().prepareStepOut();
             return finishReplySucceeded(createReply(), "StepOut enabled");
         }
     };
@@ -503,13 +675,13 @@
     public static final REPLHandler STEP_OVER_HANDLER = new REPLHandler(REPLMessage.STEP_OVER) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext debugServerContextFrame) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
             Integer repeat = request.getIntValue(REPLMessage.REPEAT);
             if (repeat == null) {
                 repeat = 1;
             }
-            debugServerContextFrame.prepareStepOver(repeat);
+            replServer.getCurrentContext().prepareStepOver(repeat);
             return finishReplySucceeded(reply, "StepOver <" + repeat + "> enabled");
         }
     };
@@ -517,12 +689,12 @@
     public static final REPLHandler TRUFFLE_HANDLER = new REPLHandler(REPLMessage.TRUFFLE) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
-            final ASTPrinter astPrinter = serverContext.getVisualizer().getASTPrinter();
+            final ASTPrinter astPrinter = replServer.getVisualizer().getASTPrinter();
             final String topic = request.get(REPLMessage.TOPIC);
             reply.put(REPLMessage.TOPIC, topic);
-            Node node = serverContext.getNodeAtHalt();
+            Node node = replServer.getCurrentContext().getNodeAtHalt();
             if (node == null) {
                 return finishReplyFailed(reply, "no current AST node");
             }
@@ -536,7 +708,7 @@
                         while (node.getParent() != null) {
                             node = node.getParent();
                         }
-                        final String astText = astPrinter.printTreeToString(node, depth, serverContext.getNodeAtHalt());
+                        final String astText = astPrinter.printTreeToString(node, depth, replServer.getCurrentContext().getNodeAtHalt());
                         return finishReplySucceeded(reply, astText);
                     case REPLMessage.SUBTREE:
                     case REPLMessage.SUB:
@@ -547,7 +719,7 @@
                 }
 
             } catch (Exception ex) {
-                return finishReplyFailed(reply, ex.toString());
+                return finishReplyFailed(reply, ex);
             }
         }
     };
@@ -555,33 +727,33 @@
     public static final REPLHandler UNSET_BREAK_CONDITION_HANDLER = new REPLHandler(REPLMessage.UNSET_BREAK_CONDITION) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.UNSET_BREAK_CONDITION);
             Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID);
             if (breakpointNumber == null) {
                 return finishReplyFailed(message, "missing breakpoint number");
             }
             message.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
-            final Breakpoint breakpoint = serverContext.findBreakpoint(breakpointNumber);
-            if (breakpoint == null) {
+            final BreakpointInfo breakpointInfo = replServer.findBreakpoint(breakpointNumber);
+            if (breakpointInfo == null) {
                 return finishReplyFailed(message, "no breakpoint number " + breakpointNumber);
             }
             try {
-                breakpoint.setCondition(null);
-            } catch (IOException e) {
-                return finishReplyFailed(message, e.getMessage());
+                breakpointInfo.setCondition(null);
+            } catch (Exception ex) {
+                return finishReplyFailed(message, ex);
             }
-            return finishReplyFailed(message, "Breakpoint " + breakpointNumber + " condition cleared");
+            return finishReplySucceeded(message, "Breakpoint " + breakpointNumber + " condition cleared");
         }
     };
 
     public static final REPLHandler TRUFFLE_NODE_HANDLER = new REPLHandler(REPLMessage.TRUFFLE_NODE) {
 
         @Override
-        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+        public REPLMessage[] receive(REPLMessage request, REPLServer replServer) {
             final REPLMessage reply = createReply();
-            final ASTPrinter astPrinter = serverContext.getVisualizer().getASTPrinter();
-            final Node node = serverContext.getNodeAtHalt();
+            final ASTPrinter astPrinter = replServer.getVisualizer().getASTPrinter();
+            final Node node = replServer.getCurrentContext().getNodeAtHalt();
             if (node == null) {
                 return finishReplyFailed(reply, "no current AST node");
             }
@@ -599,7 +771,7 @@
                 }
                 return finishReplySucceeded(reply, sb.toString());
             } catch (Exception ex) {
-                return finishReplyFailed(reply, ex.toString());
+                return finishReplyFailed(reply, ex);
             }
         }
     };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServer.java	Mon Dec 07 21:17:46 2015 -0800
@@ -0,0 +1,678 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.tools.debug.shell.server;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.WeakHashMap;
+
+import com.oracle.truffle.api.debug.Breakpoint;
+import com.oracle.truffle.api.debug.Breakpoint.State;
+import com.oracle.truffle.api.debug.Debugger;
+import com.oracle.truffle.api.debug.ExecutionEvent;
+import com.oracle.truffle.api.debug.SuspendedEvent;
+import com.oracle.truffle.api.frame.FrameInstance;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.instrument.StandardSyntaxTag;
+import com.oracle.truffle.api.instrument.SyntaxTag;
+import com.oracle.truffle.api.instrument.Visualizer;
+import com.oracle.truffle.api.instrument.impl.DefaultVisualizer;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.source.LineLocation;
+import com.oracle.truffle.api.source.Source;
+import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.api.vm.EventConsumer;
+import com.oracle.truffle.api.vm.PolyglotEngine;
+import com.oracle.truffle.api.vm.PolyglotEngine.Language;
+import com.oracle.truffle.api.vm.PolyglotEngine.Value;
+import com.oracle.truffle.tools.debug.shell.REPLMessage;
+import com.oracle.truffle.tools.debug.shell.client.SimpleREPLClient;
+
+/**
+ * The server side of a simple message-based protocol for a possibly remote language
+ * Read-Eval-Print-Loop.
+ */
+public final class REPLServer {
+
+    enum BreakpointKind {
+        LINE,
+        TAG
+    }
+
+    private static int nextBreakpointUID = 0;
+
+    // Language-agnostic
+    private final PolyglotEngine engine;
+    private Debugger db;
+    private Context currentServerContext;
+    private SimpleREPLClient replClient = null;
+    private String statusPrefix;
+    private final Map<String, REPLHandler> handlerMap = new HashMap<>();
+
+    /** Languages sorted by name. */
+    private final TreeSet<Language> engineLanguages = new TreeSet<>(new Comparator<Language>() {
+
+        public int compare(Language lang1, Language lang2) {
+            return lang1.getName().compareTo(lang2.getName());
+        }
+    });
+
+    /** MAP: language name => Language. */
+    private final Map<String, Language> nameToLanguage = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+    // TODO (mlvdv) Language-specific
+    private PolyglotEngine.Language defaultLanguage;
+    private final Visualizer visualizer;
+
+    private Map<Integer, BreakpointInfo> breakpoints = new WeakHashMap<>();
+
+    public REPLServer(String defaultMIMEType, Visualizer visualizer) {
+        this.visualizer = visualizer == null ? new DefaultVisualizer() : visualizer;
+        this.engine = PolyglotEngine.newBuilder().onEvent(onHalted).onEvent(onExec).build();
+        engineLanguages.addAll(engine.getLanguages().values());
+        if (engineLanguages.size() == 0) {
+            throw new RuntimeException("No language implementations installed");
+        }
+        for (Language language : engineLanguages) {
+            nameToLanguage.put(language.getName(), language);
+        }
+
+        if (defaultMIMEType == null) {
+            defaultLanguage = engineLanguages.iterator().next();
+        } else {
+            this.defaultLanguage = engine.getLanguages().get(defaultMIMEType);
+            if (defaultLanguage == null) {
+                throw new RuntimeException("Implementation not found for \"" + defaultMIMEType + "\"");
+            }
+        }
+        statusPrefix = languageName(defaultLanguage);
+    }
+
+    private final EventConsumer<SuspendedEvent> onHalted = new EventConsumer<SuspendedEvent>(SuspendedEvent.class) {
+        @Override
+        protected void on(SuspendedEvent ev) {
+            REPLServer.this.haltedAt(ev);
+        }
+    };
+
+    private final EventConsumer<ExecutionEvent> onExec = new EventConsumer<ExecutionEvent>(ExecutionEvent.class) {
+        @Override
+        protected void on(ExecutionEvent event) {
+            if (db == null) {
+                db = event.getDebugger();
+                for (BreakpointInfo breakpointInfo : breakpoints.values()) {
+                    breakpointInfo.activate(db);
+                }
+            }
+            if (currentServerContext.steppingInto) {
+                event.prepareStepInto();
+            }
+        }
+    };
+
+    public void add(REPLHandler handler) {
+        handlerMap.put(handler.getOp(), handler);
+    }
+
+    /**
+     * Starts up a server; status returned in a message.
+     */
+    public void start() {
+
+        add(REPLHandler.BACKTRACE_HANDLER);
+        add(REPLHandler.BREAK_AT_LINE_HANDLER);
+        add(REPLHandler.BREAK_AT_LINE_ONCE_HANDLER);
+        add(REPLHandler.BREAK_AT_THROW_HANDLER);
+        add(REPLHandler.BREAK_AT_THROW_ONCE_HANDLER);
+        add(REPLHandler.BREAKPOINT_INFO_HANDLER);
+        add(REPLHandler.CALL_HANDLER);
+        add(REPLHandler.CLEAR_BREAK_HANDLER);
+        add(REPLHandler.CONTINUE_HANDLER);
+        add(REPLHandler.DELETE_HANDLER);
+        add(REPLHandler.DISABLE_BREAK_HANDLER);
+        add(REPLHandler.ENABLE_BREAK_HANDLER);
+        add(REPLHandler.EVAL_HANDLER);
+        add(REPLHandler.FILE_HANDLER);
+        add(REPLHandler.FRAME_HANDLER);
+        add(REPLHandler.INFO_HANDLER);
+        add(REPLHandler.KILL_HANDLER);
+        add(REPLHandler.LOAD_HANDLER);
+        add(REPLHandler.QUIT_HANDLER);
+        add(REPLHandler.SET_BREAK_CONDITION_HANDLER);
+        add(REPLHandler.SET_LANGUAGE_HANDLER);
+        add(REPLHandler.STEP_INTO_HANDLER);
+        add(REPLHandler.STEP_OUT_HANDLER);
+        add(REPLHandler.STEP_OVER_HANDLER);
+        add(REPLHandler.TRUFFLE_HANDLER);
+        add(REPLHandler.TRUFFLE_NODE_HANDLER);
+        add(REPLHandler.UNSET_BREAK_CONDITION_HANDLER);
+        this.replClient = new SimpleREPLClient(this);
+        this.currentServerContext = new Context(null, null, defaultLanguage);
+        replClient.start();
+    }
+
+    @SuppressWarnings("static-method")
+    public String getWelcome() {
+        return "GraalVM MultiLanguage Debugger 0.9\n" + "Copyright (c) 2013-5, Oracle and/or its affiliates";
+    }
+
+    void haltedAt(SuspendedEvent event) {
+        // Message the client that execution is halted and is in a new debugging context
+        final REPLMessage message = new REPLMessage();
+        message.put(REPLMessage.OP, REPLMessage.STOPPED);
+
+        // Identify language execution where halted; default to previous context
+        Language haltedLanguage = currentServerContext.currentLanguage;
+        final String mimeType = findMime(event.getNode());
+        if (mimeType == null) {
+            message.put(REPLMessage.WARNINGS, "unable to detect language at halt");
+        } else {
+            final Language language = engine.getLanguages().get(mimeType);
+            if (language == null) {
+                message.put(REPLMessage.WARNINGS, "no language installed for MIME type \"" + mimeType + "\"");
+            } else {
+                haltedLanguage = language;
+            }
+        }
+
+        // Create and push a new debug context where execution is halted
+        currentServerContext = new Context(currentServerContext, event, haltedLanguage);
+
+        message.put(REPLMessage.LANG_NAME, haltedLanguage.getName());
+        final SourceSection src = event.getNode().getSourceSection();
+        final Source source = src.getSource();
+        message.put(REPLMessage.SOURCE_NAME, source.getName());
+        final String path = source.getPath();
+        if (path == null) {
+            message.put(REPLMessage.SOURCE_TEXT, source.getCode());
+        } else {
+            message.put(REPLMessage.FILE_PATH, path);
+        }
+        message.put(REPLMessage.LINE_NUMBER, Integer.toString(src.getStartLine()));
+
+        message.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
+        message.put(REPLMessage.DEBUG_LEVEL, Integer.toString(currentServerContext.getLevel()));
+        List<String> warnings = event.getRecentWarnings();
+        if (!warnings.isEmpty()) {
+            final StringBuilder sb = new StringBuilder();
+            for (String warning : warnings) {
+                sb.append(warning + "\n");
+            }
+            message.put(REPLMessage.WARNINGS, sb.toString());
+        }
+        try {
+            // Cheat with synchrony: call client directly about entering a nested debugging
+            // context.
+            replClient.halted(message);
+        } finally {
+            // Returns when "continue" or "kill" is called in the new debugging context
+
+            // Pop the debug context, and return so that the old context will continue
+            currentServerContext = currentServerContext.predecessor;
+        }
+    }
+
+    @SuppressWarnings("static-method")
+    private String findMime(Node node) {
+        String result = null;
+        final SourceSection section = node.getEncapsulatingSourceSection();
+        if (section != null) {
+            final Source source = section.getSource();
+            if (source != null) {
+                result = source.getMimeType();
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Execution context of a halted program, possibly nested.
+     */
+    public final class Context {
+
+        private final Context predecessor;
+        private final int level;
+        private final SuspendedEvent event;
+        private Language currentLanguage;
+        private boolean steppingInto = false;  // Only true during a "stepInto" engine call
+
+        Context(Context predecessor, SuspendedEvent event, Language language) {
+            assert language != null;
+            this.level = predecessor == null ? 0 : predecessor.getLevel() + 1;
+            this.predecessor = predecessor;
+            this.event = event;
+            this.currentLanguage = language;
+        }
+
+        /**
+         * The nesting depth of this context in the current session.
+         */
+        int getLevel() {
+            return level;
+        }
+
+        /**
+         * The AST node where execution is halted in this context.
+         */
+        Node getNodeAtHalt() {
+            return event.getNode();
+        }
+
+        Object call(String name, boolean stepInto, List<String> argList) throws IOException {
+            Value symbol = engine.findGlobalSymbol(name);
+            if (symbol == null) {
+                throw new IOException("symbol \"" + name + "\" not found");
+            }
+            final List<Object> args = new ArrayList<>();
+            for (String stringArg : argList) {
+                Integer intArg = null;
+                try {
+                    intArg = Integer.valueOf(stringArg);
+                    args.add(intArg);
+                } catch (NumberFormatException e) {
+                    args.add(stringArg);
+                }
+            }
+            this.steppingInto = stepInto;
+            try {
+                return symbol.execute(args.toArray(new Object[0])).get();
+            } finally {
+                this.steppingInto = false;
+            }
+        }
+
+        void eval(Source source, boolean stepInto) throws IOException {
+            this.steppingInto = stepInto;
+            try {
+                engine.eval(source);
+            } finally {
+                this.steppingInto = false;
+            }
+        }
+
+        /**
+         * Evaluates a code snippet in the context of a selected frame in the currently suspended
+         * execution.
+         *
+         * @param code the snippet to evaluate
+         * @param frameNumber index of the stack frame in which to evaluate, {@code null} if the
+         *            topmost frame should be used.
+         * @return result of the evaluation
+         * @throws IOException if something goes wrong
+         */
+        Object eval(String code, Integer frameNumber, boolean stepInto) throws IOException {
+            if (event == null) {
+                this.steppingInto = stepInto;
+                final String mimeType = defaultMIME(currentLanguage);
+                try {
+                    return engine.eval(Source.fromText(code, "eval(\"" + code + "\")").withMimeType(mimeType)).get();
+                } finally {
+                    this.steppingInto = false;
+                }
+            } else {
+                if (stepInto) {
+                    event.prepareStepInto(1);
+                }
+                try {
+                    final Object result = event.eval(code, frameNumber);
+                    return (result instanceof Value) ? ((Value) result).get() : result;
+                } finally {
+                    event.prepareContinue();
+                }
+            }
+        }
+
+        /**
+         * The frame where execution is halted in this context.
+         */
+        MaterializedFrame getFrameAtHalt() {
+            return event.getFrame();
+        }
+
+        /**
+         * Dispatches a REPL request to the appropriate handler.
+         */
+        REPLMessage[] receive(REPLMessage request) {
+            final String command = request.get(REPLMessage.OP);
+            final REPLHandler handler = handlerMap.get(command);
+
+            if (handler == null) {
+                final REPLMessage message = new REPLMessage();
+                message.put(REPLMessage.OP, command);
+                message.put(REPLMessage.STATUS, REPLMessage.FAILED);
+                message.put(REPLMessage.DISPLAY_MSG, statusPrefix + " op \"" + command + "\" not supported");
+                final REPLMessage[] reply = new REPLMessage[]{message};
+                return reply;
+            }
+            return handler.receive(request, REPLServer.this);
+        }
+
+        /**
+         * @return Node where halted
+         */
+        Node getNode() {
+            return event.getNode();
+        }
+
+        /**
+         * @return Frame where halted
+         */
+        MaterializedFrame getFrame() {
+            return event.getFrame();
+        }
+
+        /**
+         * Provides access to the execution stack, not counting the node/frame where halted.
+         *
+         * @return immutable list of stack elements
+         */
+        List<FrameInstance> getStack() {
+            return event.getStack();
+        }
+
+        public String getLanguageName() {
+            return currentLanguage.getName();
+        }
+
+        /**
+         * Case-insensitive; returns actual language name set.
+         *
+         * @throws IOException if fails
+         */
+        String setLanguage(String name) throws IOException {
+            assert name != null;
+            final Language language = nameToLanguage.get(name);
+            if (language == null) {
+                throw new IOException("Language \" + name + \" not supported");
+            }
+            if (language == currentLanguage) {
+                return currentLanguage.getName();
+            }
+            if (event != null) {
+                throw new IOException("Only supported at top level");
+            }
+            this.currentLanguage = language;
+            return language.getName();
+        }
+
+        void prepareStepOut() {
+            event.prepareStepOut();
+        }
+
+        void prepareStepInto(int repeat) {
+            event.prepareStepInto(repeat);
+        }
+
+        void prepareStepOver(int repeat) {
+            event.prepareStepOver(repeat);
+        }
+
+        void prepareContinue() {
+            event.prepareContinue();
+        }
+
+    }
+
+    /**
+     * Ask the server to handle a request. Return a non-empty array of messages to simulate remote
+     * operation where the protocol has possibly multiple messages being returned asynchronously in
+     * response to each request.
+     */
+    public REPLMessage[] receive(REPLMessage request) {
+        if (currentServerContext == null) {
+            final REPLMessage message = new REPLMessage();
+            message.put(REPLMessage.STATUS, REPLMessage.FAILED);
+            message.put(REPLMessage.DISPLAY_MSG, "server not started");
+            final REPLMessage[] reply = new REPLMessage[]{message};
+            return reply;
+        }
+        return currentServerContext.receive(request);
+    }
+
+    Context getCurrentContext() {
+        return currentServerContext;
+    }
+
+    Visualizer getVisualizer() {
+        return visualizer;
+    }
+
+    // TODO (mlvdv) language-specific
+    Language getLanguage() {
+        return defaultLanguage;
+    }
+
+    TreeSet<Language> getLanguages() {
+        return engineLanguages;
+    }
+
+    // TODO (mlvdv) language-specific
+    public String getLanguageName() {
+        return languageName(this.defaultLanguage);
+    }
+
+    private static String languageName(Language lang) {
+        return lang.getName() + "(" + lang.getVersion() + ")";
+    }
+
+    @SuppressWarnings("static-method")
+    private String defaultMIME(Language language) {
+        return language.getMimeTypes().iterator().next();
+    }
+
+    BreakpointInfo setLineBreakpoint(int ignoreCount, LineLocation lineLocation, boolean oneShot) {
+        return new BreakpointInfo(db, lineLocation, ignoreCount, oneShot);
+    }
+
+    BreakpointInfo setTagBreakpoint(int ignoreCount, StandardSyntaxTag tag, boolean oneShot) {
+        return new BreakpointInfo(db, tag, ignoreCount, oneShot);
+    }
+
+    synchronized BreakpointInfo findBreakpoint(int id) {
+        return breakpoints.get(id);
+    }
+
+    /**
+     * Gets a list of the currently existing breakpoints.
+     */
+    Collection<BreakpointInfo> getBreakpoints() {
+        return new ArrayList<>(breakpoints.values());
+    }
+
+    final class BreakpointInfo {
+
+        private final BreakpointKind kind;
+
+        /** Null before created in debugger or after disposal. */
+        private Breakpoint breakpoint;
+
+        /** Non-null only when breakpoint == null. */
+        private State state; // non-null iff haven't "activated" yet
+
+        private final int uid;
+
+        private boolean oneShot;
+
+        private int ignoreCount;
+
+        private Source conditionSource;
+
+        private final LineLocation lineLocation;
+
+        private final SyntaxTag tag;
+
+        private BreakpointInfo(Debugger debugger, LineLocation lineLocation, int ignoreCount, boolean oneShot) {
+            this.kind = BreakpointKind.LINE;
+            this.lineLocation = lineLocation;
+            this.tag = null;
+            this.ignoreCount = ignoreCount;
+            this.oneShot = oneShot;
+            this.uid = nextBreakpointUID++;
+            if (debugger == null) {
+                this.state = State.ENABLED_UNRESOLVED;
+            } else {
+                activate(debugger);
+            }
+            breakpoints.put(uid, this);
+        }
+
+        private BreakpointInfo(Debugger debugger, SyntaxTag tag, int ignoreCount, boolean oneShot) {
+            this.kind = BreakpointKind.TAG;
+            this.lineLocation = null;
+            this.tag = tag;
+            this.ignoreCount = ignoreCount;
+            this.oneShot = oneShot;
+            this.uid = nextBreakpointUID++;
+            if (debugger == null) {
+                this.state = State.ENABLED_UNRESOLVED;
+            } else {
+                activate(debugger);
+            }
+            breakpoints.put(uid, this);
+        }
+
+        private void activate(Debugger debugger) {
+            if (breakpoint != null) {
+                throw new IllegalStateException("Breakpoint already activated");
+            }
+            if (state == State.DISPOSED) {
+                throw new IllegalStateException("Breakpoint already disposed");
+            }
+            try {
+                switch (kind) {
+                    case LINE:
+                        breakpoint = debugger.setLineBreakpoint(ignoreCount, lineLocation, oneShot);
+                        break;
+                    case TAG:
+                        breakpoint = debugger.setTagBreakpoint(ignoreCount, tag, oneShot);
+                        break;
+                    default:
+                        throw new IllegalStateException("Unexpected breakpoint kind");
+                }
+                if (conditionSource != null) {
+                    breakpoint.setCondition(conditionSource.getCode());
+                    conditionSource = null;
+                }
+                if (state == State.DISABLED_UNRESOLVED) {
+                    breakpoint.setEnabled(false);
+                }
+                state = null;
+            } catch (IOException ex) {
+                throw new IllegalStateException("Failure to activate breakpoint " + uid + ":  " + ex.getMessage());
+            }
+        }
+
+        int getID() {
+            return uid;
+        }
+
+        String describeState() {
+            return (breakpoint == null ? state : breakpoint.getState()).getName();
+        }
+
+        String describeLocation() {
+            if (breakpoint == null) {
+                switch (kind) {
+                    case LINE:
+                        return "Line: " + lineLocation.getShortDescription();
+                    case TAG:
+                        return "Tag " + tag.name();
+                    default:
+                        throw new IllegalStateException("Unexpected breakpoint state");
+                }
+            }
+            return breakpoint.getLocationDescription();
+        }
+
+        void setEnabled(boolean enabled) {
+            if (breakpoint == null) {
+                switch (state) {
+                    case ENABLED_UNRESOLVED:
+                        if (!enabled) {
+                            state = State.DISABLED_UNRESOLVED;
+                        }
+                        break;
+                    case DISABLED_UNRESOLVED:
+                        if (enabled) {
+                            state = State.ENABLED_UNRESOLVED;
+                        }
+                        break;
+                    case DISPOSED:
+                        throw new IllegalStateException("Disposed breakpoints must stay disposed");
+                    default:
+                        throw new IllegalStateException("Unexpected breakpoint state");
+                }
+            } else {
+                breakpoint.setEnabled(enabled);
+            }
+        }
+
+        boolean isEnabled() {
+            return breakpoint == null ? (state == State.ENABLED_UNRESOLVED) : breakpoint.isEnabled();
+        }
+
+        void setCondition(String expr) throws IOException {
+            if (breakpoint == null) {
+                conditionSource = expr == null ? null : Source.fromText(expr, "breakpoint condition from text: " + expr);
+            } else {
+                breakpoint.setCondition(expr);
+            }
+        }
+
+        String getCondition() {
+            final Source source = breakpoint == null ? conditionSource : breakpoint.getCondition();
+            return source == null ? null : source.getCode();
+        }
+
+        int getIgnoreCount() {
+            return breakpoint == null ? ignoreCount : breakpoint.getIgnoreCount();
+        }
+
+        int getHitCount() {
+            return breakpoint == null ? 0 : breakpoint.getHitCount();
+        }
+
+        void dispose() {
+            if (breakpoint == null) {
+                if (state == State.DISPOSED) {
+                    throw new IllegalStateException("Breakpoint already disposed");
+                }
+            } else {
+                breakpoint.dispose();
+                breakpoint = null;
+            }
+            state = State.DISPOSED;
+            breakpoints.remove(uid);
+            conditionSource = null;
+        }
+    }
+}
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServerContext.java	Fri Dec 04 14:32:24 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package com.oracle.truffle.tools.debug.shell.server;
-
-import com.oracle.truffle.api.debug.Breakpoint;
-import com.oracle.truffle.api.debug.Debugger;
-import com.oracle.truffle.api.debug.SuspendedEvent;
-import com.oracle.truffle.api.frame.FrameInstance;
-import com.oracle.truffle.api.frame.MaterializedFrame;
-import com.oracle.truffle.api.instrument.Visualizer;
-import com.oracle.truffle.api.instrument.impl.DefaultVisualizer;
-import com.oracle.truffle.api.nodes.Node;
-import com.oracle.truffle.api.vm.PolyglotEngine;
-import com.oracle.truffle.api.vm.PolyglotEngine.Language;
-import com.oracle.truffle.tools.debug.shell.REPLMessage;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public abstract class REPLServerContext {
-
-    private final int level;
-    private final SuspendedEvent event;
-
-    protected REPLServerContext(int level, SuspendedEvent event) {
-        this.level = level;
-        this.event = event;
-    }
-
-    /**
-     * The nesting depth of this context in the current session.
-     */
-    public int getLevel() {
-        return level;
-    }
-
-    /**
-     * The AST node where execution is halted in this context.
-     */
-    public Node getNodeAtHalt() {
-        return event.getNode();
-    }
-
-    /**
-     * Evaluates given code snippet in the context of currently suspended execution.
-     * 
-     * @param code the snippet to evaluate
-     * @param frame <code>null</code> in case the evaluation should happen in top most frame,
-     *            non-null value
-     * @return result of the evaluation
-     * @throws IOException if something goes wrong
-     */
-    public Object eval(String code, FrameInstance frame) throws IOException {
-        return event.eval(code, frame);
-    }
-
-    /**
-     * The frame where execution is halted in this context.
-     */
-    public MaterializedFrame getFrameAtHalt() {
-        return event.getFrame();
-    }
-
-    public abstract Language getLanguage();
-
-    public Visualizer getVisualizer() {
-        return new DefaultVisualizer();
-    }
-
-    public PolyglotEngine engine() {
-        throw new IllegalStateException();
-    }
-
-    protected abstract Debugger db();
-
-    /**
-     * Dispatches a REPL request to the appropriate handler.
-     */
-    public abstract REPLMessage[] receive(REPLMessage request);
-
-    public abstract void registerBreakpoint(Breakpoint breakpoint);
-
-    public abstract Breakpoint findBreakpoint(int id);
-
-    public abstract int getBreakpointID(Breakpoint breakpoint);
-
-    /**
-     * Provides access to the execution stack.
-     *
-     * @return immutable list of stack elements
-     */
-    public List<FrameDebugDescription> getStack() {
-        List<FrameDebugDescription> frames = new ArrayList<>();
-        int frameCount = 1;
-        for (FrameInstance frameInstance : event.getStack()) {
-            if (frameCount == 1) {
-                frames.add(new FrameDebugDescription(frameCount, event.getNode(), frameInstance));
-            } else {
-                frames.add(new FrameDebugDescription(frameCount, frameInstance.getCallNode(), frameInstance));
-            }
-            frameCount++;
-        }
-        return Collections.unmodifiableList(frames);
-    }
-
-    void prepareStepOut() {
-        event.prepareStepOut();
-    }
-
-    void prepareStepInto(int repeat) {
-        event.prepareStepInto(repeat);
-    }
-
-    void prepareStepOver(int repeat) {
-        event.prepareStepOver(repeat);
-    }
-
-    void prepareContinue() {
-        event.prepareContinue();
-    }
-}