changeset 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 1bbef57f9a38
children 23bc51cd8654
files graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/CoverageTrackerTest.java graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/LineToProbesMapTest.java graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/NodeExecCounterTest.java graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TestNodes.java graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TruffleToolTest.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/CoverageTracker.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/LineToProbesMap.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/NodeExecCounter.java graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLHandler.java graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLServer.java graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLSourceExecutionProvider.java graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLMain.java graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/Breakpoint.java graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugClient.java graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugEngine.java graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugException.java graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/FrameDebugDescription.java graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/LineBreakpoint.java graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/LineBreakpointFactory.java graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/SourceExecutionProvider.java graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/TagBreakpoint.java graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/TagBreakpointFactory.java graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/package-info.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLClient.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLMessage.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLServer.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLClientContext.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLCommand.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLContinueException.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLFrame.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLRemoteCommand.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLineLocation.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/package-info.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLHandler.java graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServerContext.java graal/com.oracle.truffle.tools.test/src/com/oracle/truffle/tools/test/CoverageTrackerTest.java graal/com.oracle.truffle.tools.test/src/com/oracle/truffle/tools/test/LineToProbesMapTest.java graal/com.oracle.truffle.tools.test/src/com/oracle/truffle/tools/test/NodeExecCounterTest.java graal/com.oracle.truffle.tools.test/src/com/oracle/truffle/tools/test/TestNodes.java graal/com.oracle.truffle.tools.test/src/com/oracle/truffle/tools/test/TruffleToolTest.java graal/com.oracle.truffle.tools/src/com/oracle/truffle/tools/CoverageTracker.java graal/com.oracle.truffle.tools/src/com/oracle/truffle/tools/LineToProbesMap.java graal/com.oracle.truffle.tools/src/com/oracle/truffle/tools/NodeExecCounter.java mx/mx_graal.py mx/suite.py
diffstat 46 files changed, 7943 insertions(+), 1493 deletions(-) [+]
line wrap: on
line diff
--- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/CoverageTrackerTest.java	Fri May 22 10:20:38 2015 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +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.
- *
- * 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.api.test.tools;
-
-import static com.oracle.truffle.api.test.tools.TestNodes.*;
-import static org.junit.Assert.*;
-
-import org.junit.*;
-
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.tools.*;
-
-public class CoverageTrackerTest {
-
-    @Test
-    public void testNoExecution() {
-        final CoverageTracker tool = new CoverageTracker();
-        assertEquals(tool.getCounts().entrySet().size(), 0);
-        tool.install();
-        assertEquals(tool.getCounts().entrySet().size(), 0);
-        tool.setEnabled(false);
-        assertEquals(tool.getCounts().entrySet().size(), 0);
-        tool.setEnabled(true);
-        assertEquals(tool.getCounts().entrySet().size(), 0);
-        tool.reset();
-        assertEquals(tool.getCounts().entrySet().size(), 0);
-        tool.dispose();
-        assertEquals(tool.getCounts().entrySet().size(), 0);
-    }
-
-    @Test
-    public void testToolCreatedTooLate() {
-        final RootNode expr13rootNode = createExpr13TestRootNode();
-        final CoverageTracker tool = new CoverageTracker();
-        tool.install();
-        assertEquals(13, expr13rootNode.execute(null));
-        assertTrue(tool.getCounts().isEmpty());
-        tool.dispose();
-    }
-
-    @Test
-    public void testToolInstalledcTooLate() {
-        final CoverageTracker tool = new CoverageTracker();
-        final RootNode expr13rootNode = createExpr13TestRootNode();
-        tool.install();
-        assertEquals(13, expr13rootNode.execute(null));
-        assertTrue(tool.getCounts().isEmpty());
-        tool.dispose();
-    }
-
-    @Test
-    public void testCountingCoverage() {
-        final CoverageTracker tool = new CoverageTracker();
-        tool.install();
-        final RootNode expr13rootNode = createExpr13TestRootNode();
-
-        // Not probed yet.
-        assertEquals(13, expr13rootNode.execute(null));
-        assertTrue(tool.getCounts().isEmpty());
-
-        final Node addNode = expr13rootNode.getChildren().iterator().next();
-        final Probe probe = addNode.probe();
-
-        // Probed but not tagged yet.
-        assertEquals(13, expr13rootNode.execute(null));
-        assertTrue(tool.getCounts().isEmpty());
-
-        probe.tagAs(StandardSyntaxTag.STATEMENT, "fake statement for testing");
-
-        // Counting now; execute once
-        assertEquals(13, expr13rootNode.execute(null));
-
-        final Long[] longs1 = tool.getCounts().get(addNode.getSourceSection().getSource());
-        assertNotNull(longs1);
-        assertEquals(longs1.length, 2);
-        assertNull(longs1[0]);  // Line 1 is empty (text lines are 1-based)
-        assertEquals(1L, longs1[1].longValue());  // Expression is on line 2
-
-        // Execute 99 more times
-        for (int i = 0; i < 99; i++) {
-            assertEquals(13, expr13rootNode.execute(null));
-        }
-
-        final Long[] longs100 = tool.getCounts().get(addNode.getSourceSection().getSource());
-        assertNotNull(longs100);
-        assertEquals(longs100.length, 2);
-        assertNull(longs100[0]);
-        assertEquals(100L, longs100[1].longValue());
-
-        tool.dispose();
-    }
-
-}
--- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/LineToProbesMapTest.java	Fri May 22 10:20:38 2015 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +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.
- *
- * 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.api.test.tools;
-
-import static com.oracle.truffle.api.test.tools.TestNodes.*;
-import static org.junit.Assert.*;
-
-import org.junit.*;
-
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.source.*;
-import com.oracle.truffle.api.tools.*;
-
-public class LineToProbesMapTest {
-
-    @Test
-    public void testToolCreatedTooLate() {
-        final RootNode expr13rootNode = createExpr13TestRootNode();
-        final Node addNode = expr13rootNode.getChildren().iterator().next();
-        final Probe probe = addNode.probe();
-        final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation();
-        assertEquals(lineLocation, expr13Line2);
-
-        final LineToProbesMap tool = new LineToProbesMap();
-        tool.install();
-
-        assertNull(tool.findFirstProbe(expr13Line1));
-        assertNull(tool.findFirstProbe(expr13Line2));
-        tool.dispose();
-    }
-
-    @Test
-    public void testToolInstalledTooLate() {
-        final LineToProbesMap tool = new LineToProbesMap();
-
-        final RootNode expr13rootNode = createExpr13TestRootNode();
-        final Node addNode = expr13rootNode.getChildren().iterator().next();
-        final Probe probe = addNode.probe();
-        final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation();
-        assertEquals(lineLocation, expr13Line2);
-
-        tool.install();
-
-        assertNull(tool.findFirstProbe(expr13Line1));
-        assertNull(tool.findFirstProbe(expr13Line2));
-        tool.dispose();
-    }
-
-    @Test
-    public void testMapping() {
-        final LineToProbesMap tool = new LineToProbesMap();
-        tool.install();
-
-        final RootNode expr13rootNode = createExpr13TestRootNode();
-        final Node addNode = expr13rootNode.getChildren().iterator().next();
-        final Probe probe = addNode.probe();
-        final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation();
-        assertEquals(lineLocation, expr13Line2);
-
-        assertNull(tool.findFirstProbe(expr13Line1));
-        assertEquals(tool.findFirstProbe(expr13Line2), probe);
-        tool.dispose();
-    }
-
-}
--- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/NodeExecCounterTest.java	Fri May 22 10:20:38 2015 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,165 +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.
- *
- * 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.api.test.tools;
-
-import static com.oracle.truffle.api.test.tools.TestNodes.*;
-import static org.junit.Assert.*;
-
-import org.junit.*;
-
-import com.oracle.truffle.api.*;
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.test.tools.TestNodes.TestAddNode;
-import com.oracle.truffle.api.test.tools.TestNodes.TestValueNode;
-import com.oracle.truffle.api.tools.*;
-import com.oracle.truffle.api.tools.NodeExecCounter.NodeExecutionCount;
-
-public class NodeExecCounterTest {
-
-    @Test
-    public void testNoExecution() {
-        final NodeExecCounter tool = new NodeExecCounter();
-        assertEquals(tool.getCounts().length, 0);
-        tool.install();
-        assertEquals(tool.getCounts().length, 0);
-        tool.setEnabled(false);
-        assertEquals(tool.getCounts().length, 0);
-        tool.setEnabled(true);
-        assertEquals(tool.getCounts().length, 0);
-        tool.reset();
-        assertEquals(tool.getCounts().length, 0);
-        tool.dispose();
-        assertEquals(tool.getCounts().length, 0);
-    }
-
-    @Test
-    public void testToolCreatedTooLate() {
-        final CallTarget expr13callTarget = createExpr13TestCallTarget();
-        final NodeExecCounter tool = new NodeExecCounter();
-        tool.install();
-        assertEquals(13, expr13callTarget.call());
-        assertEquals(tool.getCounts().length, 0);
-        tool.dispose();
-    }
-
-    @Test
-    public void testToolInstalledcTooLate() {
-        final NodeExecCounter tool = new NodeExecCounter();
-        final CallTarget expr13callTarget = createExpr13TestCallTarget();
-        tool.install();
-        assertEquals(13, expr13callTarget.call());
-        assertEquals(tool.getCounts().length, 0);
-        tool.dispose();
-    }
-
-    @Test
-    public void testCountingAll() {
-        final NodeExecCounter tool = new NodeExecCounter();
-        tool.install();
-        final CallTarget expr13callTarget = createExpr13TestCallTarget();
-
-        // execute once
-        assertEquals(13, expr13callTarget.call());
-        final NodeExecutionCount[] count1 = tool.getCounts();
-        assertNotNull(count1);
-        assertEquals(count1.length, 2);
-        for (NodeExecutionCount count : count1) {
-            final Class<?> class1 = count.nodeClass();
-            final long executionCount = count.executionCount();
-            if (class1 == TestAddNode.class) {
-                assertEquals(executionCount, 1);
-            } else if (class1 == TestValueNode.class) {
-                assertEquals(executionCount, 2);
-            } else {
-                fail();
-            }
-        }
-
-        // Execute 99 more times
-        for (int i = 0; i < 99; i++) {
-            assertEquals(13, expr13callTarget.call());
-        }
-        final NodeExecutionCount[] counts100 = tool.getCounts();
-        assertNotNull(counts100);
-        assertEquals(counts100.length, 2);
-        for (NodeExecutionCount count : counts100) {
-            final Class<?> class1 = count.nodeClass();
-            final long executionCount = count.executionCount();
-            if (class1 == TestAddNode.class) {
-                assertEquals(executionCount, 100);
-            } else if (class1 == TestValueNode.class) {
-                assertEquals(executionCount, 200);
-            } else {
-                fail();
-            }
-        }
-
-        tool.dispose();
-    }
-
-    @Test
-    public void testCountingTagged() {
-        final NodeExecCounter tool = new NodeExecCounter(StandardSyntaxTag.STATEMENT);
-        tool.install();
-        final RootNode expr13rootNode = createExpr13TestRootNode();
-
-        // Not probed yet.
-        assertEquals(13, expr13rootNode.execute(null));
-        assertEquals(tool.getCounts().length, 0);
-
-        final Node addNode = expr13rootNode.getChildren().iterator().next();
-        final Probe probe = addNode.probe();
-
-        // Probed but not tagged yet.
-        assertEquals(13, expr13rootNode.execute(null));
-        assertEquals(tool.getCounts().length, 0);
-
-        probe.tagAs(StandardSyntaxTag.STATEMENT, "fake statement for testing");
-
-        // Counting now; execute once
-        assertEquals(13, expr13rootNode.execute(null));
-        final NodeExecutionCount[] counts1 = tool.getCounts();
-        assertNotNull(counts1);
-        assertEquals(counts1.length, 1);
-        final NodeExecutionCount count1 = counts1[0];
-        assertNotNull(count1);
-        assertEquals(count1.nodeClass(), addNode.getClass());
-        assertEquals(count1.executionCount(), 1);
-
-        // Execute 99 more times
-        for (int i = 0; i < 99; i++) {
-            assertEquals(13, expr13rootNode.execute(null));
-        }
-
-        final NodeExecutionCount[] counts100 = tool.getCounts();
-        assertNotNull(counts100);
-        assertEquals(counts100.length, 1);
-        final NodeExecutionCount count100 = counts100[0];
-        assertNotNull(count100);
-        assertEquals(count100.nodeClass(), addNode.getClass());
-        assertEquals(count100.executionCount(), 100);
-
-        tool.dispose();
-    }
-}
--- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TestNodes.java	Fri May 22 10:20:38 2015 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,217 +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.
- *
- * 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.api.test.tools;
-
-import com.oracle.truffle.api.*;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.instrument.ProbeNode.WrapperNode;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.source.*;
-
-/**
- * Nodes and an {@linkplain CallTarget executable ASTs} for testing.
- */
-class TestNodes {
-
-    /**
-     * A fake source used for testing: empty line 1, expression on line 2.
-     */
-    public static final Source expr13Source = Source.fromText("\n6+7\n", "Test Source: expression on line 2 that evaluates to 13");
-    public static final LineLocation expr13Line1 = expr13Source.createLineLocation(1);
-    public static final LineLocation expr13Line2 = expr13Source.createLineLocation(2);
-
-    /**
-     * An executable addition expression that evaluates to 13.
-     */
-    static CallTarget createExpr13TestCallTarget() {
-        final RootNode rootNode = createExpr13TestRootNode();
-        return Truffle.getRuntime().createCallTarget(rootNode);
-    }
-
-    /**
-     * Root holding an addition expression that evaluates to 13.
-     */
-    static RootNode createExpr13TestRootNode() {
-        final TestLanguageNode ast = createExpr13AST();
-        final TestRootNode rootNode = new TestRootNode(ast);
-        rootNode.adoptChildren();
-        return rootNode;
-    }
-
-    /**
-     * Addition expression that evaluates to 13, with faked source attribution.
-     */
-    static TestLanguageNode createExpr13AST() {
-        final SourceSection leftSourceSection = expr13Source.createSection("left", 1, 1);
-        final TestValueNode leftValueNode = new TestValueNode(6, leftSourceSection);
-        final SourceSection rightSourceSection = expr13Source.createSection("right", 3, 1);
-        final TestValueNode rightValueNode = new TestValueNode(7, rightSourceSection);
-        final SourceSection exprSourceSection = expr13Source.createSection("expr", 1, 3);
-        return new TestAddNode(leftValueNode, rightValueNode, exprSourceSection);
-    }
-
-    abstract static class TestLanguageNode extends Node {
-        public abstract Object execute(VirtualFrame frame);
-
-        public TestLanguageNode() {
-        }
-
-        public TestLanguageNode(SourceSection srcSection) {
-            super(srcSection);
-        }
-
-        @Override
-        public boolean isInstrumentable() {
-            return true;
-        }
-
-        @Override
-        public WrapperNode createWrapperNode() {
-            return new TestWrapperNode(this);
-        }
-    }
-
-    @NodeInfo(cost = NodeCost.NONE)
-    static class TestWrapperNode extends TestLanguageNode implements WrapperNode {
-        @Child private TestLanguageNode child;
-        @Child private ProbeNode probeNode;
-
-        public TestWrapperNode(TestLanguageNode child) {
-            assert !(child instanceof TestWrapperNode);
-            this.child = child;
-        }
-
-        @Override
-        public String instrumentationInfo() {
-            return "Wrapper node for testing";
-        }
-
-        @Override
-        public boolean isInstrumentable() {
-            return false;
-        }
-
-        @Override
-        public void insertProbe(ProbeNode newProbeNode) {
-            this.probeNode = newProbeNode;
-        }
-
-        @Override
-        public Probe getProbe() {
-            return probeNode.getProbe();
-        }
-
-        @Override
-        public Node getChild() {
-            return child;
-        }
-
-        @Override
-        public Object execute(VirtualFrame frame) {
-            probeNode.enter(child, frame);
-            Object result;
-
-            try {
-                result = child.execute(frame);
-                probeNode.returnValue(child, frame, result);
-            } catch (KillException e) {
-                throw (e);
-            } catch (Exception e) {
-                probeNode.returnExceptional(child, frame, e);
-                throw (e);
-            }
-
-            return result;
-        }
-    }
-
-    /**
-     * Truffle requires that all guest languages to have a {@link RootNode} which sits atop any AST
-     * of the guest language. This is necessary since creating a {@link CallTarget} is how Truffle
-     * completes an AST. The root nodes serves as our entry point into a program.
-     */
-    static class TestRootNode extends RootNode {
-        @Child private TestLanguageNode body;
-
-        /**
-         * This constructor emulates the global machinery that applies registered probers to every
-         * newly created AST. Global registry is not used, since that would interfere with other
-         * tests run in the same environment.
-         */
-        public TestRootNode(TestLanguageNode body) {
-            super(null);
-            this.body = body;
-        }
-
-        @Override
-        public Object execute(VirtualFrame frame) {
-            return body.execute(frame);
-        }
-
-        @Override
-        public boolean isCloningAllowed() {
-            return true;
-        }
-
-        @Override
-        public void applyInstrumentation() {
-            Probe.applyASTProbers(body);
-        }
-    }
-
-    static class TestValueNode extends TestLanguageNode {
-        private final int value;
-
-        public TestValueNode(int value) {
-            this.value = value;
-        }
-
-        public TestValueNode(int value, SourceSection srcSection) {
-            super(srcSection);
-            this.value = value;
-        }
-
-        @Override
-        public Object execute(VirtualFrame frame) {
-            return new Integer(this.value);
-        }
-    }
-
-    static class TestAddNode extends TestLanguageNode {
-        @Child private TestLanguageNode leftChild;
-        @Child private TestLanguageNode rightChild;
-
-        public TestAddNode(TestValueNode leftChild, TestValueNode rightChild, SourceSection sourceSection) {
-            super(sourceSection);
-            this.leftChild = insert(leftChild);
-            this.rightChild = insert(rightChild);
-        }
-
-        @Override
-        public Object execute(VirtualFrame frame) {
-            return new Integer(((Integer) leftChild.execute(frame)).intValue() + ((Integer) rightChild.execute(frame)).intValue());
-        }
-    }
-
-}
--- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TruffleToolTest.java	Fri May 22 10:20:38 2015 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +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.
- *
- * 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.api.test.tools;
-
-import static org.junit.Assert.*;
-
-import org.junit.*;
-
-import com.oracle.truffle.api.instrument.*;
-
-/**
- * Test the basic life cycle properties shared by all instances of {@link InstrumentationTool}.
- */
-public class TruffleToolTest {
-
-    @Test
-    public void testEmptyLifeCycle() {
-        final DummyTruffleTool tool = new DummyTruffleTool();
-        assertFalse(tool.isEnabled());
-        tool.install();
-        assertTrue(tool.isEnabled());
-        tool.reset();
-        assertTrue(tool.isEnabled());
-        tool.setEnabled(false);
-        assertFalse(tool.isEnabled());
-        tool.reset();
-        assertFalse(tool.isEnabled());
-        tool.setEnabled(true);
-        assertTrue(tool.isEnabled());
-        tool.reset();
-        assertTrue(tool.isEnabled());
-        tool.dispose();
-        assertFalse(tool.isEnabled());
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testNotYetInstalled1() {
-        final DummyTruffleTool tool = new DummyTruffleTool();
-        tool.reset();
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testNotYetInstalled2() {
-        final DummyTruffleTool tool = new DummyTruffleTool();
-        tool.setEnabled(true);
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testNotYetInstalled3() {
-        final DummyTruffleTool tool = new DummyTruffleTool();
-        tool.dispose();
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testAlreadyInstalled() {
-        final DummyTruffleTool tool = new DummyTruffleTool();
-        tool.install();
-        tool.install();
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testAlreadyDisposed1() {
-        final DummyTruffleTool tool = new DummyTruffleTool();
-        tool.install();
-        tool.dispose();
-        tool.install();
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testAlreadyDisposed2() {
-        final DummyTruffleTool tool = new DummyTruffleTool();
-        tool.install();
-        tool.dispose();
-        tool.reset();
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testAlreadyDisposed3() {
-        final DummyTruffleTool tool = new DummyTruffleTool();
-        tool.install();
-        tool.dispose();
-        tool.setEnabled(true);
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testAlreadyDisposed4() {
-        final DummyTruffleTool tool = new DummyTruffleTool();
-        tool.install();
-        tool.dispose();
-        tool.dispose();
-    }
-
-    private static final class DummyTruffleTool extends InstrumentationTool {
-
-        @Override
-        protected boolean internalInstall() {
-            return true;
-        }
-
-        @Override
-        protected void internalReset() {
-        }
-
-        @Override
-        protected void internalDispose() {
-        }
-
-    }
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/CoverageTracker.java	Fri May 22 10:20:38 2015 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,320 +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.api.tools;
-
-import java.io.*;
-import java.util.*;
-import java.util.Map.Entry;
-
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.instrument.impl.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.source.*;
-
-/**
- * An {@link InstrumentationTool} that counts interpreter <em>execution calls</em> to AST nodes that
- * hold a specified {@linkplain SyntaxTag syntax tag}, tabulated by source and line number
- * associated with each node. Syntax tags are presumed to be applied external to the tool. If no tag
- * is specified, {@linkplain StandardSyntaxTag#STATEMENT STATEMENT} is used, corresponding to
- * conventional behavior for code coverage tools.
- * <p>
- * No counts will be kept for execution in sources that hold the {@link SourceTag}
- * {@link Tags#NO_COVERAGE}.
- * <p>
- * <b>Tool Life Cycle</b>
- * <p>
- * See {@link InstrumentationTool} for the life cycle common to all such tools.
- * <p>
- * <b>Execution Counts</b>
- * <p>
- * <ul>
- * <li>"Execution call" on a node is is defined as invocation of a node method that is instrumented
- * to produce the event {@link SimpleInstrumentListener#enter(Probe)};</li>
- * <li>Execution calls are tabulated only at <em>instrumented</em> nodes, i.e. those for which
- * {@linkplain Node#isInstrumentable() isInstrumentable() == true};</li>
- * <li>Execution calls are tabulated only at nodes present in the AST when originally created;
- * dynamically added nodes will not be instrumented.</li>
- * </ul>
- * </p>
- * <b>Results</b>
- * <p>
- * A modification-safe copy of the {@linkplain #getCounts() counts} can be retrieved at any time,
- * without effect on the state of the tool.
- * </p>
- * <p>
- * A "default" {@linkplain #print(PrintStream) print()} method can summarizes the current counts at
- * any time in a simple textual format, with no other effect on the state of the tool.
- * </p>
- *
- * @see Instrument
- * @see SyntaxTag
- */
-public final class CoverageTracker extends InstrumentationTool {
-
-    public enum Tags implements SourceTag {
-
-        /**
-         * Report no counts for sources holding this tag.
-         */
-        NO_COVERAGE("No Coverage", "Coverage Tracker will igore");
-
-        private final String name;
-        private final String description;
-
-        private Tags(String name, String description) {
-            this.name = name;
-            this.description = description;
-        }
-
-        public String getName() {
-            return name;
-        }
-
-        public String getDescription() {
-            return description;
-        }
-    }
-
-    /** Counting data. */
-    private final Map<LineLocation, CoverageRecord> coverageMap = new HashMap<>();
-
-    /** Needed for disposal. */
-    private final List<Instrument> instruments = new ArrayList<>();
-
-    /**
-     * Coverage counting is restricted to nodes holding this tag.
-     */
-    private final SyntaxTag countingTag;
-
-    private final ProbeListener probeListener;
-
-    /**
-     * Create a per-line coverage tool for nodes tagged as {@linkplain StandardSyntaxTag#STATEMENT
-     * statements} in subsequently created ASTs.
-     */
-    public CoverageTracker() {
-        this(StandardSyntaxTag.STATEMENT);
-    }
-
-    /**
-     * Create a per-line coverage tool for nodes tagged as specified, presuming that tags applied
-     * outside this tool.
-     */
-    public CoverageTracker(SyntaxTag tag) {
-        this.probeListener = new CoverageProbeListener();
-        this.countingTag = tag;
-    }
-
-    @Override
-    protected boolean internalInstall() {
-        Probe.addProbeListener(probeListener);
-        return true;
-    }
-
-    @Override
-    protected void internalReset() {
-        coverageMap.clear();
-    }
-
-    @Override
-    protected void internalDispose() {
-        Probe.removeProbeListener(probeListener);
-        for (Instrument instrument : instruments) {
-            instrument.dispose();
-        }
-    }
-
-    /**
-     * Gets a modification-safe summary of the current per-type node execution counts; does not
-     * affect the counts.
-     * <p>
-     * The map holds an array for each source, and elements of the a array corresponding to the
-     * textual lines of that source. An array entry contains null if the corresponding line of
-     * source is associated with no nodes of the relevant type, i.e. where no count was made. A
-     * numeric entry represents the execution count at the corresponding line of source.
-     * <p>
-     * <b>Note:</b> source line numbers are 1-based, so array index {@code i} corresponds to source
-     * line number {@code i + 1}
-     */
-    public Map<Source, Long[]> getCounts() {
-
-        /**
-         * Counters for every {Source, line number} for which a counter was installed, i.e. for
-         * every line associated with an appropriately tagged AST node; iterable in order of source
-         * name, then line number.
-         */
-        final TreeSet<Entry<LineLocation, CoverageRecord>> entries = new TreeSet<>(new LineLocationEntryComparator());
-
-        for (Entry<LineLocation, CoverageRecord> entry : coverageMap.entrySet()) {
-            entries.add(entry);
-        }
-        final Map<Source, Long[]> result = new HashMap<>();
-        Source curSource = null;
-        Long[] curLineTable = null;
-        for (Entry<LineLocation, CoverageRecord> entry : entries) {
-            final LineLocation key = entry.getKey();
-            final Source source = key.getSource();
-            final int lineNo = key.getLineNumber();
-            if (source != curSource) {
-                if (curSource != null) {
-                    result.put(curSource, curLineTable);
-                }
-                curSource = source;
-                curLineTable = new Long[source.getLineCount()];
-            }
-            curLineTable[lineNo - 1] = entry.getValue().count;
-        }
-        if (curSource != null) {
-            result.put(curSource, curLineTable);
-        }
-        return result;
-    }
-
-    /**
-     * A default printer for the current line counts, producing lines of the form " (<count>) <line
-     * number> : <text of line>", grouped by source.
-     */
-    public void print(PrintStream out) {
-        out.println();
-        out.println(countingTag.name() + " coverage:");
-
-        /**
-         * Counters for every {Source, line number} for which a counter was installed, i.e. for
-         * every line associated with an appropriately tagged AST node; iterable in order of source
-         * name, then line number.
-         */
-        final TreeSet<Entry<LineLocation, CoverageRecord>> entries = new TreeSet<>(new LineLocationEntryComparator());
-
-        for (Entry<LineLocation, CoverageRecord> entry : coverageMap.entrySet()) {
-            entries.add(entry);
-        }
-        Source curSource = null;
-        int curLineNo = 1;
-        for (Entry<LineLocation, CoverageRecord> entry : entries) {
-            final LineLocation key = entry.getKey();
-            final Source source = key.getSource();
-            final int lineNo = key.getLineNumber();
-            if (source != curSource) {
-                if (curSource != null) {
-                    while (curLineNo <= curSource.getLineCount()) {
-                        displayLine(out, null, curSource, curLineNo++);
-                    }
-                }
-                curSource = source;
-                curLineNo = 1;
-                out.println();
-                out.println(source.getPath());
-            }
-            while (curLineNo < lineNo) {
-                displayLine(out, null, curSource, curLineNo++);
-            }
-            displayLine(out, entry.getValue(), curSource, curLineNo++);
-        }
-        if (curSource != null) {
-            while (curLineNo <= curSource.getLineCount()) {
-                displayLine(out, null, curSource, curLineNo++);
-            }
-        }
-    }
-
-    private static void displayLine(PrintStream out, CoverageRecord record, Source source, int lineNo) {
-        if (record == null) {
-            out.format("%14s", " ");
-        } else {
-            out.format("(%12d)", record.count);
-        }
-        out.format(" %3d: ", lineNo);
-        out.println(source.getCode(lineNo));
-    }
-
-    /**
-     * A listener for events at each instrumented AST location. This listener counts
-     * "execution calls" to the instrumented node.
-     */
-    private final class CoverageRecord extends DefaultSimpleInstrumentListener {
-
-        private final SourceSection srcSection; // The text of the code being counted
-        private Instrument instrument;  // The attached Instrument, in case need to remove.
-        private long count = 0;
-
-        CoverageRecord(SourceSection srcSection) {
-            this.srcSection = srcSection;
-        }
-
-        @Override
-        public void enter(Probe probe) {
-            if (isEnabled()) {
-                count++;
-            }
-        }
-
-    }
-
-    private static final class LineLocationEntryComparator implements Comparator<Entry<LineLocation, CoverageRecord>> {
-
-        public int compare(Entry<LineLocation, CoverageRecord> e1, Entry<LineLocation, CoverageRecord> e2) {
-            return LineLocation.COMPARATOR.compare(e1.getKey(), e2.getKey());
-        }
-    }
-
-    /**
-     * Attach a counting instrument to each node that is assigned a specified tag.
-     */
-    private class CoverageProbeListener extends DefaultProbeListener {
-
-        @Override
-        public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
-            if (countingTag == tag) {
-
-                final SourceSection srcSection = probe.getProbedSourceSection();
-                if (srcSection == null) {
-                    // TODO (mlvdv) report this?
-                } else if (!srcSection.getSource().isTaggedAs(Tags.NO_COVERAGE)) {
-                    // Get the source line where the
-                    final LineLocation lineLocation = srcSection.getLineLocation();
-                    CoverageRecord record = coverageMap.get(lineLocation);
-                    if (record != null) {
-                        // Another node starts on same line; count only the first (textually)
-                        if (srcSection.getCharIndex() > record.srcSection.getCharIndex()) {
-                            // Existing record, corresponds to code earlier on line
-                            return;
-                        } else {
-                            // Existing record, corresponds to code at a later position; replace it
-                            record.instrument.dispose();
-                        }
-                    }
-
-                    final CoverageRecord coverage = new CoverageRecord(srcSection);
-                    final Instrument instrument = Instrument.create(coverage, CoverageTracker.class.getSimpleName());
-                    coverage.instrument = instrument;
-                    instruments.add(instrument);
-                    probe.attach(instrument);
-                    coverageMap.put(lineLocation, coverage);
-                }
-            }
-        }
-    }
-
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/LineToProbesMap.java	Fri May 22 10:20:38 2015 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +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.
- *
- * 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.api.tools;
-
-import java.io.*;
-import java.util.*;
-
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.instrument.impl.*;
-import com.oracle.truffle.api.source.*;
-
-/**
- * An {@link InstrumentationTool} that builds a map of every {@link Probe} attached to some AST,
- * indexed by {@link Source} and line number.
- */
-public final class LineToProbesMap extends InstrumentationTool {
-
-    private static final boolean TRACE = false;
-    private static final PrintStream OUT = System.out;
-
-    private static void trace(String msg) {
-        OUT.println("LineToProbesMap: " + msg);
-    }
-
-    /**
-     * Map: Source line ==> probes associated with source sections starting on the line.
-     */
-    private final Map<LineLocation, Collection<Probe>> lineToProbesMap = new HashMap<>();
-
-    private final ProbeListener probeListener;
-
-    /**
-     * Create a map of {@link Probe}s that collects information on all probes added to subsequently
-     * created ASTs (once installed).
-     */
-    public LineToProbesMap() {
-        this.probeListener = new LineToProbesListener();
-    }
-
-    @Override
-    protected boolean internalInstall() {
-        Probe.addProbeListener(probeListener);
-        return true;
-    }
-
-    @Override
-    protected void internalReset() {
-        lineToProbesMap.clear();
-    }
-
-    @Override
-    protected void internalDispose() {
-        Probe.removeProbeListener(probeListener);
-    }
-
-    /**
-     * Returns the {@link Probe}, if any, associated with a specific line of guest language code; if
-     * more than one, return the one with the first starting character location.
-     */
-    public Probe findFirstProbe(LineLocation lineLocation) {
-        Probe probe = null;
-        final Collection<Probe> probes = findProbes(lineLocation);
-        for (Probe probesOnLine : probes) {
-            if (probe == null) {
-                probe = probesOnLine;
-            } else if (probesOnLine.getProbedSourceSection().getCharIndex() < probe.getProbedSourceSection().getCharIndex()) {
-                probe = probesOnLine;
-            }
-        }
-        return probe;
-    }
-
-    /**
-     * Returns all {@link Probe}s whose associated source begins at the given {@link LineLocation},
-     * an empty list if none.
-     */
-    public Collection<Probe> findProbes(LineLocation line) {
-        final Collection<Probe> probes = lineToProbesMap.get(line);
-        if (probes == null) {
-            return Collections.emptyList();
-        }
-        return Collections.unmodifiableCollection(probes);
-    }
-
-    private class LineToProbesListener extends DefaultProbeListener {
-
-        @Override
-        public void newProbeInserted(Probe probe) {
-            final SourceSection sourceSection = probe.getProbedSourceSection();
-            if (sourceSection != null && !(sourceSection instanceof NullSourceSection)) {
-                final LineLocation lineLocation = sourceSection.getLineLocation();
-                if (TRACE) {
-                    trace("ADD " + lineLocation.getShortDescription() + " ==> " + probe.getShortDescription());
-                }
-                Collection<Probe> probes = lineToProbesMap.get(lineLocation);
-                if (probes == null) {
-                    probes = new ArrayList<>(2);
-                    lineToProbesMap.put(lineLocation, probes);
-                } else {
-                    assert !probes.contains(probe);
-                }
-                probes.add(probe);
-            }
-        }
-    }
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/NodeExecCounter.java	Fri May 22 10:20:38 2015 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,332 +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.api.tools;
-
-import java.io.*;
-import java.util.*;
-import java.util.concurrent.atomic.*;
-
-import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.instrument.impl.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.nodes.Node.Child;
-
-/**
- * An {@link InstrumentationTool} that counts interpreter <em>execution calls</em> to AST nodes,
- * tabulated by the type of called nodes; counting can be enabled <em>all</em> nodes or restricted
- * to nodes with a specified {@linkplain SyntaxTag tag} that is presumed to be applied external to
- * the tool.
- * <p>
- * <b>Tool Life Cycle</b>
- * <p>
- * See {@link InstrumentationTool} for the life cycle common to all such tools.
- * </p>
- * <b>Execution Counts</b>
- * <p>
- * <ul>
- * <li>"Execution call" on a node is is defined as invocation of a node method that is instrumented
- * to produce the event {@link StandardInstrumentListener#enter(Probe, Node, VirtualFrame)};</li>
- * <li>Execution calls are tabulated only at <em>instrumented</em> nodes, i.e. those for which
- * {@linkplain Node#isInstrumentable() isInstrumentable() == true};</li>
- * <li>Execution calls are tabulated only at nodes present in the AST when originally created;
- * dynamically added nodes will not be instrumented.</li>
- * </ul>
- * </p>
- * <b>Failure Log</b>
- * <p>
- * For the benefit of language implementors, the tool maintains a log describing failed attempts to
- * probe AST nodes. Most failures occur when the type of the wrapper created by
- * {@link Node#createWrapperNode()} is not assignable to the relevant {@link Child} field in the
- * node's parent.
- * </p>
- * <p>
- * {@linkplain #reset() Resetting} the counts has no effect on the failure log.
- * </p>
- * <b>Results</b>
- * <p>
- * A modification-safe copy of the {@linkplain #getCounts() counts} can be retrieved at any time,
- * without effect on the state of the tool.
- * </p>
- * <p>
- * A "default" {@linkplain #print(PrintStream) print()} method can summarizes the current counts at
- * any time in a simple textual format, without effect on the state of the tool.
- * </p>
- *
- * @see Instrument
- * @see SyntaxTag
- * @see ProbeFailure
- */
-public final class NodeExecCounter extends InstrumentationTool {
-
-    /**
-     * Execution count for AST nodes of a particular type.
-     */
-    public interface NodeExecutionCount {
-        Class<?> nodeClass();
-
-        long executionCount();
-    }
-
-    /**
-     * Listener for events at instrumented nodes. Counts are maintained in a shared table, so the
-     * listener is stateless and can be shared by every {@link Instrument}.
-     */
-    private final StandardInstrumentListener instrumentListener = new DefaultStandardInstrumentListener() {
-        @Override
-        public void enter(Probe probe, Node node, VirtualFrame vFrame) {
-            if (isEnabled()) {
-                final Class<?> nodeClass = node.getClass();
-                /*
-                 * Everything up to here is inlined by Truffle compilation. Delegate the next part
-                 * to a method behind an inlining boundary.
-                 * 
-                 * Note that it is not permitted to pass a {@link VirtualFrame} across an inlining
-                 * boundary; they are truly virtual in inlined code.
-                 */
-                AtomicLong nodeCounter = getCounter(nodeClass);
-                nodeCounter.getAndIncrement();
-            }
-        }
-
-        /**
-         * Mark this method as a boundary that will stop Truffle inlining, which should not be
-         * allowed to inline the hash table method or any other complex library code.
-         */
-        @TruffleBoundary
-        private AtomicLong getCounter(Class<?> nodeClass) {
-            AtomicLong nodeCounter = counters.get(nodeClass);
-            if (nodeCounter == null) {
-                nodeCounter = new AtomicLong();
-                counters.put(nodeClass, nodeCounter);
-            }
-            return nodeCounter;
-        }
-    };
-
-    /** Counting data. */
-    private final Map<Class<?>, AtomicLong> counters = new HashMap<>();
-
-    /** Failure log. */
-    private final List<ProbeFailure> failures = new ArrayList<>();
-
-    /** For disposal. */
-    private final List<Instrument> instruments = new ArrayList<>();
-
-    /**
-     * If non-null, counting is restricted to nodes holding this tag.
-     */
-    private final SyntaxTag countingTag;
-
-    /**
-     * Prober used only when instrumenting every node.
-     */
-    private ASTProber astProber;
-
-    /**
-     * Listener used only when restricting counting to a specific tag.
-     */
-    private ProbeListener probeListener;
-
-    /**
-     * Create a per node-type execution counting tool for all nodes in subsequently created ASTs.
-     */
-    public NodeExecCounter() {
-        this.countingTag = null;
-    }
-
-    /**
-     * Creates a per-type execution counting for nodes tagged as specified in subsequently created
-     * ASTs.
-     */
-    public NodeExecCounter(SyntaxTag tag) {
-        this.countingTag = tag;
-    }
-
-    @Override
-    protected boolean internalInstall() {
-        if (countingTag == null) {
-            astProber = new ExecCounterASTProber();
-            Probe.registerASTProber(astProber);
-        } else {
-            probeListener = new NodeExecCounterProbeListener();
-            Probe.addProbeListener(probeListener);
-        }
-        return true;
-    }
-
-    @Override
-    protected void internalReset() {
-        counters.clear();
-        failures.clear();
-    }
-
-    @Override
-    protected void internalDispose() {
-        if (astProber != null) {
-            Probe.unregisterASTProber(astProber);
-        }
-        if (probeListener != null) {
-            Probe.removeProbeListener(probeListener);
-        }
-        for (Instrument instrument : instruments) {
-            instrument.dispose();
-        }
-    }
-
-    /**
-     * Gets a modification-safe summary of the current per-type node execution counts; does not
-     * affect the counts.
-     */
-    public NodeExecutionCount[] getCounts() {
-        final Collection<Map.Entry<Class<?>, AtomicLong>> entrySet = counters.entrySet();
-        final NodeExecutionCount[] result = new NodeExecCountImpl[entrySet.size()];
-        int i = 0;
-        for (Map.Entry<Class<?>, AtomicLong> entry : entrySet) {
-            result[i++] = new NodeExecCountImpl(entry.getKey(), entry.getValue().longValue());
-        }
-        return result;
-    }
-
-    /**
-     * Gets a log containing a report of every failed attempt to instrument a node.
-     */
-    public ProbeFailure[] getFailures() {
-        return failures.toArray(new ProbeFailure[failures.size()]);
-    }
-
-    /**
-     * A default printer for the current counts, producing lines of the form
-     * " <count> : <node type>" in descending order of count.
-     */
-    public void print(PrintStream out) {
-        print(out, false);
-    }
-
-    /**
-     * A default printer for the current counts, producing lines of the form
-     * " <count> : <node type>" in descending order of count.
-     *
-     * @param out
-     * @param verbose whether to describe nodes on which instrumentation failed
-     */
-    public void print(PrintStream out, boolean verbose) {
-
-        final long missedNodes = failures.size();
-        out.println();
-        if (countingTag == null) {
-            out.println("Execution counts by node type:");
-        } else {
-            out.println("\"" + countingTag.name() + "\"-tagged execution counts by node type:");
-        }
-        final StringBuilder disclaim = new StringBuilder("(");
-        if (missedNodes > 0) {
-            disclaim.append(Long.toString(missedNodes) + " original AST nodes not instrumented, ");
-        }
-        disclaim.append("dynamically added nodes not instrumented)");
-        out.println(disclaim.toString());
-        NodeExecutionCount[] execCounts = getCounts();
-        // Sort in descending order
-        Arrays.sort(execCounts, new Comparator<NodeExecutionCount>() {
-
-            public int compare(NodeExecutionCount o1, NodeExecutionCount o2) {
-                return Long.compare(o2.executionCount(), o1.executionCount());
-            }
-
-        });
-        for (NodeExecutionCount nodeCount : execCounts) {
-            out.format("%12d", nodeCount.executionCount());
-            out.println(" : " + nodeCount.nodeClass().getName());
-        }
-
-        if (verbose && missedNodes > 0) {
-            out.println("Instrumentation failures for execution counts:");
-
-            for (ProbeFailure failure : failures) {
-                out.println("\t" + failure.getMessage());
-            }
-        }
-    }
-
-    /**
-     * A prober that attempts to probe and instrument every node.
-     */
-    private class ExecCounterASTProber implements ASTProber, NodeVisitor {
-
-        public boolean visit(Node node) {
-
-            if (node.isInstrumentable()) {
-                try {
-                    final Instrument instrument = Instrument.create(instrumentListener, "NodeExecCounter");
-                    instruments.add(instrument);
-                    node.probe().attach(instrument);
-                } catch (ProbeException ex) {
-                    failures.add(ex.getFailure());
-                }
-            }
-            return true;
-        }
-
-        public void probeAST(Node node) {
-            node.accept(this);
-        }
-    }
-
-    /**
-     * A listener that assumes ASTs have been tagged external to this tool, and which instruments
-     * nodes holding a specified tag.
-     */
-    private class NodeExecCounterProbeListener extends DefaultProbeListener {
-
-        @Override
-        public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
-            if (countingTag == tag) {
-                final Instrument instrument = Instrument.create(instrumentListener, NodeExecCounter.class.getSimpleName());
-                instruments.add(instrument);
-                probe.attach(instrument);
-            }
-        }
-    }
-
-    private static class NodeExecCountImpl implements NodeExecutionCount {
-
-        private final Class<?> nodeClass;
-        private final long count;
-
-        public NodeExecCountImpl(Class<?> nodeClass, long count) {
-            this.nodeClass = nodeClass;
-            this.count = count;
-        }
-
-        public Class<?> nodeClass() {
-            return nodeClass;
-        }
-
-        public long executionCount() {
-            return count;
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLHandler.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2014, 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.sl.tools.debug;
+
+import java.util.*;
+
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.tools.debug.shell.*;
+import com.oracle.truffle.tools.debug.shell.client.*;
+import com.oracle.truffle.tools.debug.shell.server.*;
+
+/**
+ * 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);
+        }
+    };
+
+    /**
+     * Runs a source, optionally stepping into a specified tag.
+     */
+    private static REPLMessage[] loadHandler(REPLMessage request, REPLServerContext serverContext, boolean stepInto) {
+        final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.LOAD_RUN);
+        final String fileName = request.get(REPLMessage.SOURCE_NAME);
+        try {
+            final Source source = Source.fromFileName(fileName, true);
+            if (source == null) {
+                return finishReplyFailed(reply, "can't find file \"" + fileName + "\"");
+            }
+            serverContext.getDebugEngine().run(source, stepInto);
+            reply.put(REPLMessage.FILE_PATH, source.getPath());
+            return finishReplySucceeded(reply, source.getName() + "  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());
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLServer.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2014, 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.sl.tools.debug;
+
+import java.io.*;
+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.engine.*;
+import com.oracle.truffle.tools.debug.shell.*;
+import com.oracle.truffle.tools.debug.shell.client.*;
+import com.oracle.truffle.tools.debug.shell.server.*;
+import com.oracle.truffle.sl.factory.*;
+import com.oracle.truffle.sl.runtime.*;
+
+/**
+ * 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 {@link DebugEngine#eval(Source, Node, MaterializedFrame)}
+ * and {@link Breakpoint#setCondition(String)}.
+ *
+ * @see SimpleREPLClient
+ */
+public final class SLREPLServer implements REPLServer {
+    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(server.slContext, server);
+
+        // Cheating for the prototype: allow server access to client for recursive debugging
+        server.setClient(client);
+
+        try {
+            client.start();
+        } catch (QuitException ex) {
+        }
+    }
+
+    private final SLContext slContext;
+    private final DebugEngine slDebugEngine;
+    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);
+
+        // Set up an SL context
+        this.slContext = SLContextFactory.create(null, new PrintWriter(System.out));
+
+        final SLSourceExecutionProvider slSourceExecution = new SLSourceExecutionProvider(slContext);
+        final SLREPLDebugClient slDebugClient = new SLREPLDebugClient(this.slContext);
+        this.slDebugEngine = DebugEngine.create(slDebugClient, slSourceExecution);
+        this.statusPrefix = slContext.getLanguageShortName() + " REPL:";
+    }
+
+    private void setClient(SimpleREPLClient replClient) {
+        this.replClient = replClient;
+    }
+
+    public REPLMessage start() {
+
+        this.currentServerContext = new SLServerContext(null, 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, slContext.getLanguageShortName() + " started");
+        return reply;
+    }
+
+    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, Node astNode, MaterializedFrame mFrame) {
+            super(predecessor == null ? 0 : predecessor.getLevel() + 1, astNode, mFrame);
+            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 SLContext getLanguageContext() {
+            return slContext;
+        }
+
+        @Override
+        public DebugEngine getDebugEngine() {
+            return slDebugEngine;
+        }
+
+    }
+
+    /**
+     * Specialize the standard SL debug context by notifying the REPL client when execution is
+     * halted, e.g. at a breakpoint.
+     * <p>
+     * Before notification, the server creates a new context at the halted location, in which
+     * subsequent evaluations take place until such time as the client says to "continue".
+     * <p>
+     * This implementation "cheats" the intended asynchronous architecture by calling back directly
+     * to the client with the notification.
+     */
+    private final class SLREPLDebugClient implements DebugClient {
+
+        private final SLContext slContext;
+
+        SLREPLDebugClient(SLContext slContext) {
+            this.slContext = slContext;
+        }
+
+        public void haltedAt(Node node, MaterializedFrame mFrame, List<String> warnings) {
+            // Create and push a new debug context where execution is halted
+            currentServerContext = new SLServerContext(currentServerContext, node, mFrame);
+
+            // 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 = node.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()));
+            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;
+            }
+        }
+
+        public ExecutionContext getExecutionContext() {
+            return slContext;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLSourceExecutionProvider.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2014, 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.sl.tools.debug;
+
+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.sl.nodes.instrument.*;
+import com.oracle.truffle.sl.runtime.*;
+import com.oracle.truffle.tools.debug.engine.*;
+
+/**
+ * Specialization of the Truffle debugging engine for the Simple language. The engine implements
+ * basic debugging operations during Truffle-based execution.
+ */
+public final class SLSourceExecutionProvider extends SourceExecutionProvider {
+
+    private final SLContext slContext;
+
+    public SLSourceExecutionProvider(SLContext context) {
+        this.slContext = context;
+        Probe.registerASTProber(new SLStandardASTProber());
+    }
+
+    @Override
+    public void languageRun(Source source) {
+        slContext.executeMain(source);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The implementation of Simple is too, well, simple to support eval. Just for starters, the
+     * parser can only produce a whole program.
+     */
+    @Override
+    public Object languageEval(Source source, Node node, MaterializedFrame mFrame) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The implementation of Simple is too, well, simple to support this. Just for starters, the
+     * parser can only produce a whole program.
+     */
+    @Override
+    public AdvancedInstrumentRootFactory languageAdvancedInstrumentRootFactory(String expr, AdvancedInstrumentResultListener resultListener) throws DebugException {
+        throw new UnsupportedOperationException();
+    }
+}
--- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLMain.java	Fri May 22 10:20:38 2015 -0700
+++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLMain.java	Tue May 26 16:38:13 2015 -0700
@@ -31,8 +31,7 @@
 import com.oracle.truffle.api.instrument.*;
 import com.oracle.truffle.api.nodes.*;
 import com.oracle.truffle.api.source.*;
-import com.oracle.truffle.api.tools.*;
-import com.oracle.truffle.api.vm.TruffleVM;
+import com.oracle.truffle.api.vm.*;
 import com.oracle.truffle.sl.builtins.*;
 import com.oracle.truffle.sl.factory.*;
 import com.oracle.truffle.sl.nodes.*;
@@ -43,6 +42,7 @@
 import com.oracle.truffle.sl.nodes.local.*;
 import com.oracle.truffle.sl.parser.*;
 import com.oracle.truffle.sl.runtime.*;
+import com.oracle.truffle.tools.*;
 
 /**
  * SL is a simple language to demonstrate and showcase features of Truffle. The implementation is as
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/Breakpoint.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,238 @@
+/*
+ * 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.engine;
+
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.source.*;
+
+public abstract class Breakpoint {
+
+    /**
+     * A general model of the states occupied by a breakpoint during its lifetime.
+     */
+    public enum BreakpointState {
+
+        /**
+         * Not attached, enabled.
+         * <p>
+         * Created for a source location but not yet attached: perhaps just created and the source
+         * hasn't been loaded yet; perhaps source has been loaded, but the line location isn't
+         * probed so a breakpoint cannot be attached. Can be either enabled or disabled.
+         */
+        ENABLED_UNRESOLVED("Enabled/Unresolved"),
+
+        /**
+         * Not attached, disabled.
+         * <p>
+         * Created for a source location but not yet attached: perhaps just created and the source
+         * hasn't been loaded yet; perhaps source has been loaded, but the line location isn't
+         * probed so a breakpoint cannot be attached.
+         */
+        DISABLED_UNRESOLVED("Disabled/Unresolved"),
+
+        /**
+         * Attached, instrument enabled.
+         * <p>
+         * Is currently implemented by some {@link Instrument}, which is attached to a {@link Probe}
+         * at a specific node in the AST, and the breakpoint is enabled.
+         */
+        ENABLED("Enabled"),
+
+        /**
+         * Attached, instrument disabled.
+         * <p>
+         * Is currently implemented by some {@link Instrument}, which is attached to a {@link Probe}
+         * at a specific node in the AST, and the breakpoint is disabled.
+         */
+        DISABLED("Disabled"),
+
+        /**
+         * Not attached, instrument is permanently disabled.
+         */
+        DISPOSED("Disposed");
+
+        private final String name;
+
+        BreakpointState(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+
+    }
+
+    private static int nextBreakpointId = 0;
+
+    private final int id;
+    private final int groupId;
+    private final boolean isOneShot;
+
+    private int ignoreCount;
+
+    private int hitCount = 0;
+
+    private BreakpointState state;
+
+    Breakpoint(BreakpointState state, int groupId, int ignoreCount, boolean isOneShot) {
+        this.state = state;
+        this.id = nextBreakpointId++;
+        this.groupId = groupId;
+        this.isOneShot = isOneShot;
+        this.ignoreCount = ignoreCount;
+    }
+
+    /**
+     * Unique ID.
+     */
+    public final int getId() {
+        return id;
+    }
+
+    /**
+     * Group ID, set when created.
+     */
+    public final int getGroupId() {
+        return groupId;
+    }
+
+    /**
+     * Enables or disables this breakpoint's AST instrumentation. The breakpoint is enabled by
+     * default.
+     *
+     * @param enabled <code>true</code> to activate the instrumentation, <code>false</code> to
+     *            deactivate the instrumentation so that it has no effect.
+     */
+    public abstract void setEnabled(boolean enabled);
+
+    /**
+     * Is this breakpoint active?
+     */
+    public abstract boolean isEnabled();
+
+    /**
+     * Sets the condition on this breakpoint, {@code null} to make it unconditional.
+     *
+     * @param expr if non{@code -null}, a boolean expression, expressed in the guest language, to be
+     *            evaluated in the lexical context at the breakpoint location.
+     * @throws DebugException if condition is invalid
+     * @throws UnsupportedOperationException if the breakpoint does not support conditions
+     */
+    public abstract void setCondition(String expr) throws DebugException;
+
+    /**
+     * Gets the string, expressed in the Guest Language, that defines the current condition on this
+     * breakpoint; {@code null} if this breakpoint is currently unconditional.
+     */
+    public String getCondition() {
+        return null;
+    }
+
+    /**
+     * Does this breakpoint remove itself after first activation?
+     */
+    public final boolean isOneShot() {
+        return isOneShot;
+    }
+
+    /**
+     * Gets the number of hits left to be ignored before halting.
+     */
+    public final int getIgnoreCount() {
+        return ignoreCount;
+    }
+
+    /**
+     * Change the threshold for when this breakpoint should start causing a break. When both an
+     * ignore count and a {@linkplain #setCondition(String) condition} are specified, the condition
+     * is evaluated first: if {@code false} it is not considered to be a hit. In other words, the
+     * ignore count is for successful conditions only.
+     */
+    public final void setIgnoreCount(int ignoreCount) {
+        this.ignoreCount = ignoreCount;
+    }
+
+    /**
+     * Number of times this breakpoint has reached, with one exception; if the breakpoint has a
+     * condition that evaluates to {@code false}, it does not count as a hit.
+     */
+    public final int getHitCount() {
+        return hitCount;
+    }
+
+    /**
+     * Disables this breakpoint and removes any associated instrumentation; it becomes permanently
+     * inert.
+     */
+    public abstract void dispose();
+
+    /**
+     * Gets a human-sensible description of this breakpoint's location in a {@link Source}.
+     */
+    public abstract String getLocationDescription();
+
+    public final BreakpointState getState() {
+        return state;
+    }
+
+    final void assertState(BreakpointState s) {
+        assert state == s;
+    }
+
+    final void setState(BreakpointState state) {
+        this.state = state;
+    }
+
+    /**
+     * Assumes that all conditions for causing the break have been satisfied, so increments the
+     * <em>hit count</em>. Then checks if the <em>ignore count</em> has been exceeded, and if so
+     * returns {@code true}. If not, it still counts as a <em>hit</em> but should be ignored.
+     *
+     * @return whether to proceed
+     */
+    final boolean incrHitCountCheckIgnore() {
+        return ++hitCount > ignoreCount;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
+        sb.append(" state=");
+        sb.append(getState() == null ? "<none>" : getState().getName());
+        if (isOneShot()) {
+            sb.append(", " + "One-Shot");
+        }
+        if (getCondition() != null) {
+            sb.append(", condition=\"" + getCondition() + "\"");
+        }
+        return sb.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugClient.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,55 @@
+/*
+ * 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.engine;
+
+import java.util.*;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.nodes.Node;
+
+/**
+ * A client of the debugger where certain events should be posted.
+ *
+ * @see DebugEngine
+ */
+public interface DebugClient {
+
+    /**
+     * Notifies client that program execution has been halted at some location; execution will
+     * resume when this method returns.
+     *
+     * @param astNode AST node that is just about to be executed
+     * @param mFrame frame that will be passed to the node when executed
+     * @param warnings any warnings generated since thie most recent halt.
+     */
+    void haltedAt(Node astNode, MaterializedFrame mFrame, List<String> warnings);
+
+    // TODO (mlvdv) temporary; will eventually be accessible by a new Truffle language API
+    /**
+     * Gets the context for the language being debugged.
+     */
+    ExecutionContext getExecutionContext();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugEngine.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,881 @@
+/*
+ * 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.engine;
+
+import java.io.*;
+import java.util.*;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+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.engine.SourceExecutionProvider.ExecutionListener;
+
+/**
+ * Language-agnostic engine for running Truffle languages under debugging control.
+ */
+public final class DebugEngine {
+
+    private static final boolean TRACE = false;
+    private static final boolean TRACE_PROBES = false;
+    private static final String TRACE_PREFIX = "DEBUG ENGINE: ";
+
+    private static final PrintStream OUT = System.out;
+
+    private static final SyntaxTag STEPPING_TAG = StandardSyntaxTag.STATEMENT;
+    private static final SyntaxTag CALL_TAG = StandardSyntaxTag.CALL;
+
+    @SuppressWarnings("unused")
+    private static void trace(String format, Object... args) {
+        if (TRACE || TRACE_PROBES) {
+            OUT.println(TRACE_PREFIX + String.format(format, args));
+        }
+    }
+
+    interface BreakpointCallback {
+
+        /**
+         * Passes control to the debugger with execution suspended.
+         */
+        void haltedAt(Node astNode, MaterializedFrame mFrame, String haltReason);
+    }
+
+    interface WarningLog {
+
+        /**
+         * Logs a warning that is kept until the start of the next execution.
+         */
+        void addWarning(String warning);
+    }
+
+    /**
+     * The client of this engine.
+     */
+    private final DebugClient debugClient;
+
+    private final SourceExecutionProvider sourceExecutionProvider;
+
+    /**
+     * Implementation of line-oriented breakpoints.
+     */
+    private final LineBreakpointFactory lineBreaks;
+
+    /**
+     * Implementation of tag-oriented breakpoints.
+     */
+    private final TagBreakpointFactory tagBreaks;
+
+    /**
+     * Head of the stack of executions.
+     */
+    private DebugExecutionContext debugContext;
+
+    /**
+     * @param debugClient
+     * @param sourceExecutionProvider
+     */
+    private DebugEngine(DebugClient debugClient, SourceExecutionProvider sourceExecutionProvider) {
+        this.debugClient = debugClient;
+        this.sourceExecutionProvider = sourceExecutionProvider;
+
+        Source.setFileCaching(true);
+
+        // Initialize execution context stack
+        debugContext = new DebugExecutionContext(null, null);
+        prepareContinue();
+        debugContext.contextTrace("START EXEC DEFAULT");
+
+        sourceExecutionProvider.addExecutionListener(new ExecutionListener() {
+
+            public void executionStarted(Source source, boolean stepInto) {
+                // Push a new execution context onto stack
+                DebugEngine.this.debugContext = new DebugExecutionContext(source, DebugEngine.this.debugContext);
+                if (stepInto) {
+                    DebugEngine.this.prepareStepInto(1);
+                } else {
+                    DebugEngine.this.prepareContinue();
+                }
+                DebugEngine.this.debugContext.contextTrace("START EXEC ");
+            }
+
+            public void executionEnded() {
+                DebugEngine.this.lineBreaks.disposeOneShots();
+                DebugEngine.this.tagBreaks.disposeOneShots();
+                DebugEngine.this.debugContext.clearStrategy();
+                DebugEngine.this.debugContext.contextTrace("END EXEC ");
+                // Pop the stack of execution contexts.
+                DebugEngine.this.debugContext = DebugEngine.this.debugContext.predecessor;
+            }
+        });
+
+        final BreakpointCallback breakpointCallback = new BreakpointCallback() {
+
+            @TruffleBoundary
+            public void haltedAt(Node astNode, MaterializedFrame mFrame, String haltReason) {
+                debugContext.halt(astNode, mFrame, true, haltReason);
+            }
+        };
+
+        final WarningLog warningLog = new WarningLog() {
+
+            public void addWarning(String warning) {
+                assert debugContext != null;
+                debugContext.logWarning(warning);
+            }
+        };
+
+        this.lineBreaks = new LineBreakpointFactory(sourceExecutionProvider, breakpointCallback, warningLog);
+
+        this.tagBreaks = new TagBreakpointFactory(sourceExecutionProvider, breakpointCallback, warningLog);
+
+        if (TRACE_PROBES) {
+            Probe.addProbeListener(new ProbeListener() {
+
+                private Source beingProbed = null;
+
+                @Override
+                public void startASTProbing(Source source) {
+                    final String sourceName = source == null ? "<?>" : source.getShortName();
+                    trace("START PROBING %s", sourceName);
+                    beingProbed = source;
+                }
+
+                @Override
+                public void newProbeInserted(Probe probe) {
+                    trace("PROBE ADDED %s", probe.getShortDescription());
+                }
+
+                @Override
+                public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
+                    trace("PROBE TAGGED as %s: %s", tag, probe.getShortDescription());
+                }
+
+                @Override
+                public void endASTProbing(Source source) {
+                    final String sourceName = source == null ? "<?>" : source.getShortName();
+                    trace("FINISHED PROBING %s", sourceName);
+                    assert source == beingProbed;
+                    beingProbed = null;
+                }
+            });
+        }
+    }
+
+    public static DebugEngine create(DebugClient debugClient, SourceExecutionProvider sourceExecutionProvider) {
+        return new DebugEngine(debugClient, sourceExecutionProvider);
+    }
+
+    /**
+     * Runs a script. If "StepInto" is requested, halts at the first location tagged as a
+     * {@linkplain StandardSyntaxTag#STATEMENT STATEMENT}.
+     *
+     * @throws DebugException if an unexpected failure occurs
+     */
+    public void run(Source source, boolean stepInto) throws DebugException {
+        sourceExecutionProvider.run(source, stepInto);
+    }
+
+    /**
+     * Sets a breakpoint to halt at a source line.
+     *
+     * @param groupId
+     * @param ignoreCount number of hits to ignore before halting
+     * @param lineLocation where to set the breakpoint (source, line number)
+     * @param oneShot breakpoint disposes itself after fist hit, if {@code true}
+     * @return a new breakpoint, initially enabled
+     * @throws DebugException if the breakpoint can not be set.
+     */
+    @TruffleBoundary
+    public LineBreakpoint setLineBreakpoint(int groupId, int ignoreCount, LineLocation lineLocation, boolean oneShot) throws DebugException {
+        return lineBreaks.create(groupId, ignoreCount, lineLocation, oneShot);
+    }
+
+    /**
+     * Sets a breakpoint to halt at any node holding a specified {@link SyntaxTag}.
+     *
+     * @param groupId
+     * @param ignoreCount number of hits to ignore before halting
+     * @param oneShot if {@code true} breakpoint removes it self after a hit
+     * @return a new breakpoint, initially enabled
+     * @throws DebugException if the breakpoint already set
+     */
+    @TruffleBoundary
+    public Breakpoint setTagBreakpoint(int groupId, int ignoreCount, SyntaxTag tag, boolean oneShot) throws DebugException {
+        return tagBreaks.create(groupId, ignoreCount, tag, oneShot);
+    }
+
+    /**
+     * Finds a breakpoint created by this engine, but not yet disposed, by id.
+     */
+    @TruffleBoundary
+    public Breakpoint findBreakpoint(long id) {
+        final Breakpoint breakpoint = lineBreaks.find(id);
+        return breakpoint == null ? tagBreaks.find(id) : breakpoint;
+    }
+
+    /**
+     * Gets all existing breakpoints, whatever their status, in natural sorted order. Modification
+     * save.
+     */
+    @TruffleBoundary
+    public Collection<Breakpoint> getBreakpoints() {
+        final Collection<Breakpoint> result = new ArrayList<>();
+        result.addAll(lineBreaks.getAll());
+        result.addAll(tagBreaks.getAll());
+        return result;
+    }
+
+    /**
+     * Prepare to execute in Continue mode when guest language program execution resumes. In this
+     * mode:
+     * <ul>
+     * <li>Execution will continue until either:
+     * <ol>
+     * <li>execution arrives at a node to which an enabled breakpoint is attached,
+     * <strong>or:</strong></li>
+     * <li>execution completes.</li>
+     * </ol>
+     * </ul>
+     */
+    @TruffleBoundary
+    public void prepareContinue() {
+        debugContext.setStrategy(new Continue());
+    }
+
+    /**
+     * Prepare to execute in StepInto mode when guest language program execution resumes. In this
+     * mode:
+     * <ul>
+     * <li>User breakpoints are disabled.</li>
+     * <li>Execution will continue until either:
+     * <ol>
+     * <li>execution arrives at a node with the tag {@linkplain StandardSyntaxTag#STATEMENT
+     * STATMENT}, <strong>or:</strong></li>
+     * <li>execution completes.</li>
+     * </ol>
+     * <li>
+     * StepInto mode persists only through one resumption (i.e. {@code stepIntoCount} steps), and
+     * reverts by default to Continue mode.</li>
+     * </ul>
+     *
+     * @param stepCount the number of times to perform StepInto before halting
+     * @throws IllegalArgumentException if the specified number is {@code <= 0}
+     */
+    @TruffleBoundary
+    public void prepareStepInto(int stepCount) {
+        if (stepCount <= 0) {
+            throw new IllegalArgumentException();
+        }
+        debugContext.setStrategy(new StepInto(stepCount));
+    }
+
+    /**
+     * Prepare to execute in StepOut mode when guest language program execution resumes. In this
+     * mode:
+     * <ul>
+     * <li>User breakpoints are enabled.</li>
+     * <li>Execution will continue until either:
+     * <ol>
+     * <li>execution arrives at the nearest enclosing call site on the stack, <strong>or</strong></li>
+     * <li>execution completes.</li>
+     * </ol>
+     * <li>StepOut mode persists only through one resumption, and reverts by default to Continue
+     * mode.</li>
+     * </ul>
+     */
+    @TruffleBoundary
+    public void prepareStepOut() {
+        debugContext.setStrategy(new StepOut());
+    }
+
+    /**
+     * Prepare to execute in StepOver mode when guest language program execution resumes. In this
+     * mode:
+     * <ul>
+     * <li>Execution will continue until either:
+     * <ol>
+     * <li>execution arrives at a node with the tag {@linkplain StandardSyntaxTag#STATEMENT
+     * STATEMENT} when not nested in one or more function/method calls, <strong>or:</strong></li>
+     * <li>execution arrives at a node to which a breakpoint is attached and when nested in one or
+     * more function/method calls, <strong>or:</strong></li>
+     * <li>execution completes.</li>
+     * </ol>
+     * <li>StepOver mode persists only through one resumption (i.e. {@code stepOverCount} steps),
+     * and reverts by default to Continue mode.</li>
+     * </ul>
+     *
+     * @param stepCount the number of times to perform StepInto before halting
+     * @throws IllegalArgumentException if the specified number is {@code <= 0}
+     */
+    @TruffleBoundary
+    public void prepareStepOver(int stepCount) {
+        if (stepCount <= 0) {
+            throw new IllegalArgumentException();
+        }
+        debugContext.setStrategy(new StepOver(stepCount));
+    }
+
+    /**
+     * Gets the stack frames from the (topmost) halted Truffle execution; {@code null} null if no
+     * execution.
+     */
+    @TruffleBoundary
+    public List<FrameDebugDescription> getStack() {
+        return debugContext == null ? null : debugContext.getFrames();
+    }
+
+    /**
+     * Evaluates code in a halted execution context, at top-level if <code>mFrame==null</code>.
+     */
+    public Object eval(Source source, Node node, MaterializedFrame mFrame) {
+        return sourceExecutionProvider.eval(source, node, mFrame);
+    }
+
+    /**
+     * A mode of user navigation from a current code location to another, e.g "step in" vs.
+     * "step over".
+     */
+    private abstract class StepStrategy {
+
+        private DebugExecutionContext context;
+        protected final String strategyName;
+
+        protected StepStrategy() {
+            this.strategyName = getClass().getSimpleName();
+        }
+
+        final String getName() {
+            return strategyName;
+        }
+
+        /**
+         * Reconfigure the debugger so that when execution continues the program will halt at the
+         * location specified by this strategy.
+         */
+        final void enable(DebugExecutionContext c, int stackDepth) {
+            this.context = c;
+            setStrategy(stackDepth);
+        }
+
+        /**
+         * Return the debugger to the default navigation mode.
+         */
+        final void disable() {
+            unsetStrategy();
+        }
+
+        @TruffleBoundary
+        final void halt(Node astNode, MaterializedFrame mFrame, boolean before) {
+            context.halt(astNode, mFrame, before, this.getClass().getSimpleName());
+        }
+
+        @TruffleBoundary
+        final void replaceStrategy(StepStrategy newStrategy) {
+            context.setStrategy(newStrategy);
+        }
+
+        @TruffleBoundary
+        protected final void strategyTrace(String action, String format, Object... args) {
+            if (TRACE) {
+                context.contextTrace("%s (%s) %s", action, strategyName, String.format(format, args));
+            }
+        }
+
+        @TruffleBoundary
+        protected final void suspendUserBreakpoints() {
+            lineBreaks.setActive(false);
+            tagBreaks.setActive(false);
+        }
+
+        @SuppressWarnings("unused")
+        protected final void restoreUserBreakpoints() {
+            lineBreaks.setActive(true);
+            tagBreaks.setActive(true);
+        }
+
+        /**
+         * Reconfigure the debugger so that when execution continues, it will do so using this mode
+         * of navigation.
+         */
+        protected abstract void setStrategy(int stackDepth);
+
+        /**
+         * Return to the debugger to the default mode of navigation.
+         */
+        protected abstract void unsetStrategy();
+    }
+
+    /**
+     * Strategy: the null stepping strategy.
+     * <ul>
+     * <li>User breakpoints are enabled.</li>
+     * <li>Execution continues until either:
+     * <ol>
+     * <li>execution arrives at a node with attached user breakpoint, <strong>or:</strong></li>
+     * <li>execution completes.</li>
+     * </ol>
+     * </ul>
+     */
+    private final class Continue extends StepStrategy {
+
+        @Override
+        protected void setStrategy(int stackDepth) {
+        }
+
+        @Override
+        protected void unsetStrategy() {
+        }
+    }
+
+    /**
+     * Strategy: per-statement stepping.
+     * <ul>
+     * <li>User breakpoints are enabled.</li>
+     * <li>Execution continues until either:
+     * <ol>
+     * <li>execution <em>arrives</em> at a STATEMENT node, <strong>or:</strong></li>
+     * <li>execution <em>returns</em> to a CALL node and the call stack is smaller then when
+     * execution started, <strong>or:</strong></li>
+     * <li>execution completes.</li>
+     * </ol>
+     * </ul>
+     *
+     * @see DebugEngine#prepareStepInto(int)
+     */
+    private final class StepInto extends StepStrategy {
+        private int unfinishedStepCount;
+
+        StepInto(int stepCount) {
+            super();
+            this.unfinishedStepCount = stepCount;
+        }
+
+        @Override
+        protected void setStrategy(final int stackDepth) {
+            Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) {
+
+                @Override
+                public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
+                    // HALT: just before statement
+                    --unfinishedStepCount;
+                    strategyTrace("TRAP BEFORE", "unfinished steps=%d", unfinishedStepCount);
+                    // Should run in fast path
+                    if (unfinishedStepCount <= 0) {
+                        halt(node, mFrame, true);
+                    }
+                    strategyTrace("RESUME BEFORE", "");
+                }
+            });
+            Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) {
+
+                @Override
+                public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
+                    --unfinishedStepCount;
+                    strategyTrace(null, "TRAP AFTER unfinished steps=%d", unfinishedStepCount);
+                    if (currentStackDepth() < stackDepth) {
+                        // HALT: just "stepped out"
+                        if (unfinishedStepCount <= 0) {
+                            halt(node, mFrame, false);
+                        }
+                    }
+                    strategyTrace("RESUME AFTER", "");
+                }
+            });
+        }
+
+        @Override
+        protected void unsetStrategy() {
+            Probe.setBeforeTagTrap(null);
+            Probe.setAfterTagTrap(null);
+        }
+    }
+
+    /**
+     * Strategy: execution to nearest enclosing call site.
+     * <ul>
+     * <li>User breakpoints are enabled.</li>
+     * <li>Execution continues until either:
+     * <ol>
+     * <li>execution arrives at a node with attached user breakpoint, <strong>or:</strong></li>
+     * <li>execution <em>returns</em> to a CALL node and the call stack is smaller than when
+     * execution started, <strong>or:</strong></li>
+     * <li>execution completes.</li>
+     * </ol>
+     * </ul>
+     *
+     * @see DebugEngine#prepareStepOut()
+     */
+    private final class StepOut extends StepStrategy {
+
+        @Override
+        protected void setStrategy(final int stackDepth) {
+            Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) {
+
+                @TruffleBoundary
+                @Override
+                public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
+                    // HALT:
+                    final int currentStackDepth = currentStackDepth();
+                    strategyTrace("TRAP AFTER", "stackDepth: start=%d current=%d", stackDepth, currentStackDepth);
+                    if (currentStackDepth < stackDepth) {
+                        halt(node, mFrame, false);
+                    }
+                    strategyTrace("RESUME AFTER", "");
+                }
+            });
+        }
+
+        @Override
+        protected void unsetStrategy() {
+            Probe.setAfterTagTrap(null);
+        }
+    }
+
+    /**
+     * Strategy: per-statement stepping, so long as not nested in method calls (i.e. at original
+     * stack depth).
+     * <ul>
+     * <li>User breakpoints are enabled.</li>
+     * <li>Execution continues until either:
+     * <ol>
+     * <li>execution arrives at a STATEMENT node with stack depth no more than when started
+     * <strong>or:</strong></li>
+     * <li>the program completes.</li>
+     * </ol>
+     * </ul>
+     */
+    private final class StepOver extends StepStrategy {
+        private int unfinishedStepCount;
+
+        StepOver(int stepCount) {
+            this.unfinishedStepCount = stepCount;
+        }
+
+        @Override
+        protected void setStrategy(int stackDepth) {
+            Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) {
+
+                @Override
+                public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
+                    final int currentStackDepth = currentStackDepth();
+                    if (currentStackDepth <= stackDepth) {
+                        // HALT: stack depth unchanged or smaller; treat like StepInto
+                        --unfinishedStepCount;
+                        if (TRACE) {
+                            strategyTrace("TRAP BEFORE", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth);
+                        }
+                        // Test should run in fast path
+                        if (unfinishedStepCount <= 0) {
+                            halt(node, mFrame, true);
+                        }
+                    } else {
+                        // CONTINUE: Stack depth increased; don't count as a step
+                        strategyTrace("STEP INTO", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth);
+                        // Stop treating like StepInto, start treating like StepOut
+                        replaceStrategy(new StepOverNested(unfinishedStepCount, stackDepth));
+                    }
+                    strategyTrace("RESUME BEFORE", "");
+                }
+            });
+
+            Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) {
+
+                @Override
+                public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
+                    final int currentStackDepth = currentStackDepth();
+                    if (currentStackDepth < stackDepth) {
+                        // HALT: just "stepped out"
+                        --unfinishedStepCount;
+                        strategyTrace("TRAP AFTER", "unfinished steps=%d stackDepth: start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth);
+                        // Should run in fast path
+                        if (unfinishedStepCount <= 0) {
+                            halt(node, mFrame, false);
+                        }
+                        strategyTrace("RESUME AFTER", "");
+                    }
+                }
+            });
+        }
+
+        @Override
+        protected void unsetStrategy() {
+            Probe.setBeforeTagTrap(null);
+            Probe.setAfterTagTrap(null);
+        }
+    }
+
+    /**
+     * Strategy: per-statement stepping, not into method calls, in effect while at increased stack
+     * depth
+     * <ul>
+     * <li>User breakpoints are enabled.</li>
+     * <li>Execution continues until either:
+     * <ol>
+     * <li>execution arrives at a STATEMENT node with stack depth no more than when started
+     * <strong>or:</strong></li>
+     * <li>the program completes <strong>or:</strong></li>
+     * </ol>
+     * </ul>
+     */
+    private final class StepOverNested extends StepStrategy {
+        private int unfinishedStepCount;
+        private final int startStackDepth;
+
+        StepOverNested(int stepCount, int startStackDepth) {
+            this.unfinishedStepCount = stepCount;
+            this.startStackDepth = startStackDepth;
+        }
+
+        @Override
+        protected void setStrategy(int stackDepth) {
+            Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) {
+
+                @Override
+                public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
+                    final int currentStackDepth = currentStackDepth();
+                    if (currentStackDepth <= startStackDepth) {
+                        // At original step depth (or smaller) after being nested
+                        --unfinishedStepCount;
+                        strategyTrace("TRAP AFTER", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth);
+                        if (unfinishedStepCount <= 0) {
+                            halt(node, mFrame, false);
+                        }
+                        // TODO (mlvdv) fixme for multiple steps
+                        strategyTrace("RESUME BEFORE", "");
+                    }
+                }
+            });
+        }
+
+        @Override
+        protected void unsetStrategy() {
+            Probe.setBeforeTagTrap(null);
+        }
+    }
+
+    /**
+     * Information and debugging state for a single Truffle execution (which make take place over
+     * one or more suspended executions). This holds interaction state, for example what is
+     * executing (e.g. some {@link Source}), what the execution mode is ("stepping" or
+     * "continuing"). When not running, this holds a cache of the Truffle stack for this particular
+     * execution, effectively hiding the Truffle stack for any currently suspended executions (down
+     * the stack).
+     */
+    private final class DebugExecutionContext {
+
+        // Previous halted context in stack
+        private final DebugExecutionContext predecessor;
+
+        // The current execution level; first is 0.
+        private final int level;  // Number of contexts suspended below
+        private final Source source;
+        private final int contextStackBase;  // Where the stack for this execution starts
+        private final List<String> warnings = new ArrayList<>();
+
+        private boolean running;
+
+        /**
+         * The stepping strategy currently configured in the debugger.
+         */
+        private StepStrategy strategy;
+
+        /**
+         * Where halted; null if running.
+         */
+        private Node haltedNode;
+
+        /**
+         * Where halted; null if running.
+         */
+        private MaterializedFrame haltedFrame;
+
+        /**
+         * Cached list of stack frames when halted; null if running.
+         */
+        private List<FrameDebugDescription> frames = new ArrayList<>();
+
+        private DebugExecutionContext(Source executionSource, DebugExecutionContext previousContext) {
+            this.source = executionSource;
+            this.predecessor = previousContext;
+            this.level = previousContext == null ? 0 : previousContext.level + 1;
+
+            // "Base" is the number of stack frames for all nested (halted) executions.
+            this.contextStackBase = currentStackDepth();
+            this.running = true;
+            contextTrace("NEW CONTEXT");
+        }
+
+        /**
+         * Sets up a strategy for the next resumption of execution.
+         *
+         * @param stepStrategy
+         */
+        void setStrategy(StepStrategy stepStrategy) {
+            if (this.strategy == null) {
+                this.strategy = stepStrategy;
+                this.strategy.enable(this, currentStackDepth());
+                if (TRACE) {
+                    contextTrace("SET MODE <none>-->" + stepStrategy.getName());
+                }
+            } else {
+                strategy.disable();
+                strategy = stepStrategy;
+                strategy.enable(this, currentStackDepth());
+                contextTrace("SWITCH MODE %s-->%s", strategy.getName(), stepStrategy.getName());
+            }
+        }
+
+        void clearStrategy() {
+            if (strategy != null) {
+                final StepStrategy oldStrategy = strategy;
+                strategy.disable();
+                strategy = null;
+                contextTrace("CLEAR MODE %s--><none>", oldStrategy.getName());
+            }
+        }
+
+        /**
+         * Handle a program halt, caused by a breakpoint, stepping strategy, or other cause.
+         *
+         * @param astNode the guest language node at which execution is halted
+         * @param mFrame the current execution frame where execution is halted
+         * @param before {@code true} if halted <em>before</em> the node, else <em>after</em>.
+         */
+        @TruffleBoundary
+        void halt(Node astNode, MaterializedFrame mFrame, boolean before, String haltReason) {
+            assert running;
+            assert frames.isEmpty();
+            assert haltedNode == null;
+            assert haltedFrame == null;
+
+            haltedNode = astNode;
+            haltedFrame = mFrame;
+            running = false;
+
+            clearStrategy();
+
+            // Clean up, just in cased the one-shot breakpoints got confused
+            lineBreaks.disposeOneShots();
+
+            // Map the Truffle stack for this execution, ignore nested executions
+            // The top (current) frame is not produced by the iterator.
+            frames.add(new FrameDebugDescription(0, haltedNode, Truffle.getRuntime().getCurrentFrame()));
+            final int contextStackDepth = currentStackDepth() - contextStackBase;
+            final int[] frameCount = {1};
+            Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<FrameInstance>() {
+                @Override
+                public FrameInstance visitFrame(FrameInstance frameInstance) {
+                    if (frameCount[0] < contextStackDepth) {
+                        frames.add(new FrameDebugDescription(frameCount[0], frameInstance.getCallNode(), frameInstance));
+                        frameCount[0] = frameCount[0] + 1;
+                        return null;
+                    }
+                    return frameInstance;
+                }
+            });
+
+            if (TRACE) {
+                final String reason = haltReason == null ? "" : haltReason + "";
+                final String where = before ? "BEFORE" : "AFTER";
+                contextTrace("HALT %s : (%s) stack base=%d", where, reason, contextStackBase);
+                contextTrace("CURRENT STACK:");
+                printStack(OUT);
+            }
+
+            final List<String> recentWarnings = new ArrayList<>(warnings);
+            warnings.clear();
+
+            try {
+                // Pass control to the debug client with current execution suspended
+                debugClient.haltedAt(astNode, mFrame, recentWarnings);
+                // Debug client finished normally, execution resumes
+                // Presume that the client has set a new strategy (or default to Continue)
+                running = true;
+            } catch (KillException e) {
+                contextTrace("KILL");
+                throw e;
+            } finally {
+                haltedNode = null;
+                haltedFrame = null;
+                frames.clear();
+            }
+
+        }
+
+        List<FrameDebugDescription> getFrames() {
+            return Collections.unmodifiableList(frames);
+        }
+
+        void logWarning(String warning) {
+            warnings.add(warning);
+        }
+
+        // For tracing
+        private void printStack(PrintStream stream) {
+            getFrames();
+            if (frames == null) {
+                stream.println("<empty stack>");
+            } else {
+                // TODO (mlvdv) get visualizer via the (to be developed) Truffle langauge API
+                final Visualizer visualizer = debugClient.getExecutionContext().getVisualizer();
+
+                for (FrameDebugDescription frameDesc : frames) {
+                    final StringBuilder sb = new StringBuilder("    frame " + Integer.toString(frameDesc.index()));
+                    sb.append(":at " + visualizer.displaySourceLocation(frameDesc.node()));
+                    sb.append(":in '" + visualizer.displayMethodName(frameDesc.node()) + "'");
+                    stream.println(sb.toString());
+                }
+            }
+        }
+
+        void contextTrace(String format, Object... args) {
+            if (TRACE) {
+                final String srcName = (source != null) ? source.getName() : "no source";
+                DebugEngine.trace("<%d> %s (%s)", level, String.format(format, args), srcName);
+            }
+        }
+    }
+
+    // TODO (mlvdv) wish there were fast-path access to stack depth
+    /**
+     * Depth of current Truffle stack, including nested executions. Includes the top/current frame,
+     * which the standard iterator does not count: {@code 0} if no executions.
+     */
+    @TruffleBoundary
+    private static int currentStackDepth() {
+        final int[] count = {0};
+        Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Void>() {
+            @Override
+            public Void visitFrame(FrameInstance frameInstance) {
+                count[0] = count[0] + 1;
+                return null;
+            }
+        });
+        return count[0] == 0 ? 0 : count[0] + 1;
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugException.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,38 @@
+/*
+ * 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.engine;
+
+/**
+ * An unexpected failure in the operation of the {@link DebugEngine}.
+ */
+public class DebugException extends Exception {
+
+    public DebugException(String string) {
+        super(string);
+    }
+
+    private static final long serialVersionUID = 3307454453821997224L;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/FrameDebugDescription.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,67 @@
+/*
+ * 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.engine;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+
+/**
+ * A summary description of a Truffle {@link Frame} in a particular stack context.
+ *
+ * @see DebugEngine
+ */
+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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/LineBreakpoint.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,47 @@
+/*
+ * 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.engine;
+
+import com.oracle.truffle.api.source.*;
+
+// TODO (mlvdv) generic?
+/**
+ * A breakpoint associated with a {@linkplain LineLocation source line location}.
+ *
+ * @see DebugEngine
+ */
+public abstract class LineBreakpoint extends Breakpoint {
+
+    LineBreakpoint(BreakpointState state, int groupId, int ignoreCount, boolean isOneShot) {
+        super(state, groupId, ignoreCount, isOneShot);
+    }
+
+    /**
+     * Gets the {@linkplain LineLocation source line location} that specifies where this breakpoint
+     * will trigger.
+     */
+    public abstract LineLocation getLineLocation();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/LineBreakpointFactory.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,478 @@
+/*
+ * 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.engine;
+
+import static com.oracle.truffle.tools.debug.engine.Breakpoint.BreakpointState.*;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.impl.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.api.utilities.*;
+import com.oracle.truffle.tools.*;
+import com.oracle.truffle.tools.debug.engine.DebugEngine.BreakpointCallback;
+import com.oracle.truffle.tools.debug.engine.DebugEngine.WarningLog;
+
+//TODO (mlvdv) some common functionality could be factored out of this and TagBreakpointSupport
+
+/**
+ * Support class for creating and managing all existing ordinary (user visible) line breakpoints.
+ * <p>
+ * Notes:
+ * <ol>
+ * <li>Line breakpoints can only be set at nodes tagged as {@link StandardSyntaxTag#STATEMENT}.</li>
+ * <li>A newly created breakpoint looks for probes matching the location, attaches to them if found
+ * by installing an {@link Instrument} that calls back to the breakpoint.</li>
+ * <li>When Truffle "splits" or otherwise copies an AST, any attached {@link Instrument} will be
+ * copied along with the rest of the AST and will call back to the same breakpoint.</li>
+ * <li>When notification is received of a new Node being tagged as a statement, and if a
+ * breakpoint's line location matches the Probe's line location, then the breakpoint will attach a
+ * new Instrument at the probe to activate the breakpoint at that location.</li>
+ * <li>A breakpoint may have multiple Instruments deployed, one attached to each Probe that matches
+ * the breakpoint's line location; this might happen when a source is reloaded.</li>
+ * </ol>
+ *
+ */
+final class LineBreakpointFactory {
+
+    private static final boolean TRACE = false;
+    private static final PrintStream OUT = System.out;
+
+    private static final String BREAKPOINT_NAME = "LINE BREAKPOINT";
+
+    private static void trace(String format, Object... args) {
+        if (TRACE) {
+            OUT.println(String.format("%s: %s", BREAKPOINT_NAME, String.format(format, args)));
+        }
+    }
+
+    private static final Comparator<Entry<LineLocation, LineBreakpointImpl>> BREAKPOINT_COMPARATOR = new Comparator<Entry<LineLocation, LineBreakpointImpl>>() {
+
+        @Override
+        public int compare(Entry<LineLocation, LineBreakpointImpl> entry1, Entry<LineLocation, LineBreakpointImpl> entry2) {
+            final LineLocation line1 = entry1.getKey();
+            final LineLocation line2 = entry2.getKey();
+            final int nameOrder = line1.getSource().getShortName().compareTo(line2.getSource().getShortName());
+            if (nameOrder != 0) {
+                return nameOrder;
+            }
+            return Integer.compare(line1.getLineNumber(), line2.getLineNumber());
+        }
+    };
+
+    private final SourceExecutionProvider sourceExecutionProvider;
+    private final BreakpointCallback breakpointCallback;
+    private final WarningLog warningLog;
+
+    /**
+     * Map: Source lines ==> attached breakpoints. There may be no more than one line breakpoint
+     * associated with a line.
+     */
+    private final Map<LineLocation, LineBreakpointImpl> lineToBreakpoint = new HashMap<>();
+
+    /**
+     * A map of {@link LineLocation} to a collection of {@link Probe}s. This list must be
+     * initialized and filled prior to being used by this class.
+     */
+    private final LineToProbesMap lineToProbesMap;
+
+    /**
+     * Globally suspends all line breakpoint activity when {@code false}, ignoring whether
+     * individual breakpoints are enabled.
+     */
+    @CompilationFinal private boolean breakpointsActive = true;
+    private final CyclicAssumption breakpointsActiveUnchanged = new CyclicAssumption(BREAKPOINT_NAME + " globally active");
+
+    LineBreakpointFactory(SourceExecutionProvider sourceExecutionProvider, BreakpointCallback breakpointCallback, WarningLog warningLog) {
+        this.sourceExecutionProvider = sourceExecutionProvider;
+        this.breakpointCallback = breakpointCallback;
+        this.warningLog = warningLog;
+
+        lineToProbesMap = new LineToProbesMap();
+        lineToProbesMap.install();
+
+        Probe.addProbeListener(new DefaultProbeListener() {
+
+            @Override
+            public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
+                if (tag == StandardSyntaxTag.STATEMENT) {
+                    final SourceSection sourceSection = probe.getProbedSourceSection();
+                    if (sourceSection != null) {
+                        final LineLocation lineLocation = sourceSection.getLineLocation();
+                        if (lineLocation != null) {
+                            // A Probe with line location tagged STATEMENT we haven't seen before.
+                            final LineBreakpointImpl breakpoint = lineToBreakpoint.get(lineLocation);
+                            if (breakpoint != null) {
+                                try {
+                                    breakpoint.attach(probe);
+                                } catch (DebugException e) {
+                                    warningLog.addWarning(BREAKPOINT_NAME + " failure attaching to newly tagged Probe: " + e.getMessage());
+                                    if (TRACE) {
+                                        OUT.println(BREAKPOINT_NAME + " failure: " + e.getMessage());
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    /**
+     * Globally enables line breakpoint activity; all breakpoints are ignored when set to
+     * {@code false}. When set to {@code true}, the enabled/disabled status of each breakpoint
+     * determines whether it will trigger when flow of execution reaches it.
+     *
+     * @param breakpointsActive
+     */
+    void setActive(boolean breakpointsActive) {
+        if (this.breakpointsActive != breakpointsActive) {
+            breakpointsActiveUnchanged.invalidate();
+            this.breakpointsActive = breakpointsActive;
+        }
+    }
+
+    /**
+     * Returns the (not yet disposed) breakpoint by id; null if none.
+     */
+    LineBreakpoint find(long id) {
+        for (LineBreakpoint breakpoint : lineToBreakpoint.values()) {
+            if (breakpoint.getId() == id) {
+                return breakpoint;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets all current line breakpoints,regardless of status; sorted and modification safe.
+     */
+    List<LineBreakpoint> getAll() {
+        ArrayList<Entry<LineLocation, LineBreakpointImpl>> entries = new ArrayList<>(lineToBreakpoint.entrySet());
+        Collections.sort(entries, BREAKPOINT_COMPARATOR);
+
+        final ArrayList<LineBreakpoint> breakpoints = new ArrayList<>(entries.size());
+        for (Entry<LineLocation, LineBreakpointImpl> entry : entries) {
+            breakpoints.add(entry.getValue());
+        }
+        return breakpoints;
+    }
+
+    /**
+     * Creates a new line breakpoint if one doesn't already exist. If one does exist, then resets
+     * the <em>ignore count</em>.
+     *
+     * @param lineLocation where to set the breakpoint
+     * @param ignoreCount number of initial hits before the breakpoint starts causing breaks.
+     * @param oneShot whether the breakpoint should dispose itself after one hit
+     * @return a possibly new breakpoint
+     * @throws DebugException if a breakpoint already exists at the location and the ignore count is
+     *             the same
+     */
+    LineBreakpoint create(int groupId, int ignoreCount, LineLocation lineLocation, boolean oneShot) throws DebugException {
+
+        LineBreakpointImpl breakpoint = lineToBreakpoint.get(lineLocation);
+
+        if (breakpoint == null) {
+            breakpoint = new LineBreakpointImpl(groupId, ignoreCount, lineLocation, oneShot);
+
+            if (TRACE) {
+                trace("NEW " + breakpoint.getShortDescription());
+            }
+
+            lineToBreakpoint.put(lineLocation, breakpoint);
+
+            for (Probe probe : lineToProbesMap.findProbes(lineLocation)) {
+                if (probe.isTaggedAs(StandardSyntaxTag.STATEMENT)) {
+                    breakpoint.attach(probe);
+                    break;
+                }
+            }
+        } else {
+            if (ignoreCount == breakpoint.getIgnoreCount()) {
+                throw new DebugException(BREAKPOINT_NAME + " already set at line " + lineLocation);
+            }
+            breakpoint.setIgnoreCount(ignoreCount);
+            if (TRACE) {
+                trace("CHANGED ignoreCount %s", breakpoint.getShortDescription());
+            }
+        }
+        return breakpoint;
+    }
+
+    /**
+     * Returns the {@link LineBreakpoint} for a given line. There should only ever be one breakpoint
+     * per line.
+     *
+     * @param lineLocation The {@link LineLocation} to get the breakpoint for.
+     * @return The breakpoint for the given line.
+     */
+    LineBreakpoint get(LineLocation lineLocation) {
+        return lineToBreakpoint.get(lineLocation);
+    }
+
+    /**
+     * Removes the associated instrumentation for all one-shot breakpoints only.
+     */
+    void disposeOneShots() {
+        List<LineBreakpointImpl> breakpoints = new ArrayList<>(lineToBreakpoint.values());
+        for (LineBreakpointImpl breakpoint : breakpoints) {
+            if (breakpoint.isOneShot()) {
+                breakpoint.dispose();
+            }
+        }
+    }
+
+    /**
+     * Removes all knowledge of a breakpoint, presumed disposed.
+     */
+    private void forget(LineBreakpointImpl breakpoint) {
+        lineToBreakpoint.remove(breakpoint.getLineLocation());
+    }
+
+    /**
+     * Concrete representation of a line breakpoint, implemented by attaching an instrument to a
+     * probe at the designated source location.
+     */
+    private final class LineBreakpointImpl extends LineBreakpoint implements AdvancedInstrumentResultListener {
+
+        private static final String SHOULD_NOT_HAPPEN = "LineBreakpointImpl:  should not happen";
+
+        private final LineLocation lineLocation;
+
+        // Cached assumption that the global status of line breakpoint activity has not changed.
+        private Assumption breakpointsActiveAssumption;
+
+        // Whether this breakpoint is enable/disabled
+        @CompilationFinal private boolean isEnabled;
+        private Assumption enabledUnchangedAssumption;
+
+        private String conditionExpr;
+
+        /**
+         * The instrument(s) that this breakpoint currently has attached to a {@link Probe}:
+         * {@code null} if not attached.
+         */
+        private List<Instrument> instruments = new ArrayList<>();
+
+        public LineBreakpointImpl(int groupId, int ignoreCount, LineLocation lineLocation, boolean oneShot) {
+            super(ENABLED_UNRESOLVED, groupId, ignoreCount, oneShot);
+            this.lineLocation = lineLocation;
+
+            this.breakpointsActiveAssumption = LineBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption();
+            this.isEnabled = true;
+            this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption(BREAKPOINT_NAME + " enabled state unchanged");
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return isEnabled;
+        }
+
+        @Override
+        public void setEnabled(boolean enabled) {
+            if (enabled != isEnabled) {
+                switch (getState()) {
+                    case ENABLED:
+                        assert !enabled : SHOULD_NOT_HAPPEN;
+                        doSetEnabled(false);
+                        changeState(DISABLED);
+                        break;
+                    case ENABLED_UNRESOLVED:
+                        assert !enabled : SHOULD_NOT_HAPPEN;
+                        doSetEnabled(false);
+                        changeState(DISABLED_UNRESOLVED);
+                        break;
+                    case DISABLED:
+                        assert enabled : SHOULD_NOT_HAPPEN;
+                        doSetEnabled(true);
+                        changeState(ENABLED);
+                        break;
+                    case DISABLED_UNRESOLVED:
+                        assert enabled : SHOULD_NOT_HAPPEN;
+                        doSetEnabled(true);
+                        changeState(DISABLED_UNRESOLVED);
+                        break;
+                    case DISPOSED:
+                        assert false : "breakpoint disposed";
+                        break;
+                    default:
+                        assert false : SHOULD_NOT_HAPPEN;
+                        break;
+                }
+            }
+        }
+
+        @Override
+        public void setCondition(String expr) throws DebugException {
+            if (this.conditionExpr != null || expr != null) {
+                // De-instrument the Probes instrumented by this breakpoint
+                final ArrayList<Probe> probes = new ArrayList<>();
+                for (Instrument instrument : instruments) {
+                    probes.add(instrument.getProbe());
+                    instrument.dispose();
+                }
+                instruments.clear();
+                this.conditionExpr = expr;
+                // Re-instrument the probes previously instrumented
+                for (Probe probe : probes) {
+                    attach(probe);
+                }
+            }
+        }
+
+        @Override
+        public String getCondition() {
+            return conditionExpr;
+        }
+
+        @Override
+        public void dispose() {
+            if (getState() != DISPOSED) {
+                for (Instrument instrument : instruments) {
+                    instrument.dispose();
+                }
+                changeState(DISPOSED);
+                LineBreakpointFactory.this.forget(this);
+            }
+        }
+
+        private void attach(Probe newProbe) throws DebugException {
+            if (getState() == DISPOSED) {
+                throw new IllegalStateException("Attempt to attach a disposed " + BREAKPOINT_NAME);
+            }
+            Instrument newInstrument = null;
+            if (conditionExpr == null) {
+                newInstrument = Instrument.create(new UnconditionalLineBreakInstrumentListener(), BREAKPOINT_NAME);
+            } else {
+                newInstrument = Instrument.create(this, sourceExecutionProvider.createAdvancedInstrumentRootFactory(conditionExpr, this), Boolean.class, BREAKPOINT_NAME);
+            }
+            newProbe.attach(newInstrument);
+            instruments.add(newInstrument);
+            changeState(isEnabled ? ENABLED : DISABLED);
+        }
+
+        private void doSetEnabled(boolean enabled) {
+            if (this.isEnabled != enabled) {
+                enabledUnchangedAssumption.invalidate();
+                this.isEnabled = enabled;
+            }
+        }
+
+        private String getShortDescription() {
+            return BREAKPOINT_NAME + "@" + getLineLocation().getShortDescription();
+        }
+
+        private void changeState(BreakpointState after) {
+            if (TRACE) {
+                trace("STATE %s-->%s %s", getState().getName(), after.getName(), getShortDescription());
+            }
+            setState(after);
+        }
+
+        private void doBreak(Node node, VirtualFrame vFrame) {
+            if (incrHitCountCheckIgnore()) {
+                breakpointCallback.haltedAt(node, vFrame.materialize(), BREAKPOINT_NAME);
+            }
+        }
+
+        /**
+         * Receives notification from the attached instrument that execution is about to enter node
+         * where the breakpoint is set. Designed so that when in the fast path, there is either an
+         * unconditional "halt" call to the debugger or nothing.
+         */
+        private void nodeEnter(Node astNode, VirtualFrame vFrame) {
+
+            // Deopt if the global active/inactive flag has changed
+            try {
+                this.breakpointsActiveAssumption.check();
+            } catch (InvalidAssumptionException ex) {
+                this.breakpointsActiveAssumption = LineBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption();
+            }
+
+            // Deopt if the enabled/disabled state of this breakpoint has changed
+            try {
+                this.enabledUnchangedAssumption.check();
+            } catch (InvalidAssumptionException ex) {
+                this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption("LineBreakpoint enabled state unchanged");
+            }
+
+            if (LineBreakpointFactory.this.breakpointsActive && this.isEnabled) {
+                if (isOneShot()) {
+                    dispose();
+                }
+                LineBreakpointImpl.this.doBreak(astNode, vFrame);
+            }
+        }
+
+        public void notifyResult(Node node, VirtualFrame vFrame, Object result) {
+            final boolean condition = (Boolean) result;
+            if (TRACE) {
+                trace("breakpoint condition = %b  %s", condition, getShortDescription());
+            }
+            if (condition) {
+                nodeEnter(node, vFrame);
+            }
+        }
+
+        @TruffleBoundary
+        public void notifyFailure(Node node, VirtualFrame vFrame, RuntimeException ex) {
+            warningLog.addWarning(String.format("Exception in %s:  %s", getShortDescription(), ex.getMessage()));
+            if (TRACE) {
+                trace("breakpoint failure = %s  %s", ex.toString(), getShortDescription());
+            }
+            // Take the breakpoint if evaluation fails.
+            nodeEnter(node, vFrame);
+        }
+
+        @Override
+        public String getLocationDescription() {
+            return "Line: " + lineLocation.getShortDescription();
+        }
+
+        @Override
+        public LineLocation getLineLocation() {
+            return lineLocation;
+        }
+
+        private final class UnconditionalLineBreakInstrumentListener extends DefaultStandardInstrumentListener {
+
+            @Override
+            public void enter(Probe probe, Node node, VirtualFrame vFrame) {
+                LineBreakpointImpl.this.nodeEnter(node, vFrame);
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/SourceExecutionProvider.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,142 @@
+/*
+ * 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.engine;
+
+import java.util.*;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+
+/**
+ * Base for language-specific support required by the {@link DebugEngine} and any other tools that
+ * run sources.
+ */
+public abstract class SourceExecutionProvider {
+
+    interface ExecutionListener {
+
+        /**
+         * Notifies that execution is about to start and requests initial execution mode.
+         */
+        void executionStarted(Source source, boolean stepInto);
+
+        /**
+         * Notification that the current execution has just ended.
+         */
+        void executionEnded();
+    }
+
+    private final List<ExecutionListener> listeners = new ArrayList<>();
+
+    final void addExecutionListener(ExecutionListener listener) {
+        assert listener != null;
+        listeners.add(listener);
+    }
+
+    /**
+     * Runs a script. If "StepInto" is specified, halts at the first location tagged as a
+     * {@linkplain StandardSyntaxTag#STATEMENT STATEMENT}.
+     */
+    final void run(Source source, boolean stepInto) throws DebugException {
+        for (ExecutionListener listener : listeners) {
+            listener.executionStarted(source, stepInto);
+        }
+        try {
+            languageRun(source);
+        } finally {
+            for (ExecutionListener listener : listeners) {
+                listener.executionEnded();
+            }
+        }
+    }
+
+    /**
+     * Evaluates string of language code in a halted execution context, at top level if
+     * <code>mFrame==null</code>.
+     */
+    final Object eval(Source source, Node node, MaterializedFrame mFrame) {
+        for (ExecutionListener listener : listeners) {
+            listener.executionStarted(source, false);
+        }
+        try {
+            return languageEval(source, node, mFrame);
+        } finally {
+            for (ExecutionListener listener : listeners) {
+                listener.executionEnded();
+            }
+        }
+    }
+
+    /**
+     * Creates a language-specific factory to produce instances of {@link AdvancedInstrumentRoot}
+     * that, when executed, computes the result of a textual expression in the language; used to
+     * create an
+     * {@linkplain Instrument#create(AdvancedInstrumentResultListener, AdvancedInstrumentRootFactory, Class, String)
+     * Advanced Instrument}.
+     *
+     * @param expr a guest language expression
+     * @param resultListener optional listener for the result of each evaluation.
+     * @return a new factory
+     * @throws DebugException if the factory cannot be created, for example if the expression is
+     *             badly formed.
+     */
+    final AdvancedInstrumentRootFactory createAdvancedInstrumentRootFactory(String expr, AdvancedInstrumentResultListener resultListener) throws DebugException {
+        return languageAdvancedInstrumentRootFactory(expr, resultListener);
+    }
+
+    /**
+     * Runs source code.
+     *
+     * @param source code
+     * @throws DebugException if unable to run successfully
+     */
+    public abstract void languageRun(Source source) throws DebugException;
+
+    /**
+     * Runs source code in a halted execution context, or at top level.
+     *
+     * @param source the code to run
+     * @param node node where execution halted, {@code null} if no execution context
+     * @param mFrame frame where execution halted, {@code null} if no execution context
+     * @return result of running the code in the context, or at top level if no execution context.
+     */
+    public abstract Object languageEval(Source source, Node node, MaterializedFrame mFrame);
+
+    /**
+     * Creates a {@linkplain AdvancedInstrumentRootFactory factory} that produces AST fragments from
+     * a textual expression, suitable for execution in context by the Instrumentation Framework.
+     *
+     * @param expr
+     * @param resultListener
+     * @return a factory that returns AST fragments that compute the expression
+     * @throws DebugException if the expression cannot be processed
+     *
+     * @see Instrument#create(AdvancedInstrumentResultListener, AdvancedInstrumentRootFactory,
+     *      Class, String)
+     */
+    public abstract AdvancedInstrumentRootFactory languageAdvancedInstrumentRootFactory(String expr, AdvancedInstrumentResultListener resultListener) throws DebugException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/TagBreakpoint.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,46 @@
+/*
+ * 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.engine;
+
+import com.oracle.truffle.api.instrument.*;
+
+// TODO (mlvdv) generic?
+/**
+ * A breakpoint associated with a {@link SyntaxTag}.
+ *
+ * @see DebugEngine
+ */
+public abstract class TagBreakpoint extends Breakpoint {
+
+    TagBreakpoint(BreakpointState state, int groupId, int ignoreCount, boolean isOneShot) {
+        super(state, groupId, ignoreCount, isOneShot);
+    }
+
+    /**
+     * Gets the tag that specifies where this breakpoint will trigger.
+     */
+    public abstract SyntaxTag getTag();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/TagBreakpointFactory.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,441 @@
+/*
+ * 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.engine;
+
+import static com.oracle.truffle.tools.debug.engine.Breakpoint.BreakpointState.*;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.impl.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.utilities.*;
+import com.oracle.truffle.tools.debug.engine.DebugEngine.BreakpointCallback;
+import com.oracle.truffle.tools.debug.engine.DebugEngine.WarningLog;
+
+// TODO (mlvdv) some common functionality could be factored out of this and LineBreakpointSupport
+
+/**
+ * Support class for creating and managing "Tag Breakpoints". A Tag Breakpoint halts execution just
+ * before reaching any node whose Probe carries a specified {@linkplain SyntaxTag Tag}.
+ * <p>
+ * The {@linkplain Probe#setBeforeTagTrap(SyntaxTagTrap) Tag Trap}, which is built directly into the
+ * Instrumentation Framework, does the same thing more efficiently, but there may only be one Tag
+ * Trap active at a time. Any number of tag breakpoints may coexist with the Tag Trap, but it would
+ * be confusing to have a Tag Breakpoint set for the same Tag as the current Tag Trap.
+ * <p>
+ * Notes:
+ * <ol>
+ * <li>Only one Tag Breakpoint can be active for a specific {@linkplain SyntaxTag Tag}.</li>
+ * <li>A newly created breakpoint looks for probes matching the tag, attaches to them if found by
+ * installing an {@link Instrument}.</li>
+ * <li>When Truffle "splits" or otherwise copies an AST, any attached {@link Instrument} will be
+ * copied along with the rest of the AST and will call back to the same breakpoint.</li>
+ * <li>When notification is received that the breakpoint's Tag has been newly added to a Node, then
+ * the breakpoint will attach a new Instrument at the probe to activate the breakpoint at that
+ * location.</li>
+ * <li>A breakpoint may have multiple Instruments deployed, one attached to each Probe that holds
+ * the breakpoint's tag; this might happen when a source is reloaded.</li>
+ * </ol>
+ */
+final class TagBreakpointFactory {
+
+    private static final boolean TRACE = false;
+    private static final PrintStream OUT = System.out;
+
+    private static final String BREAKPOINT_NAME = "TAG BREAKPOINT";
+
+    private static void trace(String format, Object... args) {
+        if (TRACE) {
+            OUT.println(String.format("%s: %s", BREAKPOINT_NAME, String.format(format, args)));
+        }
+    }
+
+    private static final Comparator<Entry<SyntaxTag, TagBreakpointImpl>> BREAKPOINT_COMPARATOR = new Comparator<Entry<SyntaxTag, TagBreakpointImpl>>() {
+
+        @Override
+        public int compare(Entry<SyntaxTag, TagBreakpointImpl> entry1, Entry<SyntaxTag, TagBreakpointImpl> entry2) {
+            return entry1.getKey().name().compareTo(entry2.getKey().name());
+        }
+    };
+
+    private final SourceExecutionProvider sourceExecutionProvider;
+    private final BreakpointCallback breakpointCallback;
+    private final WarningLog warningLog;
+
+    /**
+     * Map: Tags ==> Tag Breakpoints. There may be no more than one breakpoint per Tag.
+     */
+    private final Map<SyntaxTag, TagBreakpointImpl> tagToBreakpoint = new HashMap<>();
+
+    /**
+     * Globally suspends all line breakpoint activity when {@code false}, ignoring whether
+     * individual breakpoints are enabled.
+     */
+    @CompilationFinal private boolean breakpointsActive = true;
+    private final CyclicAssumption breakpointsActiveUnchanged = new CyclicAssumption(BREAKPOINT_NAME + " globally active");
+
+    TagBreakpointFactory(SourceExecutionProvider sourceExecutionProvider, BreakpointCallback breakpointCallback, WarningLog warningLog) {
+        this.sourceExecutionProvider = sourceExecutionProvider;
+        this.breakpointCallback = breakpointCallback;
+        this.warningLog = warningLog;
+
+        Probe.addProbeListener(new DefaultProbeListener() {
+
+            @Override
+            public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
+                final TagBreakpointImpl breakpoint = tagToBreakpoint.get(tag);
+                if (breakpoint != null) {
+                    try {
+                        breakpoint.attach(probe);
+                    } catch (DebugException e) {
+                        warningLog.addWarning(BREAKPOINT_NAME + " failure attaching to newly tagged Probe: " + e.getMessage());
+                        if (TRACE) {
+                            OUT.println(BREAKPOINT_NAME + " failure: " + e.getMessage());
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    /**
+     * Globally enables tag breakpoint activity; all breakpoints are ignored when set to
+     * {@code false}. When set to {@code true}, the enabled/disabled status of each breakpoint
+     * determines whether it will trigger when flow of execution reaches it.
+     *
+     * @param breakpointsActive
+     */
+    void setActive(boolean breakpointsActive) {
+        if (this.breakpointsActive != breakpointsActive) {
+            breakpointsActiveUnchanged.invalidate();
+            this.breakpointsActive = breakpointsActive;
+        }
+    }
+
+    /**
+     * Returns the (not yet disposed) breakpoint by id, if any; null if none.
+     */
+    TagBreakpoint find(long id) {
+        for (TagBreakpointImpl breakpoint : tagToBreakpoint.values()) {
+            if (breakpoint.getId() == id) {
+                return breakpoint;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets all current tag breakpoints,regardless of status; sorted and modification safe.
+     */
+    List<TagBreakpoint> getAll() {
+        ArrayList<Entry<SyntaxTag, TagBreakpointImpl>> entries = new ArrayList<>(tagToBreakpoint.entrySet());
+        Collections.sort(entries, BREAKPOINT_COMPARATOR);
+
+        final ArrayList<TagBreakpoint> breakpoints = new ArrayList<>(entries.size());
+        for (Entry<SyntaxTag, TagBreakpointImpl> entry : entries) {
+            breakpoints.add(entry.getValue());
+        }
+        return breakpoints;
+    }
+
+    /**
+     * Creates a new tag breakpoint if one doesn't already exist. If one does exist, then resets the
+     * <em>ignore count</em>.
+     *
+     * @param tag where to set the breakpoint
+     * @param ignoreCount number of initial hits before the breakpoint starts causing breaks.
+     * @param oneShot whether the breakpoint should dispose itself after one hit
+     * @return a possibly new breakpoint
+     * @throws DebugException if a breakpoint already exists for the tag and the ignore count is the
+     *             same
+     */
+    TagBreakpoint create(int groupId, int ignoreCount, SyntaxTag tag, boolean oneShot) throws DebugException {
+
+        TagBreakpointImpl breakpoint = tagToBreakpoint.get(tag);
+
+        if (breakpoint == null) {
+            breakpoint = new TagBreakpointImpl(groupId, ignoreCount, tag, oneShot);
+
+            if (TRACE) {
+                trace("NEW " + breakpoint.getShortDescription());
+            }
+
+            tagToBreakpoint.put(tag, breakpoint);
+
+            for (Probe probe : Probe.findProbesTaggedAs(tag)) {
+                breakpoint.attach(probe);
+            }
+        } else {
+            if (ignoreCount == breakpoint.getIgnoreCount()) {
+                throw new DebugException(BREAKPOINT_NAME + " already set for tag " + tag.name());
+            }
+            breakpoint.setIgnoreCount(ignoreCount);
+            if (TRACE) {
+                trace("CHANGED ignoreCount %s", breakpoint.getShortDescription());
+            }
+        }
+        return breakpoint;
+    }
+
+    /**
+     * Returns the {@link TagBreakpoint} for a given tag, {@code null} if none.
+     */
+    TagBreakpoint get(SyntaxTag tag) {
+        return tagToBreakpoint.get(tag);
+    }
+
+    /**
+     * Removes the associated instrumentation for all one-shot breakpoints only.
+     */
+    void disposeOneShots() {
+        List<TagBreakpointImpl> breakpoints = new ArrayList<>(tagToBreakpoint.values());
+        for (TagBreakpointImpl breakpoint : breakpoints) {
+            if (breakpoint.isOneShot()) {
+                breakpoint.dispose();
+            }
+        }
+    }
+
+    /**
+     * Removes all knowledge of a breakpoint, presumed disposed.
+     */
+    private void forget(TagBreakpointImpl breakpoint) {
+        tagToBreakpoint.remove(breakpoint.getTag());
+    }
+
+    /**
+     * Concrete representation of a line breakpoint, implemented by attaching an instrument to a
+     * probe at the designated source location.
+     */
+    private final class TagBreakpointImpl extends TagBreakpoint implements AdvancedInstrumentResultListener {
+
+        private static final String SHOULD_NOT_HAPPEN = "TagBreakpointImpl:  should not happen";
+
+        private final SyntaxTag tag;
+
+        // Cached assumption that the global status of tag breakpoint activity has not changed.
+        private Assumption breakpointsActiveAssumption;
+
+        // Whether this breakpoint is enable/disabled
+        @CompilationFinal private boolean isEnabled;
+        private Assumption enabledUnchangedAssumption;
+
+        private String conditionExpr;
+
+        /**
+         * The instrument(s) that this breakpoint currently has attached to a {@link Probe}:
+         * {@code null} if not attached.
+         */
+        private List<Instrument> instruments = new ArrayList<>();
+
+        private TagBreakpointImpl(int groupId, int ignoreCount, SyntaxTag tag, boolean oneShot) {
+            super(ENABLED, groupId, ignoreCount, oneShot);
+            this.tag = tag;
+            this.breakpointsActiveAssumption = TagBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption();
+            this.isEnabled = true;
+            this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption(BREAKPOINT_NAME + " enabled state unchanged");
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return isEnabled;
+        }
+
+        @Override
+        public void setEnabled(boolean enabled) {
+            // Tag Breakpoints are never unresolved
+            if (enabled != isEnabled) {
+                switch (getState()) {
+                    case ENABLED:
+                        assert !enabled : SHOULD_NOT_HAPPEN;
+                        doSetEnabled(false);
+                        changeState(DISABLED);
+                        break;
+                    case DISABLED:
+                        assert enabled : SHOULD_NOT_HAPPEN;
+                        doSetEnabled(true);
+                        changeState(ENABLED);
+                        break;
+                    case DISPOSED:
+                        assert false : "breakpoint disposed";
+                        break;
+                    case ENABLED_UNRESOLVED:
+                    case DISABLED_UNRESOLVED:
+                    default:
+                        assert false : SHOULD_NOT_HAPPEN;
+                        break;
+                }
+            }
+        }
+
+        @Override
+        public void setCondition(String expr) throws DebugException {
+            if (this.conditionExpr != null || expr != null) {
+                // De-instrument the Probes instrumented by this breakpoint
+                final ArrayList<Probe> probes = new ArrayList<>();
+                for (Instrument instrument : instruments) {
+                    probes.add(instrument.getProbe());
+                    instrument.dispose();
+                }
+                instruments.clear();
+                this.conditionExpr = expr;
+                // Re-instrument the probes previously instrumented
+                for (Probe probe : probes) {
+                    attach(probe);
+                }
+            }
+        }
+
+        @Override
+        public String getCondition() {
+            return conditionExpr;
+        }
+
+        @Override
+        public void dispose() {
+            if (getState() != DISPOSED) {
+                for (Instrument instrument : instruments) {
+                    instrument.dispose();
+                }
+                changeState(DISPOSED);
+                TagBreakpointFactory.this.forget(this);
+            }
+        }
+
+        private void attach(Probe newProbe) throws DebugException {
+            if (getState() == DISPOSED) {
+                throw new IllegalStateException("Attempt to attach a disposed " + BREAKPOINT_NAME);
+            }
+            Instrument newInstrument = null;
+            if (conditionExpr == null) {
+                newInstrument = Instrument.create(new UnconditionalTagBreakInstrumentListener(), BREAKPOINT_NAME);
+            } else {
+                newInstrument = Instrument.create(this, sourceExecutionProvider.createAdvancedInstrumentRootFactory(conditionExpr, this), Boolean.class, BREAKPOINT_NAME);
+            }
+            newProbe.attach(newInstrument);
+            instruments.add(newInstrument);
+            changeState(isEnabled ? ENABLED : DISABLED);
+        }
+
+        private void doSetEnabled(boolean enabled) {
+            if (this.isEnabled != enabled) {
+                enabledUnchangedAssumption.invalidate();
+                this.isEnabled = enabled;
+            }
+        }
+
+        private String getShortDescription() {
+            return BREAKPOINT_NAME + "@" + tag.name();
+        }
+
+        private void changeState(BreakpointState after) {
+            if (TRACE) {
+                trace("STATE %s-->%s %s", getState().getName(), after.getName(), getShortDescription());
+            }
+            setState(after);
+        }
+
+        private void doBreak(Node node, VirtualFrame vFrame) {
+            if (incrHitCountCheckIgnore()) {
+                breakpointCallback.haltedAt(node, vFrame.materialize(), BREAKPOINT_NAME);
+            }
+        }
+
+        /**
+         * Receives notification from the attached instrument that execution is about to enter node
+         * where the breakpoint is set. Designed so that when in the fast path, there is either an
+         * unconditional "halt" call to the debugger or nothing.
+         */
+        private void nodeEnter(Node astNode, VirtualFrame vFrame) {
+
+            // Deopt if the global active/inactive flag has changed
+            try {
+                this.breakpointsActiveAssumption.check();
+            } catch (InvalidAssumptionException ex) {
+                this.breakpointsActiveAssumption = TagBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption();
+            }
+
+            // Deopt if the enabled/disabled state of this breakpoint has changed
+            try {
+                this.enabledUnchangedAssumption.check();
+            } catch (InvalidAssumptionException ex) {
+                this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption("LineBreakpoint enabled state unchanged");
+            }
+
+            if (TagBreakpointFactory.this.breakpointsActive && this.isEnabled) {
+                if (isOneShot()) {
+                    dispose();
+                }
+                TagBreakpointImpl.this.doBreak(astNode, vFrame);
+            }
+
+        }
+
+        public void notifyResult(Node node, VirtualFrame vFrame, Object result) {
+            final boolean condition = (Boolean) result;
+            if (TRACE) {
+                trace("breakpoint condition = %b  %s", condition, getShortDescription());
+            }
+            if (condition) {
+                nodeEnter(node, vFrame);
+            }
+        }
+
+        public void notifyFailure(Node node, VirtualFrame vFrame, RuntimeException ex) {
+            warningLog.addWarning(String.format("Exception in %s:  %s", getShortDescription(), ex.getMessage()));
+            if (TRACE) {
+                trace("breakpoint failure = %s  %s", ex.toString(), getShortDescription());
+            }
+            // Take the breakpoint if evaluation fails.
+            nodeEnter(node, vFrame);
+        }
+
+        @Override
+        public String getLocationDescription() {
+            return "Tag " + tag.name();
+        }
+
+        @Override
+        public SyntaxTag getTag() {
+            return tag;
+        }
+
+        private final class UnconditionalTagBreakInstrumentListener extends DefaultStandardInstrumentListener {
+
+            @Override
+            public void enter(Probe probe, Node node, VirtualFrame vFrame) {
+                TagBreakpointImpl.this.nodeEnter(node, vFrame);
+            }
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/package-info.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package contains the shared (language-agnostic) support for implementing debuggers
+ * that work with Truffle-implemented languages.
+ * <p>
+ * This implementation is made possible by the general purpose Instrumentation Framework built
+ * into the Truffle platform.  Some online documentation for the Instrumentation Framework is available
+ * online:
+ * <quote>
+ * <a href="https://wiki.openjdk.java.net/display/Graal/Instrumentation+API">https://wiki.openjdk.java.net/display/Graal/Instrumentation+API</a>
+ * </quote>
+ * <p>
+ * Debugging services for a Truffle-implemented language are provided by creating an instance
+ * of {@link com.oracle.truffle.tools.debug.engine.DebugEngine} specialized for a specific language.  The DebugEngine can:
+ * <ul>
+ * <li>Load and run sources in the language</li>
+ * <li>Set breakpoints possibly with conditions and other attributes, on source lines</li>
+ * <li>Navigate by Continue, StepIn, StepOver, or StepOut</li>
+ * <li>Examine the execution stack</li>
+ * <li>Examine the contents of a stack frame</li>
+ * <li>Evaluate a code fragment in the context of a stack frame</li>
+ * </ul>
+ * <p>
+ * Specialization of the DebugEngine for a Truffle-implemented language takes several forms:
+ * <ol>
+ * <li>A specification from the language implementor that adds Instrumentation "tags" to the nodes
+ * that a debugger should know about, for example Statements, Calls, and Throws</li>
+ * <li>Methods to run programs/scripts generally, and more specifically run text fragments in the context of
+ * a particular frame/Node in a halted Truffle execution</li>
+ * <li>Utility methods, such as providing textual displays of Objects that represent values in the language</li>
+ * </ol>
+ * <p>
+ * <strong>Note:</strong> Both the functionality and API for this package are under active development.
+ * <p>
+ * @see com.oracle.truffle.api.instrument
+ */
+package com.oracle.truffle.tools.debug.engine;
+
--- /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/REPLClient.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * The client side of a simple message-based protocol for a possibly remote language
+ * Read-Eval-Print-Loop.
+ */
+public interface REPLClient {
+
+    /**
+     * Accepts a reply from the server; there may be more than one reply in response to a request.
+     */
+    REPLMessage receive(REPLMessage reply);
+
+}
--- /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/REPLMessage.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,172 @@
+/*
+ * 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 java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+
+/**
+ * A message for communication between a Read-Eval-Print-Loop server associated with a language
+ * implementation and a possibly remote client.
+ *
+ * @see REPLClient
+ * @see REPLServer
+ */
+public final class REPLMessage {
+
+    // Some standard keys and values
+    public static final String AST = "ast";
+    public static final String AST_DEPTH = "show-max-depth";
+    public static final String BACKTRACE = "backtrace";
+    public static final String BREAK_AT_LINE = "break-at-line";
+    public static final String BREAK_AT_LINE_ONCE = "break-at-line-once";
+    public static final String BREAK_AT_THROW = "break-at-throw";
+    public static final String BREAK_AT_THROW_ONCE = "break-at-throw-once";
+    public static final String BREAKPOINT_CONDITION = "breakpoint-condition";
+    public static final String BREAKPOINT_GROUP_ID = "breakpoint-group-id";
+    public static final String BREAKPOINT_HIT_COUNT = "breakpoint-hit-count";
+    public static final String BREAKPOINT_ID = "breakpoint-id";
+    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 CLEAR_BREAK = "clear-breakpoint";
+    public static final String CODE = "code";
+    public static final String CONTINUE = "continue";
+    public static final String DEBUG_LEVEL = "debug-level";
+    public static final String DELETE_BREAK = "delete-breakpoint";
+    public static final String DISABLE_BREAK = "disable-breakpoint";
+    public static final String DISPLAY_MSG = "displayable-message";
+    public static final String DOWN = "down";
+    public static final String ENABLE_BREAK = "enable-breakpoint";
+    public static final String EVAL = "eval";
+    public static final String FAILED = "failed";
+    public static final String FILE = "file";
+    public static final String FILE_PATH = "path";
+    public static final String FRAME = "frame";
+    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_KEY = "info-key";
+    public static final String INFO_VALUE = "info-value";
+    public static final String KILL = "kill";
+    public static final String LANGUAGE = "language";
+    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 METHOD_NAME = "method-name";
+    public static final String OP = "op";
+    public static final String OPTION = "option";
+    public static final String QUIT = "quit";
+    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 SOURCE_LINE_TEXT = "source-line-text";
+    public static final String SOURCE_LOCATION = "source-location";
+    public static final String SOURCE_NAME = "source-name";
+    public static final String SOURCE_TEXT = "source-text";
+    public static final String STACK_SIZE = "stack-size";
+    public static final String STATUS = "status";
+    public static final String STEP_INTO = "step-into";
+    public static final String STEP_OUT = "step-out";
+    public static final String STEP_OVER = "step-over";
+    public static final String STOPPED = "stopped";
+    public static final String SUB = "sub";
+    public static final String SUBTREE = "subtree";
+    public static final String SUCCEEDED = "succeeded";
+    public static final String TOPIC = "topic";
+    public static final String TRUFFLE = "truffle";
+    public static final String TRUFFLE_AST = "truffle-ast";
+    public static final String TRUFFLE_NODE = "truffle-node";
+    public static final String TRUFFLE_SUBTREE = "truffle-subtree";
+    public static final String UNSET_BREAK_CONDITION = "unset-breakpoint-condition";
+    public static final String UP = "up";
+    public static final String VALUE = "value";
+    public static final String WARNINGS = "warnings";
+    private final Map<String, String> map;
+
+    /**
+     * Creates an empty REPL message.
+     */
+    public REPLMessage() {
+        this.map = new TreeMap<>();
+    }
+
+    /**
+     * Creates a REPL message with an initial entry.
+     */
+    public REPLMessage(String key, String value) {
+        this();
+        map.put(key, value);
+    }
+
+    public REPLMessage(REPLMessage message) {
+        this.map = new TreeMap<>(message.map);
+    }
+
+    public String get(String key) {
+        return map.get(key);
+    }
+
+    /**
+     * Returns the specified key value as an integer; {@code null} if missing or non-numeric.
+     */
+    public Integer getIntValue(String key) {
+        final String value = map.get(key);
+        if (value != null) {
+            try {
+                return Integer.parseInt(value);
+            } catch (NumberFormatException e) {
+
+            }
+        }
+        return null;
+    }
+
+    public String put(String key, String value) {
+        return map.put(key, value);
+    }
+
+    public String remove(String key) {
+        return map.remove(key);
+    }
+
+    public Set<String> keys() {
+        return map.keySet();
+    }
+
+    public void print(PrintStream out, String linePrefix) {
+        map.keySet();
+
+        for (Entry<String, String> entry : map.entrySet()) {
+            String value = entry.getValue();
+            if (value != null && value.length() > 50) {
+                value = value.substring(0, 50) + " ...";
+            }
+            out.println(linePrefix + entry.getKey() + " = \"" + value + "\"");
+        }
+    }
+}
--- /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/REPLServer.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/**
+ * The server side of a simple message-based protocol for a possibly remote language
+ * Read-Eval-Print-Loop.
+ */
+public interface REPLServer {
+
+    /**
+     * Starts up a server; status returned in a message.
+     */
+    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.
+     */
+    REPLMessage[] receive(REPLMessage request);
+
+}
--- /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/client/REPLClientContext.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,100 @@
+/*
+ * 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.client;
+
+import java.util.*;
+
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.tools.debug.shell.*;
+
+/**
+ * Client context for interaction with a program halted by the {@link REPLServer}.
+ */
+public interface REPLClientContext {
+
+    /**
+     * The source code halted in this context.
+     */
+    Source source();
+
+    /**
+     * The 1-based line at which execution is halted in this context; 0 means unknown.
+     */
+    int lineNumber();
+
+    /**
+     * The Truffle stack where execution is halted in this context.
+     */
+    List<REPLFrame> frames();
+
+    /**
+     * The nesting level of the execution context: 0 means evaluating shell commands outside any
+     * executing program.
+     */
+    int level();
+
+    /**
+     * The source currently selected by the user; defaults to where halted.
+     */
+    Source getSelectedSource();
+
+    /**
+     * The frame number in this execution context currently selected by the user; defaults to 0.
+     */
+    int getSelectedFrameNumber();
+
+    /**
+     * Issue a command to the REPLServer that
+     * <ul>
+     * <li>can be specified by a single "op",</li>
+     * <li>produces information in the form of a single string, and</li>
+     * <li>has no effect on the execution state.</li>
+     * </ul>
+     */
+    String stringQuery(String op);
+
+    /**
+     * Sets a new "default" frame number for frame-related commands.
+     */
+    void selectFrameNumber(int frameNumber);
+
+    /**
+     * Sends an information message.
+     */
+    void displayInfo(String message);
+
+    void displayStack();
+
+    /**
+     * Send a message related to handling a command in this context.
+     */
+    void displayReply(String message);
+
+    /**
+     * Send a message related to failure while handling a command in this context.
+     */
+    void displayFailReply(String message);
+
+}
--- /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/client/REPLCommand.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,56 @@
+/*
+ * 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.client;
+
+// TODO (mlvdv)  write a real command line parser
+abstract class REPLCommand {
+
+    private final String command;
+    private final String abbreviation;
+    private final String description;
+
+    public REPLCommand(String command, String abbreviation, String description) {
+        this.command = command;
+        this.abbreviation = abbreviation;
+        this.description = description;
+    }
+
+    public final String getCommand() {
+        return command;
+    }
+
+    public final String getAbbreviation() {
+        return abbreviation;
+    }
+
+    public final String getDescription() {
+        return description;
+    }
+
+    public String[] getHelp() {
+        return new String[]{getCommand() + ": " + getDescription()};
+    }
+
+}
--- /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/client/REPLContinueException.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,31 @@
+/*
+ * 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.client;
+
+class REPLContinueException extends RuntimeException {
+
+    private static final long serialVersionUID = 4763527450877123326L;
+
+}
--- /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/client/REPLFrame.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,62 @@
+/*
+ * 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.client;
+
+/**
+ * Information about a stack frame in the current {@link REPLClientContext}.
+ */
+interface REPLFrame {
+
+    /**
+     * Index of the frame.
+     */
+    int index();
+
+    /**
+     * File path; null if unknown.
+     */
+    String locationFilePath();
+
+    /**
+     * Line number of frame location; null if unknown.
+     */
+    Integer locationLineNumber();
+
+    /**
+     * Short description of the code location.
+     */
+    String locationDescription();
+
+    /**
+     * Name of the method body containing the location.
+     */
+    String name();
+
+    /**
+     * The line of text at the code location.
+     */
+    String sourceLineText();
+
+}
--- /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/client/REPLRemoteCommand.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,696 @@
+/*
+ * 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.client;
+
+import java.io.*;
+import java.util.*;
+
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.tools.debug.shell.*;
+
+// TODO (mlvdv)  write a real command line parser
+public abstract class REPLRemoteCommand extends REPLCommand {
+
+    public REPLRemoteCommand(String command, String abbreviation, String description) {
+        super(command, abbreviation, description);
+    }
+
+    protected abstract REPLMessage createRequest(REPLClientContext context, String[] args);
+
+    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 {
+            final String result = firstReply.get(REPLMessage.DISPLAY_MSG);
+            context.displayReply(result != null ? result : firstReply.toString());
+        }
+
+        for (int i = 1; i < replies.length; i++) {
+            REPLMessage reply = replies[i];
+            final String result = reply.get(REPLMessage.DISPLAY_MSG);
+            context.displayInfo(result != null ? result : reply.toString());
+        }
+    }
+
+    public static final REPLRemoteCommand BREAK_AT_LINE_CMD = new REPLRemoteCommand("break-at-line", "break", "Set a breakpoint") {
+
+        private final String[] help = {"break <n> [ignore=<n>] : set breakpoint at line <n> in current file", "break <filename>:<n> [ignore=<n>] : set breakpoint at line <n> in <filename>",
+                        " optionally ignore first <n> hits (default 0)"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            try {
+                final REPLineLocation lineLocation = REPLineLocation.parse(context, args);
+                final REPLMessage requestMessage = lineLocation.createMessage(REPLMessage.BREAK_AT_LINE);
+                int ignoreCount = 0;
+                if (args.length > 2) {
+                    final String ignoreText = args[2];
+                    if (ignoreText.equals("ignore")) {
+                        throw new IllegalArgumentException("No ignore count specified");
+                    }
+                    final String[] split = ignoreText.split("=");
+                    if (split.length == 2 && split[0].equals("ignore")) {
+                        try {
+                            ignoreCount = Integer.parseInt(split[1]);
+                            if (ignoreCount < 0) {
+                                throw new IllegalArgumentException("Illegal ignore count: " + split[1]);
+                            }
+                        } catch (NumberFormatException e) {
+                            throw new IllegalArgumentException("No ignore count specified");
+                        }
+                    } else {
+                        throw new IllegalArgumentException("Unrecognized argument \"" + ignoreText + "\"");
+                    }
+                }
+                requestMessage.put(REPLMessage.BREAKPOINT_IGNORE_COUNT, Integer.toString(ignoreCount));
+                return requestMessage;
+            } catch (IllegalArgumentException ex) {
+                context.displayFailReply(ex.getMessage());
+            }
+            return null;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            REPLMessage firstReply = replies[0];
+
+            if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) {
+                final String number = firstReply.get(REPLMessage.BREAKPOINT_ID);
+                final String fileName = firstReply.get(REPLMessage.SOURCE_NAME);
+                final String lineNumber = firstReply.get(REPLMessage.LINE_NUMBER);
+                firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + number + " set at " + fileName + ":" + lineNumber);
+            }
+            super.processReply(context, replies);
+        }
+    };
+
+    public static final REPLRemoteCommand BREAK_AT_LINE_ONCE_CMD = new REPLRemoteCommand("break-at-line-once", "break1", "Set a one-shot breakpoint") {
+
+        private final String[] help = {"break <n>: set one-shot breakpoint at line <n> in current file", "break <filename>:<n>: set one-shot breakpoint at line <n> in current file"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            try {
+                return REPLineLocation.parse(context, args).createMessage(REPLMessage.BREAK_AT_LINE_ONCE);
+            } catch (IllegalArgumentException ex) {
+                context.displayFailReply(ex.getMessage());
+            }
+            return null;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            REPLMessage firstReply = replies[0];
+
+            if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) {
+                final String fileName = firstReply.get(REPLMessage.SOURCE_NAME);
+                final String lineNumber = firstReply.get(REPLMessage.LINE_NUMBER);
+                firstReply.put(REPLMessage.DISPLAY_MSG, "one-shot breakpoint set at " + fileName + ":" + lineNumber);
+            }
+            super.processReply(context, replies);
+        }
+    };
+
+    public static final REPLRemoteCommand BREAK_AT_THROW_CMD = new REPLRemoteCommand("break-at-throw", "breakthrow", "Break at any throw") {
+
+        private final String[] help = {"break-at-throw: set breakpoint on any throw"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.BREAK_AT_THROW);
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            REPLMessage firstReply = replies[0];
+
+            if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) {
+                firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint at any throw set");
+            }
+            super.processReply(context, replies);
+        }
+    };
+
+    public static final REPLRemoteCommand BREAK_AT_THROW_ONCE_CMD = new REPLRemoteCommand("break-at-throw-once", "break1throw", "Break once at any throw") {
+
+        private final String[] help = {"break-at-throw: set one-short breakpoint on any throw"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.BREAK_AT_THROW_ONCE);
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            REPLMessage firstReply = replies[0];
+
+            if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) {
+                firstReply.put(REPLMessage.DISPLAY_MSG, "one-shot breakpoint at any throw set");
+            }
+            super.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>"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (args.length == 1) {
+                context.displayFailReply("breakpoint number not speciified:  \"break <n>\"");
+            } else if (args.length > 2) {
+                context.displayFailReply("breakpoint number not understood:  \"break <n>\"");
+            } else {
+                try {
+                    final int breakpointNumber = Integer.parseInt(args[1]);
+                    final REPLMessage request = new REPLMessage();
+                    request.put(REPLMessage.OP, REPLMessage.CLEAR_BREAK);
+                    request.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
+                    return request;
+                } catch (IllegalArgumentException ex) {
+                    context.displayFailReply(ex.getMessage());
+                }
+            }
+            return null;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            REPLMessage firstReply = replies[0];
+
+            if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) {
+                final int breakpointNumber = firstReply.getIntValue(REPLMessage.BREAKPOINT_ID);
+                firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + breakpointNumber + " cleared");
+            }
+            super.processReply(context, replies);
+        }
+    };
+
+    public static final REPLRemoteCommand CONDITION_BREAK_CMD = new REPLRemoteCommand("cond", null, "Set new condition on a breakpoint") {
+
+        private final String[] help = {"cond <n> [expr]: sets new condition on breakpoint number <n>; make unconditional if no [expr]"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (args.length == 1) {
+                context.displayFailReply("breakpoint number not speciified:  \"cond <n>\"");
+            } else {
+                try {
+                    final int breakpointNumber = Integer.parseInt(args[1]);
+                    final REPLMessage request = new REPLMessage();
+                    request.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
+                    if (args.length == 2) {
+                        request.put(REPLMessage.OP, REPLMessage.UNSET_BREAK_CONDITION);
+                    } else {
+                        final StringBuilder exprBuilder = new StringBuilder();
+                        for (int i = 2; i < args.length; i++) {
+                            exprBuilder.append(args[i]).append(" ");
+                        }
+                        request.put(REPLMessage.BREAKPOINT_CONDITION, exprBuilder.toString().trim());
+                        request.put(REPLMessage.OP, REPLMessage.SET_BREAK_CONDITION);
+                    }
+                    return request;
+                } catch (IllegalArgumentException ex) {
+                    context.displayFailReply(ex.getMessage());
+                }
+            }
+            return null;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            REPLMessage firstReply = replies[0];
+            if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) {
+                final int breakpointNumber = firstReply.getIntValue(REPLMessage.BREAKPOINT_ID);
+                final String condition = firstReply.get(REPLMessage.BREAKPOINT_CONDITION);
+                if (condition == null) {
+                    firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + breakpointNumber + " condition cleared");
+                } else {
+                    firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + breakpointNumber + " condition=\"" + condition + "\"");
+                }
+            }
+            super.processReply(context, replies);
+        }
+    };
+
+    public static final REPLRemoteCommand CONTINUE_CMD = new REPLRemoteCommand("continue", "c", "Continue execution") {
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (context.level() == 0) {
+                context.displayFailReply("no active execution");
+                return null;
+            }
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.CONTINUE);
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+
+            throw new REPLContinueException();
+
+        }
+    };
+
+    public static final REPLRemoteCommand DELETE_CMD = new REPLRemoteCommand("delete", "d", "Delete all breakpoints") {
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.DELETE_BREAK);
+            return request;
+        }
+    };
+
+    public static final REPLRemoteCommand DISABLE_CMD = new REPLRemoteCommand("disable", null, "Disable a breakpoint") {
+
+        private final String[] help = {"disable <n>: disable breakpoint number <n>"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (args.length == 1) {
+                context.displayFailReply("breakpoint number not speciified:  \"disable <n>\"");
+            } else if (args.length > 2) {
+                context.displayFailReply("breakpoint number not understood:  \"disable <n>\"");
+            } else {
+                try {
+                    final int breakpointNumber = Integer.parseInt(args[1]);
+                    final REPLMessage request = new REPLMessage();
+                    request.put(REPLMessage.OP, REPLMessage.DISABLE_BREAK);
+                    request.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
+                    return request;
+                } catch (IllegalArgumentException ex) {
+                    context.displayFailReply(ex.getMessage());
+                }
+            }
+            return null;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            REPLMessage firstReply = replies[0];
+            if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) {
+                final int breakpointNumber = firstReply.getIntValue(REPLMessage.BREAKPOINT_ID);
+                firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + breakpointNumber + " disabled");
+            }
+            super.processReply(context, replies);
+        }
+    };
+
+    public static final REPLRemoteCommand DOWN_CMD = new REPLRemoteCommand("down", null, "Move down a stack frame") {
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (context.level() == 0) {
+                context.displayFailReply("no active execution");
+                return null;
+            }
+            final List<REPLFrame> frames = context.frames();
+            final int newFrameSelection = context.getSelectedFrameNumber() + 1;
+            if (newFrameSelection > frames.size() - 1) {
+                context.displayFailReply("at bottom of stack");
+                return null;
+            }
+            context.selectFrameNumber(newFrameSelection);
+            return FRAME_CMD.createRequest(context, Arrays.copyOfRange(args, 0, 0));
+        }
+
+        @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.displayStack();
+            }
+        }
+    };
+
+    public static final REPLRemoteCommand ENABLE_CMD = new REPLRemoteCommand("enable", null, "Enable a breakpoint") {
+
+        private final String[] help = {"enable <n>: enable breakpoint number <n>"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (args.length == 1) {
+                context.displayFailReply("breakpoint number not speciified:  \"enable <n>\"");
+            } else if (args.length > 2) {
+                context.displayFailReply("breakpoint number not understood:  \"enable <n>\"");
+            } else {
+                try {
+                    final int breakpointNumber = Integer.parseInt(args[1]);
+                    final REPLMessage request = new REPLMessage();
+                    request.put(REPLMessage.OP, REPLMessage.ENABLE_BREAK);
+                    request.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber));
+                    return request;
+                } catch (IllegalArgumentException ex) {
+                    context.displayFailReply(ex.getMessage());
+                }
+            }
+            return null;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            REPLMessage firstReply = replies[0];
+
+            if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) {
+                final int breakpointNumber = firstReply.getIntValue(REPLMessage.BREAKPOINT_ID);
+                firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + breakpointNumber + " enabled");
+            }
+            super.processReply(context, replies);
+        }
+    };
+
+    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>"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (context.level() == 0) {
+                context.displayFailReply("no active execution");
+                return null;
+            }
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.FRAME);
+
+            int frameNumber = context.getSelectedFrameNumber();
+            if (args.length > 1) {
+                if (args.length == 2) {
+                    try {
+                        frameNumber = Integer.parseInt(args[1]);
+                    } catch (NumberFormatException e) {
+                        throw new IllegalArgumentException("Unrecognized argument \"" + args[1] + "\"");
+                    }
+                } else {
+                    throw new IllegalArgumentException("Unrecognized argument \"" + args[2] + "\"");
+                }
+            }
+            request.put(REPLMessage.FRAME_NUMBER, Integer.toString(frameNumber));
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
+                context.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG));
+            } else {
+                Integer frameNumber = replies[0].getIntValue(REPLMessage.FRAME_NUMBER);
+                context.selectFrameNumber(frameNumber);
+                context.displayReply("Frame " + frameNumber + ":");
+                for (REPLMessage message : replies) {
+                    for (String line : message.get(REPLMessage.DISPLAY_MSG).split("\n")) {
+                        context.displayInfo(line);
+                    }
+                }
+            }
+        }
+    };
+
+    public static final REPLRemoteCommand KILL_CMD = new REPLRemoteCommand("kill", null, "Stop program execution") {
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (context.level() == 0) {
+                context.displayFailReply("no active execution");
+                return null;
+            }
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, "kill");
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) {
+                context.displayReply(replies[0].get(REPLMessage.DISPLAY_MSG));
+            } else {
+                context.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG));
+            }
+        }
+    };
+
+    public static final REPLRemoteCommand LOAD_RUN_CMD = new REPLRemoteCommand("load-run", "loadr", "Load and run a source") {
+
+        @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 = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.LOAD_RUN);
+            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") {
+
+        @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 = 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.") {
+
+        @Override
+        public String[] getHelp() {
+            return new String[]{"step into:  step to next statement (into calls)", "step <n>: step to nth next statement (into calls)"};
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (context.level() == 0) {
+                context.displayFailReply("no active execution");
+                return null;
+            }
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.STEP_INTO);
+
+            if (args.length >= 2) {
+                final String nText = args[1];
+                try {
+                    final int nSteps = Integer.parseInt(nText);
+                    if (nSteps > 0) {
+                        request.put(REPLMessage.REPEAT, Integer.toString(nSteps));
+                    } else {
+                        return null;
+                    }
+                } catch (NumberFormatException e) {
+                    context.displayFailReply("Step into count \"" + nText + "\" not recognized");
+                    return null;
+                }
+            }
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+
+            throw new REPLContinueException();
+        }
+    };
+
+    public static final REPLRemoteCommand STEP_OUT_CMD = new REPLRemoteCommand("finish", null, "(StepOut) continue to end of function") {
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (context.level() == 0) {
+                context.displayFailReply("no active execution");
+                return null;
+            }
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.STEP_OUT);
+
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+
+            throw new REPLContinueException();
+        }
+    };
+
+    public static final REPLRemoteCommand STEP_OVER_CMD = new REPLRemoteCommand("next", "n", "(StepOver) execute next line of code, not 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)"};
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (context.level() == 0) {
+                context.displayFailReply("no active execution");
+                return null;
+            }
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.STEP_OVER);
+
+            if (args.length >= 2) {
+                final String nText = args[1];
+                try {
+                    final int nSteps = Integer.parseInt(nText);
+                    if (nSteps > 0) {
+                        request.put(REPLMessage.REPEAT, Integer.toString(nSteps));
+                    } else {
+                        return null;
+                    }
+                } catch (NumberFormatException e) {
+                    context.displayFailReply("Next count \"" + nText + "\" not recognized");
+                    return null;
+                }
+            }
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+
+            throw new REPLContinueException();
+        }
+    };
+
+    public static final REPLRemoteCommand UP_CMD = new REPLRemoteCommand("up", null, "Move up a stack frame") {
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (context.level() == 0) {
+                context.displayFailReply("no active execution");
+                return null;
+            }
+            final int newFrameSelection = context.getSelectedFrameNumber() - 1;
+            if (newFrameSelection < 0) {
+                context.displayFailReply("at top of stack");
+                return null;
+            }
+            context.selectFrameNumber(newFrameSelection);
+            return FRAME_CMD.createRequest(context, Arrays.copyOfRange(args, 0, 0));
+        }
+
+        @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.displayStack();
+            }
+        }
+    };
+
+}
--- /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/client/REPLineLocation.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,113 @@
+/*
+ * 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.client;
+
+import java.io.*;
+
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.tools.debug.shell.*;
+
+final class REPLineLocation {
+
+    private final Source source;
+    private final int lineNumber;
+
+    /**
+     * Attempts to extract description of a source line from {@code arg[1]}, either
+     * "<source name>:<n>" or just "<n>".
+     */
+    static REPLineLocation parse(REPLClientContext context, String[] args) throws IllegalArgumentException {
+        if (args.length == 1) {
+            throw new IllegalArgumentException("no location specified");
+        }
+
+        Source source = null;
+        int lineNumber = -1;
+        String lineNumberText = null;
+
+        final String[] split = args[1].split(":");
+        if (split.length == 1) {
+            // Specification only has one part; it should be a line number
+            lineNumberText = split[0];
+            try {
+                lineNumber = Integer.parseInt(lineNumberText);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("no line number specified");
+            }
+            // If only line number specified, then there must be a selected file
+            final Source selectedSource = context.getSelectedSource();
+            if (selectedSource == null) {
+                throw new IllegalArgumentException("no selected file set");
+            }
+            source = selectedSource;
+
+        } else {
+            final String fileName = split[0];
+            lineNumberText = split[1];
+            try {
+                source = Source.fromFileName(fileName);
+            } catch (IOException e1) {
+                throw new IllegalArgumentException("Can't find file \"" + fileName + "\"");
+            }
+            try {
+                lineNumber = Integer.parseInt(lineNumberText);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("Invalid line number \"" + lineNumberText + "\"");
+            }
+            if (lineNumber <= 0) {
+                throw new IllegalArgumentException("Invalid line number \"" + lineNumberText + "\"");
+            }
+        }
+
+        return new REPLineLocation(source, lineNumber);
+    }
+
+    REPLineLocation(Source source, int lineNumber) {
+        this.source = source;
+        this.lineNumber = lineNumber;
+    }
+
+    public Source getSource() {
+        return source;
+    }
+
+    public int getLineNumber() {
+        return lineNumber;
+    }
+
+    /**
+     * Creates a message containing an "op" and a line location.
+     *
+     * @param op the operation to be performed on this location
+     */
+    public REPLMessage createMessage(String op) {
+        final REPLMessage msg = new REPLMessage(REPLMessage.OP, op);
+        msg.put(REPLMessage.SOURCE_NAME, source.getShortName());
+        msg.put(REPLMessage.FILE_PATH, source.getPath());
+        msg.put(REPLMessage.LINE_NUMBER, Integer.toString(lineNumber));
+        return msg;
+    }
+
+}
--- /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/client/SimpleREPLClient.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,1399 @@
+/*
+ * 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.client;
+
+import java.io.*;
+import java.util.*;
+
+import jline.console.*;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.tools.debug.shell.*;
+
+/**
+ * A very simple line-oriented, language-agnostic debugging client shell: the first step toward a
+ * general, extensible debugging framework designed to be adapted for remote debugging.
+ * <p>
+ * The architecture of this debugging framework is modeled loosely on <a
+ * href="https://github.com/clojure/tools.nrepl">nREPL</a>, a network REPL developed by the Clojure
+ * community with a focus on generality:
+ * <ul>
+ * <li>Client and (possibly remote) server communicate via <em>messages</em> carried over some
+ * <em>transport</em>;</li>
+ * <li>A message is a <em>map</em> of key/value pairs;</li>
+ * <li>Keys and values are <em>strings</em>;</li>
+ * <li>The client sends messages as <em>requests</em> to a server;</li>
+ * <li>A server dispatches each incoming request to an appropriate <em>handler</em> that takes
+ * appropriate action and responds to the client with one or more messages; and</li>
+ * <li>Many implementations of the <em>transport</em> are possible.</li>
+ * </ul>
+ * <p>
+ * <strong>Compromises:</strong>
+ * <p>
+ * In order to get
+ * <ol>
+ * <li>A debugging session should start from this shell, but there is no machinery in place for
+ * doing that; instead, an entry into the language implementation creates both the server and this
+ * shell;</li>
+ * <li>The current startup sequence is based on method calls, not messages;</li>
+ * <li>Only a very few request types and keys are implemented, omitting for example request and
+ * session ids;</li>
+ * <li>Message passing is synchronous and "transported" via method calls;</li>
+ * <li>Asynchrony is emulated by having each call to the server pass only a message, and by having
+ * the server return only a list of messages.</li>
+ * </ol>
+ *
+ * @see REPLServer
+ * @see REPLMessage
+ */
+public class SimpleREPLClient implements REPLClient {
+
+    private static final String REPLY_PREFIX = "==> ";
+    private static final String FAIL_PREFIX = "**> ";
+    private static final String WARNING_PREFIX = "!!> ";
+    private static final String TRACE_PREFIX = ">>> ";
+    private static final String[] NULL_ARGS = new String[0];
+
+    static final String INFO_LINE_FORMAT = "    %s\n";
+    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 ExecutionContext executionContext;  // Language context
+
+    // Top level commands
+    private final Map<String, REPLCommand> commandMap = new HashMap<>();
+    private final Collection<String> commandNames = new TreeSet<>();
+
+    // Local options
+    private final Map<String, LocalOption> localOptions = new HashMap<>();
+    private final Collection<String> optionNames = new TreeSet<>();
+
+    // 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;
+
+    private final REPLServer replServer;
+
+    private final LocalOption astDepthOption = new IntegerOption(9, "astdepth", "default depth for AST display");
+
+    private final LocalOption autoWhereOption = new BooleanOption(true, "autowhere", "run the \"where\" command after each navigation");
+
+    private final LocalOption autoNodeOption = new BooleanOption(false, "autonode", "run the \"truffle node\" command after each navigation");
+
+    private final LocalOption autoSubtreeOption = new BooleanOption(false, "autosubtree", "run the \"truffle subtree\" command after each navigation");
+
+    private final LocalOption autoASTOption = new BooleanOption(false, "autoast", "run the \"truffle ast\" command after each navigation");
+
+    private final LocalOption listSizeOption = new IntegerOption(25, "listsize", "default number of lines to list");
+
+    private final LocalOption traceMessagesOption = new BooleanOption(false, "tracemessages", "trace REPL messages between client and server");
+
+    private final LocalOption verboseBreakpointInfoOption = new BooleanOption(true, "verbosebreakpointinfo", "\"info breakpoint\" displays more info");
+
+    private void addOption(LocalOption localOption) {
+        final String optionName = localOption.getName();
+        localOptions.put(optionName, localOption);
+        optionNames.add(optionName);
+    }
+
+    /**
+     * Non-null when the user has named a file other than where halted, providing context for
+     * commands such as "break"; if no explicit selection, then defaults to where halted. This is
+     * session state, so it persists across halting contexts.
+     */
+    private Source selectedSource = null;
+
+    public SimpleREPLClient(ExecutionContext context, REPLServer replServer) {
+        this.executionContext = context;
+        this.replServer = replServer;
+        this.writer = System.out;
+        try {
+            this.reader = new ConsoleReader();
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to create console " + e);
+        }
+
+        addCommand(backtraceCommand);
+        addCommand(REPLRemoteCommand.BREAK_AT_LINE_CMD);
+        addCommand(REPLRemoteCommand.BREAK_AT_LINE_ONCE_CMD);
+        addCommand(REPLRemoteCommand.BREAK_AT_THROW_CMD);
+        addCommand(REPLRemoteCommand.BREAK_AT_THROW_ONCE_CMD);
+        addCommand(REPLRemoteCommand.CLEAR_BREAK_CMD);
+        addCommand(REPLRemoteCommand.CONDITION_BREAK_CMD);
+        addCommand(REPLRemoteCommand.CONTINUE_CMD);
+        addCommand(REPLRemoteCommand.DELETE_CMD);
+        addCommand(REPLRemoteCommand.DISABLE_CMD);
+        addCommand(REPLRemoteCommand.DOWN_CMD);
+        addCommand(REPLRemoteCommand.ENABLE_CMD);
+        addCommand(evalCommand);
+        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(quitCommand);
+        addCommand(setCommand);
+        addCommand(REPLRemoteCommand.STEP_INTO_CMD);
+        addCommand(REPLRemoteCommand.STEP_OUT_CMD);
+        addCommand(REPLRemoteCommand.STEP_OVER_CMD);
+        addCommand(truffleCommand);
+        addCommand(REPLRemoteCommand.UP_CMD);
+        addCommand(whereCommand);
+
+        infoCommand.addCommand(infoBreakCommand);
+        infoCommand.addCommand(infoLanguageCommand);
+        infoCommand.addCommand(infoSetCommand);
+
+        truffleCommand.addCommand(truffleASTCommand);
+        truffleCommand.addCommand(truffleNodeCommand);
+        truffleCommand.addCommand(truffleSubtreeCommand);
+
+        addOption(astDepthOption);
+        addOption(autoASTOption);
+        addOption(autoNodeOption);
+        addOption(autoSubtreeOption);
+        addOption(autoWhereOption);
+        addOption(listSizeOption);
+        addOption(traceMessagesOption);
+        addOption(verboseBreakpointInfoOption);
+    }
+
+    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 {
+            clientContext.startSession();
+        } finally {
+            clientContext.displayReply("Goodbye from " + executionContext.getLanguageShortName() + "/REPL");
+        }
+
+    }
+
+    public void addCommand(REPLCommand replCommand) {
+        final String commandName = replCommand.getCommand();
+        final String abbreviation = replCommand.getAbbreviation();
+
+        commandNames.add(commandName);
+        commandMap.put(commandName, replCommand);
+        if (abbreviation != null) {
+            commandMap.put(abbreviation, replCommand);
+        }
+    }
+
+    private class ClientContextImpl implements REPLClientContext {
+
+        private final ClientContextImpl predecessor;
+        private final int level;
+
+        // Information about where the execution is halted
+        /** The source where execution, if any, is halted; null if none. */
+        private Source haltedSource = null;
+        /** The line number where execution, if any, is halted; 0 if none. */
+        private int haltedLineNumber = 0;
+        /** The stack where execution, if any, is halted; null if none. Evaluated lazily. */
+        private List<REPLFrame> frames = null;
+
+        /** The frame number currently selected by user. */
+        private int selectedFrameNumber = 0;
+
+        private String currentPrompt;
+
+        /**
+         * Create a new context on the occasion of an execution halting.
+         */
+        public ClientContextImpl(ClientContextImpl predecessor, REPLMessage message) {
+            this.predecessor = predecessor;
+            this.level = predecessor == null ? 0 : predecessor.level + 1;
+
+            if (message != null) {
+                try {
+                    this.haltedSource = Source.fromFileName(message.get(REPLMessage.SOURCE_NAME));
+                    selectedSource = this.haltedSource;
+                    try {
+                        haltedLineNumber = Integer.parseInt(message.get(REPLMessage.LINE_NUMBER));
+                    } catch (NumberFormatException e) {
+                        haltedLineNumber = 0;
+                    }
+                } catch (IOException e1) {
+                    this.haltedSource = null;
+                    this.haltedLineNumber = 0;
+                }
+            }
+            updatePrompt();
+        }
+
+        private void selectSource(String fileName) {
+            try {
+                selectedSource = Source.fromFileName(fileName);
+            } catch (IOException e1) {
+                selectedSource = null;
+            }
+            updatePrompt();
+        }
+
+        private void updatePrompt() {
+            if (level == 0) {
+                // 0-level context; no executions halted.
+                if (selectedSource == null) {
+                    final String languageName = executionContext.getLanguageShortName();
+                    currentPrompt = languageName == null ? "() " : "(" + languageName + ") ";
+                } else {
+                    currentPrompt = "(" + selectedSource.getShortName() + ") ";
+                }
+            } 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(") ");
+                currentPrompt = sb.toString();
+            } else {
+                // Prompt reveals where currently halted.
+                final StringBuilder sb = new StringBuilder();
+                sb.append("(<" + Integer.toString(level) + "> ");
+                sb.append(haltedSource == null ? "??" : haltedSource.getShortName());
+                if (haltedLineNumber > 0) {
+                    sb.append(":" + Integer.toString(haltedLineNumber));
+                }
+                sb.append(") ");
+                currentPrompt = sb.toString();
+            }
+
+        }
+
+        public Source source() {
+            return haltedSource;
+        }
+
+        public int lineNumber() {
+            return haltedLineNumber;
+        }
+
+        public List<REPLFrame> frames() {
+            if (frames == null) {
+                final REPLMessage request = new REPLMessage(REPLMessage.OP, REPLMessage.BACKTRACE);
+                final REPLMessage[] replies = sendToServer(request);
+                if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
+                    return null;
+                }
+                frames = new ArrayList<>();
+                for (REPLMessage reply : replies) {
+                    final int index = reply.getIntValue(REPLMessage.FRAME_NUMBER);
+                    final String locationFilePath = reply.get(REPLMessage.FILE_PATH);
+                    final Integer locationLineNumber = reply.getIntValue(REPLMessage.LINE_NUMBER);
+                    final String locationDescription = reply.get(REPLMessage.SOURCE_LOCATION);
+                    final String name = reply.get(REPLMessage.METHOD_NAME);
+                    final String sourceLineText = reply.get(REPLMessage.SOURCE_LINE_TEXT);
+                    frames.add(new REPLFrameImpl(index, locationFilePath, locationLineNumber, locationDescription, name, sourceLineText));
+                }
+                frames = Collections.unmodifiableList(frames);
+            }
+            return frames;
+        }
+
+        public int level() {
+            return this.level;
+        }
+
+        public Source getSelectedSource() {
+            return selectedSource == null ? haltedSource : selectedSource;
+        }
+
+        public int getSelectedFrameNumber() {
+            return selectedFrameNumber;
+        }
+
+        public String stringQuery(String op) {
+            assert op != null;
+            REPLMessage request = null;
+            switch (op) {
+                case REPLMessage.TRUFFLE_AST:
+                    request = truffleASTCommand.createRequest(clientContext, NULL_ARGS);
+                    break;
+                case REPLMessage.TRUFFLE_SUBTREE:
+                    request = truffleSubtreeCommand.createRequest(clientContext, NULL_ARGS);
+                    break;
+                default:
+                    request = new REPLMessage();
+                    request.put(REPLMessage.OP, op);
+            }
+            if (request == null) {
+                return null;
+            }
+            final REPLMessage[] replies = sendToServer(request);
+            if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
+                return null;
+            }
+            return replies[0].get(REPLMessage.DISPLAY_MSG);
+        }
+
+        public void selectFrameNumber(int frameNumber) {
+            this.selectedFrameNumber = frameNumber;
+        }
+
+        void displayWhere() {
+            if (level == 0) {
+                displayFailReply("no active execution");
+                return;
+            }
+
+            Source whereSource = null;
+            int whereLineNumber = 0;
+
+            if (selectedFrameNumber == 0) {
+                whereSource = haltedSource;
+                whereLineNumber = haltedLineNumber;
+            } else {
+                final REPLFrame frame = frames().get(selectedFrameNumber);
+                final String locationFileName = frame.locationFilePath();
+                if (locationFileName != null) {
+                    try {
+                        whereSource = Source.fromFileName(locationFileName);
+                    } catch (IOException e) {
+                    }
+                }
+                whereLineNumber = frame.locationLineNumber();
+            }
+            if (whereSource == null) {
+                displayFailReply("Frame " + selectedFrameNumber + ": source unavailable");
+                return;
+            }
+            final int listSize = listSizeOption.getInt();
+
+            final int fileLineCount = whereSource.getLineCount();
+            final String code = whereSource.getCode();
+
+            writer.println("Frame " + selectedFrameNumber + ": " + whereSource.getShortName() + "\n");
+            final int halfListSize = listSize / 2;
+            final int startLineNumber = Math.max(1, whereLineNumber - halfListSize);
+            final int lastLineNumber = Math.min(startLineNumber + listSize - 1, fileLineCount);
+            for (int line = startLineNumber; line <= lastLineNumber; line++) {
+                final int offset = whereSource.getLineStartOffset(line);
+                final String lineText = code.substring(offset, offset + whereSource.getLineLength(line));
+                if (line == whereLineNumber) {
+                    writer.format(CODE_LINE_BREAK_FORMAT, line, lineText);
+                } else {
+                    writer.format(CODE_LINE_FORMAT, line, lineText);
+                }
+            }
+        }
+
+        public void displayStack() {
+            final List<REPLFrame> frameList = frames();
+            if (frameList == null) {
+                writer.println("<empty stack>");
+            } else {
+                for (REPLFrame frame : frameList) {
+                    String sourceLineText = frame.sourceLineText();
+                    if (sourceLineText == null) {
+                        sourceLineText = "<??>";
+                    }
+                    if (frame.index() == selectedFrameNumber) {
+                        writer.format(STACK_FRAME_SELECTED_FORMAT, frame.index(), frame.locationDescription(), frame.name(), sourceLineText);
+                    } else {
+                        writer.format(STACK_FRAME_FORMAT, frame.index(), frame.locationDescription(), frame.name(), sourceLineText);
+                    }
+                }
+            }
+        }
+
+        public void displayInfo(String message) {
+            writer.format(INFO_LINE_FORMAT, message);
+        }
+
+        public void displayReply(String message) {
+            writer.println(REPLY_PREFIX + message);
+        }
+
+        public void displayFailReply(String message) {
+            writer.println(FAIL_PREFIX + message);
+        }
+
+        public void displayWarnings(String warnings) {
+            for (String warning : warnings.split("\\n")) {
+                writer.println(WARNING_PREFIX + warning);
+            }
+        }
+
+        public void traceMessage(String message) {
+            writer.println(TRACE_PREFIX + message);
+        }
+
+        public void startSession() {
+
+            while (true) {
+                try {
+                    String[] args;
+                    String line = reader.readLine(currentPrompt).trim();
+                    if (line.startsWith("eval ")) {
+                        args = new String[]{"eval", line.substring(5)};
+                    } else {
+                        args = line.split("[ \t]+");
+                    }
+                    if (args.length == 0) {
+                        break;
+                    }
+                    final String cmd = args[0];
+
+                    if (cmd.isEmpty()) {
+                        continue;
+                    }
+
+                    REPLCommand command = commandMap.get(cmd);
+                    while (command instanceof REPLIndirectCommand) {
+                        if (traceMessagesOption.getBool()) {
+                            traceMessage("Executing indirect: " + command.getCommand());
+                        }
+                        command = ((REPLIndirectCommand) command).getCommand(args);
+                    }
+                    if (command == null) {
+                        clientContext.displayFailReply("Unrecognized command \"" + cmd + "\"");
+                        continue;
+                    }
+                    if (command instanceof REPLLocalCommand) {
+                        if (traceMessagesOption.getBool()) {
+                            traceMessage("Executing local: " + command.getCommand());
+                        }
+                        ((REPLLocalCommand) command).execute(args);
+
+                    } 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);
+                    } else {
+                        assert false; // Should not happen.
+                    }
+
+                } catch (REPLContinueException ex) {
+                    break;
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+        }
+
+        private REPLMessage[] sendToServer(REPLMessage request) {
+            if (traceMessagesOption.getBool()) {
+                clientContext.traceMessage("Sever request:");
+                request.print(writer, "  ");
+            }
+
+            REPLMessage[] replies = replServer.receive(request);
+
+            assert replies != null && replies.length > 0;
+            if (traceMessagesOption.getBool()) {
+                if (replies.length > 1) {
+                    clientContext.traceMessage("Received " + replies.length + " server replies");
+                    int replyCount = 0;
+                    for (REPLMessage reply : replies) {
+                        clientContext.traceMessage("Server Reply " + replyCount++ + ":");
+                        reply.print(writer, "  ");
+                    }
+                } else {
+                    clientContext.traceMessage("Received reply:");
+                    replies[0].print(writer, "  ");
+                }
+            }
+            return replies;
+        }
+
+        private final class REPLFrameImpl implements REPLFrame {
+
+            private final int index;
+            private final String locationFilePath;
+            private final Integer locationLineNumber;
+            private final String locationDescription;
+            private final String name;
+            private final String sourceLineText;
+
+            REPLFrameImpl(int index, String locationFilePath, Integer locationLineNumber, String locationDescription, String name, String sourceLineText) {
+                this.index = index;
+                this.locationFilePath = locationFilePath;
+                this.locationLineNumber = locationLineNumber;
+                this.locationDescription = locationDescription;
+                this.name = name;
+                this.sourceLineText = sourceLineText;
+            }
+
+            public int index() {
+                return index;
+            }
+
+            public String locationFilePath() {
+                return locationFilePath;
+            }
+
+            public Integer locationLineNumber() {
+                return locationLineNumber;
+            }
+
+            public String locationDescription() {
+                return locationDescription;
+            }
+
+            public String name() {
+                return name;
+            }
+
+            public String sourceLineText() {
+                return sourceLineText;
+            }
+
+        }
+
+    }
+
+    // Cheating with synchrony: asynchronous replies should arrive here, but don't.
+    @Override
+    public REPLMessage receive(REPLMessage request) {
+        final String result = request.get("result");
+        clientContext.displayReply(result != null ? result : request.toString());
+        return null;
+    }
+
+    /**
+     * Cheating with synchrony: take a direct call from the server that execution has halted and
+     * we've entered a nested debugging context.
+     */
+    public void halted(REPLMessage message) {
+
+        // Push a new context for where we've stopped.
+        clientContext = new ClientContextImpl(clientContext, message);
+        final String warnings = message.get(REPLMessage.WARNINGS);
+        if (warnings != null) {
+            clientContext.displayWarnings(warnings);
+        }
+        if (autoWhereOption.getBool()) {
+            clientContext.displayWhere();
+        }
+        if (autoNodeOption.getBool()) {
+            final String result = clientContext.stringQuery(REPLMessage.TRUFFLE_NODE);
+            if (result != null) {
+                displayTruffleNode(result);
+            }
+        }
+        if (autoASTOption.getBool()) {
+            final String result = clientContext.stringQuery(REPLMessage.TRUFFLE_AST);
+            if (result != null) {
+                displayTruffleAST(result);
+            }
+        }
+        if (autoSubtreeOption.getBool()) {
+            final String result = clientContext.stringQuery(REPLMessage.TRUFFLE_SUBTREE);
+            if (result != null) {
+                displayTruffleSubtree(result);
+            }
+        }
+
+        try {
+            clientContext.startSession();
+        } finally {
+
+            // To continue execution, pop the context and return
+            this.clientContext = clientContext.predecessor;
+        }
+    }
+
+    /**
+     * A command that can be executed without (direct) communication with the server; it may rely on
+     * some other method that goes to the server for information.
+     */
+    private abstract class REPLLocalCommand extends REPLCommand {
+
+        public REPLLocalCommand(String command, String abbreviation, String description) {
+            super(command, abbreviation, description);
+        }
+
+        abstract void execute(String[] args);
+    }
+
+    /**
+     * A command that redirects to other commands, based on arguments.
+     */
+    private abstract class REPLIndirectCommand extends REPLCommand {
+
+        public REPLIndirectCommand(String command, String abbreviation, String description) {
+            super(command, abbreviation, description);
+        }
+
+        abstract void addCommand(REPLCommand command);
+
+        abstract REPLCommand getCommand(String[] args);
+    }
+
+    private final REPLCommand backtraceCommand = new REPLLocalCommand("backtrace", "bt", "Display current stack") {
+
+        @Override
+        void execute(String[] args) {
+            if (clientContext.level == 0) {
+                clientContext.displayFailReply("no active execution");
+            } else {
+                clientContext.displayStack();
+            }
+        }
+    };
+
+    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"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (args.length == 1) {
+                final Source source = clientContext.getSelectedSource();
+                if (source == null) {
+                    clientContext.displayFailReply("no file currently selected");
+                } else {
+                    clientContext.displayReply(source.getPath());
+                }
+                return null;
+            }
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.FILE);
+            request.put(REPLMessage.SOURCE_NAME, args[1]);
+            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);
+                clientContext.displayFailReply(result != null ? result : firstReply.toString());
+                return;
+            }
+            final String fileName = firstReply.get(REPLMessage.SOURCE_NAME);
+            final String path = firstReply.get(REPLMessage.FILE_PATH);
+            clientContext.selectSource(path == null ? fileName : path);
+            clientContext.displayReply(clientContext.getSelectedSource().getPath());
+
+            for (int i = 1; i < replies.length; i++) {
+                REPLMessage reply = replies[i];
+                final String result = reply.get(REPLMessage.DISPLAY_MSG);
+                clientContext.displayInfo(result != null ? result : reply.toString());
+            }
+        }
+
+    };
+
+    private final REPLCommand helpCommand = new REPLLocalCommand("help", null, "Describe commands") {
+
+        final String[] help = {"help:  list available commands", "help <command>: additional information about <command>"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public void execute(String[] args) {
+
+            if (args.length == 1) {
+                clientContext.displayReply("Available commands:");
+                for (String commandName : commandNames) {
+                    final REPLCommand command = commandMap.get(commandName);
+                    if (command == null) {
+                        clientContext.displayInfo(commandName + ": Error, no implementation for command");
+                    } else {
+                        final String abbrev = command.getAbbreviation();
+                        if (abbrev == null) {
+                            clientContext.displayInfo(commandName + ": " + command.getDescription());
+                        } else {
+                            clientContext.displayInfo(commandName + "(" + abbrev + "): " + command.getDescription());
+                        }
+                    }
+                }
+            } else {
+                final String cmdName = args[1];
+                final REPLCommand cmd = commandMap.get(cmdName);
+                if (cmd == null) {
+                    clientContext.displayReply("command \"" + cmdName + "\" not recognized");
+                } else {
+                    final String[] helpLines = cmd.getHelp();
+                    if (helpLines == null) {
+                        clientContext.displayReply("\"" + cmdName + "\":");
+                    } else if (helpLines.length == 1) {
+                        clientContext.displayInfo(helpLines[0]);
+                    } else {
+                        clientContext.displayReply("\"" + cmdName + "\":");
+                        for (String line : helpLines) {
+                            clientContext.displayInfo(line);
+                        }
+                    }
+                }
+            }
+        }
+    };
+
+    private final REPLIndirectCommand infoCommand = new REPLIndirectCommand(REPLMessage.INFO, null, "Additional information on topics") {
+
+        // "Info" commands
+        private final Map<String, REPLCommand> infoCommandMap = new HashMap<>();
+        private final Collection<String> infoCommandNames = new TreeSet<>();
+
+        @Override
+        public String[] getHelp() {
+            final ArrayList<String> lines = new ArrayList<>();
+            for (String infoCommandName : infoCommandNames) {
+                final REPLCommand cmd = infoCommandMap.get(infoCommandName);
+                if (cmd == null) {
+                    lines.add("\"" + REPLMessage.INFO + " " + infoCommandName + "\" not implemented");
+                } else {
+                    lines.add("\"" + REPLMessage.INFO + " " + infoCommandName + "\": " + cmd.getDescription());
+                }
+            }
+            return lines.toArray(new String[0]);
+        }
+
+        @Override
+        void addCommand(REPLCommand replCommand) {
+            final String commandName = replCommand.getCommand();
+            final String abbreviation = replCommand.getAbbreviation();
+
+            infoCommandNames.add(commandName);
+            infoCommandMap.put(commandName, replCommand);
+            if (abbreviation != null) {
+                infoCommandMap.put(abbreviation, replCommand);
+            }
+        }
+
+        @Override
+        REPLCommand getCommand(String[] args) {
+            if (args.length == 1) {
+                clientContext.displayFailReply("info topic not specified; try \"help info\"");
+                return null;
+            }
+            final String topic = args[1];
+            REPLCommand command = infoCommandMap.get(topic);
+            if (command == null) {
+                clientContext.displayFailReply("topic \"" + topic + "\" not recognized");
+                return null;
+            }
+
+            return command;
+        }
+
+    };
+
+    private final REPLCommand infoBreakCommand = new REPLRemoteCommand("breakpoint", "break", "info about breakpoints") {
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.BREAKPOINT_INFO);
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
+                clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG));
+            } else {
+                Arrays.sort(replies, new Comparator<REPLMessage>() {
+
+                    public int compare(REPLMessage o1, REPLMessage o2) {
+                        try {
+                            final int n1 = Integer.parseInt(o1.get(REPLMessage.BREAKPOINT_ID));
+                            final int n2 = Integer.parseInt(o2.get(REPLMessage.BREAKPOINT_ID));
+                            return Integer.compare(n1, n2);
+                        } catch (Exception ex) {
+                        }
+                        return 0;
+                    }
+
+                });
+                clientContext.displayReply("Breakpoints set:");
+                for (REPLMessage message : replies) {
+                    final StringBuilder sb = new StringBuilder();
+
+                    sb.append(Integer.parseInt(message.get(REPLMessage.BREAKPOINT_ID)) + ": ");
+                    sb.append("@" + message.get(REPLMessage.INFO_VALUE));
+                    sb.append(" (state=" + message.get(REPLMessage.BREAKPOINT_STATE));
+                    if (verboseBreakpointInfoOption.getBool()) {
+                        sb.append(", group=" + Integer.parseInt(message.get(REPLMessage.BREAKPOINT_GROUP_ID)));
+                        sb.append(", hits=" + Integer.parseInt(message.get(REPLMessage.BREAKPOINT_HIT_COUNT)));
+                        sb.append(", ignore=" + Integer.parseInt(message.get(REPLMessage.BREAKPOINT_IGNORE_COUNT)));
+                    }
+                    final String condition = message.get(REPLMessage.BREAKPOINT_CONDITION);
+                    if (condition != null) {
+                        sb.append(", condition=\"" + condition + "\"");
+                    }
+                    sb.append(")");
+                    clientContext.displayInfo(sb.toString());
+                }
+            }
+        }
+    };
+
+    private final REPLCommand infoLanguageCommand = new REPLRemoteCommand("language", "lang", "language and implementation details") {
+
+        final String[] help = {"info language:  list details about the language implementation"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.INFO);
+            request.put(REPLMessage.TOPIC, REPLMessage.LANGUAGE);
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
+                clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG));
+            } else {
+                clientContext.displayReply("Language info:");
+                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));
+                    clientContext.displayInfo(sb.toString());
+                }
+            }
+        }
+    };
+
+    private final REPLCommand infoSetCommand = new REPLLocalCommand("set", null, "info about settings") {
+
+        final String[] help = {"info sets:  list local options that can be set"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public void execute(String[] args) {
+
+            clientContext.displayReply("Settable options:");
+
+            for (String optionName : optionNames) {
+                final LocalOption localOption = localOptions.get(optionName);
+                if (localOption == null) {
+                    clientContext.displayInfo(localOption + ": Error, no implementation for option");
+                } else {
+                    clientContext.displayInfo(optionName + "=" + localOption.getValue() + ": " + localOption.getDescription());
+                }
+            }
+        }
+    };
+
+    private final REPLCommand listCommand = new REPLLocalCommand("list", null, "Display selected source file") {
+
+        final String[] help = {"list:  list <listsize> lines of selected file (see option \"listsize\")", "list all: list all lines", "list <n>: list <listsize> lines centered around line <n>"};
+
+        private Source lastListedSource = null;
+
+        private int nextLineToList = 1;
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public void execute(String[] args) {
+            final Source source = clientContext.getSelectedSource();
+            if (source == null) {
+                clientContext.displayFailReply("No selected file");
+                reset();
+                return;
+            }
+            final int listSize = listSizeOption.getInt();
+
+            if (args.length == 1) {
+                if (!source.equals(lastListedSource)) {
+                    reset();
+                } else if (nextLineToList > source.getLineCount()) {
+                    reset();
+                }
+                final int lastListedLine = printLines(source, nextLineToList, listSize);
+                lastListedSource = source;
+                nextLineToList = lastListedLine > source.getLineCount() ? 1 : lastListedLine + 1;
+            } else if (args.length == 2) {
+                reset();
+                if (args[1].equals("all")) {
+                    printLines(source, 1, source.getLineCount());
+                } else {
+                    try {
+                        final int line = Integer.parseInt(args[1]);
+                        final int halfListSize = listSize / 2;
+                        final int start = Math.max(1, line - halfListSize);
+                        final int count = Math.min(source.getLineCount() + 1 - start, listSize);
+                        printLines(source, start, count);
+                    } catch (NumberFormatException e) {
+                        clientContext.displayFailReply("\"" + args[1] + "\" not recognized");
+                    }
+
+                }
+            }
+        }
+
+        private int printLines(Source printSource, int start, int listSize) {
+
+            clientContext.displayReply(printSource.getShortName() + ":");
+            final int lastLineNumber = Math.min(start + listSize - 1, printSource.getLineCount());
+            for (int line = start; line <= lastLineNumber; line++) {
+                writer.format(CODE_LINE_FORMAT, line, printSource.getCode(line));
+            }
+            return lastLineNumber;
+        }
+
+        /**
+         * Forget where we were in a sequence of list commands with no arguments
+         */
+        private void reset() {
+            lastListedSource = clientContext.getSelectedSource();
+            nextLineToList = 1;
+        }
+    };
+
+    private final REPLCommand quitCommand = new REPLRemoteCommand("quit", "q", "Quit execution and REPL") {
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.QUIT);
+            return request;
+        }
+
+    };
+
+    private final REPLCommand setCommand = new REPLLocalCommand("set", null, "set <option>=<value>") {
+
+        @Override
+        public String[] getHelp() {
+            return new String[]{"Sets an option \"set <option-name>=<value>\";  see also \"info set\""};
+        }
+
+        @Override
+        public void execute(String[] args) {
+            REPLMessage request = null;
+            if (args.length == 1) {
+                clientContext.displayFailReply("No option specified, try \"help set\"");
+            } else if (args.length == 2) {
+                String[] split = new String[0];
+                try {
+                    split = args[1].split("=");
+                } catch (Exception ex) {
+                }
+                if (split.length == 0) {
+                    clientContext.displayFailReply("Arguments not understood, try \"help set\"");
+                } else if (split.length == 1) {
+                    clientContext.displayFailReply("No option value specified, try \"help set\"");
+                } else if (split.length > 2) {
+                    clientContext.displayFailReply("Arguments not understood, try \"help set\"");
+                } else {
+                    final String optionName = split[0];
+                    final String newValue = split[1];
+                    final LocalOption localOption = localOptions.get(optionName);
+                    if (localOption != null) {
+                        if (!localOption.setValue(newValue)) {
+                            clientContext.displayFailReply("Invalid option value \"" + newValue + "\"");
+                        }
+                        clientContext.displayInfo(localOption.name + " = " + localOption.getValue());
+                    } else {
+                        request = new REPLMessage();
+                        request.put(REPLMessage.OP, REPLMessage.SET);
+                        request.put(REPLMessage.OPTION, optionName);
+                        request.put(REPLMessage.VALUE, newValue);
+                    }
+                }
+            } else {
+                clientContext.displayFailReply("Arguments not understood, try \"help set\"");
+            }
+        }
+    };
+
+    private final REPLIndirectCommand truffleCommand = new REPLIndirectCommand(REPLMessage.TRUFFLE, "t", "Access to Truffle internals") {
+
+        // "Truffle" commands
+        private final Map<String, REPLCommand> truffleCommandMap = new HashMap<>();
+        private final Collection<String> truffleCommandNames = new TreeSet<>();
+
+        @Override
+        public String[] getHelp() {
+            final ArrayList<String> lines = new ArrayList<>();
+            for (String truffleCommandName : truffleCommandNames) {
+                final REPLCommand cmd = truffleCommandMap.get(truffleCommandName);
+                if (cmd == null) {
+                    lines.add("\"" + REPLMessage.TRUFFLE + " " + truffleCommandName + "\" not implemented");
+                } else {
+                    for (String line : cmd.getHelp()) {
+                        lines.add(line);
+                    }
+                }
+            }
+            return lines.toArray(new String[0]);
+        }
+
+        @Override
+        void addCommand(REPLCommand replCommand) {
+            final String commandName = replCommand.getCommand();
+            final String abbreviation = replCommand.getAbbreviation();
+
+            truffleCommandNames.add(commandName);
+            truffleCommandMap.put(commandName, replCommand);
+            if (abbreviation != null) {
+                truffleCommandMap.put(abbreviation, replCommand);
+            }
+        }
+
+        @Override
+        REPLCommand getCommand(String[] args) {
+            if (args.length == 1) {
+                clientContext.displayFailReply("truffle request not specified; try \"help truffle\"");
+                return null;
+            }
+            final String topic = args[1];
+            REPLCommand command = truffleCommandMap.get(topic);
+            if (command == null) {
+                clientContext.displayFailReply("truffle request \"" + topic + "\" not recognized");
+                return null;
+            }
+            return command;
+        }
+    };
+
+    private final REPLRemoteCommand truffleASTCommand = new REPLRemoteCommand("ast", null, "print the AST that contains the current node") {
+
+        final String[] help = {"truffle ast:  print the AST subtree that contains current node (see \"set treedepth\")",
+                        "truffle ast <n>:  print the AST subtree that contains current node to a maximum depth of <n>"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (clientContext.level() == 0) {
+                context.displayFailReply("no active execution");
+                return null;
+            }
+
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.TRUFFLE);
+            request.put(REPLMessage.TOPIC, REPLMessage.AST);
+
+            int astDepth = astDepthOption.getInt();
+            if (args.length > 2) {
+                final String depthText = args[2];
+                try {
+                    astDepth = Integer.parseInt(depthText);
+                } catch (NumberFormatException e) {
+                }
+            }
+            request.put(REPLMessage.AST_DEPTH, Integer.toString(astDepth));
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
+                clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG));
+            } else {
+                clientContext.displayReply("AST containing the Current Node:");
+                for (REPLMessage message : replies) {
+                    for (String line : message.get(REPLMessage.DISPLAY_MSG).split("\n")) {
+                        clientContext.displayInfo(line);
+                    }
+                }
+            }
+        }
+    };
+
+    private void displayTruffleAST(String text) {
+        clientContext.displayReply("AST containing Current Node:");
+        for (String line : text.split("\n")) {
+            clientContext.displayInfo(line);
+        }
+    }
+
+    private final REPLRemoteCommand truffleNodeCommand = new REPLRemoteCommand("node", null, "describe current AST node") {
+
+        final String[] help = {"truffle node:  describe the AST node at the current execution context"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (clientContext.level() == 0) {
+                context.displayFailReply("no active execution");
+                return null;
+            }
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.TRUFFLE_NODE);
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
+                clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG));
+            } else {
+                displayTruffleNode(replies[0].get(REPLMessage.DISPLAY_MSG));
+            }
+        }
+    };
+
+    private void displayTruffleNode(String nodeString) {
+        clientContext.displayReply("Current Node: " + nodeString);
+    }
+
+    private final REPLRemoteCommand truffleSubtreeCommand = new REPLRemoteCommand("subtree", "sub", "print the AST subtree rooted at the current node") {
+
+        final String[] help = {"truffle sub:  print the AST subtree at the current node (see \"set treedepth\")", "truffle sub <n>:  print the AST subtree at the current node to maximum depth <n>",
+                        "truffle subtree:   print the AST subtree at the current node (see \"set treedepth\")", "truffle sub <n>:  print the AST subtree at the current node to maximum depth <n>"};
+
+        @Override
+        public String[] getHelp() {
+            return help;
+        }
+
+        @Override
+        public REPLMessage createRequest(REPLClientContext context, String[] args) {
+            if (clientContext.level() == 0) {
+                context.displayFailReply("no active execution");
+                return null;
+            }
+
+            final REPLMessage request = new REPLMessage();
+            request.put(REPLMessage.OP, REPLMessage.TRUFFLE);
+            request.put(REPLMessage.TOPIC, REPLMessage.SUBTREE);
+
+            int astDepth = astDepthOption.getInt();
+            if (args.length > 2) {
+                final String depthText = args[2];
+                try {
+                    astDepth = Integer.parseInt(depthText);
+                } catch (NumberFormatException e) {
+                }
+            }
+            request.put(REPLMessage.AST_DEPTH, Integer.toString(astDepth));
+            return request;
+        }
+
+        @Override
+        void processReply(REPLClientContext context, REPLMessage[] replies) {
+            if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) {
+                clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG));
+            } else {
+                clientContext.displayReply("AST subtree at Current Node:");
+                for (REPLMessage message : replies) {
+                    for (String line : message.get(REPLMessage.DISPLAY_MSG).split("\n")) {
+                        clientContext.displayInfo(line);
+                    }
+                }
+            }
+        }
+    };
+
+    private void displayTruffleSubtree(String text) {
+        clientContext.displayReply("AST subtree at Current Node:");
+        for (String line : text.split("\n")) {
+            clientContext.displayInfo(line);
+        }
+    }
+
+    private final REPLCommand whereCommand = new REPLLocalCommand("where", null, "Show code around current break location") {
+
+        @Override
+        public void execute(String[] args) {
+            clientContext.displayWhere();
+        }
+    };
+
+    private abstract static class LocalOption {
+        private final String name;
+        private final String description;
+
+        protected LocalOption(String name, String description) {
+            this.name = name;
+            this.description = description;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
+        public abstract boolean setValue(String newValue);
+
+        public boolean getBool() {
+            assert false;
+            return false;
+        }
+
+        public int getInt() {
+            assert false;
+            return 0;
+        }
+
+        public abstract String getValue();
+    }
+
+    private static final class BooleanOption extends LocalOption {
+
+        private Boolean value;
+
+        public BooleanOption(boolean value, String name, String description) {
+            super(name, description);
+            this.value = value;
+        }
+
+        @Override
+        public boolean setValue(String newValue) {
+            final Boolean valueOf = Boolean.valueOf(newValue);
+            if (valueOf == null) {
+                return false;
+            }
+            value = valueOf;
+            return true;
+        }
+
+        @Override
+        public boolean getBool() {
+            return value;
+        }
+
+        @Override
+        public String getValue() {
+            return value.toString();
+        }
+    }
+
+    private static final class IntegerOption extends LocalOption {
+
+        private Integer value;
+
+        public IntegerOption(int value, String name, String description) {
+            super(name, description);
+            this.value = value;
+        }
+
+        @Override
+        public boolean setValue(String newValue) {
+            Integer valueOf;
+            try {
+                valueOf = Integer.valueOf(newValue);
+            } catch (NumberFormatException e) {
+                return false;
+            }
+            value = valueOf;
+            return true;
+        }
+
+        @Override
+        public int getInt() {
+            return value;
+        }
+
+        @Override
+        public String getValue() {
+            return value.toString();
+        }
+
+    }
+
+}
--- /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/package-info.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+/**
+ * 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
+ * {@link com.oracle.truffle.tools.debug.engine.DebugEngine}, 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:
+ * <quote>
+ * <a href="https://wiki.openjdk.java.net/display/Graal/Instrumentation+API">https://wiki.openjdk.java.net/display/Graal/Instrumentation+API</a>
+ * </quote>
+ * <p>
+ * Building one of these command line debuggers requires creating language-specific instances of:
+ * <ol>
+ * <li>{@link com.oracle.truffle.tools.debug.engine.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 {@link com.oracle.truffle.tools.debug.engine.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>
+ * @see com.oracle.truffle.api.instrument
+ * @see com.oracle.truffle.tools.debug.engine
+ */
+package com.oracle.truffle.tools.debug.shell;
+
--- /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());
+            }
+        }
+    };
+}
--- /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/REPLServerContext.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,75 @@
+/*
+ * 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.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.tools.debug.shell.*;
+import com.oracle.truffle.tools.debug.engine.*;
+
+public abstract class REPLServerContext {
+
+    private final int level;
+    private final Node astNode;
+    private final MaterializedFrame mFrame;
+
+    protected REPLServerContext(int level, Node astNode, MaterializedFrame mFrame) {
+        this.level = level;
+        this.astNode = astNode;
+        this.mFrame = mFrame;
+    }
+
+    /**
+     * 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 getNode() {
+        return astNode;
+    }
+
+    /**
+     * The frame where execution is halted in this context.
+     */
+    public MaterializedFrame getFrame() {
+        return mFrame;
+    }
+
+    public abstract ExecutionContext getLanguageContext();
+
+    public abstract DebugEngine getDebugEngine();
+
+    /**
+     * Dispatches a REPL request to the appropriate handler.
+     */
+    public abstract REPLMessage[] receive(REPLMessage request);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.test/src/com/oracle/truffle/tools/test/CoverageTrackerTest.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2014, 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.test;
+
+import static com.oracle.truffle.tools.test.TestNodes.*;
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.tools.*;
+
+public class CoverageTrackerTest {
+
+    @Test
+    public void testNoExecution() {
+        final CoverageTracker tool = new CoverageTracker();
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+        tool.install();
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+        tool.setEnabled(false);
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+        tool.setEnabled(true);
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+        tool.reset();
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+        tool.dispose();
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+    }
+
+    @Test
+    public void testToolCreatedTooLate() {
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+        final CoverageTracker tool = new CoverageTracker();
+        tool.install();
+        assertEquals(13, expr13rootNode.execute(null));
+        assertTrue(tool.getCounts().isEmpty());
+        tool.dispose();
+    }
+
+    @Test
+    public void testToolInstalledcTooLate() {
+        final CoverageTracker tool = new CoverageTracker();
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+        tool.install();
+        assertEquals(13, expr13rootNode.execute(null));
+        assertTrue(tool.getCounts().isEmpty());
+        tool.dispose();
+    }
+
+    @Test
+    public void testCountingCoverage() {
+        final CoverageTracker tool = new CoverageTracker();
+        tool.install();
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+
+        // Not probed yet.
+        assertEquals(13, expr13rootNode.execute(null));
+        assertTrue(tool.getCounts().isEmpty());
+
+        final Node addNode = expr13rootNode.getChildren().iterator().next();
+        final Probe probe = addNode.probe();
+
+        // Probed but not tagged yet.
+        assertEquals(13, expr13rootNode.execute(null));
+        assertTrue(tool.getCounts().isEmpty());
+
+        probe.tagAs(StandardSyntaxTag.STATEMENT, "fake statement for testing");
+
+        // Counting now; execute once
+        assertEquals(13, expr13rootNode.execute(null));
+
+        final Long[] longs1 = tool.getCounts().get(addNode.getSourceSection().getSource());
+        assertNotNull(longs1);
+        assertEquals(longs1.length, 2);
+        assertNull(longs1[0]);  // Line 1 is empty (text lines are 1-based)
+        assertEquals(1L, longs1[1].longValue());  // Expression is on line 2
+
+        // Execute 99 more times
+        for (int i = 0; i < 99; i++) {
+            assertEquals(13, expr13rootNode.execute(null));
+        }
+
+        final Long[] longs100 = tool.getCounts().get(addNode.getSourceSection().getSource());
+        assertNotNull(longs100);
+        assertEquals(longs100.length, 2);
+        assertNull(longs100[0]);
+        assertEquals(100L, longs100[1].longValue());
+
+        tool.dispose();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.test/src/com/oracle/truffle/tools/test/LineToProbesMapTest.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2014, 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.test;
+
+import static com.oracle.truffle.tools.test.TestNodes.*;
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.tools.*;
+
+public class LineToProbesMapTest {
+
+    @Test
+    public void testToolCreatedTooLate() {
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+        final Node addNode = expr13rootNode.getChildren().iterator().next();
+        final Probe probe = addNode.probe();
+        final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation();
+        assertEquals(lineLocation, expr13Line2);
+
+        final LineToProbesMap tool = new LineToProbesMap();
+        tool.install();
+
+        assertNull(tool.findFirstProbe(expr13Line1));
+        assertNull(tool.findFirstProbe(expr13Line2));
+        tool.dispose();
+    }
+
+    @Test
+    public void testToolInstalledTooLate() {
+        final LineToProbesMap tool = new LineToProbesMap();
+
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+        final Node addNode = expr13rootNode.getChildren().iterator().next();
+        final Probe probe = addNode.probe();
+        final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation();
+        assertEquals(lineLocation, expr13Line2);
+
+        tool.install();
+
+        assertNull(tool.findFirstProbe(expr13Line1));
+        assertNull(tool.findFirstProbe(expr13Line2));
+        tool.dispose();
+    }
+
+    @Test
+    public void testMapping() {
+        final LineToProbesMap tool = new LineToProbesMap();
+        tool.install();
+
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+        final Node addNode = expr13rootNode.getChildren().iterator().next();
+        final Probe probe = addNode.probe();
+        final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation();
+        assertEquals(lineLocation, expr13Line2);
+
+        assertNull(tool.findFirstProbe(expr13Line1));
+        assertEquals(tool.findFirstProbe(expr13Line2), probe);
+        tool.dispose();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.test/src/com/oracle/truffle/tools/test/NodeExecCounterTest.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2014, 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.test;
+
+import static com.oracle.truffle.tools.test.TestNodes.*;
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.tools.*;
+import com.oracle.truffle.tools.NodeExecCounter.NodeExecutionCount;
+import com.oracle.truffle.tools.test.TestNodes.TestAddNode;
+import com.oracle.truffle.tools.test.TestNodes.TestValueNode;
+
+public class NodeExecCounterTest {
+
+    @Test
+    public void testNoExecution() {
+        final NodeExecCounter tool = new NodeExecCounter();
+        assertEquals(tool.getCounts().length, 0);
+        tool.install();
+        assertEquals(tool.getCounts().length, 0);
+        tool.setEnabled(false);
+        assertEquals(tool.getCounts().length, 0);
+        tool.setEnabled(true);
+        assertEquals(tool.getCounts().length, 0);
+        tool.reset();
+        assertEquals(tool.getCounts().length, 0);
+        tool.dispose();
+        assertEquals(tool.getCounts().length, 0);
+    }
+
+    @Test
+    public void testToolCreatedTooLate() {
+        final CallTarget expr13callTarget = createExpr13TestCallTarget();
+        final NodeExecCounter tool = new NodeExecCounter();
+        tool.install();
+        assertEquals(13, expr13callTarget.call());
+        assertEquals(tool.getCounts().length, 0);
+        tool.dispose();
+    }
+
+    @Test
+    public void testToolInstalledcTooLate() {
+        final NodeExecCounter tool = new NodeExecCounter();
+        final CallTarget expr13callTarget = createExpr13TestCallTarget();
+        tool.install();
+        assertEquals(13, expr13callTarget.call());
+        assertEquals(tool.getCounts().length, 0);
+        tool.dispose();
+    }
+
+    @Test
+    public void testCountingAll() {
+        final NodeExecCounter tool = new NodeExecCounter();
+        tool.install();
+        final CallTarget expr13callTarget = createExpr13TestCallTarget();
+
+        // execute once
+        assertEquals(13, expr13callTarget.call());
+        final NodeExecutionCount[] count1 = tool.getCounts();
+        assertNotNull(count1);
+        assertEquals(count1.length, 2);
+        for (NodeExecutionCount count : count1) {
+            final Class<?> class1 = count.nodeClass();
+            final long executionCount = count.executionCount();
+            if (class1 == TestAddNode.class) {
+                assertEquals(executionCount, 1);
+            } else if (class1 == TestValueNode.class) {
+                assertEquals(executionCount, 2);
+            } else {
+                fail();
+            }
+        }
+
+        // Execute 99 more times
+        for (int i = 0; i < 99; i++) {
+            assertEquals(13, expr13callTarget.call());
+        }
+        final NodeExecutionCount[] counts100 = tool.getCounts();
+        assertNotNull(counts100);
+        assertEquals(counts100.length, 2);
+        for (NodeExecutionCount count : counts100) {
+            final Class<?> class1 = count.nodeClass();
+            final long executionCount = count.executionCount();
+            if (class1 == TestAddNode.class) {
+                assertEquals(executionCount, 100);
+            } else if (class1 == TestValueNode.class) {
+                assertEquals(executionCount, 200);
+            } else {
+                fail();
+            }
+        }
+
+        tool.dispose();
+    }
+
+    @Test
+    public void testCountingTagged() {
+        final NodeExecCounter tool = new NodeExecCounter(StandardSyntaxTag.STATEMENT);
+        tool.install();
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+
+        // Not probed yet.
+        assertEquals(13, expr13rootNode.execute(null));
+        assertEquals(tool.getCounts().length, 0);
+
+        final Node addNode = expr13rootNode.getChildren().iterator().next();
+        final Probe probe = addNode.probe();
+
+        // Probed but not tagged yet.
+        assertEquals(13, expr13rootNode.execute(null));
+        assertEquals(tool.getCounts().length, 0);
+
+        probe.tagAs(StandardSyntaxTag.STATEMENT, "fake statement for testing");
+
+        // Counting now; execute once
+        assertEquals(13, expr13rootNode.execute(null));
+        final NodeExecutionCount[] counts1 = tool.getCounts();
+        assertNotNull(counts1);
+        assertEquals(counts1.length, 1);
+        final NodeExecutionCount count1 = counts1[0];
+        assertNotNull(count1);
+        assertEquals(count1.nodeClass(), addNode.getClass());
+        assertEquals(count1.executionCount(), 1);
+
+        // Execute 99 more times
+        for (int i = 0; i < 99; i++) {
+            assertEquals(13, expr13rootNode.execute(null));
+        }
+
+        final NodeExecutionCount[] counts100 = tool.getCounts();
+        assertNotNull(counts100);
+        assertEquals(counts100.length, 1);
+        final NodeExecutionCount count100 = counts100[0];
+        assertNotNull(count100);
+        assertEquals(count100.nodeClass(), addNode.getClass());
+        assertEquals(count100.executionCount(), 100);
+
+        tool.dispose();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.test/src/com/oracle/truffle/tools/test/TestNodes.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2014, 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.test;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.ProbeNode.WrapperNode;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+
+/**
+ * Nodes and an {@linkplain CallTarget executable ASTs} for testing.
+ */
+class TestNodes {
+
+    /**
+     * A fake source used for testing: empty line 1, expression on line 2.
+     */
+    public static final Source expr13Source = Source.fromText("\n6+7\n", "Test Source: expression on line 2 that evaluates to 13");
+    public static final LineLocation expr13Line1 = expr13Source.createLineLocation(1);
+    public static final LineLocation expr13Line2 = expr13Source.createLineLocation(2);
+
+    /**
+     * An executable addition expression that evaluates to 13.
+     */
+    static CallTarget createExpr13TestCallTarget() {
+        final RootNode rootNode = createExpr13TestRootNode();
+        return Truffle.getRuntime().createCallTarget(rootNode);
+    }
+
+    /**
+     * Root holding an addition expression that evaluates to 13.
+     */
+    static RootNode createExpr13TestRootNode() {
+        final TestLanguageNode ast = createExpr13AST();
+        final TestRootNode rootNode = new TestRootNode(ast);
+        rootNode.adoptChildren();
+        return rootNode;
+    }
+
+    /**
+     * Addition expression that evaluates to 13, with faked source attribution.
+     */
+    static TestLanguageNode createExpr13AST() {
+        final SourceSection leftSourceSection = expr13Source.createSection("left", 1, 1);
+        final TestValueNode leftValueNode = new TestValueNode(6, leftSourceSection);
+        final SourceSection rightSourceSection = expr13Source.createSection("right", 3, 1);
+        final TestValueNode rightValueNode = new TestValueNode(7, rightSourceSection);
+        final SourceSection exprSourceSection = expr13Source.createSection("expr", 1, 3);
+        return new TestAddNode(leftValueNode, rightValueNode, exprSourceSection);
+    }
+
+    abstract static class TestLanguageNode extends Node {
+        public abstract Object execute(VirtualFrame frame);
+
+        public TestLanguageNode() {
+        }
+
+        public TestLanguageNode(SourceSection srcSection) {
+            super(srcSection);
+        }
+
+        @Override
+        public boolean isInstrumentable() {
+            return true;
+        }
+
+        @Override
+        public WrapperNode createWrapperNode() {
+            return new TestWrapperNode(this);
+        }
+    }
+
+    @NodeInfo(cost = NodeCost.NONE)
+    static class TestWrapperNode extends TestLanguageNode implements WrapperNode {
+        @Child private TestLanguageNode child;
+        @Child private ProbeNode probeNode;
+
+        public TestWrapperNode(TestLanguageNode child) {
+            assert !(child instanceof TestWrapperNode);
+            this.child = child;
+        }
+
+        @Override
+        public String instrumentationInfo() {
+            return "Wrapper node for testing";
+        }
+
+        @Override
+        public boolean isInstrumentable() {
+            return false;
+        }
+
+        @Override
+        public void insertProbe(ProbeNode newProbeNode) {
+            this.probeNode = newProbeNode;
+        }
+
+        @Override
+        public Probe getProbe() {
+            return probeNode.getProbe();
+        }
+
+        @Override
+        public Node getChild() {
+            return child;
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            probeNode.enter(child, frame);
+            Object result;
+
+            try {
+                result = child.execute(frame);
+                probeNode.returnValue(child, frame, result);
+            } catch (KillException e) {
+                throw (e);
+            } catch (Exception e) {
+                probeNode.returnExceptional(child, frame, e);
+                throw (e);
+            }
+
+            return result;
+        }
+    }
+
+    /**
+     * Truffle requires that all guest languages to have a {@link RootNode} which sits atop any AST
+     * of the guest language. This is necessary since creating a {@link CallTarget} is how Truffle
+     * completes an AST. The root nodes serves as our entry point into a program.
+     */
+    static class TestRootNode extends RootNode {
+        @Child private TestLanguageNode body;
+
+        /**
+         * This constructor emulates the global machinery that applies registered probers to every
+         * newly created AST. Global registry is not used, since that would interfere with other
+         * tests run in the same environment.
+         */
+        public TestRootNode(TestLanguageNode body) {
+            super(null);
+            this.body = body;
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            return body.execute(frame);
+        }
+
+        @Override
+        public boolean isCloningAllowed() {
+            return true;
+        }
+
+        @Override
+        public void applyInstrumentation() {
+            Probe.applyASTProbers(body);
+        }
+    }
+
+    static class TestValueNode extends TestLanguageNode {
+        private final int value;
+
+        public TestValueNode(int value) {
+            this.value = value;
+        }
+
+        public TestValueNode(int value, SourceSection srcSection) {
+            super(srcSection);
+            this.value = value;
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            return new Integer(this.value);
+        }
+    }
+
+    static class TestAddNode extends TestLanguageNode {
+        @Child private TestLanguageNode leftChild;
+        @Child private TestLanguageNode rightChild;
+
+        public TestAddNode(TestValueNode leftChild, TestValueNode rightChild, SourceSection sourceSection) {
+            super(sourceSection);
+            this.leftChild = insert(leftChild);
+            this.rightChild = insert(rightChild);
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            return new Integer(((Integer) leftChild.execute(frame)).intValue() + ((Integer) rightChild.execute(frame)).intValue());
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools.test/src/com/oracle/truffle/tools/test/TruffleToolTest.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2014, 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.test;
+
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.instrument.*;
+
+/**
+ * Test the basic life cycle properties shared by all instances of {@link InstrumentationTool}.
+ */
+public class TruffleToolTest {
+
+    @Test
+    public void testEmptyLifeCycle() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        assertFalse(tool.isEnabled());
+        tool.install();
+        assertTrue(tool.isEnabled());
+        tool.reset();
+        assertTrue(tool.isEnabled());
+        tool.setEnabled(false);
+        assertFalse(tool.isEnabled());
+        tool.reset();
+        assertFalse(tool.isEnabled());
+        tool.setEnabled(true);
+        assertTrue(tool.isEnabled());
+        tool.reset();
+        assertTrue(tool.isEnabled());
+        tool.dispose();
+        assertFalse(tool.isEnabled());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testNotYetInstalled1() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.reset();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testNotYetInstalled2() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.setEnabled(true);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testNotYetInstalled3() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.dispose();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAlreadyInstalled() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.install();
+        tool.install();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAlreadyDisposed1() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.install();
+        tool.dispose();
+        tool.install();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAlreadyDisposed2() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.install();
+        tool.dispose();
+        tool.reset();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAlreadyDisposed3() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.install();
+        tool.dispose();
+        tool.setEnabled(true);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAlreadyDisposed4() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.install();
+        tool.dispose();
+        tool.dispose();
+    }
+
+    private static final class DummyTruffleTool extends InstrumentationTool {
+
+        @Override
+        protected boolean internalInstall() {
+            return true;
+        }
+
+        @Override
+        protected void internalReset() {
+        }
+
+        @Override
+        protected void internalDispose() {
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools/src/com/oracle/truffle/tools/CoverageTracker.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,320 @@
+/*
+ * 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;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.impl.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+
+/**
+ * An {@link InstrumentationTool} that counts interpreter <em>execution calls</em> to AST nodes that
+ * hold a specified {@linkplain SyntaxTag syntax tag}, tabulated by source and line number
+ * associated with each node. Syntax tags are presumed to be applied external to the tool. If no tag
+ * is specified, {@linkplain StandardSyntaxTag#STATEMENT STATEMENT} is used, corresponding to
+ * conventional behavior for code coverage tools.
+ * <p>
+ * No counts will be kept for execution in sources that hold the {@link SourceTag}
+ * {@link Tags#NO_COVERAGE}.
+ * <p>
+ * <b>Tool Life Cycle</b>
+ * <p>
+ * See {@link InstrumentationTool} for the life cycle common to all such tools.
+ * <p>
+ * <b>Execution Counts</b>
+ * <p>
+ * <ul>
+ * <li>"Execution call" on a node is is defined as invocation of a node method that is instrumented
+ * to produce the event {@link SimpleInstrumentListener#enter(Probe)};</li>
+ * <li>Execution calls are tabulated only at <em>instrumented</em> nodes, i.e. those for which
+ * {@linkplain Node#isInstrumentable() isInstrumentable() == true};</li>
+ * <li>Execution calls are tabulated only at nodes present in the AST when originally created;
+ * dynamically added nodes will not be instrumented.</li>
+ * </ul>
+ * </p>
+ * <b>Results</b>
+ * <p>
+ * A modification-safe copy of the {@linkplain #getCounts() counts} can be retrieved at any time,
+ * without effect on the state of the tool.
+ * </p>
+ * <p>
+ * A "default" {@linkplain #print(PrintStream) print()} method can summarizes the current counts at
+ * any time in a simple textual format, with no other effect on the state of the tool.
+ * </p>
+ *
+ * @see Instrument
+ * @see SyntaxTag
+ */
+public final class CoverageTracker extends InstrumentationTool {
+
+    public enum Tags implements SourceTag {
+
+        /**
+         * Report no counts for sources holding this tag.
+         */
+        NO_COVERAGE("No Coverage", "Coverage Tracker will igore");
+
+        private final String name;
+        private final String description;
+
+        private Tags(String name, String description) {
+            this.name = name;
+            this.description = description;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+    }
+
+    /** Counting data. */
+    private final Map<LineLocation, CoverageRecord> coverageMap = new HashMap<>();
+
+    /** Needed for disposal. */
+    private final List<Instrument> instruments = new ArrayList<>();
+
+    /**
+     * Coverage counting is restricted to nodes holding this tag.
+     */
+    private final SyntaxTag countingTag;
+
+    private final ProbeListener probeListener;
+
+    /**
+     * Create a per-line coverage tool for nodes tagged as {@linkplain StandardSyntaxTag#STATEMENT
+     * statements} in subsequently created ASTs.
+     */
+    public CoverageTracker() {
+        this(StandardSyntaxTag.STATEMENT);
+    }
+
+    /**
+     * Create a per-line coverage tool for nodes tagged as specified, presuming that tags applied
+     * outside this tool.
+     */
+    public CoverageTracker(SyntaxTag tag) {
+        this.probeListener = new CoverageProbeListener();
+        this.countingTag = tag;
+    }
+
+    @Override
+    protected boolean internalInstall() {
+        Probe.addProbeListener(probeListener);
+        return true;
+    }
+
+    @Override
+    protected void internalReset() {
+        coverageMap.clear();
+    }
+
+    @Override
+    protected void internalDispose() {
+        Probe.removeProbeListener(probeListener);
+        for (Instrument instrument : instruments) {
+            instrument.dispose();
+        }
+    }
+
+    /**
+     * Gets a modification-safe summary of the current per-type node execution counts; does not
+     * affect the counts.
+     * <p>
+     * The map holds an array for each source, and elements of the a array corresponding to the
+     * textual lines of that source. An array entry contains null if the corresponding line of
+     * source is associated with no nodes of the relevant type, i.e. where no count was made. A
+     * numeric entry represents the execution count at the corresponding line of source.
+     * <p>
+     * <b>Note:</b> source line numbers are 1-based, so array index {@code i} corresponds to source
+     * line number {@code i + 1}
+     */
+    public Map<Source, Long[]> getCounts() {
+
+        /**
+         * Counters for every {Source, line number} for which a counter was installed, i.e. for
+         * every line associated with an appropriately tagged AST node; iterable in order of source
+         * name, then line number.
+         */
+        final TreeSet<Entry<LineLocation, CoverageRecord>> entries = new TreeSet<>(new LineLocationEntryComparator());
+
+        for (Entry<LineLocation, CoverageRecord> entry : coverageMap.entrySet()) {
+            entries.add(entry);
+        }
+        final Map<Source, Long[]> result = new HashMap<>();
+        Source curSource = null;
+        Long[] curLineTable = null;
+        for (Entry<LineLocation, CoverageRecord> entry : entries) {
+            final LineLocation key = entry.getKey();
+            final Source source = key.getSource();
+            final int lineNo = key.getLineNumber();
+            if (source != curSource) {
+                if (curSource != null) {
+                    result.put(curSource, curLineTable);
+                }
+                curSource = source;
+                curLineTable = new Long[source.getLineCount()];
+            }
+            curLineTable[lineNo - 1] = entry.getValue().count;
+        }
+        if (curSource != null) {
+            result.put(curSource, curLineTable);
+        }
+        return result;
+    }
+
+    /**
+     * A default printer for the current line counts, producing lines of the form " (<count>) <line
+     * number> : <text of line>", grouped by source.
+     */
+    public void print(PrintStream out) {
+        out.println();
+        out.println(countingTag.name() + " coverage:");
+
+        /**
+         * Counters for every {Source, line number} for which a counter was installed, i.e. for
+         * every line associated with an appropriately tagged AST node; iterable in order of source
+         * name, then line number.
+         */
+        final TreeSet<Entry<LineLocation, CoverageRecord>> entries = new TreeSet<>(new LineLocationEntryComparator());
+
+        for (Entry<LineLocation, CoverageRecord> entry : coverageMap.entrySet()) {
+            entries.add(entry);
+        }
+        Source curSource = null;
+        int curLineNo = 1;
+        for (Entry<LineLocation, CoverageRecord> entry : entries) {
+            final LineLocation key = entry.getKey();
+            final Source source = key.getSource();
+            final int lineNo = key.getLineNumber();
+            if (source != curSource) {
+                if (curSource != null) {
+                    while (curLineNo <= curSource.getLineCount()) {
+                        displayLine(out, null, curSource, curLineNo++);
+                    }
+                }
+                curSource = source;
+                curLineNo = 1;
+                out.println();
+                out.println(source.getPath());
+            }
+            while (curLineNo < lineNo) {
+                displayLine(out, null, curSource, curLineNo++);
+            }
+            displayLine(out, entry.getValue(), curSource, curLineNo++);
+        }
+        if (curSource != null) {
+            while (curLineNo <= curSource.getLineCount()) {
+                displayLine(out, null, curSource, curLineNo++);
+            }
+        }
+    }
+
+    private static void displayLine(PrintStream out, CoverageRecord record, Source source, int lineNo) {
+        if (record == null) {
+            out.format("%14s", " ");
+        } else {
+            out.format("(%12d)", record.count);
+        }
+        out.format(" %3d: ", lineNo);
+        out.println(source.getCode(lineNo));
+    }
+
+    /**
+     * A listener for events at each instrumented AST location. This listener counts
+     * "execution calls" to the instrumented node.
+     */
+    private final class CoverageRecord extends DefaultSimpleInstrumentListener {
+
+        private final SourceSection srcSection; // The text of the code being counted
+        private Instrument instrument;  // The attached Instrument, in case need to remove.
+        private long count = 0;
+
+        CoverageRecord(SourceSection srcSection) {
+            this.srcSection = srcSection;
+        }
+
+        @Override
+        public void enter(Probe probe) {
+            if (isEnabled()) {
+                count++;
+            }
+        }
+
+    }
+
+    private static final class LineLocationEntryComparator implements Comparator<Entry<LineLocation, CoverageRecord>> {
+
+        public int compare(Entry<LineLocation, CoverageRecord> e1, Entry<LineLocation, CoverageRecord> e2) {
+            return LineLocation.COMPARATOR.compare(e1.getKey(), e2.getKey());
+        }
+    }
+
+    /**
+     * Attach a counting instrument to each node that is assigned a specified tag.
+     */
+    private class CoverageProbeListener extends DefaultProbeListener {
+
+        @Override
+        public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
+            if (countingTag == tag) {
+
+                final SourceSection srcSection = probe.getProbedSourceSection();
+                if (srcSection == null) {
+                    // TODO (mlvdv) report this?
+                } else if (!srcSection.getSource().isTaggedAs(Tags.NO_COVERAGE)) {
+                    // Get the source line where the
+                    final LineLocation lineLocation = srcSection.getLineLocation();
+                    CoverageRecord record = coverageMap.get(lineLocation);
+                    if (record != null) {
+                        // Another node starts on same line; count only the first (textually)
+                        if (srcSection.getCharIndex() > record.srcSection.getCharIndex()) {
+                            // Existing record, corresponds to code earlier on line
+                            return;
+                        } else {
+                            // Existing record, corresponds to code at a later position; replace it
+                            record.instrument.dispose();
+                        }
+                    }
+
+                    final CoverageRecord coverage = new CoverageRecord(srcSection);
+                    final Instrument instrument = Instrument.create(coverage, CoverageTracker.class.getSimpleName());
+                    coverage.instrument = instrument;
+                    instruments.add(instrument);
+                    probe.attach(instrument);
+                    coverageMap.put(lineLocation, coverage);
+                }
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools/src/com/oracle/truffle/tools/LineToProbesMap.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2014, 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;
+
+import java.io.*;
+import java.util.*;
+
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.impl.*;
+import com.oracle.truffle.api.source.*;
+
+/**
+ * An {@link InstrumentationTool} that builds a map of every {@link Probe} attached to some AST,
+ * indexed by {@link Source} and line number.
+ */
+public final class LineToProbesMap extends InstrumentationTool {
+
+    private static final boolean TRACE = false;
+    private static final PrintStream OUT = System.out;
+
+    private static void trace(String msg) {
+        OUT.println("LineToProbesMap: " + msg);
+    }
+
+    /**
+     * Map: Source line ==> probes associated with source sections starting on the line.
+     */
+    private final Map<LineLocation, Collection<Probe>> lineToProbesMap = new HashMap<>();
+
+    private final ProbeListener probeListener;
+
+    /**
+     * Create a map of {@link Probe}s that collects information on all probes added to subsequently
+     * created ASTs (once installed).
+     */
+    public LineToProbesMap() {
+        this.probeListener = new LineToProbesListener();
+    }
+
+    @Override
+    protected boolean internalInstall() {
+        Probe.addProbeListener(probeListener);
+        return true;
+    }
+
+    @Override
+    protected void internalReset() {
+        lineToProbesMap.clear();
+    }
+
+    @Override
+    protected void internalDispose() {
+        Probe.removeProbeListener(probeListener);
+    }
+
+    /**
+     * Returns the {@link Probe}, if any, associated with a specific line of guest language code; if
+     * more than one, return the one with the first starting character location.
+     */
+    public Probe findFirstProbe(LineLocation lineLocation) {
+        Probe probe = null;
+        final Collection<Probe> probes = findProbes(lineLocation);
+        for (Probe probesOnLine : probes) {
+            if (probe == null) {
+                probe = probesOnLine;
+            } else if (probesOnLine.getProbedSourceSection().getCharIndex() < probe.getProbedSourceSection().getCharIndex()) {
+                probe = probesOnLine;
+            }
+        }
+        return probe;
+    }
+
+    /**
+     * Returns all {@link Probe}s whose associated source begins at the given {@link LineLocation},
+     * an empty list if none.
+     */
+    public Collection<Probe> findProbes(LineLocation line) {
+        final Collection<Probe> probes = lineToProbesMap.get(line);
+        if (probes == null) {
+            return Collections.emptyList();
+        }
+        return Collections.unmodifiableCollection(probes);
+    }
+
+    private class LineToProbesListener extends DefaultProbeListener {
+
+        @Override
+        public void newProbeInserted(Probe probe) {
+            final SourceSection sourceSection = probe.getProbedSourceSection();
+            if (sourceSection != null && !(sourceSection instanceof NullSourceSection)) {
+                final LineLocation lineLocation = sourceSection.getLineLocation();
+                if (TRACE) {
+                    trace("ADD " + lineLocation.getShortDescription() + " ==> " + probe.getShortDescription());
+                }
+                Collection<Probe> probes = lineToProbesMap.get(lineLocation);
+                if (probes == null) {
+                    probes = new ArrayList<>(2);
+                    lineToProbesMap.put(lineLocation, probes);
+                } else {
+                    assert !probes.contains(probe);
+                }
+                probes.add(probe);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.tools/src/com/oracle/truffle/tools/NodeExecCounter.java	Tue May 26 16:38:13 2015 -0700
@@ -0,0 +1,332 @@
+/*
+ * 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;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.impl.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.nodes.Node.Child;
+
+/**
+ * An {@link InstrumentationTool} that counts interpreter <em>execution calls</em> to AST nodes,
+ * tabulated by the type of called nodes; counting can be enabled <em>all</em> nodes or restricted
+ * to nodes with a specified {@linkplain SyntaxTag tag} that is presumed to be applied external to
+ * the tool.
+ * <p>
+ * <b>Tool Life Cycle</b>
+ * <p>
+ * See {@link InstrumentationTool} for the life cycle common to all such tools.
+ * </p>
+ * <b>Execution Counts</b>
+ * <p>
+ * <ul>
+ * <li>"Execution call" on a node is is defined as invocation of a node method that is instrumented
+ * to produce the event {@link StandardInstrumentListener#enter(Probe, Node, VirtualFrame)};</li>
+ * <li>Execution calls are tabulated only at <em>instrumented</em> nodes, i.e. those for which
+ * {@linkplain Node#isInstrumentable() isInstrumentable() == true};</li>
+ * <li>Execution calls are tabulated only at nodes present in the AST when originally created;
+ * dynamically added nodes will not be instrumented.</li>
+ * </ul>
+ * </p>
+ * <b>Failure Log</b>
+ * <p>
+ * For the benefit of language implementors, the tool maintains a log describing failed attempts to
+ * probe AST nodes. Most failures occur when the type of the wrapper created by
+ * {@link Node#createWrapperNode()} is not assignable to the relevant {@link Child} field in the
+ * node's parent.
+ * </p>
+ * <p>
+ * {@linkplain #reset() Resetting} the counts has no effect on the failure log.
+ * </p>
+ * <b>Results</b>
+ * <p>
+ * A modification-safe copy of the {@linkplain #getCounts() counts} can be retrieved at any time,
+ * without effect on the state of the tool.
+ * </p>
+ * <p>
+ * A "default" {@linkplain #print(PrintStream) print()} method can summarizes the current counts at
+ * any time in a simple textual format, without effect on the state of the tool.
+ * </p>
+ *
+ * @see Instrument
+ * @see SyntaxTag
+ * @see ProbeFailure
+ */
+public final class NodeExecCounter extends InstrumentationTool {
+
+    /**
+     * Execution count for AST nodes of a particular type.
+     */
+    public interface NodeExecutionCount {
+        Class<?> nodeClass();
+
+        long executionCount();
+    }
+
+    /**
+     * Listener for events at instrumented nodes. Counts are maintained in a shared table, so the
+     * listener is stateless and can be shared by every {@link Instrument}.
+     */
+    private final StandardInstrumentListener instrumentListener = new DefaultStandardInstrumentListener() {
+        @Override
+        public void enter(Probe probe, Node node, VirtualFrame vFrame) {
+            if (isEnabled()) {
+                final Class<?> nodeClass = node.getClass();
+                /*
+                 * Everything up to here is inlined by Truffle compilation. Delegate the next part
+                 * to a method behind an inlining boundary.
+                 * 
+                 * Note that it is not permitted to pass a {@link VirtualFrame} across an inlining
+                 * boundary; they are truly virtual in inlined code.
+                 */
+                AtomicLong nodeCounter = getCounter(nodeClass);
+                nodeCounter.getAndIncrement();
+            }
+        }
+
+        /**
+         * Mark this method as a boundary that will stop Truffle inlining, which should not be
+         * allowed to inline the hash table method or any other complex library code.
+         */
+        @TruffleBoundary
+        private AtomicLong getCounter(Class<?> nodeClass) {
+            AtomicLong nodeCounter = counters.get(nodeClass);
+            if (nodeCounter == null) {
+                nodeCounter = new AtomicLong();
+                counters.put(nodeClass, nodeCounter);
+            }
+            return nodeCounter;
+        }
+    };
+
+    /** Counting data. */
+    private final Map<Class<?>, AtomicLong> counters = new HashMap<>();
+
+    /** Failure log. */
+    private final List<ProbeFailure> failures = new ArrayList<>();
+
+    /** For disposal. */
+    private final List<Instrument> instruments = new ArrayList<>();
+
+    /**
+     * If non-null, counting is restricted to nodes holding this tag.
+     */
+    private final SyntaxTag countingTag;
+
+    /**
+     * Prober used only when instrumenting every node.
+     */
+    private ASTProber astProber;
+
+    /**
+     * Listener used only when restricting counting to a specific tag.
+     */
+    private ProbeListener probeListener;
+
+    /**
+     * Create a per node-type execution counting tool for all nodes in subsequently created ASTs.
+     */
+    public NodeExecCounter() {
+        this.countingTag = null;
+    }
+
+    /**
+     * Creates a per-type execution counting for nodes tagged as specified in subsequently created
+     * ASTs.
+     */
+    public NodeExecCounter(SyntaxTag tag) {
+        this.countingTag = tag;
+    }
+
+    @Override
+    protected boolean internalInstall() {
+        if (countingTag == null) {
+            astProber = new ExecCounterASTProber();
+            Probe.registerASTProber(astProber);
+        } else {
+            probeListener = new NodeExecCounterProbeListener();
+            Probe.addProbeListener(probeListener);
+        }
+        return true;
+    }
+
+    @Override
+    protected void internalReset() {
+        counters.clear();
+        failures.clear();
+    }
+
+    @Override
+    protected void internalDispose() {
+        if (astProber != null) {
+            Probe.unregisterASTProber(astProber);
+        }
+        if (probeListener != null) {
+            Probe.removeProbeListener(probeListener);
+        }
+        for (Instrument instrument : instruments) {
+            instrument.dispose();
+        }
+    }
+
+    /**
+     * Gets a modification-safe summary of the current per-type node execution counts; does not
+     * affect the counts.
+     */
+    public NodeExecutionCount[] getCounts() {
+        final Collection<Map.Entry<Class<?>, AtomicLong>> entrySet = counters.entrySet();
+        final NodeExecutionCount[] result = new NodeExecCountImpl[entrySet.size()];
+        int i = 0;
+        for (Map.Entry<Class<?>, AtomicLong> entry : entrySet) {
+            result[i++] = new NodeExecCountImpl(entry.getKey(), entry.getValue().longValue());
+        }
+        return result;
+    }
+
+    /**
+     * Gets a log containing a report of every failed attempt to instrument a node.
+     */
+    public ProbeFailure[] getFailures() {
+        return failures.toArray(new ProbeFailure[failures.size()]);
+    }
+
+    /**
+     * A default printer for the current counts, producing lines of the form
+     * " <count> : <node type>" in descending order of count.
+     */
+    public void print(PrintStream out) {
+        print(out, false);
+    }
+
+    /**
+     * A default printer for the current counts, producing lines of the form
+     * " <count> : <node type>" in descending order of count.
+     *
+     * @param out
+     * @param verbose whether to describe nodes on which instrumentation failed
+     */
+    public void print(PrintStream out, boolean verbose) {
+
+        final long missedNodes = failures.size();
+        out.println();
+        if (countingTag == null) {
+            out.println("Execution counts by node type:");
+        } else {
+            out.println("\"" + countingTag.name() + "\"-tagged execution counts by node type:");
+        }
+        final StringBuilder disclaim = new StringBuilder("(");
+        if (missedNodes > 0) {
+            disclaim.append(Long.toString(missedNodes) + " original AST nodes not instrumented, ");
+        }
+        disclaim.append("dynamically added nodes not instrumented)");
+        out.println(disclaim.toString());
+        NodeExecutionCount[] execCounts = getCounts();
+        // Sort in descending order
+        Arrays.sort(execCounts, new Comparator<NodeExecutionCount>() {
+
+            public int compare(NodeExecutionCount o1, NodeExecutionCount o2) {
+                return Long.compare(o2.executionCount(), o1.executionCount());
+            }
+
+        });
+        for (NodeExecutionCount nodeCount : execCounts) {
+            out.format("%12d", nodeCount.executionCount());
+            out.println(" : " + nodeCount.nodeClass().getName());
+        }
+
+        if (verbose && missedNodes > 0) {
+            out.println("Instrumentation failures for execution counts:");
+
+            for (ProbeFailure failure : failures) {
+                out.println("\t" + failure.getMessage());
+            }
+        }
+    }
+
+    /**
+     * A prober that attempts to probe and instrument every node.
+     */
+    private class ExecCounterASTProber implements ASTProber, NodeVisitor {
+
+        public boolean visit(Node node) {
+
+            if (node.isInstrumentable()) {
+                try {
+                    final Instrument instrument = Instrument.create(instrumentListener, "NodeExecCounter");
+                    instruments.add(instrument);
+                    node.probe().attach(instrument);
+                } catch (ProbeException ex) {
+                    failures.add(ex.getFailure());
+                }
+            }
+            return true;
+        }
+
+        public void probeAST(Node node) {
+            node.accept(this);
+        }
+    }
+
+    /**
+     * A listener that assumes ASTs have been tagged external to this tool, and which instruments
+     * nodes holding a specified tag.
+     */
+    private class NodeExecCounterProbeListener extends DefaultProbeListener {
+
+        @Override
+        public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
+            if (countingTag == tag) {
+                final Instrument instrument = Instrument.create(instrumentListener, NodeExecCounter.class.getSimpleName());
+                instruments.add(instrument);
+                probe.attach(instrument);
+            }
+        }
+    }
+
+    private static class NodeExecCountImpl implements NodeExecutionCount {
+
+        private final Class<?> nodeClass;
+        private final long count;
+
+        public NodeExecCountImpl(Class<?> nodeClass, long count) {
+            this.nodeClass = nodeClass;
+            this.count = count;
+        }
+
+        public Class<?> nodeClass() {
+            return nodeClass;
+        }
+
+        public long executionCount() {
+            return count;
+        }
+    }
+}
--- a/mx/mx_graal.py	Fri May 22 10:20:38 2015 -0700
+++ b/mx/mx_graal.py	Tue May 26 16:38:13 2015 -0700
@@ -2407,6 +2407,11 @@
     vmArgs, slArgs = _extract_VM_args(args)
     vm(vmArgs + ['-cp', mx.classpath(["TRUFFLE", "com.oracle.truffle.sl"]), "com.oracle.truffle.sl.SLMain"] + slArgs)
 
+def sldebug(args):
+    """run a simple command line debugger for the Simple Language"""
+    vmArgs, slArgs = _extract_VM_args(args, useDoubleDash=True)
+    vm(vmArgs + ['-cp', mx.classpath("com.oracle.truffle.sl.tools.debug"), "com.oracle.truffle.sl.tools.debug.SLREPLServer"] + slArgs)
+
 def isGraalEnabled(vm):
     return vm != 'original' and not vm.endswith('nograal')
 
@@ -2653,6 +2658,7 @@
         'deoptalot' : [deoptalot, '[n]'],
         'longtests' : [longtests, ''],
         'sl' : [sl, '[SL args|@VM options]'],
+        'sldebug' : [sldebug, '[SL args|@VM options]'],
         'jol' : [jol, ''],
     }
 
--- a/mx/suite.py	Fri May 22 10:20:38 2015 -0700
+++ b/mx/suite.py	Tue May 26 16:38:13 2015 -0700
@@ -2,6 +2,16 @@
   "mxversion" : "1.0",
   "name" : "graal",
   "libraries" : {
+
+      "JLINE" : {
+      "path" : "lib/jline-2.11.jar",
+      "urls" : [
+        "http://lafo.ssw.uni-linz.ac.at/graal-external-deps/jline-2.11.jar",
+        "https://search.maven.org/remotecontent?filepath=jline/jline/2.11/jline-2.11.jar",
+      ],
+      "sha1" : "9504d5e2da5d78237239c5226e8200ec21182040",
+    },
+
     "JUNIT" : {
       "path" : "lib/junit-4.11.jar",
       "urls" : [
@@ -1072,12 +1082,55 @@
       "workingSets" : "Truffle",
     },
 
+    "com.oracle.truffle.tools" : {
+      "subDir" : "graal",
+      "sourceDirs" : ["src"],
+      "dependencies" : ["com.oracle.truffle.api"],
+      "checkstyle" : "com.oracle.truffle.api",
+      "javaCompliance" : "1.8",
+      "workingSets" : "Truffle,Tools",
+    },
+
+    "com.oracle.truffle.tools.test" : {
+      "subDir" : "graal",
+      "sourceDirs" : ["src"],
+      "dependencies" : [
+          "com.oracle.truffle.tools",
+          "JUNIT"
+          ],
+      "checkstyle" : "com.oracle.truffle.api",
+      "javaCompliance" : "1.8",
+      "workingSets" : "Truffle,Tools",
+    },
+
+    "com.oracle.truffle.tools.debug.engine" : {
+      "subDir" : "graal",
+      "sourceDirs" : ["src"],
+      "dependencies" : ["com.oracle.truffle.api",
+                        "com.oracle.truffle.tools"
+                        ],
+      "checkstyle" : "com.oracle.truffle.api",
+      "javaCompliance" : "1.8",
+      "workingSets" : "Truffle,Tools",
+    },
+
+    "com.oracle.truffle.tools.debug.shell" : {
+      "subDir" : "graal",
+      "sourceDirs" : ["src"],
+      "dependencies" : ["com.oracle.truffle.tools.debug.engine",
+                        "JLINE"],
+      "checkstyle" : "com.oracle.truffle.api",
+      "javaCompliance" : "1.8",
+      "workingSets" : "Truffle,Tools",
+    },
+
     "com.oracle.truffle.sl" : {
       "subDir" : "graal",
       "sourceDirs" : ["src"],
       "dependencies" : [
         "com.oracle.truffle.api.dsl",
         "com.oracle.truffle.api.object",
+        "com.oracle.truffle.tools",
         "FINDBUGS"
       ],
       "checkstyle" : "com.oracle.graal.graph",
@@ -1098,6 +1151,18 @@
       "workingSets" : "Truffle,SimpleLanguage,Test",
     },
 
+     "com.oracle.truffle.sl.tools" : {
+      "subDir" : "graal",
+      "sourceDirs" : ["src"],
+      "dependencies" : [
+        "com.oracle.truffle.sl",
+        "com.oracle.truffle.tools.debug.shell",
+      ],
+      "checkstyle" : "com.oracle.truffle.api",
+      "javaCompliance" : "1.8",
+      "workingSets" : "Truffle,SimpleLanguage,Tools",
+    },
+
     "com.oracle.graal.truffle" : {
       "subDir" : "graal",
       "sourceDirs" : ["src"],