# HG changeset patch # User Michael Van De Vanter # Date 1449551866 28800 # Node ID 07f3efb4e32115139c11a0d8e7d3ae8712af64c2 # Parent 50baaa7da8e81caff71b23d4c07364dfc236f923# Parent 4a83dc15e774bdf6f01e409982b81d6338296d69 Merge with 4a83dc15e774bdf6f01e409982b81d6338296d69 diff -r 50baaa7da8e8 -r 07f3efb4e321 mx.truffle/mx_truffle.py --- a/mx.truffle/mx_truffle.py Fri Dec 04 14:32:24 2015 +0100 +++ b/mx.truffle/mx_truffle.py Mon Dec 07 21:17:46 2015 -0800 @@ -53,7 +53,7 @@ def sldebug(args): """run a simple command line debugger for the Simple Language""" vmArgs, slArgs = mx.extract_VM_args(args, useDoubleDash=True) - mx.run_java(vmArgs + ['-cp', mx.classpath("com.oracle.truffle.sl.tools"), "com.oracle.truffle.sl.tools.debug.SLREPLServer"] + slArgs) + mx.run_java(vmArgs + ['-cp', mx.classpath("com.oracle.truffle.sl.tools"), "com.oracle.truffle.sl.tools.debug.SLREPL"] + slArgs) def _truffle_gate_runner(args, tasks): with Task('Truffle UnitTests', tasks) as t: diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api.vm/src/com/oracle/truffle/api/vm/PolyglotEngine.java diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java --- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java Mon Dec 07 21:17:46 2015 -0800 @@ -32,22 +32,23 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Map; +import java.util.Objects; import com.oracle.truffle.api.debug.Debugger; import com.oracle.truffle.api.debug.SuspendedEvent; -import com.oracle.truffle.api.frame.FrameInstance; import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.impl.Accessor; import com.oracle.truffle.api.impl.FindContextNode; import com.oracle.truffle.api.instrument.ASTProber; import com.oracle.truffle.api.instrument.Instrumenter; +import com.oracle.truffle.api.instrument.KillException; +import com.oracle.truffle.api.instrument.QuitException; import com.oracle.truffle.api.instrument.SyntaxTag; import com.oracle.truffle.api.instrument.Visualizer; import com.oracle.truffle.api.instrument.WrapperNode; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.source.Source; -import java.util.Objects; /** * An entry point for everyone who wants to implement a Truffle based language. By providing an @@ -391,7 +392,7 @@ * . The names of arguments are parameters for the resulting {#link CallTarget} that allow * the source to reference the actual parameters passed to * {@link CallTarget#call(java.lang.Object...)}. - * + * * @param source the source to evaluate * @param argumentNames the names of {@link CallTarget#call(java.lang.Object...)} arguments * that can be referenced from the source @@ -470,21 +471,21 @@ } try { return target.call(); + } catch (KillException | QuitException ex) { + throw ex; } catch (Throwable ex) { throw new IOException(ex); } } @Override - protected Object evalInContext(Object vm, SuspendedEvent ev, String code, FrameInstance frame) throws IOException { - Node n = frame == null ? ev.getNode() : frame.getCallNode(); - RootNode rootNode = n.getRootNode(); + protected Object evalInContext(Object vm, SuspendedEvent ev, String code, Node node, MaterializedFrame frame) throws IOException { + RootNode rootNode = node.getRootNode(); Class languageType = findLanguage(rootNode); - Env env = findLanguage(vm, languageType); - TruffleLanguage lang = findLanguage(env); - Source source = Source.fromText(code, "eval in context"); - CallTarget target = lang.parse(source, n); - return target.call(); + final Env env = findLanguage(vm, languageType); + final TruffleLanguage lang = findLanguage(env); + final Source source = Source.fromText(code, "eval in context"); + return lang.evalInContext(source, node, frame); } @Override diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/Breakpoint.java diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/Debugger.java --- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/Debugger.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/Debugger.java Mon Dec 07 21:17:46 2015 -0800 @@ -29,6 +29,7 @@ import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; @@ -50,6 +51,7 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.LineLocation; import com.oracle.truffle.api.source.Source; +import com.oracle.truffle.api.source.SourceSection; /** * Represents debugging related state of a {@link com.oracle.truffle.api.vm.PolyglotEngine}. @@ -678,6 +680,12 @@ */ private MaterializedFrame haltedFrame; + /** + * Subset of the Truffle stack corresponding to the current execution, not including the + * current frame. + */ + private List contextStack; + private DebugExecutionContext(Source executionSource, DebugExecutionContext previousContext) { this(executionSource, previousContext, -1); } @@ -748,7 +756,55 @@ // Clean up, just in cased the one-shot breakpoints got confused lineBreaks.disposeOneShots(); - final int contextStackDepth = currentStackDepth() - contextStackBase; + // Includes the "caller" frame (not iterated) + final int contextStackDepth = (currentStackDepth() - contextStackBase) + 1; + + final List recentWarnings = new ArrayList<>(warnings); + warnings.clear(); + + final List frames = new ArrayList<>(); + // Map the Truffle stack for this execution, ignore nested executions + // Ignore frames for which no CallNode is available. + // The top/current/0 frame is not produced by the iterator; reported separately + Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor() { + int stackIndex = 1; + int frameIndex = 1; + + @Override + public FrameInstance visitFrame(FrameInstance frameInstance) { + if (stackIndex < contextStackDepth) { + final Node callNode = frameInstance.getCallNode(); + if (callNode != null) { + final SourceSection sourceSection = callNode.getEncapsulatingSourceSection(); + if (sourceSection != null && !sourceSection.getIdentifier().equals("")) { + frames.add(frameInstance); + frameIndex++; + } else if (TRACE) { + if (callNode != null) { + contextTrace("HIDDEN frame added: " + callNode); + } else { + contextTrace("HIDDEN frame added"); + } + frames.add(frameInstance); + frameIndex++; + } + } else if (TRACE) { + if (callNode != null) { + contextTrace("HIDDEN frame added: " + callNode); + } else { + contextTrace("HIDDEN frame added"); + } + frames.add(frameInstance); + frameIndex++; + } + stackIndex++; + return null; + } + return frameInstance; + } + }); + contextStack = Collections.unmodifiableList(frames); + if (TRACE) { final String reason = haltReason == null ? "" : haltReason + ""; final String where = before ? "BEFORE" : "AFTER"; @@ -757,12 +813,9 @@ // printStack(OUT); } - final List recentWarnings = new ArrayList<>(warnings); - warnings.clear(); - try { // Pass control to the debug client with current execution suspended - ACCESSOR.dispatchEvent(vm, new SuspendedEvent(Debugger.this, astNode, mFrame, recentWarnings, contextStackDepth)); + ACCESSOR.dispatchEvent(vm, new SuspendedEvent(Debugger.this, haltedNode, haltedFrame, contextStack, recentWarnings)); // Debug client finished normally, execution resumes // Presume that the client has set a new strategy (or default to Continue) running = true; @@ -840,8 +893,8 @@ debugContext = debugContext.predecessor; } - Object evalInContext(SuspendedEvent ev, String code, FrameInstance frame) throws IOException { - return ACCESSOR.evalInContext(vm, ev, code, frame); + Object evalInContext(SuspendedEvent ev, String code, Node node, MaterializedFrame frame) throws IOException { + return ACCESSOR.evalInContext(vm, ev, code, node, frame); } @SuppressWarnings("rawtypes") @@ -874,8 +927,8 @@ } @Override - protected Object evalInContext(Object vm, SuspendedEvent ev, String code, FrameInstance frame) throws IOException { - return super.evalInContext(vm, ev, code, frame); + protected Object evalInContext(Object vm, SuspendedEvent ev, String code, Node node, MaterializedFrame frame) throws IOException { + return super.evalInContext(vm, ev, code, node, frame); } } diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/LineBreakpoint.java diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/LineBreakpointFactory.java diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/SuspendedEvent.java --- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/SuspendedEvent.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/SuspendedEvent.java Mon Dec 07 21:17:46 2015 -0800 @@ -24,17 +24,17 @@ */ package com.oracle.truffle.api.debug; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Collections; +import java.util.List; + import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.frame.FrameInstance; -import com.oracle.truffle.api.frame.FrameInstanceVisitor; +import com.oracle.truffle.api.frame.FrameInstance.FrameAccess; import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.instrument.StandardSyntaxTag; import com.oracle.truffle.api.nodes.Node; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; /** * This event is delivered to all @@ -48,36 +48,32 @@ */ @SuppressWarnings("javadoc") public final class SuspendedEvent { - private final List recentWarnings; - private final MaterializedFrame mFrame; - private final Node astNode; - private final List frames; - private final Debugger debugger; + + private static boolean TRACE = false; + private static final String TRACE_PREFIX = "EVENT: "; + private static final PrintStream OUT = System.out; - SuspendedEvent(Debugger prepares, Node astNode, MaterializedFrame mFrame, List recentWarnings, final int stackDepth) { - this.debugger = prepares; - this.astNode = astNode; - this.mFrame = mFrame; - this.recentWarnings = recentWarnings; + private static void trace(String format, Object... args) { + if (TRACE) { + OUT.println(TRACE_PREFIX + String.format(format, args)); + } + } - this.frames = new ArrayList<>(); - // Map the Truffle stack for this execution, ignore nested executions - // The top (current) frame is not produced by the iterator. - frames.add(Truffle.getRuntime().getCurrentFrame()); - Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor() { - int frameCount = 1; + private final Debugger debugger; + private final Node haltedNode; + private final MaterializedFrame haltedFrame; + private final List stack; + private final List warnings; - @Override - public FrameInstance visitFrame(FrameInstance frameInstance) { - if (frameCount < stackDepth) { - frames.add(frameInstance); - frameCount++; - return null; - } - return frameInstance; - } - }); - + SuspendedEvent(Debugger debugger, Node haltedNode, MaterializedFrame haltedFrame, List stack, List warnings) { + this.debugger = debugger; + this.haltedNode = haltedNode; + this.haltedFrame = haltedFrame; + this.stack = stack; + this.warnings = warnings; + if (TRACE) { + trace("Execution suspended at Node=" + haltedNode); + } } /** @@ -93,26 +89,27 @@ } public Node getNode() { - return astNode; + return haltedNode; } public MaterializedFrame getFrame() { - return mFrame; + return haltedFrame; } public List getRecentWarnings() { - return Collections.unmodifiableList(recentWarnings); + return Collections.unmodifiableList(warnings); } /** * Gets the stack frames from the currently halted - * {@link com.oracle.truffle.api.vm.PolyglotEngine} execution. + * {@link com.oracle.truffle.api.vm.PolyglotEngine} execution, not counting the Node and Frame + * where halted. * * @return list of stack frames */ @CompilerDirectives.TruffleBoundary public List getStack() { - return Collections.unmodifiableList(frames); + return stack; } /** @@ -198,13 +195,20 @@ * Evaluates given code snippet in the context of currently suspended execution. * * @param code the snippet to evaluate - * @param frame null in case the evaluation should happen in top most frame, - * non-null value to specify a frame from those {@link #getStack() currently on - * stack} to perform the evaluation in context of + * @param frameNumber specify a frame from those {@link #getStack() currently on stack} to + * perform the evaluation in context of * @return the computed value * @throws IOException in case an evaluation goes wrong */ - public Object eval(String code, FrameInstance frame) throws IOException { - return debugger.evalInContext(this, code, frame); + public Object eval(String code, Integer frameNumber) throws IOException { + if (frameNumber < 0 || frameNumber >= stack.size()) { + throw new IOException("invalid frame number"); + } + if (frameNumber == 0) { + return debugger.evalInContext(this, code, haltedNode, haltedFrame); + } + final FrameInstance instance = stack.get(frameNumber - 1); + final MaterializedFrame frame = instance.getFrame(FrameAccess.MATERIALIZE, true).materialize(); + return debugger.evalInContext(this, code, instance.getCallNode(), frame); } } diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/TagBreakpoint.java diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/TagBreakpointFactory.java --- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/TagBreakpointFactory.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/TagBreakpointFactory.java Mon Dec 07 21:17:46 2015 -0800 @@ -447,7 +447,5 @@ TagBreakpointImpl.this.nodeEnter(node, vFrame); } } - } - } diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java --- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java Mon Dec 07 21:17:46 2015 -0800 @@ -39,7 +39,6 @@ import com.oracle.truffle.api.TruffleLanguage.Env; import com.oracle.truffle.api.debug.Debugger; import com.oracle.truffle.api.debug.SuspendedEvent; -import com.oracle.truffle.api.frame.FrameInstance; import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.instrument.ASTProber; import com.oracle.truffle.api.instrument.Instrumenter; @@ -158,8 +157,8 @@ return API.eval(l, s, cache); } - protected Object evalInContext(Object vm, SuspendedEvent ev, String code, FrameInstance frame) throws IOException { - return API.evalInContext(vm, ev, code, frame); + protected Object evalInContext(Object vm, SuspendedEvent ev, String code, Node node, MaterializedFrame frame) throws IOException { + return API.evalInContext(vm, ev, code, node, frame); } protected Object importSymbol(Object vm, TruffleLanguage queryingLang, String globalName) { @@ -320,10 +319,6 @@ return API.findContext(env); } - protected TruffleLanguage findLanguage(Env env) { - return API.findLanguage(env); - } - /** Applies all registered {@linkplain ASTProber probers} to the AST. */ protected void probeAST(RootNode rootNode) { INSTRUMENT.probeAST(rootNode); @@ -339,6 +334,10 @@ return parse(truffleLanguage, code, context, argumentNames); } + protected TruffleLanguage findLanguage(Env env) { + return API.findLanguage(env); + } + protected CallTarget parse(TruffleLanguage truffleLanguage, Source code, Node context, String... argumentNames) throws IOException { return API.parse(truffleLanguage, code, context, argumentNames); } diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ProbeNode.java diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultVisualizer.java --- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultVisualizer.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultVisualizer.java Mon Dec 07 21:17:46 2015 -0800 @@ -76,6 +76,9 @@ } public String displayValue(Object value, int trim) { + if (value == null) { + return ""; + } return trim(value.toString(), trim); } diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/LineLocation.java --- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/LineLocation.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/LineLocation.java Mon Dec 07 21:17:46 2015 -0800 @@ -45,7 +45,7 @@ /** * Gets the 1-based number of a line in the source. - * + * * @return value from 1 to infinity */ public int getLineNumber() { @@ -90,7 +90,15 @@ @Override public int compareTo(LineLocation o) { - final int sourceResult = this.getSource().getPath().compareTo(o.getSource().getPath()); + int sourceResult = 0; + final Source thisSource = this.getSource(); + final String thisPath = thisSource.getPath(); + if (thisPath == null) { + sourceResult = thisSource.getCode().compareTo(o.getSource().getCode()); + } else { + final String thatPath = o.getSource().getPath(); + sourceResult = thisPath.compareTo(thatPath); + } if (sourceResult != 0) { return sourceResult; } diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java --- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java Mon Dec 07 21:17:46 2015 -0800 @@ -728,7 +728,7 @@ @Override public String getPath() { - return description; + return null; } @Override diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceSection.java diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPL.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPL.java Mon Dec 07 21:17:46 2015 -0800 @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.sl.tools.debug; + +import com.oracle.truffle.sl.nodes.instrument.SLDefaultVisualizer; +import com.oracle.truffle.tools.debug.shell.server.REPLServer; + +// TODO (mlvdv) delete when the REPL is fully multi-language +/** + * Start a single-language REPL for the Simple Language implementation. + */ +public final class SLREPL { + + public static void main(String[] args) { + + // Cheating for the prototype: start from SL, rather than from the client. + final REPLServer server = new REPLServer("application/x-sl", new SLDefaultVisualizer()); + server.start(); + } + + private SLREPL() { + } +} diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLHandler.java --- a/truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLHandler.java Fri Dec 04 14:32:24 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * The Universal Permissive License (UPL), Version 1.0 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.oracle.truffle.sl.tools.debug; - -import com.oracle.truffle.api.instrument.KillException; -import com.oracle.truffle.api.instrument.QuitException; -import com.oracle.truffle.api.source.Source; -import com.oracle.truffle.api.vm.PolyglotEngine; -import com.oracle.truffle.tools.debug.shell.REPLMessage; -import com.oracle.truffle.tools.debug.shell.client.SimpleREPLClient; -import com.oracle.truffle.tools.debug.shell.server.REPLHandler; -import com.oracle.truffle.tools.debug.shell.server.REPLServerContext; -import java.io.File; -import java.util.ArrayList; - -/** - * Instantiation of the "server handler" part of the "REPL*" debugger for the simple language. - *

- * 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 langMessages = new ArrayList<>(); - - final REPLMessage msg1 = new REPLMessage(REPLMessage.OP, REPLMessage.INFO); - msg1.put(REPLMessage.TOPIC, REPLMessage.LANGUAGE); - msg1.put(REPLMessage.INFO_KEY, "Language"); - msg1.put(REPLMessage.INFO_VALUE, "Simple"); - msg1.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); - langMessages.add(msg1); - - return langMessages.toArray(new REPLMessage[0]); - } - - public static final SLREPLHandler LOAD_RUN_SOURCE_HANDLER = new SLREPLHandler(REPLMessage.LOAD_RUN) { - - @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { - return loadHandler(request, serverContext, false); - } - }; - - public static final SLREPLHandler LOAD_STEP_SOURCE_HANDLER = new SLREPLHandler(REPLMessage.LOAD_STEP) { - - @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { - return loadHandler(request, serverContext, true); - } - }; - - // TODO (mlvdv) re-implement stepInto when vm support replaced - /** - * Runs a source, optionally stepping into a specified tag. - */ - private static REPLMessage[] loadHandler(REPLMessage request, REPLServerContext serverContext, @SuppressWarnings("unused") boolean stepInto) { - final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.LOAD_RUN); - final String fileName = request.get(REPLMessage.SOURCE_NAME); - try { - final File file = new File(fileName); - if (!file.canRead()) { - return finishReplyFailed(reply, "can't find file \"" + fileName + "\""); - } - final PolyglotEngine vm = serverContext.engine(); - vm.eval(Source.fromFileName(file.getPath())); - PolyglotEngine.Value main = vm.findGlobalSymbol("main"); - if (main != null) { - main.execute(); - } - final String path = file.getCanonicalPath(); - reply.put(REPLMessage.FILE_PATH, path); - return finishReplySucceeded(reply, fileName + " exited"); - } catch (QuitException ex) { - throw ex; - } catch (KillException ex) { - return finishReplySucceeded(reply, fileName + " killed"); - } catch (Exception ex) { - return finishReplyFailed(reply, "error loading file \"" + fileName + "\": " + ex.getMessage()); - } - } -} diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLServer.java --- a/truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLServer.java Fri Dec 04 14:32:24 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,281 +0,0 @@ -/* - * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * The Universal Permissive License (UPL), Version 1.0 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.oracle.truffle.sl.tools.debug; - -import com.oracle.truffle.api.debug.Breakpoint; -import com.oracle.truffle.api.debug.Debugger; -import com.oracle.truffle.api.debug.ExecutionEvent; -import com.oracle.truffle.api.debug.SuspendedEvent; -import com.oracle.truffle.api.instrument.QuitException; -import com.oracle.truffle.api.instrument.Visualizer; -import com.oracle.truffle.api.source.Source; -import com.oracle.truffle.api.source.SourceSection; -import com.oracle.truffle.api.vm.EventConsumer; -import com.oracle.truffle.api.vm.PolyglotEngine; -import com.oracle.truffle.api.vm.PolyglotEngine.Language; -import com.oracle.truffle.sl.SLLanguage; -import com.oracle.truffle.sl.nodes.instrument.SLDefaultVisualizer; -import com.oracle.truffle.tools.debug.shell.REPLMessage; -import com.oracle.truffle.tools.debug.shell.REPLServer; -import com.oracle.truffle.tools.debug.shell.client.SimpleREPLClient; -import com.oracle.truffle.tools.debug.shell.server.REPLHandler; -import com.oracle.truffle.tools.debug.shell.server.REPLServerContext; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Instantiation of the "server" side of the "REPL*" debugger for the Simple language. - *

- * The SL parser is not equipped to parse program fragments, so any debugging functions that depend - * on this are not supported, for example "eval" and breakpoint conditions. - * - * @see SimpleREPLClient - */ -public final class SLREPLServer extends REPLServer { - - // TODO (mlvdv) remove when there's a better way to express this dependency - @SuppressWarnings("unused") private static final Class DYNAMIC_DEPENDENCY = com.oracle.truffle.sl.SLLanguage.class; - - public static void main(String[] args) { - // Cheating for the prototype: start from SL, rather than from the client. - final SLREPLServer server = new SLREPLServer(); - final SimpleREPLClient client = new SimpleREPLClient(getShortName(server.language), server); - - // Cheating for the prototype: allow server access to client for recursive debugging - server.setClient(client); - - try { - client.start(); - } catch (QuitException ex) { - } - } - - private static String getShortName(Language language) { - return language.getName() + "(" + language.getVersion() + ")"; - } - - private final Language language; - private final PolyglotEngine vm; - private Debugger db; - private final String statusPrefix; - private final Map handlerMap = new HashMap<>(); - private SLServerContext currentServerContext; - private SimpleREPLClient replClient = null; - - private void add(REPLHandler fileHandler) { - handlerMap.put(fileHandler.getOp(), fileHandler); - } - - public SLREPLServer() { - add(REPLHandler.BACKTRACE_HANDLER); - add(REPLHandler.BREAK_AT_LINE_HANDLER); - add(REPLHandler.BREAK_AT_LINE_ONCE_HANDLER); - add(REPLHandler.BREAK_AT_THROW_HANDLER); - add(REPLHandler.BREAK_AT_THROW_ONCE_HANDLER); - add(REPLHandler.BREAKPOINT_INFO_HANDLER); - add(REPLHandler.CLEAR_BREAK_HANDLER); - add(REPLHandler.CONTINUE_HANDLER); - add(REPLHandler.DELETE_HANDLER); - add(REPLHandler.DISABLE_BREAK_HANDLER); - add(REPLHandler.ENABLE_BREAK_HANDLER); - add(REPLHandler.FILE_HANDLER); - add(REPLHandler.FRAME_HANDLER); - add(SLREPLHandler.INFO_HANDLER); - add(REPLHandler.KILL_HANDLER); - add(SLREPLHandler.LOAD_RUN_SOURCE_HANDLER); - add(SLREPLHandler.LOAD_STEP_SOURCE_HANDLER); - add(REPLHandler.QUIT_HANDLER); - add(REPLHandler.STEP_INTO_HANDLER); - add(REPLHandler.STEP_OUT_HANDLER); - add(REPLHandler.STEP_OVER_HANDLER); - add(REPLHandler.TRUFFLE_HANDLER); - add(REPLHandler.TRUFFLE_NODE_HANDLER); - - EventConsumer onHalted = new EventConsumer(SuspendedEvent.class) { - @Override - protected void on(SuspendedEvent ev) { - SLREPLServer.this.haltedAt(ev); - } - }; - EventConsumer onExec = new EventConsumer(ExecutionEvent.class) { - @Override - protected void on(ExecutionEvent event) { - event.prepareStepInto(); - db = event.getDebugger(); - } - }; - - PolyglotEngine newVM = PolyglotEngine.newBuilder().onEvent(onHalted).onEvent(onExec).build(); - this.language = newVM.getLanguages().get("application/x-sl"); - assert language != null; - - this.vm = newVM; - this.statusPrefix = getShortName(language) + " REPL:"; - } - - private void setClient(SimpleREPLClient replClient) { - this.replClient = replClient; - } - - @Override - public REPLMessage start() { - - this.currentServerContext = new SLServerContext(null, null); - - // SL doesn't load modules (like other languages), so we just return a success - final REPLMessage reply = new REPLMessage(); - reply.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); - reply.put(REPLMessage.DISPLAY_MSG, getShortName(language) + " started"); - return reply; - } - - @Override - public REPLMessage[] receive(REPLMessage request) { - if (currentServerContext == null) { - final REPLMessage message = new REPLMessage(); - message.put(REPLMessage.STATUS, REPLMessage.FAILED); - message.put(REPLMessage.DISPLAY_MSG, "server not started"); - final REPLMessage[] reply = new REPLMessage[]{message}; - return reply; - } - return currentServerContext.receive(request); - } - - /** - * Execution context of a halted SL program. - */ - public final class SLServerContext extends REPLServerContext { - - private final SLServerContext predecessor; - - public SLServerContext(SLServerContext predecessor, SuspendedEvent event) { - super(predecessor == null ? 0 : predecessor.getLevel() + 1, event); - this.predecessor = predecessor; - } - - @Override - public REPLMessage[] receive(REPLMessage request) { - final String command = request.get(REPLMessage.OP); - final REPLHandler handler = handlerMap.get(command); - - if (handler == null) { - final REPLMessage message = new REPLMessage(); - message.put(REPLMessage.OP, command); - message.put(REPLMessage.STATUS, REPLMessage.FAILED); - message.put(REPLMessage.DISPLAY_MSG, statusPrefix + " op \"" + command + "\" not supported"); - final REPLMessage[] reply = new REPLMessage[]{message}; - return reply; - } - return handler.receive(request, currentServerContext); - } - - @Override - public Language getLanguage() { - return language; - } - - @Override - public Visualizer getVisualizer() { - return new SLDefaultVisualizer(); - } - - @Override - public PolyglotEngine engine() { - return vm; - } - - @Override - protected Debugger db() { - return db; - } - - @Override - public void registerBreakpoint(Breakpoint breakpoint) { - SLREPLServer.this.registerBreakpoint(breakpoint); - } - - @Override - public Breakpoint findBreakpoint(int id) { - return SLREPLServer.this.findBreakpoint(id); - } - - @Override - public int getBreakpointID(Breakpoint breakpoint) { - return SLREPLServer.this.getBreakpointID(breakpoint); - } - - } - - void haltedAt(SuspendedEvent event) { - // Create and push a new debug context where execution is halted - currentServerContext = new SLServerContext(currentServerContext, event); - - // Message the client that execution is halted and is in a new debugging context - final REPLMessage message = new REPLMessage(); - message.put(REPLMessage.OP, REPLMessage.STOPPED); - final SourceSection src = event.getNode().getSourceSection(); - final Source source = src.getSource(); - message.put(REPLMessage.SOURCE_NAME, source.getName()); - message.put(REPLMessage.FILE_PATH, source.getPath()); - message.put(REPLMessage.LINE_NUMBER, Integer.toString(src.getStartLine())); - message.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); - message.put(REPLMessage.DEBUG_LEVEL, Integer.toString(currentServerContext.getLevel())); - List warnings = event.getRecentWarnings(); - if (!warnings.isEmpty()) { - final StringBuilder sb = new StringBuilder(); - for (String warning : warnings) { - sb.append(warning + "\n"); - } - message.put(REPLMessage.WARNINGS, sb.toString()); - } - try { - // Cheat with synchrony: call client directly about entering a nested debugging - // context. - replClient.halted(message); - } finally { - // Returns when "continue" is called in the new debugging context - - // Pop the debug context, and return so that the old context will continue - currentServerContext = currentServerContext.predecessor; - } - } -} diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java --- a/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java Mon Dec 07 21:17:46 2015 -0800 @@ -190,17 +190,6 @@ * with the new version. * */ - -/* - * - *

Tools:
The use of some of Truffle's support for developer tools (based on the - * Truffle {@linkplain Instrumenter Instrumentation Framework}) are demonstrated in this file, for - * example:

  • a {@linkplain NodeExecCounter counter for node executions}, tabulated by node - * type; and
  • a simple {@linkplain CoverageTracker code coverage engine}.
In each - * case, the tool is enabled if a corresponding local boolean variable in this file is set to {@code - * true}. Results are printed at the end of the execution using each tool's default - * printer. - */ @TruffleLanguage.Registration(name = "SL", version = "0.5", mimeType = "application/x-sl") public final class SLLanguage extends TruffleLanguage { public static final String builtinKind = "SL builtin"; @@ -228,11 +217,6 @@ return context; } - /* Small tools that can be installed for demonstration */ - // private static NodeExecCounter nodeExecCounter = null; - // private static NodeExecCounter statementExecCounter = null; - // private static CoverageTracker coverageTracker = null; - /** * The main entry point. Use the mx command "mx sl" to run it with the correct class path setup. */ @@ -240,8 +224,6 @@ PolyglotEngine vm = PolyglotEngine.newBuilder().build(); assert vm.getLanguages().containsKey("application/x-sl"); - setupToolDemos(); - int repeats = 1; if (args.length >= 2) { repeats = Integer.parseInt(args[1]); @@ -261,7 +243,6 @@ while (repeats-- > 0) { main.execute(); } - reportToolDemos(); } public static int parsingCount() { @@ -525,7 +506,7 @@ @Override protected Object evalInContext(Source source, Node node, MaterializedFrame mFrame) throws IOException { - throw new IllegalStateException("evalInContext not supported in this language"); + throw new IllegalStateException("evalInContext not supported in SL"); } public Node createFindContextNode0() { @@ -535,38 +516,4 @@ public SLContext findContext0(Node contextNode) { return findContext(contextNode); } - - // TODO (mlvdv) remove the static hack when we no longer have the static demo variables - private static void setupToolDemos() { - // if (nodeExecCounts) { - // nodeExecCounter = new NodeExecCounter(); - // nodeExecCounter.install(); - // } - // - // if (statementCounts) { - // statementExecCounter = new NodeExecCounter(StandardSyntaxTag.STATEMENT); - // statementExecCounter.install(); - // } - // - // if (coverage) { - // coverageTracker = new CoverageTracker(); - // coverageTracker.install(); - // } - } - - private static void reportToolDemos() { - // if (nodeExecCounter != null) { - // nodeExecCounter.print(System.out); - // nodeExecCounter.dispose(); - // } - // if (statementExecCounter != null) { - // statementExecCounter.print(System.out); - // statementExecCounter.dispose(); - // } - // if (coverageTracker != null) { - // coverageTracker.print(System.out); - // coverageTracker.dispose(); - // } - } - } diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/SLRootNode.java diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/instrument/SLDefaultVisualizer.java --- a/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/instrument/SLDefaultVisualizer.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/instrument/SLDefaultVisualizer.java Mon Dec 07 21:17:46 2015 -0800 @@ -94,7 +94,10 @@ @Override public String displayValue(Object value, int trim) { - if (value == null || value == SLNull.SINGLETON) { + if (value == null) { + return ""; + } + if (value == SLNull.SINGLETON) { return "null"; } return trim(value.toString(), trim); diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLMessage.java --- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLMessage.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLMessage.java Mon Dec 07 21:17:46 2015 -0800 @@ -30,6 +30,8 @@ import java.util.Set; import java.util.TreeMap; +import com.oracle.truffle.tools.debug.shell.server.REPLServer; + /** * A message for communication between a Read-Eval-Print-Loop server associated with a language * implementation and a possibly remote client. @@ -53,6 +55,18 @@ public static final String BREAKPOINT_IGNORE_COUNT = "breakpoint-ignore-count"; public static final String BREAKPOINT_INFO = "breakpoint-info"; public static final String BREAKPOINT_STATE = "breakpoint-state"; + public static final String CALL = "call"; + public static final String ARG0 = "call-argument-0"; + public static final String ARG1 = "call-argument-1"; + public static final String ARG2 = "call-argument-2"; + public static final String ARG3 = "call-argument-3"; + public static final String ARG4 = "call-argument-4"; + public static final String ARG5 = "call-argument-5"; + public static final String ARG6 = "call-argument-6"; + public static final String ARG7 = "call-argument-7"; + public static final String ARG8 = "call-argument-8"; + public static final String ARG9 = "call-argument-9"; + public static final String CALL_NAME = "call-name"; public static final String CLEAR_BREAK = "clear-breakpoint"; public static final String CODE = "code"; public static final String CONTINUE = "continue"; @@ -70,14 +84,17 @@ public static final String FRAME_INFO = "frame-info"; public static final String FRAME_NUMBER = "frame-number"; public static final String INFO = "info"; + public static final String INFO_CURRENT_LANGUAGE = "info-current-language"; public static final String INFO_KEY = "info-key"; + public static final String INFO_SUPPORTED_LANGUAGES = "info-supported-languages"; public static final String INFO_VALUE = "info-value"; public static final String KILL = "kill"; - public static final String LANGUAGE = "language"; + public static final String LANG_NAME = "language-name"; + public static final String LANG_VER = "language-version"; + public static final String LANG_MIME = "language-MIME type"; public static final String LINE_NUMBER = "line-number"; public static final String LIST = "list"; - public static final String LOAD_RUN = "load-run-source"; - public static final String LOAD_STEP = "load-step-into-source"; + public static final String LOAD_SOURCE = "load-source"; public static final String METHOD_NAME = "method-name"; public static final String OP = "op"; public static final String OPTION = "option"; @@ -85,6 +102,10 @@ public static final String REPEAT = "repeat"; public static final String SET = "set"; public static final String SET_BREAK_CONDITION = "set-breakpoint-condition"; + public static final String SET_LANGUAGE = "set-language"; + public static final String SLOT_ID = "slot-identifier"; + public static final String SLOT_INDEX = "slot-index"; + public static final String SLOT_VALUE = "slot-value"; public static final String SOURCE_LINE_TEXT = "source-line-text"; public static final String SOURCE_LOCATION = "source-location"; public static final String SOURCE_NAME = "source-name"; @@ -99,6 +120,7 @@ public static final String SUBTREE = "subtree"; public static final String SUCCEEDED = "succeeded"; public static final String TOPIC = "topic"; + public static final String TRUE = "true"; public static final String TRUFFLE = "truffle"; public static final String TRUFFLE_AST = "truffle-ast"; public static final String TRUFFLE_NODE = "truffle-node"; @@ -107,6 +129,8 @@ public static final String UP = "up"; public static final String VALUE = "value"; public static final String WARNINGS = "warnings"; + public static final String WELCOME_MESSAGE = "welcome-message"; + public static final String[] ARG_NAMES = new String[]{ARG0, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8, ARG9}; private final Map map; /** diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLServer.java --- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLServer.java Fri Dec 04 14:32:24 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.truffle.tools.debug.shell; - -import com.oracle.truffle.api.debug.Breakpoint; -import java.util.Map; -import java.util.WeakHashMap; - -/** - * The server side of a simple message-based protocol for a possibly remote language - * Read-Eval-Print-Loop. - */ -public abstract class REPLServer { - - /** - * Starts up a server; status returned in a message. - */ - public abstract REPLMessage start(); - - /** - * Ask the server to handle a request. Return a non-empty array of messages to simulate remote - * operation where the protocol has possibly multiple messages being returned asynchronously in - * response to each request. - */ - public abstract REPLMessage[] receive(REPLMessage request); - - private int breakpointCounter; - private Map breakpoints = new WeakHashMap<>(); - - protected final synchronized void registerBreakpoint(Breakpoint breakpoint) { - breakpoints.put(breakpoint, breakpointCounter++); - } - - protected final synchronized Breakpoint findBreakpoint(int id) { - for (Map.Entry entrySet : breakpoints.entrySet()) { - if (id == entrySet.getValue()) { - return entrySet.getKey(); - } - } - return null; - } - - protected final synchronized int getBreakpointID(Breakpoint breakpoint) { - final Integer id = breakpoints.get(breakpoint); - return id == null ? -1 : id; - } - -} diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLClientContext.java --- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLClientContext.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLClientContext.java Mon Dec 07 21:17:46 2015 -0800 @@ -24,14 +24,15 @@ */ package com.oracle.truffle.tools.debug.shell.client; +import java.util.List; + import com.oracle.truffle.api.source.Source; -import com.oracle.truffle.tools.debug.shell.REPLServer; -import java.util.List; +import com.oracle.truffle.tools.debug.shell.server.REPLServer; /** * Client context for interaction with a program halted by the {@link REPLServer}. */ -public interface REPLClientContext { +interface REPLClientContext { /** * The source code halted in this context. @@ -96,4 +97,9 @@ */ void displayFailReply(String message); + /** + * Recompute the client's command line prompt. + */ + void updatePrompt(); + } diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLRemoteCommand.java --- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLRemoteCommand.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLRemoteCommand.java Mon Dec 07 21:17:46 2015 -0800 @@ -26,6 +26,7 @@ import com.oracle.truffle.api.source.Source; import com.oracle.truffle.tools.debug.shell.REPLMessage; + import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -200,6 +201,71 @@ } }; + public static final REPLRemoteCommand CALL_CMD = new REPLRemoteCommand("call", null, "call a method/function") { + + private final String[] help = {"call : calls a function by name"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (args.length == 1) { + context.displayFailReply("name to call not speciified"); + return null; + } + final int maxArgs = REPLMessage.ARG_NAMES.length; + if (args.length > maxArgs + 2) { + context.displayFailReply("too many call arguments; no more than " + maxArgs + " supported"); + return null; + } + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.CALL); + request.put(REPLMessage.CALL_NAME, args[1]); + for (int argIn = 2, argOut = 0; argIn < args.length; argIn++, argOut++) { + request.put(REPLMessage.ARG_NAMES[argOut], args[argIn]); + } + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + final String result = firstReply.get(REPLMessage.DISPLAY_MSG); + context.displayFailReply(result != null ? result : firstReply.toString()); + } else { + context.displayReply(firstReply.get(REPLMessage.VALUE)); + } + } + }; + + public static final REPLRemoteCommand CALL_STEP_INTO_CMD = new REPLRemoteCommand("call-step-into", "calls", "Call a method/function and step into") { + + private final String[] help = {"call : calls function by name and step into"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + final REPLMessage request = CALL_CMD.createRequest(context, args); + if (request != null) { + request.put(REPLMessage.STEP_INTO, REPLMessage.TRUE); + } + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + CALL_CMD.processReply(context, replies); + } + }; + public static final REPLRemoteCommand CLEAR_BREAK_CMD = new REPLRemoteCommand("clear", null, "Clear a breakpoint") { private final String[] help = {"clear : clear breakpoint number "}; @@ -436,6 +502,59 @@ } }; + public static final REPLRemoteCommand EVAL_CMD = new REPLRemoteCommand("eval", null, "Evaluate a string, in context of the current frame if any") { + + private int evalCounter = 0; + + private final String[] help = {"eval : evaluate in context of the current frame if any"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (args.length > 1) { + final String code = args[1]; + if (!code.isEmpty()) { + // Create a fake entry in the file maps and cache, based on this unique name + final String fakeFileName = ""; + Source.fromNamedText(fakeFileName, code); + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.EVAL); + request.put(REPLMessage.CODE, code); + request.put(REPLMessage.SOURCE_NAME, fakeFileName); + if (context.level() > 0) { + // Specify a requested execution context, if one exists; otherwise top level + request.put(REPLMessage.FRAME_NUMBER, Integer.toString(context.getSelectedFrameNumber())); + } + return request; + } + } + return null; + } + }; + + public static final REPLRemoteCommand EVAL_STEP_INTO_CMD = new REPLRemoteCommand("eval-step-into", "evals", "Evaluate and step into a string, in context of the current frame if any") { + + private final String[] help = {"eval-step-into : evaluate in context of the current frame if any, and step into"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + final REPLMessage request = EVAL_CMD.createRequest(context, args); + if (request != null) { + request.put(REPLMessage.STEP_INTO, REPLMessage.TRUE); + } + return request; + } + }; + public static final REPLRemoteCommand FRAME_CMD = new REPLRemoteCommand("frame", null, "Display a stack frame") { private final String[] help = {"frame : display currently selected frame", "frame : display frame "}; @@ -479,9 +598,11 @@ context.selectFrameNumber(frameNumber); context.displayReply("Frame " + frameNumber + ":"); for (REPLMessage message : replies) { - for (String line : message.get(REPLMessage.DISPLAY_MSG).split("\n")) { - context.displayInfo(line); - } + final StringBuilder sb = new StringBuilder(); + sb.append("#" + message.get(REPLMessage.SLOT_INDEX) + ": "); + sb.append(message.get(REPLMessage.SLOT_ID) + " = "); + sb.append(message.get(REPLMessage.SLOT_VALUE)); + context.displayInfo(sb.toString()); } } } @@ -510,7 +631,14 @@ } }; - public static final REPLRemoteCommand LOAD_RUN_CMD = new REPLRemoteCommand("load-run", "loadr", "Load and run a source") { + public static final REPLRemoteCommand LOAD_CMD = new REPLRemoteCommand("load", null, "Load source") { + + private final String[] help = new String[]{"load : load currently selected file source", "load : load file "}; + + @Override + public String[] getHelp() { + return help; + } @Override public REPLMessage createRequest(REPLClientContext context, String[] args) { @@ -530,43 +658,66 @@ } } final REPLMessage request = new REPLMessage(); - request.put(REPLMessage.OP, REPLMessage.LOAD_RUN); + request.put(REPLMessage.OP, REPLMessage.LOAD_SOURCE); request.put(REPLMessage.SOURCE_NAME, runSource.getPath()); return request; } }; - public static final REPLRemoteCommand LOAD_STEP_CMD = new REPLRemoteCommand("load-step", "loads", "Load and step into a source") { + public static final REPLRemoteCommand LOAD_STEP_INTO_CMD = new REPLRemoteCommand("load-step-into", "loads", "Load source and step in") { + + private final String[] help = new String[]{"load : load currently selected file source and step in", "load : load file and step in"}; + + @Override + public String[] getHelp() { + return help; + } @Override public REPLMessage createRequest(REPLClientContext context, String[] args) { - Source runSource = null; - if (args.length == 1) { - runSource = context.getSelectedSource(); - if (runSource == null) { - context.displayFailReply("No file selected"); - return null; - } - } else { - try { - runSource = Source.fromFileName(args[1]); - } catch (IOException e) { - context.displayFailReply("Can't find file: " + args[1]); - return null; - } + final REPLMessage request = LOAD_CMD.createRequest(context, args); + if (request != null) { + request.put(REPLMessage.STEP_INTO, REPLMessage.TRUE); } - final REPLMessage request = new REPLMessage(); - request.put(REPLMessage.OP, REPLMessage.LOAD_STEP); - request.put(REPLMessage.SOURCE_NAME, runSource.getPath()); return request; } }; - public static final REPLRemoteCommand STEP_INTO_CMD = new REPLRemoteCommand("step", "s", "(StepInto) next statement, going into functions.") { + public static final REPLRemoteCommand SET_LANG_CMD = new REPLRemoteCommand("language", "lang", "Set current language") { + + private final String[] help = new String[]{"lang : set default language, \"info lang\" displays choices"}; @Override public String[] getHelp() { - return new String[]{"step into: step to next statement (into calls)", "step : step to nth next statement (into calls)"}; + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.SET_LANGUAGE); + if (args.length > 1) { + request.put(REPLMessage.LANG_NAME, args[1]); + } + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + context.updatePrompt(); + super.processReply(context, replies); + } + + }; + + public static final REPLRemoteCommand STEP_INTO_CMD = new REPLRemoteCommand("step", "s", "(StepInto) next statement, going into functions.") { + + private final String[] help = new String[]{"step into: step to next statement (into calls)", "step : step to nth next statement (into calls)"}; + + @Override + public String[] getHelp() { + return help; } @Override @@ -625,9 +776,12 @@ public static final REPLRemoteCommand STEP_OVER_CMD = new REPLRemoteCommand("next", "n", "(StepOver) execute next line of code, not into functions.") { + private final String[] help = new String[]{"next: (StepOver) execute next line of code, not into functions.", + "next : (StepOver) execute to nth next statement (not counting into functions)"}; + @Override public String[] getHelp() { - return new String[]{"next: (StepOver) execute next line of code, not into functions.", "next : (StepOver) execute to nth next statement (not counting into functions)"}; + return help; } @Override diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java --- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java Mon Dec 07 21:17:46 2015 -0800 @@ -24,10 +24,12 @@ */ package com.oracle.truffle.tools.debug.shell.client; +import com.oracle.truffle.api.instrument.QuitException; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.tools.debug.shell.REPLClient; import com.oracle.truffle.tools.debug.shell.REPLMessage; -import com.oracle.truffle.tools.debug.shell.REPLServer; +import com.oracle.truffle.tools.debug.shell.server.REPLServer; + import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; @@ -39,6 +41,7 @@ import java.util.List; import java.util.Map; import java.util.TreeSet; + import jline.console.ConsoleReader; /** @@ -79,6 +82,8 @@ */ public class SimpleREPLClient implements REPLClient { + // TODO (mlvdv) Temporarily in hybrid mode; will work either single language or multi (sort of) + private static final String REPLY_PREFIX = "==> "; private static final String FAIL_PREFIX = "**> "; private static final String WARNING_PREFIX = "!!> "; @@ -89,10 +94,8 @@ static final String CODE_LINE_FORMAT = " %3d %s\n"; static final String CODE_LINE_BREAK_FORMAT = "--> %3d %s\n"; - private static final String STACK_FRAME_FORMAT = " %3d: at %s in %s line =\"%s\"\n"; - private static final String STACK_FRAME_SELECTED_FORMAT = "==> %3d: at %s in %s line =\"%s\"\n"; - - private final String languageName; + private static final String STACK_FRAME_FORMAT = " %3d: at %s in %s %s\n"; + private static final String STACK_FRAME_SELECTED_FORMAT = "==> %3d: at %s in %s %s\n"; // Top level commands private final Map commandMap = new HashMap<>(); @@ -105,13 +108,6 @@ // Current local context ClientContextImpl clientContext; - // Cheating for the prototype; prototype startup now happens from the language server. - // So this isn't used. - public static void main(String[] args) { - final SimpleREPLClient repl = new SimpleREPLClient(null, null); - repl.start(); - } - private final ConsoleReader reader; private final PrintStream writer; @@ -147,8 +143,7 @@ */ private Source selectedSource = null; - public SimpleREPLClient(String languageName, REPLServer replServer) { - this.languageName = languageName; + public SimpleREPLClient(REPLServer replServer) { this.replServer = replServer; this.writer = System.out; try { @@ -162,6 +157,8 @@ addCommand(REPLRemoteCommand.BREAK_AT_LINE_ONCE_CMD); addCommand(REPLRemoteCommand.BREAK_AT_THROW_CMD); addCommand(REPLRemoteCommand.BREAK_AT_THROW_ONCE_CMD); + addCommand(REPLRemoteCommand.CALL_CMD); + addCommand(REPLRemoteCommand.CALL_STEP_INTO_CMD); addCommand(REPLRemoteCommand.CLEAR_BREAK_CMD); addCommand(REPLRemoteCommand.CONDITION_BREAK_CMD); addCommand(REPLRemoteCommand.CONTINUE_CMD); @@ -169,17 +166,19 @@ addCommand(REPLRemoteCommand.DISABLE_CMD); addCommand(REPLRemoteCommand.DOWN_CMD); addCommand(REPLRemoteCommand.ENABLE_CMD); - addCommand(evalCommand); + addCommand(REPLRemoteCommand.EVAL_CMD); + addCommand(REPLRemoteCommand.EVAL_STEP_INTO_CMD); addCommand(fileCommand); addCommand(REPLRemoteCommand.FRAME_CMD); addCommand(helpCommand); addCommand(infoCommand); addCommand(REPLRemoteCommand.KILL_CMD); addCommand(listCommand); - addCommand(REPLRemoteCommand.LOAD_RUN_CMD); - addCommand(REPLRemoteCommand.LOAD_STEP_CMD); + addCommand(REPLRemoteCommand.LOAD_CMD); + addCommand(REPLRemoteCommand.LOAD_STEP_INTO_CMD); addCommand(quitCommand); addCommand(setCommand); + addCommand(REPLRemoteCommand.SET_LANG_CMD); addCommand(REPLRemoteCommand.STEP_INTO_CMD); addCommand(REPLRemoteCommand.STEP_OUT_CMD); addCommand(REPLRemoteCommand.STEP_OVER_CMD); @@ -207,21 +206,24 @@ public void start() { - REPLMessage startReply = replServer.start(); - - if (startReply.get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { - clientContext.displayFailReply(startReply.get(REPLMessage.DISPLAY_MSG)); - throw new RuntimeException("Can't start REPL server"); + this.clientContext = new ClientContextImpl(null, null); + try { + showWelcome(); + clientContext.startContextSession(); + } catch (QuitException ex) { + clientContext.displayReply("Goodbye"); } + } - this.clientContext = new ClientContextImpl(null, null); - - try { - clientContext.startSession(); - } finally { - clientContext.displayReply("Goodbye from " + languageName + "/REPL"); + private void showWelcome() { + final REPLMessage request = new REPLMessage(REPLMessage.OP, REPLMessage.INFO); + request.put(REPLMessage.TOPIC, REPLMessage.WELCOME_MESSAGE); + final REPLMessage[] replies = clientContext.sendToServer(request); + if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + clientContext.displayReply("Welcome"); + } else { + clientContext.displayReply(replies[0].get(REPLMessage.INFO_VALUE)); } - } public void addCommand(REPLCommand replCommand) { @@ -263,18 +265,26 @@ this.level = predecessor == null ? 0 : predecessor.level + 1; if (message != null) { + final String sourceName = message.get(REPLMessage.SOURCE_NAME); try { - this.haltedSource = Source.fromFileName(message.get(REPLMessage.SOURCE_NAME)); - selectedSource = this.haltedSource; + this.haltedSource = Source.fromFileName(sourceName); + } catch (IOException ex) { + final String code = message.get(REPLMessage.SOURCE_TEXT); + if (code != null) { + this.haltedSource = Source.fromText(code, sourceName); + } + } + if (this.haltedSource != null) { + selectedSource = haltedSource; try { haltedLineNumber = Integer.parseInt(message.get(REPLMessage.LINE_NUMBER)); } catch (NumberFormatException e) { haltedLineNumber = 0; } - } catch (IOException e1) { + } else { this.haltedSource = null; this.haltedLineNumber = 0; - this.unknownSourceName = message.get(REPLMessage.SOURCE_NAME); + this.unknownSourceName = sourceName; } } updatePrompt(); @@ -289,20 +299,33 @@ updatePrompt(); } - private void updatePrompt() { + @Override + public void updatePrompt() { + + String languageName = "???"; + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.INFO); + request.put(REPLMessage.TOPIC, REPLMessage.INFO_CURRENT_LANGUAGE); + final REPLMessage[] replies = replServer.receive(request); + if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) { + languageName = replies[0].get(REPLMessage.LANG_NAME); + } + final String showLang = languageName == null ? "() " : "( " + languageName + " )"; if (level == 0) { // 0-level context; no executions halted. if (selectedSource == null) { - currentPrompt = languageName == null ? "() " : "( " + languageName + " ) "; + currentPrompt = showLang + " "; } else { - currentPrompt = "(" + selectedSource.getShortName() + ") "; + currentPrompt = "(" + selectedSource.getShortName() + ") " + showLang + " "; } } else if (selectedSource != null && selectedSource != haltedSource) { // User is focusing somewhere else than the current locn; show no line number. final StringBuilder sb = new StringBuilder(); sb.append("(<" + Integer.toString(level) + "> "); sb.append(selectedSource.getShortName()); - sb.append(") "); + sb.append(")"); + sb.append(showLang); + sb.append(" "); currentPrompt = sb.toString(); } else { // Prompt reveals where currently halted. @@ -312,7 +335,9 @@ if (haltedLineNumber > 0) { sb.append(":" + Integer.toString(haltedLineNumber)); } - sb.append(") "); + sb.append(")"); + sb.append(showLang); + sb.append(" "); currentPrompt = sb.toString(); } @@ -443,7 +468,9 @@ for (REPLFrame frame : frameList) { String sourceLineText = frame.sourceLineText(); if (sourceLineText == null) { - sourceLineText = ""; + sourceLineText = ""; + } else { + sourceLineText = "line=\"" + sourceLineText + "\""; } if (frame.index() == selectedFrameNumber) { writer.format(STACK_FRAME_SELECTED_FORMAT, frame.index(), frame.locationDescription(), frame.name(), sourceLineText); @@ -476,7 +503,7 @@ writer.println(TRACE_PREFIX + message); } - public void startSession() { + private void startContextSession() { while (true) { try { @@ -515,15 +542,19 @@ } else if (command instanceof REPLRemoteCommand) { final REPLRemoteCommand remoteCommand = (REPLRemoteCommand) command; - final REPLMessage request = remoteCommand.createRequest(clientContext, args); if (request == null) { continue; } REPLMessage[] replies = sendToServer(request); + remoteCommand.processReply(clientContext, replies); - remoteCommand.processReply(clientContext, replies); + final String path = replies[0].get(REPLMessage.FILE_PATH); + if (path != null && !path.isEmpty()) { + selectSource(path); + } + } else { assert false; // Should not happen. } @@ -651,7 +682,7 @@ } try { - clientContext.startSession(); + clientContext.startContextSession(); } finally { // To continue execution, pop the context and return @@ -698,33 +729,6 @@ } }; - private final REPLCommand evalCommand = new REPLRemoteCommand("eval", null, "Evaluate a string, in context of the current frame if any") { - - private int evalCounter = 0; - - @Override - public REPLMessage createRequest(REPLClientContext context, String[] args) { - if (args.length > 1) { - final String code = args[1]; - if (!code.isEmpty()) { - // Create a fake entry in the file maps and cache, based on this unique name - final String fakeFileName = ""; - 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 : Set file to be current file for viewing"}; @@ -922,9 +926,9 @@ } }; - private final REPLCommand infoLanguageCommand = new REPLRemoteCommand("language", "lang", "language and implementation details") { + private final REPLCommand infoLanguageCommand = new REPLRemoteCommand("languages", "lang", "languages supported") { - final String[] help = {"info language: list details about the language implementation"}; + final String[] help = {"info language: list details about supported languages"}; @Override public String[] getHelp() { @@ -935,7 +939,7 @@ public REPLMessage createRequest(REPLClientContext context, String[] args) { final REPLMessage request = new REPLMessage(); request.put(REPLMessage.OP, REPLMessage.INFO); - request.put(REPLMessage.TOPIC, REPLMessage.LANGUAGE); + request.put(REPLMessage.TOPIC, REPLMessage.INFO_SUPPORTED_LANGUAGES); return request; } @@ -944,12 +948,12 @@ if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG)); } else { - clientContext.displayReply("Language info:"); + clientContext.displayReply("Languages supported:"); for (REPLMessage message : replies) { final StringBuilder sb = new StringBuilder(); - sb.append(message.get(REPLMessage.INFO_KEY)); - sb.append(": "); - sb.append(message.get(REPLMessage.INFO_VALUE)); + sb.append(message.get(REPLMessage.LANG_NAME)); + sb.append(" ver. "); + sb.append(message.get(REPLMessage.LANG_VER)); clientContext.displayInfo(sb.toString()); } } diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/package-info.java Mon Dec 07 21:17:46 2015 -0800 @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + @ApiInfo( + group="To Review" + ) + */ + +/** + * This package contains REPL*: an experimental framework for + * building a language-agnostic command-line oriented debugger that: + *
    + *
  • works with every Truffle-implemented language "out of the box", + * i.e. requiring minimal additional support by language implementors;
  • + *
  • works simultaneously, without special configuration, for all Truffle language implementations + * available to it; and
  • + *
  • demonstrates Truffle language-interopability by debugging seamlessly + * across Truffle cross-language calls.
  • + *
+ *

+ *

Goals for building REPL*

+ *
    + *
  1. Exercise and test in action the built-in + * {@linkplain com.oracle.truffle.api.debug.Debugger} debugging support services + * provided as part of the Truffle API, which in turn relies on Truffle + * {@linkplain com.oracle.truffle.api.instrument.Instrumenter Instrumentation}.
  2. + *
  3. Emulates a client/server architecture to demonstrate that + * language-agnostic debugging can be implemented over wire protocols. Wire + * protocol communication between client and server is partially emulated + * by passing messages expressed as textual key-value pairs. The emulation is + * partial because both run interleaved on a single JVM thread, with some + * sharing of resources.
  4. + *
  5. Provide a working debugger that is always available during development + * of new Truffle language implementations.
  6. + *
  7. Provide a working debugger with extra support for Truffle language + * development, in particular the ability to inspect the current structure + * of the Truffle AST around a halted location.
  8. + *
+ *

Command Set

+ * The Command Line Interface (CLI) for REPL* is based as + * much as possible on the CLI for the + * GDB Debugger. + *

REPL* Functionality

+ * Basic navigation: + *
    + *
  • StepIn (n times)
  • + *
  • StepOut (n times)
  • + *
  • StepOver (n times)
  • + *
+ * Execution: + *
    + *
  • Load a file source
  • + *
  • Call a defined symbol
  • + *
+ * Stack: + *
    + *
  • List frames in current execution stack
  • + *
  • Select a frame
  • + *
  • Display selected frame contents
  • + *
  • Move frame selection up/down
  • + *
+ * Evaluate: + *
    + *
  • Evaluate a Language string in halted context
  • + *
  • Evaluate a Language string in selected frame
  • + *
+ * Breakpoints: + *
    + *
  • Set/create on a specified line
  • + *
  • Set/create on any throw (before exception created)
  • + *
  • Enable / Disable
  • + *
  • One-shot (once only)
  • + *
  • Get Hit count
  • + *
  • Set Ignore count
  • + *
  • Unset/dispose
  • + *
  • Get all breakpoints
  • + *
  • Find a breakpoint by UID
  • + *
  • Set/clear the condition on a breakpoint
  • + *
+ * Others: + *
    + *
  • Display halted location in source
  • + *
  • Nested execution + *
  • Help
  • + *
  • Info displays
  • + *
  • Set/display options
  • + *
  • Display Truffle AST structure
  • + *
+ */ +package com.oracle.truffle.tools.debug.shell; + diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/package.html --- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/package.html Fri Dec 04 14:32:24 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ - - - - - This package contains an experimental framework for building simple command-line oriented - debuggers that work with Truffle-implemented languages; it is used mainly for testing Truffle's - built-in , which actually provides the - debugging services. -

- 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: - https://wiki. - openjdk.java.net/display/Graal/Instrumentation+API -

- Building one of these command line debuggers requires creating language-specific instances of: -

    -
  1. DebugEngine, noting that this instance also - depends on related services provided by the language implementation,
  2. -
  3. {@link com.oracle.truffle.tools.debug.shell.REPLServer}, best accomplished by copying the - implementation for Truffle's demonstration language "Simple" (a.k.a. "SL").
  4. -
- - Disclaimer: although these command line debuggers are useful, they are not - intended, and will not be maintained as, fully functioning debuggers. They should be considered - valuable tools for the maintainers of the - DebugEngine, as well as for Truffle language - implementors for whom concurrent access to any kind debugging services can be quite helpful. -

- Note: Both the functionality and API for this package are under active - development. -

- - diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/FrameDebugDescription.java --- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/FrameDebugDescription.java Fri Dec 04 14:32:24 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.truffle.tools.debug.shell.server; - -import com.oracle.truffle.api.frame.Frame; -import com.oracle.truffle.api.frame.FrameInstance; -import com.oracle.truffle.api.nodes.Node; - -public final class FrameDebugDescription { - private final int index; - private final Node node; - private final FrameInstance frameInstance; - - FrameDebugDescription(int index, Node node, FrameInstance frameInstance) { - this.index = index; - this.node = node; - this.frameInstance = frameInstance; - } - - /** - * Position in the current stack: {@code 0} at the top. - */ - public int index() { - return index; - } - - /** - * AST location. - */ - public Node node() { - return node; - } - - /** - * Access to the Truffle {@link Frame}. - */ - public FrameInstance frameInstance() { - return frameInstance; - } -} diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLHandler.java --- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLHandler.java Fri Dec 04 14:32:24 2015 +0100 +++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLHandler.java Mon Dec 07 21:17:46 2015 -0800 @@ -24,11 +24,15 @@ */ package com.oracle.truffle.tools.debug.shell.server; -import com.oracle.truffle.api.debug.Breakpoint; -import com.oracle.truffle.api.frame.Frame; -import com.oracle.truffle.api.frame.FrameDescriptor; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + import com.oracle.truffle.api.frame.FrameInstance; +import com.oracle.truffle.api.frame.FrameInstance.FrameAccess; import com.oracle.truffle.api.frame.FrameSlot; +import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.instrument.ASTPrinter; import com.oracle.truffle.api.instrument.KillException; import com.oracle.truffle.api.instrument.QuitException; @@ -37,10 +41,10 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.SourceSection; +import com.oracle.truffle.api.vm.PolyglotEngine.Language; import com.oracle.truffle.tools.debug.shell.REPLMessage; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import com.oracle.truffle.tools.debug.shell.server.REPLServer.BreakpointInfo; +import com.oracle.truffle.tools.debug.shell.server.REPLServer.Context; /** * Server-side REPL implementation of an {@linkplain REPLMessage "op"}. @@ -61,30 +65,23 @@ /** * Gets the "op" implemented by this handler. */ - public final String getOp() { + final String getOp() { return op; } /** * Passes a request to this handler. */ - public abstract REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext); + abstract REPLMessage[] receive(REPLMessage request, REPLServer replServer); /** * Creates skeleton for a reply message that identifies the operation currently being handled. */ - protected final REPLMessage createReply() { + REPLMessage createReply() { return new REPLMessage(REPLMessage.OP, op); } /** - * Creates skeleton for a reply message that identifies a specified operation. - */ - protected static final REPLMessage createReply(String opString) { - return new REPLMessage(REPLMessage.OP, opString); - } - - /** * Completes a reply, reporting and explaining successful handling. */ protected static final REPLMessage[] finishReplySucceeded(REPLMessage reply, String explanation) { @@ -104,25 +101,32 @@ return replies; } - static final REPLMessage createBreakpointInfoMessage(Breakpoint breakpoint, REPLServerContext serverContext) { + protected static final REPLMessage[] finishReplyFailed(REPLMessage reply, Exception ex) { + reply.put(REPLMessage.STATUS, REPLMessage.FAILED); + String message = ex.getMessage(); + reply.put(REPLMessage.DISPLAY_MSG, message == null ? ex.getClass().getSimpleName() : message); + final REPLMessage[] replies = new REPLMessage[]{reply}; + return replies; + } + + protected static final REPLMessage createBreakpointInfoMessage(BreakpointInfo info) { final REPLMessage infoMessage = new REPLMessage(REPLMessage.OP, REPLMessage.BREAKPOINT_INFO); - infoMessage.put(REPLMessage.BREAKPOINT_ID, Integer.toString(serverContext.getBreakpointID(breakpoint))); - infoMessage.put(REPLMessage.BREAKPOINT_HIT_COUNT, Integer.toString(breakpoint.getHitCount())); - infoMessage.put(REPLMessage.BREAKPOINT_IGNORE_COUNT, Integer.toString(breakpoint.getIgnoreCount())); - infoMessage.put(REPLMessage.INFO_VALUE, breakpoint.getLocationDescription().toString()); - if (breakpoint.getCondition() != null) { - infoMessage.put(REPLMessage.BREAKPOINT_CONDITION, breakpoint.getCondition().getCode()); + infoMessage.put(REPLMessage.BREAKPOINT_ID, Integer.toString(info.getID())); + infoMessage.put(REPLMessage.BREAKPOINT_STATE, info.describeState()); + infoMessage.put(REPLMessage.BREAKPOINT_HIT_COUNT, Integer.toString(info.getHitCount())); + infoMessage.put(REPLMessage.BREAKPOINT_IGNORE_COUNT, Integer.toString(info.getIgnoreCount())); + infoMessage.put(REPLMessage.INFO_VALUE, info.describeLocation()); + if (info.getCondition() != null) { + infoMessage.put(REPLMessage.BREAKPOINT_CONDITION, info.getCondition()); } infoMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); return infoMessage; } - protected static REPLMessage createFrameInfoMessage(final REPLServerContext serverContext, FrameDebugDescription frame) { - final Visualizer visualizer = serverContext.getVisualizer(); + protected static final REPLMessage createFrameInfoMessage(final REPLServer replServer, int number, Node node) { + final Visualizer visualizer = replServer.getVisualizer(); final REPLMessage infoMessage = new REPLMessage(REPLMessage.OP, REPLMessage.FRAME_INFO); - infoMessage.put(REPLMessage.FRAME_NUMBER, Integer.toString(frame.index())); - final Node node = frame.node(); - + infoMessage.put(REPLMessage.FRAME_NUMBER, Integer.toString(number)); infoMessage.put(REPLMessage.SOURCE_LOCATION, visualizer.displaySourceLocation(node)); infoMessage.put(REPLMessage.METHOD_NAME, visualizer.displayMethodName(node)); @@ -131,7 +135,7 @@ if (section == null) { section = node.getEncapsulatingSourceSection(); } - if (section != null) { + if (section != null && section.getSource() != null) { infoMessage.put(REPLMessage.FILE_PATH, section.getSource().getPath()); infoMessage.put(REPLMessage.LINE_NUMBER, Integer.toString(section.getStartLine())); infoMessage.put(REPLMessage.SOURCE_LINE_TEXT, section.getSource().getCode(section.getStartLine())); @@ -144,23 +148,46 @@ public static final REPLHandler BACKTRACE_HANDLER = new REPLHandler(REPLMessage.BACKTRACE) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { - final REPLMessage reply = createReply(); - final ArrayList frameMessages = new ArrayList<>(); - for (FrameDebugDescription frame : serverContext.getStack()) { - frameMessages.add(createFrameInfoMessage(serverContext, frame)); + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { + final Visualizer visualizer = replServer.getVisualizer(); + final ArrayList replies = new ArrayList<>(); + final Context currentContext = replServer.getCurrentContext(); + final List stack = currentContext.getStack(); + replies.add(btMessage(0, currentContext.getNode(), visualizer)); + for (int i = 1; i <= stack.size(); i++) { + replies.add(btMessage(i, stack.get(i - 1).getCallNode(), visualizer)); } - if (frameMessages.size() > 0) { - return frameMessages.toArray(new REPLMessage[0]); + if (replies.size() > 0) { + return replies.toArray(new REPLMessage[0]); } - return finishReplyFailed(reply, "No stack"); + return finishReplyFailed(new REPLMessage(REPLMessage.OP, REPLMessage.BACKTRACE), "No stack"); } }; + private static REPLMessage btMessage(int index, Node node, Visualizer visualizer) { + final REPLMessage btMessage = new REPLMessage(REPLMessage.OP, REPLMessage.BACKTRACE); + btMessage.put(REPLMessage.FRAME_NUMBER, Integer.toString(index)); + if (node != null) { + btMessage.put(REPLMessage.SOURCE_LOCATION, visualizer.displaySourceLocation(node)); + btMessage.put(REPLMessage.METHOD_NAME, visualizer.displayMethodName(node)); + SourceSection section = node.getSourceSection(); + if (section == null) { + section = node.getEncapsulatingSourceSection(); + } + if (section != null && section.getSource() != null) { + btMessage.put(REPLMessage.FILE_PATH, section.getSource().getPath()); + btMessage.put(REPLMessage.LINE_NUMBER, Integer.toString(section.getStartLine())); + btMessage.put(REPLMessage.SOURCE_LINE_TEXT, section.getSource().getCode(section.getStartLine())); + } + btMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); + } + return btMessage; + } + public static final REPLHandler BREAK_AT_LINE_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_LINE) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); final String path = request.get(REPLMessage.FILE_PATH); final String fileName = request.get(REPLMessage.SOURCE_NAME); @@ -169,12 +196,12 @@ try { source = Source.fromFileName(lookupFile, true); } catch (Exception ex) { - return finishReplyFailed(reply, ex.getMessage()); + return finishReplyFailed(reply, ex); } if (source == null) { return finishReplyFailed(reply, fileName + " not found"); } - Integer lineNumber = request.getIntValue(REPLMessage.LINE_NUMBER); + final Integer lineNumber = request.getIntValue(REPLMessage.LINE_NUMBER); if (lineNumber == null) { return finishReplyFailed(reply, "missing line number"); } @@ -182,16 +209,10 @@ if (ignoreCount == null) { ignoreCount = 0; } - Breakpoint breakpoint; - try { - breakpoint = serverContext.db().setLineBreakpoint(DEFAULT_IGNORE_COUNT, source.createLineLocation(lineNumber), false); - serverContext.registerBreakpoint(breakpoint); - } catch (Exception ex) { - return finishReplyFailed(reply, ex.getMessage()); - } + final BreakpointInfo breakpointInfo = replServer.setLineBreakpoint(DEFAULT_IGNORE_COUNT, source.createLineLocation(lineNumber), false); reply.put(REPLMessage.SOURCE_NAME, fileName); reply.put(REPLMessage.FILE_PATH, source.getPath()); - reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(serverContext.getBreakpointID(breakpoint))); + reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointInfo.getID())); reply.put(REPLMessage.LINE_NUMBER, Integer.toString(lineNumber)); reply.put(REPLMessage.BREAKPOINT_IGNORE_COUNT, ignoreCount.toString()); return finishReplySucceeded(reply, "Breakpoint set"); @@ -201,7 +222,7 @@ public static final REPLHandler BREAK_AT_LINE_ONCE_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_LINE_ONCE) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); final String path = request.get(REPLMessage.FILE_PATH); final String fileName = request.get(REPLMessage.SOURCE_NAME); @@ -210,23 +231,19 @@ try { source = Source.fromFileName(lookupFile, true); } catch (Exception ex) { - return finishReplyFailed(reply, ex.getMessage()); + return finishReplyFailed(reply, ex); } if (source == null) { return finishReplyFailed(reply, fileName + " not found"); } - Integer lineNumber = request.getIntValue(REPLMessage.LINE_NUMBER); + final Integer lineNumber = request.getIntValue(REPLMessage.LINE_NUMBER); if (lineNumber == null) { return finishReplyFailed(reply, "missing line number"); } - try { - Breakpoint b = serverContext.db().setLineBreakpoint(DEFAULT_IGNORE_COUNT, source.createLineLocation(lineNumber), true); - serverContext.registerBreakpoint(b); - } catch (Exception ex) { - return finishReplyFailed(reply, ex.getMessage()); - } + final BreakpointInfo breakpointInfo = replServer.setLineBreakpoint(DEFAULT_IGNORE_COUNT, source.createLineLocation(lineNumber), true); reply.put(REPLMessage.SOURCE_NAME, fileName); reply.put(REPLMessage.FILE_PATH, source.getPath()); + reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointInfo.getID())); reply.put(REPLMessage.LINE_NUMBER, Integer.toString(lineNumber)); return finishReplySucceeded(reply, "One-shot line breakpoint set"); } @@ -235,14 +252,13 @@ public static final REPLHandler BREAK_AT_THROW_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_THROW) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); try { - Breakpoint b = serverContext.db().setTagBreakpoint(DEFAULT_IGNORE_COUNT, StandardSyntaxTag.THROW, false); - serverContext.registerBreakpoint(b); + replServer.setTagBreakpoint(DEFAULT_IGNORE_COUNT, StandardSyntaxTag.THROW, false); return finishReplySucceeded(reply, "Breakpoint at any throw set"); } catch (Exception ex) { - return finishReplyFailed(reply, ex.getMessage()); + return finishReplyFailed(reply, ex); } } }; @@ -250,13 +266,13 @@ public static final REPLHandler BREAK_AT_THROW_ONCE_HANDLER = new REPLHandler(REPLMessage.BREAK_AT_THROW_ONCE) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); try { - serverContext.db().setTagBreakpoint(DEFAULT_IGNORE_COUNT, StandardSyntaxTag.THROW, true); + replServer.setTagBreakpoint(DEFAULT_IGNORE_COUNT, StandardSyntaxTag.THROW, true); return finishReplySucceeded(reply, "One-shot breakpoint at any throw set"); } catch (Exception ex) { - return finishReplyFailed(reply, ex.getMessage()); + return finishReplyFailed(reply, ex); } } }; @@ -264,11 +280,11 @@ public static final REPLHandler BREAKPOINT_INFO_HANDLER = new REPLHandler(REPLMessage.BREAKPOINT_INFO) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); final ArrayList infoMessages = new ArrayList<>(); - for (Breakpoint breakpoint : serverContext.db().getBreakpoints()) { - infoMessages.add(createBreakpointInfoMessage(breakpoint, serverContext)); + for (BreakpointInfo breakpointInfo : replServer.getBreakpoints()) { + infoMessages.add(createBreakpointInfoMessage(breakpointInfo)); } if (infoMessages.size() > 0) { return infoMessages.toArray(new REPLMessage[0]); @@ -277,20 +293,52 @@ } }; + public static final REPLHandler CALL_HANDLER = new REPLHandler(REPLMessage.CALL) { + + @Override + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { + final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.CALL); + final String callName = request.get(REPLMessage.CALL_NAME); + if (callName == null) { + return finishReplyFailed(reply, "no name specified"); + } + final ArrayList argList = new ArrayList<>(); + for (int argCount = 0; argCount < REPLMessage.ARG_NAMES.length; argCount++) { + final String arg = request.get(REPLMessage.ARG_NAMES[argCount]); + if (arg == null) { + break; + } + argList.add(arg); + } + final boolean stepInto = REPLMessage.TRUE.equals(request.get(REPLMessage.STEP_INTO)); + try { + final Object result = replServer.getCurrentContext().call(callName, stepInto, argList); + reply.put(REPLMessage.VALUE, result == null ? "" : result.toString()); + } catch (QuitException ex) { + throw ex; + } catch (KillException ex) { + return finishReplySucceeded(reply, callName + " killed"); + } catch (Exception ex) { + return finishReplyFailed(reply, ex); + } + return finishReplySucceeded(reply, callName + " returned"); + } + }; + public static final REPLHandler CLEAR_BREAK_HANDLER = new REPLHandler(REPLMessage.CLEAR_BREAK) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); - Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID); + final Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID); if (breakpointNumber == null) { return finishReplyFailed(reply, "missing breakpoint number"); } - final Breakpoint breakpoint = serverContext.findBreakpoint(breakpointNumber); - if (breakpoint == null) { + final BreakpointInfo breakpointInfo = replServer.findBreakpoint(breakpointNumber); + if (breakpointInfo == null) { return finishReplyFailed(reply, "no breakpoint number " + breakpointNumber); } - breakpoint.dispose(); + breakpointInfo.dispose(); reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber)); return finishReplySucceeded(reply, "Breakpoint " + breakpointNumber + " cleared"); } @@ -299,9 +347,9 @@ public static final REPLHandler CONTINUE_HANDLER = new REPLHandler(REPLMessage.CONTINUE) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); - serverContext.prepareContinue(); + replServer.getCurrentContext().prepareContinue(); return finishReplySucceeded(reply, "Continue mode entered"); } }; @@ -309,34 +357,33 @@ public static final REPLHandler DELETE_HANDLER = new REPLHandler(REPLMessage.DELETE_BREAK) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); - int deleteCount = 0; - for (Breakpoint breakpoint : serverContext.db().getBreakpoints()) { - breakpoint.dispose(); - deleteCount++; - } - if (deleteCount == 0) { + final Collection breakpoints = replServer.getBreakpoints(); + if (breakpoints.isEmpty()) { return finishReplyFailed(reply, "no breakpoints to delete"); } - return finishReplySucceeded(reply, Integer.toString(deleteCount) + " breakpoints deleted"); + for (BreakpointInfo breakpointInfo : breakpoints) { + breakpointInfo.dispose(); + } + return finishReplySucceeded(reply, Integer.toString(breakpoints.size()) + " breakpoints deleted"); } }; public static final REPLHandler DISABLE_BREAK_HANDLER = new REPLHandler(REPLMessage.DISABLE_BREAK) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID); if (breakpointNumber == null) { return finishReplyFailed(reply, "missing breakpoint number"); } - final Breakpoint breakpoint = serverContext.findBreakpoint(breakpointNumber); - if (breakpoint == null) { + final BreakpointInfo breakpointInfo = replServer.findBreakpoint(breakpointNumber); + if (breakpointInfo == null) { return finishReplyFailed(reply, "no breakpoint number " + breakpointNumber); } - breakpoint.setEnabled(false); + breakpointInfo.setEnabled(false); reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber)); return finishReplySucceeded(reply, "Breakpoint " + breakpointNumber + " disabled"); } @@ -345,44 +392,68 @@ public static final REPLHandler ENABLE_BREAK_HANDLER = new REPLHandler(REPLMessage.ENABLE_BREAK) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID); if (breakpointNumber == null) { return finishReplyFailed(reply, "missing breakpoint number"); } - final Breakpoint breakpoint = serverContext.findBreakpoint(breakpointNumber); - if (breakpoint == null) { + final BreakpointInfo breakpointInfo = replServer.findBreakpoint(breakpointNumber); + if (breakpointInfo == null) { return finishReplyFailed(reply, "no breakpoint number " + breakpointNumber); } - breakpoint.setEnabled(true); + breakpointInfo.setEnabled(true); reply.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber)); return finishReplySucceeded(reply, "Breakpoint " + breakpointNumber + " enabled"); } }; + public static final REPLHandler EVAL_HANDLER = new REPLHandler(REPLMessage.EVAL) { + @Override + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { + final REPLMessage reply = createReply(); + final String sourceName = request.get(REPLMessage.SOURCE_NAME); + reply.put(REPLMessage.SOURCE_NAME, sourceName); + final Context serverContext = replServer.getCurrentContext(); + reply.put(REPLMessage.DEBUG_LEVEL, Integer.toString(serverContext.getLevel())); + final String source = request.get(REPLMessage.CODE); + final Visualizer visualizer = replServer.getVisualizer(); + final Integer frameNumber = request.getIntValue(REPLMessage.FRAME_NUMBER); + final boolean stepInto = REPLMessage.TRUE.equals(request.get(REPLMessage.STEP_INTO)); + try { + Object returnValue = serverContext.eval(source, frameNumber, stepInto); + return finishReplySucceeded(reply, visualizer.displayValue(returnValue, 0)); + } catch (QuitException ex) { + throw ex; + } catch (KillException ex) { + return finishReplySucceeded(reply, "eval (" + sourceName + ") killed"); + } catch (Exception ex) { + return finishReplyFailed(reply, ex); + } + } + }; public static final REPLHandler FILE_HANDLER = new REPLHandler(REPLMessage.FILE) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); final String fileName = request.get(REPLMessage.SOURCE_NAME); if (fileName == null) { return finishReplyFailed(reply, "no file specified"); } + reply.put(REPLMessage.SOURCE_NAME, fileName); try { Source source = Source.fromFileName(fileName); if (source == null) { - reply.put(REPLMessage.SOURCE_NAME, fileName); - return finishReplyFailed(reply, " not found"); + return finishReplyFailed(reply, "file \"" + fileName + "\" not found"); } else { - reply.put(REPLMessage.SOURCE_NAME, fileName); reply.put(REPLMessage.FILE_PATH, source.getPath()); reply.put(REPLMessage.CODE, source.getCode()); return finishReplySucceeded(reply, "file found"); } + } catch (IOException ex) { + return finishReplyFailed(reply, "can't read file \"" + fileName + "\""); } catch (Exception ex) { - reply.put(REPLMessage.SOURCE_NAME, fileName); - return finishReplyFailed(reply, "file \"" + fileName + "\" not found"); + return finishReplyFailed(reply, ex); } } }; @@ -395,70 +466,169 @@ public static final REPLHandler FRAME_HANDLER = new REPLHandler(REPLMessage.FRAME) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { - final REPLMessage reply = createReply(); + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final Integer frameNumber = request.getIntValue(REPLMessage.FRAME_NUMBER); if (frameNumber == null) { - return finishReplyFailed(reply, "no frame number specified"); + return finishReplyFailed(createReply(), "no frame number specified"); + } + final Context currentContext = replServer.getCurrentContext(); + final List stack = currentContext.getStack(); + if (frameNumber < 0 || frameNumber > stack.size()) { + return finishReplyFailed(createReply(), "frame number " + frameNumber + " out of range"); } - final List stack = serverContext.getStack(); - if (frameNumber < 0 || frameNumber >= stack.size()) { - return finishReplyFailed(reply, "frame number " + frameNumber + " out of range"); + final Visualizer visualizer = replServer.getVisualizer(); + + MaterializedFrame frame; + Node node; + if (frameNumber == 0) { + frame = currentContext.getFrame(); + node = currentContext.getNode(); + } else { + final FrameInstance instance = stack.get(frameNumber - 1); + frame = instance.getFrame(FrameAccess.MATERIALIZE, true).materialize(); + node = instance.getCallNode(); + } + List slots = frame.getFrameDescriptor().getSlots(); + if (slots.size() == 0) { + final REPLMessage emptyFrameMessage = createFrameInfoMessage(replServer, frameNumber, node); + return finishReplySucceeded(emptyFrameMessage, "empty frame"); + } + final ArrayList replies = new ArrayList<>(); + + for (FrameSlot slot : slots) { + final REPLMessage slotMessage = createFrameInfoMessage(replServer, frameNumber, node); + slotMessage.put(REPLMessage.SLOT_INDEX, Integer.toString(slot.getIndex())); + slotMessage.put(REPLMessage.SLOT_ID, visualizer.displayIdentifier(slot)); + slotMessage.put(REPLMessage.SLOT_VALUE, visualizer.displayValue(frame.getValue(slot), 0)); + slotMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); + replies.add(slotMessage); } - final FrameDebugDescription frameDescription = stack.get(frameNumber); - final REPLMessage frameMessage = createFrameInfoMessage(serverContext, frameDescription); - final Frame frame = frameDescription.frameInstance().getFrame(FrameInstance.FrameAccess.READ_ONLY, true); - final Visualizer visualizer = serverContext.getVisualizer(); - final FrameDescriptor frameDescriptor = frame.getFrameDescriptor(); - try { - final StringBuilder sb = new StringBuilder(); - for (FrameSlot slot : frameDescriptor.getSlots()) { - sb.append(Integer.toString(slot.getIndex()) + ": " + visualizer.displayIdentifier(slot) + " = "); - try { - final Object value = frame.getValue(slot); - sb.append(visualizer.displayValue(value, 0)); - } catch (Exception ex) { - sb.append("???"); + return replies.toArray(new REPLMessage[0]); + } + }; + + public static final REPLHandler INFO_HANDLER = new REPLHandler(REPLMessage.INFO) { + + @Override + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { + final String topic = request.get(REPLMessage.TOPIC); + + if (topic == null || topic.isEmpty()) { + final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.INFO); + return finishReplyFailed(message, "No info topic specified"); + } + + switch (topic) { + + case REPLMessage.INFO_SUPPORTED_LANGUAGES: + final ArrayList langMessages = new ArrayList<>(); + + for (Language language : replServer.getLanguages()) { + final REPLMessage infoMessage = new REPLMessage(REPLMessage.OP, REPLMessage.INFO); + infoMessage.put(REPLMessage.TOPIC, REPLMessage.INFO_SUPPORTED_LANGUAGES); + infoMessage.put(REPLMessage.LANG_NAME, language.getName()); + infoMessage.put(REPLMessage.LANG_VER, language.getVersion()); + infoMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); + langMessages.add(infoMessage); } - sb.append("\n"); - } - return finishReplySucceeded(frameMessage, sb.toString()); - } catch (Exception ex) { - return finishReplyFailed(frameMessage, ex.toString()); + return langMessages.toArray(new REPLMessage[0]); + + case REPLMessage.INFO_CURRENT_LANGUAGE: + final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.INFO); + reply.put(REPLMessage.TOPIC, REPLMessage.INFO_CURRENT_LANGUAGE); + final String languageName = replServer.getCurrentContext().getLanguageName(); + reply.put(REPLMessage.LANG_NAME, languageName); + return finishReplySucceeded(reply, languageName); + + case REPLMessage.WELCOME_MESSAGE: + final REPLMessage infoMessage = new REPLMessage(REPLMessage.OP, REPLMessage.INFO); + infoMessage.put(REPLMessage.TOPIC, REPLMessage.WELCOME_MESSAGE); + infoMessage.put(REPLMessage.INFO_VALUE, replServer.getWelcome()); + infoMessage.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); + return finishReplySucceeded(infoMessage, "welcome"); + + default: + final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.INFO); + return finishReplyFailed(message, "No info about topic \"" + topic + "\""); } } }; - public static final REPLHandler KILL_HANDLER = new REPLHandler(REPLMessage.KILL) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { - if (serverContext.getLevel() == 0) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { + if (replServer.getCurrentContext().getLevel() == 0) { return finishReplyFailed(createReply(), "nothing to kill"); } throw new KillException(); } }; + public static final REPLHandler LOAD_HANDLER = new REPLHandler(REPLMessage.LOAD_SOURCE) { + + @Override + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { + final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.LOAD_SOURCE); + final String fileName = request.get(REPLMessage.SOURCE_NAME); + final boolean stepInto = REPLMessage.TRUE.equals(request.get(REPLMessage.STEP_INTO)); + try { + final Source fileSource = Source.fromFileName(fileName); + replServer.getCurrentContext().eval(fileSource, stepInto); + reply.put(REPLMessage.FILE_PATH, fileName); + return finishReplySucceeded(reply, fileName + " loaded"); + } catch (QuitException ex) { + throw ex; + } catch (KillException ex) { + return finishReplySucceeded(reply, fileName + " killed"); + } catch (Exception ex) { + return finishReplyFailed(reply, ex); + } + } + }; + public static final REPLHandler QUIT_HANDLER = new REPLHandler(REPLMessage.QUIT) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { throw new QuitException(); } }; + public static final REPLHandler SET_LANGUAGE_HANDLER = new REPLHandler(REPLMessage.SET_LANGUAGE) { + + @Override + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { + final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.SET_LANGUAGE); + String languageName = request.get(REPLMessage.LANG_NAME); + if (languageName == null) { + final String oldLanguageName = replServer.getCurrentContext().getLanguageName(); + reply.put(REPLMessage.LANG_NAME, reply.put(REPLMessage.LANG_NAME, oldLanguageName)); + return finishReplySucceeded(reply, "Language set to " + oldLanguageName); + } + reply.put(REPLMessage.LANG_NAME, languageName); + try { + final String newLanguageName = replServer.getCurrentContext().setLanguage(languageName); + if (newLanguageName != null) { + return finishReplySucceeded(reply, "Language set to " + newLanguageName); + } + } catch (Exception ex) { + return finishReplyFailed(reply, ex); + } + return finishReplyFailed(reply, "Language \"" + languageName + "\" not supported"); + } + }; + public static final REPLHandler SET_BREAK_CONDITION_HANDLER = new REPLHandler(REPLMessage.SET_BREAK_CONDITION) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.SET_BREAK_CONDITION); Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID); if (breakpointNumber == null) { return finishReplyFailed(message, "missing breakpoint number"); } message.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber)); - final Breakpoint breakpoint = serverContext.findBreakpoint(breakpointNumber); - if (breakpoint == null) { + final BreakpointInfo breakpointInfo = replServer.findBreakpoint(breakpointNumber); + if (breakpointInfo == null) { return finishReplyFailed(message, "no breakpoint number " + breakpointNumber); } final String expr = request.get(REPLMessage.BREAKPOINT_CONDITION); @@ -466,11 +636,13 @@ return finishReplyFailed(message, "missing condition for " + breakpointNumber); } try { - breakpoint.setCondition(expr); + breakpointInfo.setCondition(expr); } catch (IOException ex) { return finishReplyFailed(message, "invalid condition for " + breakpointNumber); } catch (UnsupportedOperationException ex) { - return finishReplyFailed(message, "conditions not unsupported by breakpoint " + breakpointNumber); + return finishReplyFailed(message, "conditions not supported by breakpoint " + breakpointNumber); + } catch (Exception ex) { + return finishReplyFailed(message, ex); } message.put(REPLMessage.BREAKPOINT_CONDITION, expr); return finishReplySucceeded(message, "Breakpoint " + breakpointNumber + " condition=\"" + expr + "\""); @@ -480,13 +652,13 @@ public static final REPLHandler STEP_INTO_HANDLER = new REPLHandler(REPLMessage.STEP_INTO) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); Integer repeat = request.getIntValue(REPLMessage.REPEAT); if (repeat == null) { repeat = 1; } - serverContext.prepareStepInto(repeat); + replServer.getCurrentContext().prepareStepInto(repeat); return finishReplySucceeded(reply, "StepInto <" + repeat + "> enabled"); } }; @@ -494,8 +666,8 @@ public static final REPLHandler STEP_OUT_HANDLER = new REPLHandler(REPLMessage.STEP_OUT) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { - serverContext.prepareStepOut(); + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { + replServer.getCurrentContext().prepareStepOut(); return finishReplySucceeded(createReply(), "StepOut enabled"); } }; @@ -503,13 +675,13 @@ public static final REPLHandler STEP_OVER_HANDLER = new REPLHandler(REPLMessage.STEP_OVER) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext debugServerContextFrame) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); Integer repeat = request.getIntValue(REPLMessage.REPEAT); if (repeat == null) { repeat = 1; } - debugServerContextFrame.prepareStepOver(repeat); + replServer.getCurrentContext().prepareStepOver(repeat); return finishReplySucceeded(reply, "StepOver <" + repeat + "> enabled"); } }; @@ -517,12 +689,12 @@ public static final REPLHandler TRUFFLE_HANDLER = new REPLHandler(REPLMessage.TRUFFLE) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); - final ASTPrinter astPrinter = serverContext.getVisualizer().getASTPrinter(); + final ASTPrinter astPrinter = replServer.getVisualizer().getASTPrinter(); final String topic = request.get(REPLMessage.TOPIC); reply.put(REPLMessage.TOPIC, topic); - Node node = serverContext.getNodeAtHalt(); + Node node = replServer.getCurrentContext().getNodeAtHalt(); if (node == null) { return finishReplyFailed(reply, "no current AST node"); } @@ -536,7 +708,7 @@ while (node.getParent() != null) { node = node.getParent(); } - final String astText = astPrinter.printTreeToString(node, depth, serverContext.getNodeAtHalt()); + final String astText = astPrinter.printTreeToString(node, depth, replServer.getCurrentContext().getNodeAtHalt()); return finishReplySucceeded(reply, astText); case REPLMessage.SUBTREE: case REPLMessage.SUB: @@ -547,7 +719,7 @@ } } catch (Exception ex) { - return finishReplyFailed(reply, ex.toString()); + return finishReplyFailed(reply, ex); } } }; @@ -555,33 +727,33 @@ public static final REPLHandler UNSET_BREAK_CONDITION_HANDLER = new REPLHandler(REPLMessage.UNSET_BREAK_CONDITION) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.UNSET_BREAK_CONDITION); Integer breakpointNumber = request.getIntValue(REPLMessage.BREAKPOINT_ID); if (breakpointNumber == null) { return finishReplyFailed(message, "missing breakpoint number"); } message.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber)); - final Breakpoint breakpoint = serverContext.findBreakpoint(breakpointNumber); - if (breakpoint == null) { + final BreakpointInfo breakpointInfo = replServer.findBreakpoint(breakpointNumber); + if (breakpointInfo == null) { return finishReplyFailed(message, "no breakpoint number " + breakpointNumber); } try { - breakpoint.setCondition(null); - } catch (IOException e) { - return finishReplyFailed(message, e.getMessage()); + breakpointInfo.setCondition(null); + } catch (Exception ex) { + return finishReplyFailed(message, ex); } - return finishReplyFailed(message, "Breakpoint " + breakpointNumber + " condition cleared"); + return finishReplySucceeded(message, "Breakpoint " + breakpointNumber + " condition cleared"); } }; public static final REPLHandler TRUFFLE_NODE_HANDLER = new REPLHandler(REPLMessage.TRUFFLE_NODE) { @Override - public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + public REPLMessage[] receive(REPLMessage request, REPLServer replServer) { final REPLMessage reply = createReply(); - final ASTPrinter astPrinter = serverContext.getVisualizer().getASTPrinter(); - final Node node = serverContext.getNodeAtHalt(); + final ASTPrinter astPrinter = replServer.getVisualizer().getASTPrinter(); + final Node node = replServer.getCurrentContext().getNodeAtHalt(); if (node == null) { return finishReplyFailed(reply, "no current AST node"); } @@ -599,7 +771,7 @@ } return finishReplySucceeded(reply, sb.toString()); } catch (Exception ex) { - return finishReplyFailed(reply, ex.toString()); + return finishReplyFailed(reply, ex); } } }; diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServer.java Mon Dec 07 21:17:46 2015 -0800 @@ -0,0 +1,678 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.tools.debug.shell.server; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.WeakHashMap; + +import com.oracle.truffle.api.debug.Breakpoint; +import com.oracle.truffle.api.debug.Breakpoint.State; +import com.oracle.truffle.api.debug.Debugger; +import com.oracle.truffle.api.debug.ExecutionEvent; +import com.oracle.truffle.api.debug.SuspendedEvent; +import com.oracle.truffle.api.frame.FrameInstance; +import com.oracle.truffle.api.frame.MaterializedFrame; +import com.oracle.truffle.api.instrument.StandardSyntaxTag; +import com.oracle.truffle.api.instrument.SyntaxTag; +import com.oracle.truffle.api.instrument.Visualizer; +import com.oracle.truffle.api.instrument.impl.DefaultVisualizer; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.source.LineLocation; +import com.oracle.truffle.api.source.Source; +import com.oracle.truffle.api.source.SourceSection; +import com.oracle.truffle.api.vm.EventConsumer; +import com.oracle.truffle.api.vm.PolyglotEngine; +import com.oracle.truffle.api.vm.PolyglotEngine.Language; +import com.oracle.truffle.api.vm.PolyglotEngine.Value; +import com.oracle.truffle.tools.debug.shell.REPLMessage; +import com.oracle.truffle.tools.debug.shell.client.SimpleREPLClient; + +/** + * The server side of a simple message-based protocol for a possibly remote language + * Read-Eval-Print-Loop. + */ +public final class REPLServer { + + enum BreakpointKind { + LINE, + TAG + } + + private static int nextBreakpointUID = 0; + + // Language-agnostic + private final PolyglotEngine engine; + private Debugger db; + private Context currentServerContext; + private SimpleREPLClient replClient = null; + private String statusPrefix; + private final Map handlerMap = new HashMap<>(); + + /** Languages sorted by name. */ + private final TreeSet engineLanguages = new TreeSet<>(new Comparator() { + + public int compare(Language lang1, Language lang2) { + return lang1.getName().compareTo(lang2.getName()); + } + }); + + /** MAP: language name => Language. */ + private final Map nameToLanguage = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + // TODO (mlvdv) Language-specific + private PolyglotEngine.Language defaultLanguage; + private final Visualizer visualizer; + + private Map breakpoints = new WeakHashMap<>(); + + public REPLServer(String defaultMIMEType, Visualizer visualizer) { + this.visualizer = visualizer == null ? new DefaultVisualizer() : visualizer; + this.engine = PolyglotEngine.newBuilder().onEvent(onHalted).onEvent(onExec).build(); + engineLanguages.addAll(engine.getLanguages().values()); + if (engineLanguages.size() == 0) { + throw new RuntimeException("No language implementations installed"); + } + for (Language language : engineLanguages) { + nameToLanguage.put(language.getName(), language); + } + + if (defaultMIMEType == null) { + defaultLanguage = engineLanguages.iterator().next(); + } else { + this.defaultLanguage = engine.getLanguages().get(defaultMIMEType); + if (defaultLanguage == null) { + throw new RuntimeException("Implementation not found for \"" + defaultMIMEType + "\""); + } + } + statusPrefix = languageName(defaultLanguage); + } + + private final EventConsumer onHalted = new EventConsumer(SuspendedEvent.class) { + @Override + protected void on(SuspendedEvent ev) { + REPLServer.this.haltedAt(ev); + } + }; + + private final EventConsumer onExec = new EventConsumer(ExecutionEvent.class) { + @Override + protected void on(ExecutionEvent event) { + if (db == null) { + db = event.getDebugger(); + for (BreakpointInfo breakpointInfo : breakpoints.values()) { + breakpointInfo.activate(db); + } + } + if (currentServerContext.steppingInto) { + event.prepareStepInto(); + } + } + }; + + public void add(REPLHandler handler) { + handlerMap.put(handler.getOp(), handler); + } + + /** + * Starts up a server; status returned in a message. + */ + public void start() { + + add(REPLHandler.BACKTRACE_HANDLER); + add(REPLHandler.BREAK_AT_LINE_HANDLER); + add(REPLHandler.BREAK_AT_LINE_ONCE_HANDLER); + add(REPLHandler.BREAK_AT_THROW_HANDLER); + add(REPLHandler.BREAK_AT_THROW_ONCE_HANDLER); + add(REPLHandler.BREAKPOINT_INFO_HANDLER); + add(REPLHandler.CALL_HANDLER); + add(REPLHandler.CLEAR_BREAK_HANDLER); + add(REPLHandler.CONTINUE_HANDLER); + add(REPLHandler.DELETE_HANDLER); + add(REPLHandler.DISABLE_BREAK_HANDLER); + add(REPLHandler.ENABLE_BREAK_HANDLER); + add(REPLHandler.EVAL_HANDLER); + add(REPLHandler.FILE_HANDLER); + add(REPLHandler.FRAME_HANDLER); + add(REPLHandler.INFO_HANDLER); + add(REPLHandler.KILL_HANDLER); + add(REPLHandler.LOAD_HANDLER); + add(REPLHandler.QUIT_HANDLER); + add(REPLHandler.SET_BREAK_CONDITION_HANDLER); + add(REPLHandler.SET_LANGUAGE_HANDLER); + add(REPLHandler.STEP_INTO_HANDLER); + add(REPLHandler.STEP_OUT_HANDLER); + add(REPLHandler.STEP_OVER_HANDLER); + add(REPLHandler.TRUFFLE_HANDLER); + add(REPLHandler.TRUFFLE_NODE_HANDLER); + add(REPLHandler.UNSET_BREAK_CONDITION_HANDLER); + this.replClient = new SimpleREPLClient(this); + this.currentServerContext = new Context(null, null, defaultLanguage); + replClient.start(); + } + + @SuppressWarnings("static-method") + public String getWelcome() { + return "GraalVM MultiLanguage Debugger 0.9\n" + "Copyright (c) 2013-5, Oracle and/or its affiliates"; + } + + void haltedAt(SuspendedEvent event) { + // Message the client that execution is halted and is in a new debugging context + final REPLMessage message = new REPLMessage(); + message.put(REPLMessage.OP, REPLMessage.STOPPED); + + // Identify language execution where halted; default to previous context + Language haltedLanguage = currentServerContext.currentLanguage; + final String mimeType = findMime(event.getNode()); + if (mimeType == null) { + message.put(REPLMessage.WARNINGS, "unable to detect language at halt"); + } else { + final Language language = engine.getLanguages().get(mimeType); + if (language == null) { + message.put(REPLMessage.WARNINGS, "no language installed for MIME type \"" + mimeType + "\""); + } else { + haltedLanguage = language; + } + } + + // Create and push a new debug context where execution is halted + currentServerContext = new Context(currentServerContext, event, haltedLanguage); + + message.put(REPLMessage.LANG_NAME, haltedLanguage.getName()); + final SourceSection src = event.getNode().getSourceSection(); + final Source source = src.getSource(); + message.put(REPLMessage.SOURCE_NAME, source.getName()); + final String path = source.getPath(); + if (path == null) { + message.put(REPLMessage.SOURCE_TEXT, source.getCode()); + } else { + message.put(REPLMessage.FILE_PATH, path); + } + message.put(REPLMessage.LINE_NUMBER, Integer.toString(src.getStartLine())); + + message.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); + message.put(REPLMessage.DEBUG_LEVEL, Integer.toString(currentServerContext.getLevel())); + List warnings = event.getRecentWarnings(); + if (!warnings.isEmpty()) { + final StringBuilder sb = new StringBuilder(); + for (String warning : warnings) { + sb.append(warning + "\n"); + } + message.put(REPLMessage.WARNINGS, sb.toString()); + } + try { + // Cheat with synchrony: call client directly about entering a nested debugging + // context. + replClient.halted(message); + } finally { + // Returns when "continue" or "kill" is called in the new debugging context + + // Pop the debug context, and return so that the old context will continue + currentServerContext = currentServerContext.predecessor; + } + } + + @SuppressWarnings("static-method") + private String findMime(Node node) { + String result = null; + final SourceSection section = node.getEncapsulatingSourceSection(); + if (section != null) { + final Source source = section.getSource(); + if (source != null) { + result = source.getMimeType(); + } + } + return result; + } + + /** + * Execution context of a halted program, possibly nested. + */ + public final class Context { + + private final Context predecessor; + private final int level; + private final SuspendedEvent event; + private Language currentLanguage; + private boolean steppingInto = false; // Only true during a "stepInto" engine call + + Context(Context predecessor, SuspendedEvent event, Language language) { + assert language != null; + this.level = predecessor == null ? 0 : predecessor.getLevel() + 1; + this.predecessor = predecessor; + this.event = event; + this.currentLanguage = language; + } + + /** + * The nesting depth of this context in the current session. + */ + int getLevel() { + return level; + } + + /** + * The AST node where execution is halted in this context. + */ + Node getNodeAtHalt() { + return event.getNode(); + } + + Object call(String name, boolean stepInto, List argList) throws IOException { + Value symbol = engine.findGlobalSymbol(name); + if (symbol == null) { + throw new IOException("symbol \"" + name + "\" not found"); + } + final List args = new ArrayList<>(); + for (String stringArg : argList) { + Integer intArg = null; + try { + intArg = Integer.valueOf(stringArg); + args.add(intArg); + } catch (NumberFormatException e) { + args.add(stringArg); + } + } + this.steppingInto = stepInto; + try { + return symbol.execute(args.toArray(new Object[0])).get(); + } finally { + this.steppingInto = false; + } + } + + void eval(Source source, boolean stepInto) throws IOException { + this.steppingInto = stepInto; + try { + engine.eval(source); + } finally { + this.steppingInto = false; + } + } + + /** + * Evaluates a code snippet in the context of a selected frame in the currently suspended + * execution. + * + * @param code the snippet to evaluate + * @param frameNumber index of the stack frame in which to evaluate, {@code null} if the + * topmost frame should be used. + * @return result of the evaluation + * @throws IOException if something goes wrong + */ + Object eval(String code, Integer frameNumber, boolean stepInto) throws IOException { + if (event == null) { + this.steppingInto = stepInto; + final String mimeType = defaultMIME(currentLanguage); + try { + return engine.eval(Source.fromText(code, "eval(\"" + code + "\")").withMimeType(mimeType)).get(); + } finally { + this.steppingInto = false; + } + } else { + if (stepInto) { + event.prepareStepInto(1); + } + try { + final Object result = event.eval(code, frameNumber); + return (result instanceof Value) ? ((Value) result).get() : result; + } finally { + event.prepareContinue(); + } + } + } + + /** + * The frame where execution is halted in this context. + */ + MaterializedFrame getFrameAtHalt() { + return event.getFrame(); + } + + /** + * Dispatches a REPL request to the appropriate handler. + */ + REPLMessage[] receive(REPLMessage request) { + final String command = request.get(REPLMessage.OP); + final REPLHandler handler = handlerMap.get(command); + + if (handler == null) { + final REPLMessage message = new REPLMessage(); + message.put(REPLMessage.OP, command); + message.put(REPLMessage.STATUS, REPLMessage.FAILED); + message.put(REPLMessage.DISPLAY_MSG, statusPrefix + " op \"" + command + "\" not supported"); + final REPLMessage[] reply = new REPLMessage[]{message}; + return reply; + } + return handler.receive(request, REPLServer.this); + } + + /** + * @return Node where halted + */ + Node getNode() { + return event.getNode(); + } + + /** + * @return Frame where halted + */ + MaterializedFrame getFrame() { + return event.getFrame(); + } + + /** + * Provides access to the execution stack, not counting the node/frame where halted. + * + * @return immutable list of stack elements + */ + List getStack() { + return event.getStack(); + } + + public String getLanguageName() { + return currentLanguage.getName(); + } + + /** + * Case-insensitive; returns actual language name set. + * + * @throws IOException if fails + */ + String setLanguage(String name) throws IOException { + assert name != null; + final Language language = nameToLanguage.get(name); + if (language == null) { + throw new IOException("Language \" + name + \" not supported"); + } + if (language == currentLanguage) { + return currentLanguage.getName(); + } + if (event != null) { + throw new IOException("Only supported at top level"); + } + this.currentLanguage = language; + return language.getName(); + } + + void prepareStepOut() { + event.prepareStepOut(); + } + + void prepareStepInto(int repeat) { + event.prepareStepInto(repeat); + } + + void prepareStepOver(int repeat) { + event.prepareStepOver(repeat); + } + + void prepareContinue() { + event.prepareContinue(); + } + + } + + /** + * Ask the server to handle a request. Return a non-empty array of messages to simulate remote + * operation where the protocol has possibly multiple messages being returned asynchronously in + * response to each request. + */ + public REPLMessage[] receive(REPLMessage request) { + if (currentServerContext == null) { + final REPLMessage message = new REPLMessage(); + message.put(REPLMessage.STATUS, REPLMessage.FAILED); + message.put(REPLMessage.DISPLAY_MSG, "server not started"); + final REPLMessage[] reply = new REPLMessage[]{message}; + return reply; + } + return currentServerContext.receive(request); + } + + Context getCurrentContext() { + return currentServerContext; + } + + Visualizer getVisualizer() { + return visualizer; + } + + // TODO (mlvdv) language-specific + Language getLanguage() { + return defaultLanguage; + } + + TreeSet getLanguages() { + return engineLanguages; + } + + // TODO (mlvdv) language-specific + public String getLanguageName() { + return languageName(this.defaultLanguage); + } + + private static String languageName(Language lang) { + return lang.getName() + "(" + lang.getVersion() + ")"; + } + + @SuppressWarnings("static-method") + private String defaultMIME(Language language) { + return language.getMimeTypes().iterator().next(); + } + + BreakpointInfo setLineBreakpoint(int ignoreCount, LineLocation lineLocation, boolean oneShot) { + return new BreakpointInfo(db, lineLocation, ignoreCount, oneShot); + } + + BreakpointInfo setTagBreakpoint(int ignoreCount, StandardSyntaxTag tag, boolean oneShot) { + return new BreakpointInfo(db, tag, ignoreCount, oneShot); + } + + synchronized BreakpointInfo findBreakpoint(int id) { + return breakpoints.get(id); + } + + /** + * Gets a list of the currently existing breakpoints. + */ + Collection getBreakpoints() { + return new ArrayList<>(breakpoints.values()); + } + + final class BreakpointInfo { + + private final BreakpointKind kind; + + /** Null before created in debugger or after disposal. */ + private Breakpoint breakpoint; + + /** Non-null only when breakpoint == null. */ + private State state; // non-null iff haven't "activated" yet + + private final int uid; + + private boolean oneShot; + + private int ignoreCount; + + private Source conditionSource; + + private final LineLocation lineLocation; + + private final SyntaxTag tag; + + private BreakpointInfo(Debugger debugger, LineLocation lineLocation, int ignoreCount, boolean oneShot) { + this.kind = BreakpointKind.LINE; + this.lineLocation = lineLocation; + this.tag = null; + this.ignoreCount = ignoreCount; + this.oneShot = oneShot; + this.uid = nextBreakpointUID++; + if (debugger == null) { + this.state = State.ENABLED_UNRESOLVED; + } else { + activate(debugger); + } + breakpoints.put(uid, this); + } + + private BreakpointInfo(Debugger debugger, SyntaxTag tag, int ignoreCount, boolean oneShot) { + this.kind = BreakpointKind.TAG; + this.lineLocation = null; + this.tag = tag; + this.ignoreCount = ignoreCount; + this.oneShot = oneShot; + this.uid = nextBreakpointUID++; + if (debugger == null) { + this.state = State.ENABLED_UNRESOLVED; + } else { + activate(debugger); + } + breakpoints.put(uid, this); + } + + private void activate(Debugger debugger) { + if (breakpoint != null) { + throw new IllegalStateException("Breakpoint already activated"); + } + if (state == State.DISPOSED) { + throw new IllegalStateException("Breakpoint already disposed"); + } + try { + switch (kind) { + case LINE: + breakpoint = debugger.setLineBreakpoint(ignoreCount, lineLocation, oneShot); + break; + case TAG: + breakpoint = debugger.setTagBreakpoint(ignoreCount, tag, oneShot); + break; + default: + throw new IllegalStateException("Unexpected breakpoint kind"); + } + if (conditionSource != null) { + breakpoint.setCondition(conditionSource.getCode()); + conditionSource = null; + } + if (state == State.DISABLED_UNRESOLVED) { + breakpoint.setEnabled(false); + } + state = null; + } catch (IOException ex) { + throw new IllegalStateException("Failure to activate breakpoint " + uid + ": " + ex.getMessage()); + } + } + + int getID() { + return uid; + } + + String describeState() { + return (breakpoint == null ? state : breakpoint.getState()).getName(); + } + + String describeLocation() { + if (breakpoint == null) { + switch (kind) { + case LINE: + return "Line: " + lineLocation.getShortDescription(); + case TAG: + return "Tag " + tag.name(); + default: + throw new IllegalStateException("Unexpected breakpoint state"); + } + } + return breakpoint.getLocationDescription(); + } + + void setEnabled(boolean enabled) { + if (breakpoint == null) { + switch (state) { + case ENABLED_UNRESOLVED: + if (!enabled) { + state = State.DISABLED_UNRESOLVED; + } + break; + case DISABLED_UNRESOLVED: + if (enabled) { + state = State.ENABLED_UNRESOLVED; + } + break; + case DISPOSED: + throw new IllegalStateException("Disposed breakpoints must stay disposed"); + default: + throw new IllegalStateException("Unexpected breakpoint state"); + } + } else { + breakpoint.setEnabled(enabled); + } + } + + boolean isEnabled() { + return breakpoint == null ? (state == State.ENABLED_UNRESOLVED) : breakpoint.isEnabled(); + } + + void setCondition(String expr) throws IOException { + if (breakpoint == null) { + conditionSource = expr == null ? null : Source.fromText(expr, "breakpoint condition from text: " + expr); + } else { + breakpoint.setCondition(expr); + } + } + + String getCondition() { + final Source source = breakpoint == null ? conditionSource : breakpoint.getCondition(); + return source == null ? null : source.getCode(); + } + + int getIgnoreCount() { + return breakpoint == null ? ignoreCount : breakpoint.getIgnoreCount(); + } + + int getHitCount() { + return breakpoint == null ? 0 : breakpoint.getHitCount(); + } + + void dispose() { + if (breakpoint == null) { + if (state == State.DISPOSED) { + throw new IllegalStateException("Breakpoint already disposed"); + } + } else { + breakpoint.dispose(); + breakpoint = null; + } + state = State.DISPOSED; + breakpoints.remove(uid); + conditionSource = null; + } + } +} diff -r 50baaa7da8e8 -r 07f3efb4e321 truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServerContext.java --- a/truffle/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/server/REPLServerContext.java Fri Dec 04 14:32:24 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.truffle.tools.debug.shell.server; - -import com.oracle.truffle.api.debug.Breakpoint; -import com.oracle.truffle.api.debug.Debugger; -import com.oracle.truffle.api.debug.SuspendedEvent; -import com.oracle.truffle.api.frame.FrameInstance; -import com.oracle.truffle.api.frame.MaterializedFrame; -import com.oracle.truffle.api.instrument.Visualizer; -import com.oracle.truffle.api.instrument.impl.DefaultVisualizer; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.vm.PolyglotEngine; -import com.oracle.truffle.api.vm.PolyglotEngine.Language; -import com.oracle.truffle.tools.debug.shell.REPLMessage; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public abstract class REPLServerContext { - - private final int level; - private final SuspendedEvent event; - - protected REPLServerContext(int level, SuspendedEvent event) { - this.level = level; - this.event = event; - } - - /** - * The nesting depth of this context in the current session. - */ - public int getLevel() { - return level; - } - - /** - * The AST node where execution is halted in this context. - */ - public Node getNodeAtHalt() { - return event.getNode(); - } - - /** - * Evaluates given code snippet in the context of currently suspended execution. - * - * @param code the snippet to evaluate - * @param frame null in case the evaluation should happen in top most frame, - * non-null value - * @return result of the evaluation - * @throws IOException if something goes wrong - */ - public Object eval(String code, FrameInstance frame) throws IOException { - return event.eval(code, frame); - } - - /** - * The frame where execution is halted in this context. - */ - public MaterializedFrame getFrameAtHalt() { - return event.getFrame(); - } - - public abstract Language getLanguage(); - - public Visualizer getVisualizer() { - return new DefaultVisualizer(); - } - - public PolyglotEngine engine() { - throw new IllegalStateException(); - } - - protected abstract Debugger db(); - - /** - * Dispatches a REPL request to the appropriate handler. - */ - public abstract REPLMessage[] receive(REPLMessage request); - - public abstract void registerBreakpoint(Breakpoint breakpoint); - - public abstract Breakpoint findBreakpoint(int id); - - public abstract int getBreakpointID(Breakpoint breakpoint); - - /** - * Provides access to the execution stack. - * - * @return immutable list of stack elements - */ - public List getStack() { - List frames = new ArrayList<>(); - int frameCount = 1; - for (FrameInstance frameInstance : event.getStack()) { - if (frameCount == 1) { - frames.add(new FrameDebugDescription(frameCount, event.getNode(), frameInstance)); - } else { - frames.add(new FrameDebugDescription(frameCount, frameInstance.getCallNode(), frameInstance)); - } - frameCount++; - } - return Collections.unmodifiableList(frames); - } - - void prepareStepOut() { - event.prepareStepOut(); - } - - void prepareStepInto(int repeat) { - event.prepareStepInto(repeat); - } - - void prepareStepOver(int repeat) { - event.prepareStepOver(repeat); - } - - void prepareContinue() { - event.prepareContinue(); - } -}