/*
* Copyright (c) 2015, 2016, 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.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.CallTarget;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.instrumentation.TruffleInstrument.Registration;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
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.server.InstrumentationUtils.LocationPrinter;
/**
* The server side of a simple message-based protocol for a possibly remote language
* Read-Eval-Print-Loop.
*/
@SuppressWarnings("deprecation")
@Deprecated
public final class REPLServer {
private static final String REPL_SERVER_INSTRUMENT = "REPLServer";
private static int nextBreakpointUID = 0;
// Language-agnostic
private final PolyglotEngine engine;
private final String statusPrefix;
private final Map<String, REPLHandler> handlerMap = new HashMap<>();
private final LocationPrinter locationPrinter = new InstrumentationUtils.LocationPrinter();
private final REPLVisualizer visualizer = new REPLVisualizer();
private Context currentServerContext;
/** Languages sorted by name. */
private final TreeSet<Language> engineLanguages = new TreeSet<>();
/** MAP: language name => Language (case insensitive). */
private final Map<String, Language> nameToLanguage = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
// TODO (mlvdv) Language-specific
private final PolyglotEngine.Language defaultLanguage = null;
private final Map<Integer, BreakpointInfo> breakpoints = new WeakHashMap<>();
@SuppressWarnings("unused")
public REPLServer(com.oracle.truffle.tools.debug.shell.client.SimpleREPLClient client) {
this.engine = PolyglotEngine.newBuilder().build();
this.engine.getRuntime().getInstruments().get(REPL_SERVER_INSTRUMENT).setEnabled(true);
engineLanguages.addAll(engine.getLanguages().values());
for (Language language : engineLanguages) {
nameToLanguage.put(language.getName().toLowerCase(), language);
}
statusPrefix = "";
}
public void add(REPLHandler handler) {
handlerMap.put(handler.getOp(), handler);
}
/**
* Start sever: load commands, generate initial context.
*/
public void start() {
add(REPLHandler.BACKTRACE_HANDLER);
add(REPLHandler.BREAK_AT_LINE_HANDLER);
add(REPLHandler.BREAK_AT_LINE_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.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.UNSET_BREAK_CONDITION_HANDLER);
this.currentServerContext = new Context(null, defaultLanguage);
}
@SuppressWarnings("static-method")
public String getWelcome() {
return "GraalVM Polyglot Debugger 0.9\n" + "Copyright (c) 2013-6, Oracle and/or its affiliates";
}
public LocationPrinter getLocationPrinter() {
return locationPrinter;
}
/**
* Execution context of a halted program, possibly nested.
*/
public final class Context {
private final int level;
private Language currentLanguage;
Context(Context predecessor, Language language) {
this.level = predecessor == null ? 0 : predecessor.getLevel() + 1;
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 null;
}
/**
* Get access to display methods appropriate to the language at halted node.
*/
REPLVisualizer getVisualizer() {
return visualizer;
}
@SuppressWarnings("unused")
Object call(String name, boolean stepInto, List<String> argList) throws IOException {
Value symbol = engine.findGlobalSymbol(name);
if (symbol == null) {
throw new IOException("symbol \"" + name + "\" not found");
}
final List<Object> args = new ArrayList<>();
for (String stringArg : argList) {
Integer intArg = null;
try {
intArg = Integer.valueOf(stringArg);
args.add(intArg);
} catch (NumberFormatException e) {
args.add(stringArg);
}
}
return symbol.execute(args.toArray(new Object[0])).get();
}
@SuppressWarnings("unused")
void eval(Source source, boolean stepInto) {
engine.eval(source);
}
/**
* Evaluates a code snippet in the context of a selected frame in the currently suspended
* execution, if any; otherwise a top level (new) evaluation.
*
* @param code the snippet to evaluate
* @param frameNumber index of the stack frame in which to evaluate, 0 = current frame where
* halted, null = top level eval
* @return result of the evaluation
* @throws IOException if something goes wrong
*/
@SuppressWarnings("unused")
Object eval(String code, Integer frameNumber, boolean stepInto) throws IOException {
return null;
}
@SuppressWarnings("unused")
public String displayValue(Integer frameNumber, Object value, int trim) {
return null;
}
/**
* The frame where execution is halted in this context.
*/
MaterializedFrame getFrameAtHalt() {
return null;
}
/**
* Dispatches a REPL request to the appropriate handler.
*/
com.oracle.truffle.tools.debug.shell.REPLMessage[] receive(com.oracle.truffle.tools.debug.shell.REPLMessage request) {
final String command = request.get(com.oracle.truffle.tools.debug.shell.REPLMessage.OP);
final REPLHandler handler = handlerMap.get(command);
if (handler == null) {
final com.oracle.truffle.tools.debug.shell.REPLMessage message = new com.oracle.truffle.tools.debug.shell.REPLMessage();
message.put(com.oracle.truffle.tools.debug.shell.REPLMessage.OP, command);
message.put(com.oracle.truffle.tools.debug.shell.REPLMessage.STATUS, com.oracle.truffle.tools.debug.shell.REPLMessage.FAILED);
message.put(com.oracle.truffle.tools.debug.shell.REPLMessage.DISPLAY_MSG, statusPrefix + " op \"" + command + "\" not supported");
final com.oracle.truffle.tools.debug.shell.REPLMessage[] reply = new com.oracle.truffle.tools.debug.shell.REPLMessage[]{message};
return reply;
}
return handler.receive(request, REPLServer.this);
}
/**
* @return Node where halted
*/
Node getNode() {
return null;
}
/**
* @return Frame where halted
*/
MaterializedFrame getFrame() {
return null;
}
/**
* Access to the execution stack.
*
* @return immutable list of stack elements
*/
List<FrameInstance> getStack() {
return null;
}
public String getLanguageName() {
return currentLanguage == null ? null : 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.toLowerCase());
if (language == null) {
throw new IOException("Language \"" + name + "\" not supported");
}
if (language == currentLanguage) {
return currentLanguage.getName();
}
this.currentLanguage = language;
return language.getName();
}
void prepareStepOut() {
}
@SuppressWarnings("unused")
void prepareStepInto(int repeat) {
}
@SuppressWarnings("unused")
void prepareStepOver(int repeat) {
}
void prepareContinue() {
}
void kill() {
}
}
/**
* 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 com.oracle.truffle.tools.debug.shell.REPLMessage[] receive(com.oracle.truffle.tools.debug.shell.REPLMessage request) {
if (currentServerContext == null) {
final com.oracle.truffle.tools.debug.shell.REPLMessage message = new com.oracle.truffle.tools.debug.shell.REPLMessage();
message.put(com.oracle.truffle.tools.debug.shell.REPLMessage.STATUS, com.oracle.truffle.tools.debug.shell.REPLMessage.FAILED);
message.put(com.oracle.truffle.tools.debug.shell.REPLMessage.DISPLAY_MSG, "server not started");
final com.oracle.truffle.tools.debug.shell.REPLMessage[] reply = new com.oracle.truffle.tools.debug.shell.REPLMessage[]{message};
return reply;
}
return currentServerContext.receive(request);
}
Context getCurrentContext() {
return currentServerContext;
}
// TODO (mlvdv) language-specific
Language getLanguage() {
return defaultLanguage;
}
TreeSet<Language> getLanguages() {
return engineLanguages;
}
// TODO (mlvdv) language-specific
public String getLanguageName() {
return languageName(this.defaultLanguage);
}
private static String languageName(Language lang) {
return lang.getName() + "(" + lang.getVersion() + ")";
}
BreakpointInfo setLineBreakpoint(int ignoreCount, com.oracle.truffle.api.source.LineLocation lineLocation, boolean oneShot) throws IOException {
final BreakpointInfo info = new LineBreakpointInfo(lineLocation, ignoreCount, oneShot);
info.activate();
return info;
}
synchronized BreakpointInfo findBreakpoint(int id) {
return breakpoints.get(id);
}
/**
* Gets a list of the currently existing breakpoints.
*/
Collection<BreakpointInfo> getBreakpoints() {
// TODO (mlvdv) check if each is currently resolved
return new ArrayList<>(breakpoints.values());
}
@Registration(id = "REPLServer")
public static final class REPLServerInstrument extends TruffleInstrument {
@Override
protected void onCreate(Env env) {
env.registerService(env.getInstrumenter());
}
}
final class LineBreakpointInfo extends BreakpointInfo {
@SuppressWarnings("unused")
private LineBreakpointInfo(com.oracle.truffle.api.source.LineLocation lineLocation, int ignoreCount, boolean oneShot) {
super(ignoreCount, oneShot);
}
@Override
protected void activate() throws IOException {
}
@Override
String describeLocation() {
return null;
}
}
abstract class BreakpointInfo {
protected final int uid;
protected final boolean oneShot;
protected final int ignoreCount;
protected Source conditionSource;
protected BreakpointInfo(int ignoreCount, boolean oneShot) {
this.ignoreCount = ignoreCount;
this.oneShot = oneShot;
this.uid = nextBreakpointUID++;
}
protected abstract void activate() throws IOException;
abstract String describeLocation();
int getID() {
return uid;
}
String describeState() {
return null;
}
void setEnabled(@SuppressWarnings("unused") boolean enabled) {
}
boolean isEnabled() {
return false;
}
@SuppressWarnings("unused")
void setCondition(String expr) throws IOException {
}
String getCondition() {
return null;
}
int getIgnoreCount() {
return 0;
}
int getHitCount() {
return 0;
}
void dispose() {
}
String summarize() {
final StringBuilder sb = new StringBuilder("Breakpoint");
sb.append(" id=" + uid);
sb.append(" locn=(" + describeLocation());
sb.append(") " + describeState());
return sb.toString();
}
}
static class REPLVisualizer {
/**
* A short description of a source location in terms of source + line number.
*/
String displaySourceLocation(Node node) {
if (node == null) {
return "<unknown>";
}
SourceSection section = node.getSourceSection();
boolean estimated = false;
if (section == null) {
section = node.getEncapsulatingSourceSection();
estimated = true;
}
if (section == null) {
return "<error: source location>";
}
return InstrumentationUtils.getShortDescription(section) + (estimated ? "~" : "");
}
/**
* Describes the name of the method containing a node.
*/
String displayMethodName(Node node) {
if (node == null) {
return null;
}
RootNode root = node.getRootNode();
if (root != null && root.getName() != null) {
return root.getName();
}
return "??";
}
/**
* The name of the method.
*/
String displayCallTargetName(CallTarget callTarget) {
return callTarget.toString();
}
/**
* Converts a value in the guest language to a display string. If
*
* @param trim if {@code > 0}, them limit size of String to either the value of trim or the
* number of characters in the first line, whichever is lower.
*/
String displayValue(Object value, int trim) {
if (value == null) {
return "<empty>";
}
return trim(value.toString(), trim);
}
/**
* Converts a slot identifier in the guest language to a display string.
*/
String displayIdentifier(FrameSlot slot) {
return slot.getIdentifier().toString();
}
}
/**
* Trims text if {@code trim > 0} to the shorter of {@code trim} or the length of the first line
* of test. Identity if {@code trim <= 0}.
*/
protected static String trim(String text, int trim) {
if (trim == 0) {
return text;
}
final String[] lines = text.split("\n");
String result = lines[0];
if (lines.length == 1) {
if (result.length() <= trim) {
return result;
}
if (trim <= 3) {
return result.substring(0, Math.min(result.length() - 1, trim - 1));
} else {
return result.substring(0, trim - 4) + "...";
}
}
return (result.length() < trim - 3 ? result : result.substring(0, trim - 4)) + "...";
}
}