diff graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLHandler.java @ 21568:3b8bbf51d320

Truffle/Debugging: add the Truffle DebugEngine and supporting code, as well as add a crude command-line debugging tool used mainly to test the DebugEngine. Migrate the small tols out of project com.oracle.truffle.api into the new project com.oracle.truffle.tools.
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Tue, 26 May 2015 16:38:13 -0700
parents
children 894f82515e38
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLHandler.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,600 @@
+/*
+ * 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.util.*;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.tools.debug.shell.*;
+import com.oracle.truffle.tools.debug.engine.*;
+
+/**
+ * Server-side REPL implementation of an {@linkplain REPLMessage "op"}.
+ * <p>
+ * The language-agnostic handlers are implemented here.
+ */
+public abstract class REPLHandler {
+
+    // TODO (mlvdv) add support for setting/using ignore count
+    private static final int DEFAULT_IGNORE_COUNT = 0;
+
+    // TODO (mlvdv) add support for setting/using groupId
+    private static final int DEFAULT_GROUP_ID = 0;
+
+    private final String op;
+
+    protected REPLHandler(String op) {
+        this.op = op;
+    }
+
+    /**
+     * Gets the "op" implemented by this handler.
+     */
+    public final String getOp() {
+        return op;
+    }
+
+    /**
+     * Passes a request to this handler.
+     */
+    public abstract REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext);
+
+    /**
+     * Creates skeleton for a reply message that identifies the operation currently being handled.
+     */
+    protected final 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) {
+        reply.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
+        reply.put(REPLMessage.DISPLAY_MSG, explanation);
+        final REPLMessage[] replies = new REPLMessage[]{reply};
+        return replies;
+    }
+
+    /**
+     * Completes a reply, reporting and explaining failed handling.
+     */
+    protected static final REPLMessage[] finishReplyFailed(REPLMessage reply, String explanation) {
+        reply.put(REPLMessage.STATUS, REPLMessage.FAILED);
+        reply.put(REPLMessage.DISPLAY_MSG, explanation);
+        final REPLMessage[] replies = new REPLMessage[]{reply};
+        return replies;
+    }
+
+    protected static REPLMessage createBreakpointInfoMessage(Breakpoint breakpoint) {
+        final REPLMessage infoMessage = new REPLMessage(REPLMessage.OP, REPLMessage.BREAKPOINT_INFO);
+        infoMessage.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpoint.getId()));
+        infoMessage.put(REPLMessage.BREAKPOINT_GROUP_ID, Integer.toString(breakpoint.getGroupId()));
+        infoMessage.put(REPLMessage.BREAKPOINT_STATE, breakpoint.getState().toString());
+        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());
+        }
+        infoMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
+        return infoMessage;
+    }
+
+    protected static REPLMessage createFrameInfoMessage(final REPLServerContext serverContext, FrameDebugDescription frame) {
+        final Visualizer visualizer = serverContext.getLanguageContext().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.SOURCE_LOCATION, visualizer.displaySourceLocation(node));
+        infoMessage.put(REPLMessage.METHOD_NAME, visualizer.displayMethodName(node));
+
+        if (node != null) {
+            SourceSection section = node.getSourceSection();
+            if (section == null) {
+                section = node.getEncapsulatingSourceSection();
+                if (section != 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()));
+                }
+            }
+        }
+        infoMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED);
+        return infoMessage;
+    }
+
+    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.getDebugEngine().getStack()) {
+                frameMessages.add(createFrameInfoMessage(serverContext, frame));
+            }
+            if (frameMessages.size() > 0) {
+                return frameMessages.toArray(new REPLMessage[0]);
+            }
+            return finishReplyFailed(reply, "No stack");
+        }
+    };
+
+    public static final REPLHandler BREAK_AT_LINE_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_LINE) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            final String path = request.get(REPLMessage.FILE_PATH);
+            final String fileName = request.get(REPLMessage.SOURCE_NAME);
+            final String lookupFile = (path == null || path.isEmpty()) ? fileName : path;
+            Source source = null;
+            try {
+                source = Source.fromFileName(lookupFile, true);
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex.getMessage());
+            }
+            if (source == null) {
+                return finishReplyFailed(reply, fileName + " not found");
+            }
+            Integer lineNumber = request.getIntValue(REPLMessage.LINE_NUMBER);
+            if (lineNumber == null) {
+                return finishReplyFailed(reply, "missing line number");
+            }
+            Integer ignoreCount = request.getIntValue(REPLMessage.BREAKPOINT_IGNORE_COUNT);
+            if (ignoreCount == null) {
+                ignoreCount = 0;
+            }
+            LineBreakpoint breakpoint;
+            try {
+                breakpoint = serverContext.getDebugEngine().setLineBreakpoint(DEFAULT_GROUP_ID, DEFAULT_IGNORE_COUNT, source.createLineLocation(lineNumber), false);
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex.getMessage());
+            }
+            reply.put(REPLMessage.SOURCE_NAME, fileName);
+            reply.put(REPLMessage.FILE_PATH, source.getPath());
+            reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpoint.getId()));
+            reply.put(REPLMessage.LINE_NUMBER, Integer.toString(lineNumber));
+            reply.put(REPLMessage.BREAKPOINT_IGNORE_COUNT, ignoreCount.toString());
+            return finishReplySucceeded(reply, "Breakpoint set");
+        }
+    };
+
+    public static final REPLHandler BREAK_AT_LINE_ONCE_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_LINE_ONCE) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            final String path = request.get(REPLMessage.FILE_PATH);
+            final String fileName = request.get(REPLMessage.SOURCE_NAME);
+            final String lookupFile = (path == null || path.isEmpty()) ? fileName : path;
+            Source source = null;
+            try {
+                source = Source.fromFileName(lookupFile, true);
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex.getMessage());
+            }
+            if (source == null) {
+                return finishReplyFailed(reply, fileName + " not found");
+            }
+            Integer lineNumber = request.getIntValue(REPLMessage.LINE_NUMBER);
+            if (lineNumber == null) {
+                return finishReplyFailed(reply, "missing line number");
+            }
+            try {
+                serverContext.getDebugEngine().setLineBreakpoint(DEFAULT_GROUP_ID, DEFAULT_IGNORE_COUNT, source.createLineLocation(lineNumber), true);
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex.getMessage());
+            }
+            reply.put(REPLMessage.SOURCE_NAME, fileName);
+            reply.put(REPLMessage.FILE_PATH, source.getPath());
+            reply.put(REPLMessage.LINE_NUMBER, Integer.toString(lineNumber));
+            return finishReplySucceeded(reply, "One-shot line breakpoint set");
+        }
+    };
+
+    public static final REPLHandler BREAK_AT_THROW_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_THROW) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            try {
+                serverContext.getDebugEngine().setTagBreakpoint(DEFAULT_GROUP_ID, DEFAULT_IGNORE_COUNT, StandardSyntaxTag.THROW, false);
+                return finishReplySucceeded(reply, "Breakpoint at any throw set");
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex.getMessage());
+            }
+        }
+    };
+
+    public static final REPLHandler BREAK_AT_THROW_ONCE_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_THROW_ONCE) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            try {
+                serverContext.getDebugEngine().setTagBreakpoint(DEFAULT_GROUP_ID, DEFAULT_IGNORE_COUNT, StandardSyntaxTag.THROW, true);
+                return finishReplySucceeded(reply, "One-shot breakpoint at any throw set");
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex.getMessage());
+            }
+        }
+    };
+
+    public static final REPLHandler BREAKPOINT_INFO_HANDLER = new REPLHandler(REPLMessage.BREAKPOINT_INFO) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            final ArrayList<REPLMessage> infoMessages = new ArrayList<>();
+            for (Breakpoint breakpoint : serverContext.getDebugEngine().getBreakpoints()) {
+                infoMessages.add(createBreakpointInfoMessage(breakpoint));
+            }
+            if (infoMessages.size() > 0) {
+                return infoMessages.toArray(new REPLMessage[0]);
+            }
+            return finishReplyFailed(reply, "No breakpoints");
+        }
+    };
+
+    public static final REPLHandler CLEAR_BREAK_HANDLER = new REPLHandler(REPLMessage.CLEAR_BREAK) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID);
+            if (breakpointNumber == null) {
+                return finishReplyFailed(reply, "missing breakpoint number");
+            }
+            final Breakpoint breakpoint = serverContext.getDebugEngine().findBreakpoint(breakpointNumber);
+            if (breakpoint == null) {
+                return finishReplyFailed(reply, "no breakpoint number " + breakpointNumber);
+            }
+            breakpoint.dispose();
+            reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
+            return finishReplySucceeded(reply, "Breakpoint " + breakpointNumber + " cleared");
+        }
+    };
+
+    public static final REPLHandler CONTINUE_HANDLER = new REPLHandler(REPLMessage.CONTINUE) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            serverContext.getDebugEngine().prepareContinue();
+            return finishReplySucceeded(reply, "Continue mode entered");
+        }
+    };
+
+    public static final REPLHandler DELETE_HANDLER = new REPLHandler(REPLMessage.DELETE_BREAK) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            int deleteCount = 0;
+            for (Breakpoint breakpoint : serverContext.getDebugEngine().getBreakpoints()) {
+                breakpoint.dispose();
+                deleteCount++;
+            }
+            if (deleteCount == 0) {
+                return finishReplyFailed(reply, "no breakpoints to delete");
+            }
+            return finishReplySucceeded(reply, Integer.toString(deleteCount) + " breakpoints deleted");
+        }
+    };
+
+    public static final REPLHandler DISABLE_BREAK_HANDLER = new REPLHandler(REPLMessage.DISABLE_BREAK) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID);
+            if (breakpointNumber == null) {
+                return finishReplyFailed(reply, "missing breakpoint number");
+            }
+            final Breakpoint breakpoint = serverContext.getDebugEngine().findBreakpoint(breakpointNumber);
+            if (breakpoint == null) {
+                return finishReplyFailed(reply, "no breakpoint number " + breakpointNumber);
+            }
+            breakpoint.setEnabled(false);
+            reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
+            return finishReplySucceeded(reply, "Breakpoint " + breakpointNumber + " disabled");
+        }
+    };
+
+    public static final REPLHandler ENABLE_BREAK_HANDLER = new REPLHandler(REPLMessage.ENABLE_BREAK) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID);
+            if (breakpointNumber == null) {
+                return finishReplyFailed(reply, "missing breakpoint number");
+            }
+            final Breakpoint breakpoint = serverContext.getDebugEngine().findBreakpoint(breakpointNumber);
+            if (breakpoint == null) {
+                return finishReplyFailed(reply, "no breakpoint number " + breakpointNumber);
+            }
+            breakpoint.setEnabled(true);
+            reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
+            return finishReplySucceeded(reply, "Breakpoint " + breakpointNumber + " enabled");
+        }
+    };
+
+    public static final REPLHandler FILE_HANDLER = new REPLHandler(REPLMessage.FILE) {
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            final String fileName = request.get(REPLMessage.SOURCE_NAME);
+            if (fileName == null) {
+                return finishReplyFailed(reply, "no file specified");
+            }
+            try {
+                Source source = Source.fromFileName(fileName);
+                if (source == null) {
+                    reply.put(REPLMessage.SOURCE_NAME, fileName);
+                    return finishReplyFailed(reply, " 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 (Exception ex) {
+                reply.put(REPLMessage.SOURCE_NAME, fileName);
+                return finishReplyFailed(reply, "file \"" + fileName + "\" not found");
+            }
+        }
+    };
+
+    // TODO (mlvdv) deal with slot locals explicitly
+    /**
+     * Returns a general description of the frame, plus a textual summary of the slot values: one
+     * per line.
+     */
+    public static final REPLHandler FRAME_HANDLER = new REPLHandler(REPLMessage.FRAME) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            final Integer frameNumber = request.getIntValue(REPLMessage.FRAME_NUMBER);
+            if (frameNumber == null) {
+                return finishReplyFailed(reply, "no frame number specified");
+            }
+            final List<FrameDebugDescription> stack = serverContext.getDebugEngine().getStack();
+            if (frameNumber < 0 || frameNumber >= stack.size()) {
+                return finishReplyFailed(reply, "frame number " + frameNumber + " out of range");
+            }
+            final FrameDebugDescription frameDescription = stack.get(frameNumber);
+            final REPLMessage frameMessage = createFrameInfoMessage(serverContext, frameDescription);
+            final Frame frame = frameDescription.frameInstance().getFrame(FrameInstance.FrameAccess.READ_ONLY, true);
+            final ExecutionContext context = serverContext.getLanguageContext();
+            final FrameDescriptor frameDescriptor = frame.getFrameDescriptor();
+            try {
+                final StringBuilder sb = new StringBuilder();
+                for (FrameSlot slot : frameDescriptor.getSlots()) {
+                    sb.append(Integer.toString(slot.getIndex()) + ": " + context.getVisualizer().displayIdentifier(slot) + " = ");
+                    try {
+                        final Object value = frame.getValue(slot);
+                        sb.append(context.getVisualizer().displayValue(context, value, 0));
+                    } catch (Exception ex) {
+                        sb.append("???");
+                    }
+                    sb.append("\n");
+                }
+                return finishReplySucceeded(frameMessage, sb.toString());
+            } catch (Exception ex) {
+                return finishReplyFailed(frameMessage, ex.toString());
+            }
+        }
+    };
+
+    public static final REPLHandler KILL_HANDLER = new REPLHandler(REPLMessage.KILL) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            if (serverContext.getLevel() == 0) {
+                return finishReplyFailed(createReply(), "nothing to kill");
+            }
+            throw new KillException();
+        }
+    };
+
+    public static final REPLHandler QUIT_HANDLER = new REPLHandler(REPLMessage.QUIT) {
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            throw new QuitException();
+        }
+    };
+
+    public static final REPLHandler SET_BREAK_CONDITION_HANDLER = new REPLHandler(REPLMessage.SET_BREAK_CONDITION) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            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.getDebugEngine().findBreakpoint(breakpointNumber);
+            if (breakpoint == null) {
+                return finishReplyFailed(message, "no breakpoint number " + breakpointNumber);
+            }
+            final String expr = request.get(REPLMessage.BREAKPOINT_CONDITION);
+            if (expr == null || expr.isEmpty()) {
+                return finishReplyFailed(message, "missing condition for " + breakpointNumber);
+            }
+            try {
+                breakpoint.setCondition(expr);
+            } catch (DebugException ex) {
+                return finishReplyFailed(message, "invalid condition for " + breakpointNumber);
+            } catch (UnsupportedOperationException ex) {
+                return finishReplyFailed(message, "conditions not unsupported by breakpoint " + breakpointNumber);
+            }
+            message.put(REPLMessage.BREAKPOINT_CONDITION, expr);
+            return finishReplySucceeded(message, "Breakpoint " + breakpointNumber + " condition=\"" + expr + "\"");
+        }
+    };
+
+    public static final REPLHandler STEP_INTO_HANDLER = new REPLHandler(REPLMessage.STEP_INTO) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            Integer repeat = request.getIntValue(REPLMessage.REPEAT);
+            if (repeat == null) {
+                repeat = 1;
+            }
+            serverContext.getDebugEngine().prepareStepInto(repeat);
+            return finishReplySucceeded(reply, "StepInto <" + repeat + "> enabled");
+        }
+    };
+
+    public static final REPLHandler STEP_OUT_HANDLER = new REPLHandler(REPLMessage.STEP_OUT) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            serverContext.getDebugEngine().prepareStepOut();
+            return finishReplySucceeded(createReply(), "StepOut enabled");
+        }
+    };
+
+    public static final REPLHandler STEP_OVER_HANDLER = new REPLHandler(REPLMessage.STEP_OVER) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext debugServerContextFrame) {
+            final REPLMessage reply = createReply();
+            Integer repeat = request.getIntValue(REPLMessage.REPEAT);
+            if (repeat == null) {
+                repeat = 1;
+            }
+            debugServerContextFrame.getDebugEngine().prepareStepOver(repeat);
+            return finishReplySucceeded(reply, "StepOver <" + repeat + "> enabled");
+        }
+    };
+
+    public static final REPLHandler TRUFFLE_HANDLER = new REPLHandler(REPLMessage.TRUFFLE) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            final REPLMessage reply = createReply();
+            final ASTPrinter astPrinter = serverContext.getLanguageContext().getVisualizer().getASTPrinter();
+            final String topic = request.get(REPLMessage.TOPIC);
+            reply.put(REPLMessage.TOPIC, topic);
+            Node node = serverContext.getNode();
+            if (node == null) {
+                return finishReplyFailed(reply, "no current AST node");
+            }
+            final Integer depth = request.getIntValue(REPLMessage.AST_DEPTH);
+            if (depth == null) {
+                return finishReplyFailed(reply, "missing AST depth");
+            }
+            try {
+                switch (topic) {
+                    case REPLMessage.AST:
+                        while (node.getParent() != null) {
+                            node = node.getParent();
+                        }
+                        final String astText = astPrinter.printTreeToString(node, depth, serverContext.getNode());
+                        return finishReplySucceeded(reply, astText);
+                    case REPLMessage.SUBTREE:
+                    case REPLMessage.SUB:
+                        final String subTreeText = astPrinter.printTreeToString(node, depth);
+                        return finishReplySucceeded(reply, subTreeText);
+                    default:
+                        return finishReplyFailed(reply, "Unknown \"" + REPLMessage.TRUFFLE.toString() + "\" topic");
+                }
+
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex.toString());
+            }
+        }
+    };
+
+    public static final REPLHandler UNSET_BREAK_CONDITION_HANDLER = new REPLHandler(REPLMessage.UNSET_BREAK_CONDITION) {
+
+        @Override
+        public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) {
+            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.getDebugEngine().findBreakpoint(breakpointNumber);
+            if (breakpoint == null) {
+                return finishReplyFailed(message, "no breakpoint number " + breakpointNumber);
+            }
+            try {
+                breakpoint.setCondition(null);
+            } catch (DebugException e) {
+                return finishReplyFailed(message, e.getMessage());
+            }
+            return finishReplyFailed(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) {
+            final REPLMessage reply = createReply();
+            final ASTPrinter astPrinter = serverContext.getLanguageContext().getVisualizer().getASTPrinter();
+            final Node node = serverContext.getNode();
+            if (node == null) {
+                return finishReplyFailed(reply, "no current AST node");
+            }
+
+            try {
+                final StringBuilder sb = new StringBuilder();
+                sb.append(astPrinter.printNodeWithInstrumentation(node));
+
+                final SourceSection sourceSection = node.getSourceSection();
+                if (sourceSection != null) {
+                    final String code = sourceSection.getCode();
+                    sb.append(" \"");
+                    sb.append(code.substring(0, Math.min(code.length(), 15)));
+                    sb.append("...\"");
+                }
+                return finishReplySucceeded(reply, sb.toString());
+            } catch (Exception ex) {
+                return finishReplyFailed(reply, ex.toString());
+            }
+        }
+    };
+}