changeset 22461:c17794973b64

Truffle/REPL debugger: move closer to multi-lang
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Tue, 17 Nov 2015 17:29:52 -0800
parents c350ef0119c6
children 460f85e7c64c
files truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLMessage.java truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLClientContext.java truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLRemoteCommand.java truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLHandler.java truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServer.java
diffstat 6 files changed, 263 insertions(+), 88 deletions(-) [+]
line wrap: on
line diff
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLMessage.java	Tue Nov 17 17:28:29 2015 -0800
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLMessage.java	Tue Nov 17 17:29:52 2015 -0800
@@ -74,10 +74,14 @@
     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_SOURCE = "load-source";
@@ -88,6 +92,7 @@
     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 SOURCE_LINE_TEXT = "source-line-text";
     public static final String SOURCE_LOCATION = "source-location";
     public static final String SOURCE_NAME = "source-name";
@@ -110,6 +115,7 @@
     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";
     private final Map<String, String> map;
 
     /**
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLClientContext.java	Tue Nov 17 17:28:29 2015 -0800
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLClientContext.java	Tue Nov 17 17:29:52 2015 -0800
@@ -24,11 +24,11 @@
  */
 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.server.REPLServer;
 
-import java.util.List;
-
 /**
  * Client context for interaction with a program halted by the {@link REPLServer}.
  */
@@ -97,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	Tue Nov 17 17:28:29 2015 -0800
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLRemoteCommand.java	Tue Nov 17 17:29:52 2015 -0800
@@ -561,6 +561,28 @@
         }
     };
 
+    public static final REPLRemoteCommand SET_LANG_CMD = new REPLRemoteCommand("language", "lang", "Set current language") {
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (args.length == 1) {
+                context.displayFailReply("no language specified");
+                return null;
+            }
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.SET_LANGUAGE);
+            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.") {
 
         @Override
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java	Tue Nov 17 17:28:29 2015 -0800
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java	Tue Nov 17 17:29:52 2015 -0800
@@ -82,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 = "!!> ";
@@ -95,8 +97,6 @@
     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;
-
     // Top level commands
     private final Map<String, REPLCommand> commandMap = new HashMap<>();
     private final Collection<String> commandNames = new TreeSet<>();
@@ -145,8 +145,6 @@
 
     public SimpleREPLClient(REPLServer replServer) {
         this.replServer = replServer;
-        // TODO (mlvdv) language-dependent
-        this.languageName = replServer.getLanguageName();
         this.writer = System.out;
         try {
             this.reader = new ConsoleReader();
@@ -177,6 +175,7 @@
         addCommand(REPLRemoteCommand.LOAD_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);
@@ -206,11 +205,22 @@
 
         this.clientContext = new ClientContextImpl(null, null);
         try {
+            showWelcome();
             clientContext.startContextSession();
         } catch (QuitException ex) {
-            clientContext.displayReply("Goodbye from " + languageName + "/REPL");
+            clientContext.displayReply("Goodbye");
         }
+    }
 
+    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) {
@@ -286,7 +296,17 @@
             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);
+            }
             if (level == 0) {
                 // 0-level context; no executions halted.
                 if (selectedSource == null) {
@@ -299,7 +319,9 @@
                 final StringBuilder sb = new StringBuilder();
                 sb.append("(<" + Integer.toString(level) + "> ");
                 sb.append(selectedSource.getShortName());
-                sb.append(") ");
+                sb.append(")");
+                sb.append("(" + languageName + ")");
+                sb.append(" ");
                 currentPrompt = sb.toString();
             } else {
                 // Prompt reveals where currently halted.
@@ -309,7 +331,9 @@
                 if (haltedLineNumber > 0) {
                     sb.append(":" + Integer.toString(haltedLineNumber));
                 }
-                sb.append(") ");
+                sb.append(")");
+                sb.append("(" + languageName + ")");
+                sb.append(" ");
                 currentPrompt = sb.toString();
             }
 
@@ -923,9 +947,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() {
@@ -936,7 +960,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;
         }
 
@@ -945,12 +969,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());
                 }
             }
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLHandler.java	Tue Nov 17 17:28:29 2015 -0800
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLHandler.java	Tue Nov 17 17:29:52 2015 -0800
@@ -109,23 +109,6 @@
         return replies;
     }
 
-    protected static final REPLMessage[] createLanguageInfoReply(REPLServer replServer) {
-        final Language language = replServer.getLanguage();
-        final ArrayList<REPLMessage> langMessages = new ArrayList<>();
-        langMessages.add(createLanguageInfoMessage("Language Name", language.getName()));
-        langMessages.add(createLanguageInfoMessage("Language version", language.getVersion()));
-        return langMessages.toArray(new REPLMessage[0]);
-    }
-
-    protected static final REPLMessage createLanguageInfoMessage(String key, String value) {
-        final REPLMessage infoMessage = new REPLMessage(REPLMessage.OP, REPLMessage.INFO);
-        infoMessage.put(REPLMessage.TOPIC, REPLMessage.LANGUAGE);
-        infoMessage.put(REPLMessage.INFO_KEY, key);
-        infoMessage.put(REPLMessage.INFO_VALUE, value);
-        infoMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
-        return infoMessage;
-    }
-
     protected static final REPLMessage createBreakpointInfoMessage(Breakpoint breakpoint, REPLServer replServer) {
         final REPLMessage infoMessage = new REPLMessage(REPLMessage.OP, REPLMessage.BREAKPOINT_INFO);
         infoMessage.put(REPLMessage.BREAKPOINT_ID, Integer.toString(replServer.getBreakpointID(breakpoint)));
@@ -508,8 +491,32 @@
 
             switch (topic) {
 
-                case REPLMessage.LANGUAGE:
-                    return createLanguageInfoReply(replServer);
+                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);
+                    }
+                    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);
@@ -558,6 +565,28 @@
         }
     };
 
+    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) {
+                return finishReplyFailed(reply, "missing language name");
+            }
+            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
--- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServer.java	Tue Nov 17 17:28:29 2015 -0800
+++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServer.java	Tue Nov 17 17:29:52 2015 -0800
@@ -28,9 +28,12 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+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;
@@ -70,9 +73,19 @@
     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 final String mimeType;
-    private final PolyglotEngine.Language language;
+    private PolyglotEngine.Language defaultLanguage;
     private final Visualizer visualizer;
 
     private int nextBreakpointUID = 0;
@@ -89,58 +102,68 @@
      */
     private Map<Breakpoint, Integer> breakpoints = new WeakHashMap<>();
 
-    /**
-     * Create a single-language server.
-     */
-    public REPLServer(String mimeType, Visualizer visualizer) {
-        this.mimeType = mimeType;
+    public REPLServer(String defaultMIMEType, Visualizer visualizer) {
         this.visualizer = visualizer == null ? new DefaultVisualizer() : visualizer;
-        EventConsumer<SuspendedEvent> onHalted = new EventConsumer<SuspendedEvent>(SuspendedEvent.class) {
-            @Override
-            protected void on(SuspendedEvent ev) {
-                REPLServer.this.haltedAt(ev);
+        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 + "\"");
             }
-        };
-        EventConsumer<ExecutionEvent> onExec = new EventConsumer<ExecutionEvent>(ExecutionEvent.class) {
-            @Override
-            protected void on(ExecutionEvent event) {
-                if (db == null) {
-                    db = event.getDebugger();
-                    if (!breakpoints.isEmpty()) {
-                        ArrayList<? extends Breakpoint> pendingBreakpoints = new ArrayList<>(breakpoints.keySet());
-                        try {
-                            for (Breakpoint pending : pendingBreakpoints) {
+        }
+        statusPrefix = languageName(defaultLanguage);
+    }
+
+    private final EventConsumer<SuspendedEvent> onHalted = new EventConsumer<SuspendedEvent>(SuspendedEvent.class) {
+        @Override
+        protected void on(SuspendedEvent ev) {
+            REPLServer.this.haltedAt(ev);
+        }
+    };
 
-                                Integer uid = breakpoints.get(pending);
-                                pending.dispose();
-                                Breakpoint replacement = null;
-                                if (pending instanceof PendingLineBreakpoint) {
-                                    final PendingLineBreakpoint lineBreak = (PendingLineBreakpoint) pending;
-                                    replacement = db.setLineBreakpoint(lineBreak.getIgnoreCount(), lineBreak.getLineLocation(), lineBreak.isOneShot());
+    private final EventConsumer<ExecutionEvent> onExec = new EventConsumer<ExecutionEvent>(ExecutionEvent.class) {
+        @Override
+        protected void on(ExecutionEvent event) {
+            if (db == null) {
+                db = event.getDebugger();
+                if (!breakpoints.isEmpty()) {
+                    ArrayList<? extends Breakpoint> pendingBreakpoints = new ArrayList<>(breakpoints.keySet());
+                    try {
+                        for (Breakpoint pending : pendingBreakpoints) {
 
-                                } else if (pending instanceof PendingTagBreakpoint) {
-                                    final PendingTagBreakpoint tagBreak = (PendingTagBreakpoint) pending;
-                                    replacement = db.setTagBreakpoint(tagBreak.getIgnoreCount(), tagBreak.getTag(), tagBreak.isOneShot());
-                                }
-                                breakpoints.put(replacement, uid);
+                            Integer uid = breakpoints.get(pending);
+                            pending.dispose();
+                            Breakpoint replacement = null;
+                            if (pending instanceof PendingLineBreakpoint) {
+                                final PendingLineBreakpoint lineBreak = (PendingLineBreakpoint) pending;
+                                replacement = db.setLineBreakpoint(lineBreak.getIgnoreCount(), lineBreak.getLineLocation(), lineBreak.isOneShot());
+
+                            } else if (pending instanceof PendingTagBreakpoint) {
+                                final PendingTagBreakpoint tagBreak = (PendingTagBreakpoint) pending;
+                                replacement = db.setTagBreakpoint(tagBreak.getIgnoreCount(), tagBreak.getTag(), tagBreak.isOneShot());
                             }
-                        } catch (IOException e) {
-                            throw new IllegalStateException("pending breakpoints should all be valid");
+                            breakpoints.put(replacement, uid);
                         }
+                    } catch (IOException e) {
+                        throw new IllegalStateException("pending breakpoints should all be valid");
                     }
                 }
-                if (!currentServerContext.isEval) {
-                    event.prepareStepInto();
-                }
             }
-        };
-        engine = PolyglotEngine.newBuilder().onEvent(onHalted).onEvent(onExec).build();
-        this.language = engine.getLanguages().get(mimeType);
-        if (language == null) {
-            throw new RuntimeException("Implementation not found for \"" + mimeType + "\"");
+            if (!currentServerContext.isEval) {
+                event.prepareStepInto();
+            }
         }
-        statusPrefix = languageName(language);
-    }
+    };
 
     public void add(REPLHandler handler) {
         handlerMap.put(handler.getOp(), handler);
@@ -171,6 +194,7 @@
         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);
@@ -178,17 +202,38 @@
         add(REPLHandler.TRUFFLE_NODE_HANDLER);
         add(REPLHandler.UNSET_BREAK_CONDITION_HANDLER);
         this.replClient = new SimpleREPLClient(this);
-        this.currentServerContext = new Context(null, null);
+        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) {
-        // Create and push a new debug context where execution is halted
-        currentServerContext = new Context(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);
+
+        // 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());
@@ -199,6 +244,7 @@
             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();
@@ -221,6 +267,19 @@
         }
     }
 
+    @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.
      */
@@ -229,12 +288,15 @@
         private final Context predecessor;
         private final int level;
         private final SuspendedEvent event;
+        private Language currentLanguage;
         private boolean isEval = false;  // When true, run without StepInto
 
-        Context(Context predecessor, SuspendedEvent event) {
+        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;
         }
 
         /**
@@ -265,6 +327,7 @@
             if (event == null) {
                 try {
                     isEval = true;
+                    final String mimeType = defaultMIME(currentLanguage);
                     final Value value = engine.eval(Source.fromText(code, "eval(\"" + code + "\")").withMimeType(mimeType));
                     return value.get();
                 } finally {
@@ -318,6 +381,22 @@
             return Collections.unmodifiableList(frames);
         }
 
+        public String getLanguageName() {
+            return currentLanguage.getName();
+        }
+
+        /**
+         * Case-insensitive; returns actual language name set.
+         */
+        String setLanguage(String name) {
+            final Language language = nameToLanguage.get(name);
+            if (language == null) {
+                return null;
+            }
+            this.currentLanguage = language;
+            return language.getName();
+        }
+
         void prepareStepOut() {
             event.prepareStepOut();
         }
@@ -333,6 +412,7 @@
         void prepareContinue() {
             event.prepareContinue();
         }
+
     }
 
     /**
@@ -361,18 +441,27 @@
 
     // TODO (mlvdv) language-specific
     Language getLanguage() {
-        return language;
+        return defaultLanguage;
+    }
+
+    TreeSet<Language> getLanguages() {
+        return engineLanguages;
     }
 
     // TODO (mlvdv) language-specific
     public String getLanguageName() {
-        return languageName(this.language);
+        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();
+    }
+
     void eval(Source source) throws IOException {
         engine.eval(source);
     }