Mercurial > hg > graal-compiler
comparison graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java @ 21568:3b8bbf51d320
Truffle/Debugging: add the Truffle DebugEngine and supporting code, as well as add a crude command-line debugging tool used mainly to test the DebugEngine. Migrate the small tols out of project com.oracle.truffle.api into the new project com.oracle.truffle.tools.
author | Michael Van De Vanter <michael.van.de.vanter@oracle.com> |
---|---|
date | Tue, 26 May 2015 16:38:13 -0700 |
parents | |
children | 894f82515e38 |
comparison
equal
deleted
inserted
replaced
21470:1bbef57f9a38 | 21568:3b8bbf51d320 |
---|---|
1 /* | |
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. | |
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
4 * | |
5 * This code is free software; you can redistribute it and/or modify it | |
6 * under the terms of the GNU General Public License version 2 only, as | |
7 * published by the Free Software Foundation. Oracle designates this | |
8 * particular file as subject to the "Classpath" exception as provided | |
9 * by Oracle in the LICENSE file that accompanied this code. | |
10 * | |
11 * This code is distributed in the hope that it will be useful, but WITHOUT | |
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
14 * version 2 for more details (a copy is included in the LICENSE file that | |
15 * accompanied this code). | |
16 * | |
17 * You should have received a copy of the GNU General Public License version | |
18 * 2 along with this work; if not, write to the Free Software Foundation, | |
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
20 * | |
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
22 * or visit www.oracle.com if you need additional information or have any | |
23 * questions. | |
24 */ | |
25 package com.oracle.truffle.tools.debug.shell.client; | |
26 | |
27 import java.io.*; | |
28 import java.util.*; | |
29 | |
30 import jline.console.*; | |
31 | |
32 import com.oracle.truffle.api.*; | |
33 import com.oracle.truffle.api.source.*; | |
34 import com.oracle.truffle.tools.debug.shell.*; | |
35 | |
36 /** | |
37 * A very simple line-oriented, language-agnostic debugging client shell: the first step toward a | |
38 * general, extensible debugging framework designed to be adapted for remote debugging. | |
39 * <p> | |
40 * The architecture of this debugging framework is modeled loosely on <a | |
41 * href="https://github.com/clojure/tools.nrepl">nREPL</a>, a network REPL developed by the Clojure | |
42 * community with a focus on generality: | |
43 * <ul> | |
44 * <li>Client and (possibly remote) server communicate via <em>messages</em> carried over some | |
45 * <em>transport</em>;</li> | |
46 * <li>A message is a <em>map</em> of key/value pairs;</li> | |
47 * <li>Keys and values are <em>strings</em>;</li> | |
48 * <li>The client sends messages as <em>requests</em> to a server;</li> | |
49 * <li>A server dispatches each incoming request to an appropriate <em>handler</em> that takes | |
50 * appropriate action and responds to the client with one or more messages; and</li> | |
51 * <li>Many implementations of the <em>transport</em> are possible.</li> | |
52 * </ul> | |
53 * <p> | |
54 * <strong>Compromises:</strong> | |
55 * <p> | |
56 * In order to get | |
57 * <ol> | |
58 * <li>A debugging session should start from this shell, but there is no machinery in place for | |
59 * doing that; instead, an entry into the language implementation creates both the server and this | |
60 * shell;</li> | |
61 * <li>The current startup sequence is based on method calls, not messages;</li> | |
62 * <li>Only a very few request types and keys are implemented, omitting for example request and | |
63 * session ids;</li> | |
64 * <li>Message passing is synchronous and "transported" via method calls;</li> | |
65 * <li>Asynchrony is emulated by having each call to the server pass only a message, and by having | |
66 * the server return only a list of messages.</li> | |
67 * </ol> | |
68 * | |
69 * @see REPLServer | |
70 * @see REPLMessage | |
71 */ | |
72 public class SimpleREPLClient implements REPLClient { | |
73 | |
74 private static final String REPLY_PREFIX = "==> "; | |
75 private static final String FAIL_PREFIX = "**> "; | |
76 private static final String WARNING_PREFIX = "!!> "; | |
77 private static final String TRACE_PREFIX = ">>> "; | |
78 private static final String[] NULL_ARGS = new String[0]; | |
79 | |
80 static final String INFO_LINE_FORMAT = " %s\n"; | |
81 static final String CODE_LINE_FORMAT = " %3d %s\n"; | |
82 static final String CODE_LINE_BREAK_FORMAT = "--> %3d %s\n"; | |
83 | |
84 private static final String STACK_FRAME_FORMAT = " %3d: at %s in %s line =\"%s\"\n"; | |
85 private static final String STACK_FRAME_SELECTED_FORMAT = "==> %3d: at %s in %s line =\"%s\"\n"; | |
86 | |
87 private final ExecutionContext executionContext; // Language context | |
88 | |
89 // Top level commands | |
90 private final Map<String, REPLCommand> commandMap = new HashMap<>(); | |
91 private final Collection<String> commandNames = new TreeSet<>(); | |
92 | |
93 // Local options | |
94 private final Map<String, LocalOption> localOptions = new HashMap<>(); | |
95 private final Collection<String> optionNames = new TreeSet<>(); | |
96 | |
97 // Current local context | |
98 ClientContextImpl clientContext; | |
99 | |
100 // Cheating for the prototype; prototype startup now happens from the language server. | |
101 // So this isn't used. | |
102 public static void main(String[] args) { | |
103 final SimpleREPLClient repl = new SimpleREPLClient(null, null); | |
104 repl.start(); | |
105 } | |
106 | |
107 private final ConsoleReader reader; | |
108 | |
109 private final PrintStream writer; | |
110 | |
111 private final REPLServer replServer; | |
112 | |
113 private final LocalOption astDepthOption = new IntegerOption(9, "astdepth", "default depth for AST display"); | |
114 | |
115 private final LocalOption autoWhereOption = new BooleanOption(true, "autowhere", "run the \"where\" command after each navigation"); | |
116 | |
117 private final LocalOption autoNodeOption = new BooleanOption(false, "autonode", "run the \"truffle node\" command after each navigation"); | |
118 | |
119 private final LocalOption autoSubtreeOption = new BooleanOption(false, "autosubtree", "run the \"truffle subtree\" command after each navigation"); | |
120 | |
121 private final LocalOption autoASTOption = new BooleanOption(false, "autoast", "run the \"truffle ast\" command after each navigation"); | |
122 | |
123 private final LocalOption listSizeOption = new IntegerOption(25, "listsize", "default number of lines to list"); | |
124 | |
125 private final LocalOption traceMessagesOption = new BooleanOption(false, "tracemessages", "trace REPL messages between client and server"); | |
126 | |
127 private final LocalOption verboseBreakpointInfoOption = new BooleanOption(true, "verbosebreakpointinfo", "\"info breakpoint\" displays more info"); | |
128 | |
129 private void addOption(LocalOption localOption) { | |
130 final String optionName = localOption.getName(); | |
131 localOptions.put(optionName, localOption); | |
132 optionNames.add(optionName); | |
133 } | |
134 | |
135 /** | |
136 * Non-null when the user has named a file other than where halted, providing context for | |
137 * commands such as "break"; if no explicit selection, then defaults to where halted. This is | |
138 * session state, so it persists across halting contexts. | |
139 */ | |
140 private Source selectedSource = null; | |
141 | |
142 public SimpleREPLClient(ExecutionContext context, REPLServer replServer) { | |
143 this.executionContext = context; | |
144 this.replServer = replServer; | |
145 this.writer = System.out; | |
146 try { | |
147 this.reader = new ConsoleReader(); | |
148 } catch (IOException e) { | |
149 throw new RuntimeException("Unable to create console " + e); | |
150 } | |
151 | |
152 addCommand(backtraceCommand); | |
153 addCommand(REPLRemoteCommand.BREAK_AT_LINE_CMD); | |
154 addCommand(REPLRemoteCommand.BREAK_AT_LINE_ONCE_CMD); | |
155 addCommand(REPLRemoteCommand.BREAK_AT_THROW_CMD); | |
156 addCommand(REPLRemoteCommand.BREAK_AT_THROW_ONCE_CMD); | |
157 addCommand(REPLRemoteCommand.CLEAR_BREAK_CMD); | |
158 addCommand(REPLRemoteCommand.CONDITION_BREAK_CMD); | |
159 addCommand(REPLRemoteCommand.CONTINUE_CMD); | |
160 addCommand(REPLRemoteCommand.DELETE_CMD); | |
161 addCommand(REPLRemoteCommand.DISABLE_CMD); | |
162 addCommand(REPLRemoteCommand.DOWN_CMD); | |
163 addCommand(REPLRemoteCommand.ENABLE_CMD); | |
164 addCommand(evalCommand); | |
165 addCommand(fileCommand); | |
166 addCommand(REPLRemoteCommand.FRAME_CMD); | |
167 addCommand(helpCommand); | |
168 addCommand(infoCommand); | |
169 addCommand(REPLRemoteCommand.KILL_CMD); | |
170 addCommand(listCommand); | |
171 addCommand(REPLRemoteCommand.LOAD_RUN_CMD); | |
172 addCommand(REPLRemoteCommand.LOAD_STEP_CMD); | |
173 addCommand(quitCommand); | |
174 addCommand(setCommand); | |
175 addCommand(REPLRemoteCommand.STEP_INTO_CMD); | |
176 addCommand(REPLRemoteCommand.STEP_OUT_CMD); | |
177 addCommand(REPLRemoteCommand.STEP_OVER_CMD); | |
178 addCommand(truffleCommand); | |
179 addCommand(REPLRemoteCommand.UP_CMD); | |
180 addCommand(whereCommand); | |
181 | |
182 infoCommand.addCommand(infoBreakCommand); | |
183 infoCommand.addCommand(infoLanguageCommand); | |
184 infoCommand.addCommand(infoSetCommand); | |
185 | |
186 truffleCommand.addCommand(truffleASTCommand); | |
187 truffleCommand.addCommand(truffleNodeCommand); | |
188 truffleCommand.addCommand(truffleSubtreeCommand); | |
189 | |
190 addOption(astDepthOption); | |
191 addOption(autoASTOption); | |
192 addOption(autoNodeOption); | |
193 addOption(autoSubtreeOption); | |
194 addOption(autoWhereOption); | |
195 addOption(listSizeOption); | |
196 addOption(traceMessagesOption); | |
197 addOption(verboseBreakpointInfoOption); | |
198 } | |
199 | |
200 public void start() { | |
201 | |
202 REPLMessage startReply = replServer.start(); | |
203 | |
204 if (startReply.get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { | |
205 clientContext.displayFailReply(startReply.get(REPLMessage.DISPLAY_MSG)); | |
206 throw new RuntimeException("Can't start REPL server"); | |
207 } | |
208 | |
209 this.clientContext = new ClientContextImpl(null, null); | |
210 | |
211 try { | |
212 clientContext.startSession(); | |
213 } finally { | |
214 clientContext.displayReply("Goodbye from " + executionContext.getLanguageShortName() + "/REPL"); | |
215 } | |
216 | |
217 } | |
218 | |
219 public void addCommand(REPLCommand replCommand) { | |
220 final String commandName = replCommand.getCommand(); | |
221 final String abbreviation = replCommand.getAbbreviation(); | |
222 | |
223 commandNames.add(commandName); | |
224 commandMap.put(commandName, replCommand); | |
225 if (abbreviation != null) { | |
226 commandMap.put(abbreviation, replCommand); | |
227 } | |
228 } | |
229 | |
230 private class ClientContextImpl implements REPLClientContext { | |
231 | |
232 private final ClientContextImpl predecessor; | |
233 private final int level; | |
234 | |
235 // Information about where the execution is halted | |
236 /** The source where execution, if any, is halted; null if none. */ | |
237 private Source haltedSource = null; | |
238 /** The line number where execution, if any, is halted; 0 if none. */ | |
239 private int haltedLineNumber = 0; | |
240 /** The stack where execution, if any, is halted; null if none. Evaluated lazily. */ | |
241 private List<REPLFrame> frames = null; | |
242 | |
243 /** The frame number currently selected by user. */ | |
244 private int selectedFrameNumber = 0; | |
245 | |
246 private String currentPrompt; | |
247 | |
248 /** | |
249 * Create a new context on the occasion of an execution halting. | |
250 */ | |
251 public ClientContextImpl(ClientContextImpl predecessor, REPLMessage message) { | |
252 this.predecessor = predecessor; | |
253 this.level = predecessor == null ? 0 : predecessor.level + 1; | |
254 | |
255 if (message != null) { | |
256 try { | |
257 this.haltedSource = Source.fromFileName(message.get(REPLMessage.SOURCE_NAME)); | |
258 selectedSource = this.haltedSource; | |
259 try { | |
260 haltedLineNumber = Integer.parseInt(message.get(REPLMessage.LINE_NUMBER)); | |
261 } catch (NumberFormatException e) { | |
262 haltedLineNumber = 0; | |
263 } | |
264 } catch (IOException e1) { | |
265 this.haltedSource = null; | |
266 this.haltedLineNumber = 0; | |
267 } | |
268 } | |
269 updatePrompt(); | |
270 } | |
271 | |
272 private void selectSource(String fileName) { | |
273 try { | |
274 selectedSource = Source.fromFileName(fileName); | |
275 } catch (IOException e1) { | |
276 selectedSource = null; | |
277 } | |
278 updatePrompt(); | |
279 } | |
280 | |
281 private void updatePrompt() { | |
282 if (level == 0) { | |
283 // 0-level context; no executions halted. | |
284 if (selectedSource == null) { | |
285 final String languageName = executionContext.getLanguageShortName(); | |
286 currentPrompt = languageName == null ? "() " : "(" + languageName + ") "; | |
287 } else { | |
288 currentPrompt = "(" + selectedSource.getShortName() + ") "; | |
289 } | |
290 } else if (selectedSource != null && selectedSource != haltedSource) { | |
291 // User is focusing somewhere else than the current locn; show no line number. | |
292 final StringBuilder sb = new StringBuilder(); | |
293 sb.append("(<" + Integer.toString(level) + "> "); | |
294 sb.append(selectedSource.getShortName()); | |
295 sb.append(") "); | |
296 currentPrompt = sb.toString(); | |
297 } else { | |
298 // Prompt reveals where currently halted. | |
299 final StringBuilder sb = new StringBuilder(); | |
300 sb.append("(<" + Integer.toString(level) + "> "); | |
301 sb.append(haltedSource == null ? "??" : haltedSource.getShortName()); | |
302 if (haltedLineNumber > 0) { | |
303 sb.append(":" + Integer.toString(haltedLineNumber)); | |
304 } | |
305 sb.append(") "); | |
306 currentPrompt = sb.toString(); | |
307 } | |
308 | |
309 } | |
310 | |
311 public Source source() { | |
312 return haltedSource; | |
313 } | |
314 | |
315 public int lineNumber() { | |
316 return haltedLineNumber; | |
317 } | |
318 | |
319 public List<REPLFrame> frames() { | |
320 if (frames == null) { | |
321 final REPLMessage request = new REPLMessage(REPLMessage.OP, REPLMessage.BACKTRACE); | |
322 final REPLMessage[] replies = sendToServer(request); | |
323 if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { | |
324 return null; | |
325 } | |
326 frames = new ArrayList<>(); | |
327 for (REPLMessage reply : replies) { | |
328 final int index = reply.getIntValue(REPLMessage.FRAME_NUMBER); | |
329 final String locationFilePath = reply.get(REPLMessage.FILE_PATH); | |
330 final Integer locationLineNumber = reply.getIntValue(REPLMessage.LINE_NUMBER); | |
331 final String locationDescription = reply.get(REPLMessage.SOURCE_LOCATION); | |
332 final String name = reply.get(REPLMessage.METHOD_NAME); | |
333 final String sourceLineText = reply.get(REPLMessage.SOURCE_LINE_TEXT); | |
334 frames.add(new REPLFrameImpl(index, locationFilePath, locationLineNumber, locationDescription, name, sourceLineText)); | |
335 } | |
336 frames = Collections.unmodifiableList(frames); | |
337 } | |
338 return frames; | |
339 } | |
340 | |
341 public int level() { | |
342 return this.level; | |
343 } | |
344 | |
345 public Source getSelectedSource() { | |
346 return selectedSource == null ? haltedSource : selectedSource; | |
347 } | |
348 | |
349 public int getSelectedFrameNumber() { | |
350 return selectedFrameNumber; | |
351 } | |
352 | |
353 public String stringQuery(String op) { | |
354 assert op != null; | |
355 REPLMessage request = null; | |
356 switch (op) { | |
357 case REPLMessage.TRUFFLE_AST: | |
358 request = truffleASTCommand.createRequest(clientContext, NULL_ARGS); | |
359 break; | |
360 case REPLMessage.TRUFFLE_SUBTREE: | |
361 request = truffleSubtreeCommand.createRequest(clientContext, NULL_ARGS); | |
362 break; | |
363 default: | |
364 request = new REPLMessage(); | |
365 request.put(REPLMessage.OP, op); | |
366 } | |
367 if (request == null) { | |
368 return null; | |
369 } | |
370 final REPLMessage[] replies = sendToServer(request); | |
371 if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { | |
372 return null; | |
373 } | |
374 return replies[0].get(REPLMessage.DISPLAY_MSG); | |
375 } | |
376 | |
377 public void selectFrameNumber(int frameNumber) { | |
378 this.selectedFrameNumber = frameNumber; | |
379 } | |
380 | |
381 void displayWhere() { | |
382 if (level == 0) { | |
383 displayFailReply("no active execution"); | |
384 return; | |
385 } | |
386 | |
387 Source whereSource = null; | |
388 int whereLineNumber = 0; | |
389 | |
390 if (selectedFrameNumber == 0) { | |
391 whereSource = haltedSource; | |
392 whereLineNumber = haltedLineNumber; | |
393 } else { | |
394 final REPLFrame frame = frames().get(selectedFrameNumber); | |
395 final String locationFileName = frame.locationFilePath(); | |
396 if (locationFileName != null) { | |
397 try { | |
398 whereSource = Source.fromFileName(locationFileName); | |
399 } catch (IOException e) { | |
400 } | |
401 } | |
402 whereLineNumber = frame.locationLineNumber(); | |
403 } | |
404 if (whereSource == null) { | |
405 displayFailReply("Frame " + selectedFrameNumber + ": source unavailable"); | |
406 return; | |
407 } | |
408 final int listSize = listSizeOption.getInt(); | |
409 | |
410 final int fileLineCount = whereSource.getLineCount(); | |
411 final String code = whereSource.getCode(); | |
412 | |
413 writer.println("Frame " + selectedFrameNumber + ": " + whereSource.getShortName() + "\n"); | |
414 final int halfListSize = listSize / 2; | |
415 final int startLineNumber = Math.max(1, whereLineNumber - halfListSize); | |
416 final int lastLineNumber = Math.min(startLineNumber + listSize - 1, fileLineCount); | |
417 for (int line = startLineNumber; line <= lastLineNumber; line++) { | |
418 final int offset = whereSource.getLineStartOffset(line); | |
419 final String lineText = code.substring(offset, offset + whereSource.getLineLength(line)); | |
420 if (line == whereLineNumber) { | |
421 writer.format(CODE_LINE_BREAK_FORMAT, line, lineText); | |
422 } else { | |
423 writer.format(CODE_LINE_FORMAT, line, lineText); | |
424 } | |
425 } | |
426 } | |
427 | |
428 public void displayStack() { | |
429 final List<REPLFrame> frameList = frames(); | |
430 if (frameList == null) { | |
431 writer.println("<empty stack>"); | |
432 } else { | |
433 for (REPLFrame frame : frameList) { | |
434 String sourceLineText = frame.sourceLineText(); | |
435 if (sourceLineText == null) { | |
436 sourceLineText = "<??>"; | |
437 } | |
438 if (frame.index() == selectedFrameNumber) { | |
439 writer.format(STACK_FRAME_SELECTED_FORMAT, frame.index(), frame.locationDescription(), frame.name(), sourceLineText); | |
440 } else { | |
441 writer.format(STACK_FRAME_FORMAT, frame.index(), frame.locationDescription(), frame.name(), sourceLineText); | |
442 } | |
443 } | |
444 } | |
445 } | |
446 | |
447 public void displayInfo(String message) { | |
448 writer.format(INFO_LINE_FORMAT, message); | |
449 } | |
450 | |
451 public void displayReply(String message) { | |
452 writer.println(REPLY_PREFIX + message); | |
453 } | |
454 | |
455 public void displayFailReply(String message) { | |
456 writer.println(FAIL_PREFIX + message); | |
457 } | |
458 | |
459 public void displayWarnings(String warnings) { | |
460 for (String warning : warnings.split("\\n")) { | |
461 writer.println(WARNING_PREFIX + warning); | |
462 } | |
463 } | |
464 | |
465 public void traceMessage(String message) { | |
466 writer.println(TRACE_PREFIX + message); | |
467 } | |
468 | |
469 public void startSession() { | |
470 | |
471 while (true) { | |
472 try { | |
473 String[] args; | |
474 String line = reader.readLine(currentPrompt).trim(); | |
475 if (line.startsWith("eval ")) { | |
476 args = new String[]{"eval", line.substring(5)}; | |
477 } else { | |
478 args = line.split("[ \t]+"); | |
479 } | |
480 if (args.length == 0) { | |
481 break; | |
482 } | |
483 final String cmd = args[0]; | |
484 | |
485 if (cmd.isEmpty()) { | |
486 continue; | |
487 } | |
488 | |
489 REPLCommand command = commandMap.get(cmd); | |
490 while (command instanceof REPLIndirectCommand) { | |
491 if (traceMessagesOption.getBool()) { | |
492 traceMessage("Executing indirect: " + command.getCommand()); | |
493 } | |
494 command = ((REPLIndirectCommand) command).getCommand(args); | |
495 } | |
496 if (command == null) { | |
497 clientContext.displayFailReply("Unrecognized command \"" + cmd + "\""); | |
498 continue; | |
499 } | |
500 if (command instanceof REPLLocalCommand) { | |
501 if (traceMessagesOption.getBool()) { | |
502 traceMessage("Executing local: " + command.getCommand()); | |
503 } | |
504 ((REPLLocalCommand) command).execute(args); | |
505 | |
506 } else if (command instanceof REPLRemoteCommand) { | |
507 final REPLRemoteCommand remoteCommand = (REPLRemoteCommand) command; | |
508 | |
509 final REPLMessage request = remoteCommand.createRequest(clientContext, args); | |
510 if (request == null) { | |
511 continue; | |
512 } | |
513 | |
514 REPLMessage[] replies = sendToServer(request); | |
515 | |
516 remoteCommand.processReply(clientContext, replies); | |
517 } else { | |
518 assert false; // Should not happen. | |
519 } | |
520 | |
521 } catch (REPLContinueException ex) { | |
522 break; | |
523 } catch (IOException e) { | |
524 e.printStackTrace(); | |
525 } | |
526 } | |
527 | |
528 } | |
529 | |
530 private REPLMessage[] sendToServer(REPLMessage request) { | |
531 if (traceMessagesOption.getBool()) { | |
532 clientContext.traceMessage("Sever request:"); | |
533 request.print(writer, " "); | |
534 } | |
535 | |
536 REPLMessage[] replies = replServer.receive(request); | |
537 | |
538 assert replies != null && replies.length > 0; | |
539 if (traceMessagesOption.getBool()) { | |
540 if (replies.length > 1) { | |
541 clientContext.traceMessage("Received " + replies.length + " server replies"); | |
542 int replyCount = 0; | |
543 for (REPLMessage reply : replies) { | |
544 clientContext.traceMessage("Server Reply " + replyCount++ + ":"); | |
545 reply.print(writer, " "); | |
546 } | |
547 } else { | |
548 clientContext.traceMessage("Received reply:"); | |
549 replies[0].print(writer, " "); | |
550 } | |
551 } | |
552 return replies; | |
553 } | |
554 | |
555 private final class REPLFrameImpl implements REPLFrame { | |
556 | |
557 private final int index; | |
558 private final String locationFilePath; | |
559 private final Integer locationLineNumber; | |
560 private final String locationDescription; | |
561 private final String name; | |
562 private final String sourceLineText; | |
563 | |
564 REPLFrameImpl(int index, String locationFilePath, Integer locationLineNumber, String locationDescription, String name, String sourceLineText) { | |
565 this.index = index; | |
566 this.locationFilePath = locationFilePath; | |
567 this.locationLineNumber = locationLineNumber; | |
568 this.locationDescription = locationDescription; | |
569 this.name = name; | |
570 this.sourceLineText = sourceLineText; | |
571 } | |
572 | |
573 public int index() { | |
574 return index; | |
575 } | |
576 | |
577 public String locationFilePath() { | |
578 return locationFilePath; | |
579 } | |
580 | |
581 public Integer locationLineNumber() { | |
582 return locationLineNumber; | |
583 } | |
584 | |
585 public String locationDescription() { | |
586 return locationDescription; | |
587 } | |
588 | |
589 public String name() { | |
590 return name; | |
591 } | |
592 | |
593 public String sourceLineText() { | |
594 return sourceLineText; | |
595 } | |
596 | |
597 } | |
598 | |
599 } | |
600 | |
601 // Cheating with synchrony: asynchronous replies should arrive here, but don't. | |
602 @Override | |
603 public REPLMessage receive(REPLMessage request) { | |
604 final String result = request.get("result"); | |
605 clientContext.displayReply(result != null ? result : request.toString()); | |
606 return null; | |
607 } | |
608 | |
609 /** | |
610 * Cheating with synchrony: take a direct call from the server that execution has halted and | |
611 * we've entered a nested debugging context. | |
612 */ | |
613 public void halted(REPLMessage message) { | |
614 | |
615 // Push a new context for where we've stopped. | |
616 clientContext = new ClientContextImpl(clientContext, message); | |
617 final String warnings = message.get(REPLMessage.WARNINGS); | |
618 if (warnings != null) { | |
619 clientContext.displayWarnings(warnings); | |
620 } | |
621 if (autoWhereOption.getBool()) { | |
622 clientContext.displayWhere(); | |
623 } | |
624 if (autoNodeOption.getBool()) { | |
625 final String result = clientContext.stringQuery(REPLMessage.TRUFFLE_NODE); | |
626 if (result != null) { | |
627 displayTruffleNode(result); | |
628 } | |
629 } | |
630 if (autoASTOption.getBool()) { | |
631 final String result = clientContext.stringQuery(REPLMessage.TRUFFLE_AST); | |
632 if (result != null) { | |
633 displayTruffleAST(result); | |
634 } | |
635 } | |
636 if (autoSubtreeOption.getBool()) { | |
637 final String result = clientContext.stringQuery(REPLMessage.TRUFFLE_SUBTREE); | |
638 if (result != null) { | |
639 displayTruffleSubtree(result); | |
640 } | |
641 } | |
642 | |
643 try { | |
644 clientContext.startSession(); | |
645 } finally { | |
646 | |
647 // To continue execution, pop the context and return | |
648 this.clientContext = clientContext.predecessor; | |
649 } | |
650 } | |
651 | |
652 /** | |
653 * A command that can be executed without (direct) communication with the server; it may rely on | |
654 * some other method that goes to the server for information. | |
655 */ | |
656 private abstract class REPLLocalCommand extends REPLCommand { | |
657 | |
658 public REPLLocalCommand(String command, String abbreviation, String description) { | |
659 super(command, abbreviation, description); | |
660 } | |
661 | |
662 abstract void execute(String[] args); | |
663 } | |
664 | |
665 /** | |
666 * A command that redirects to other commands, based on arguments. | |
667 */ | |
668 private abstract class REPLIndirectCommand extends REPLCommand { | |
669 | |
670 public REPLIndirectCommand(String command, String abbreviation, String description) { | |
671 super(command, abbreviation, description); | |
672 } | |
673 | |
674 abstract void addCommand(REPLCommand command); | |
675 | |
676 abstract REPLCommand getCommand(String[] args); | |
677 } | |
678 | |
679 private final REPLCommand backtraceCommand = new REPLLocalCommand("backtrace", "bt", "Display current stack") { | |
680 | |
681 @Override | |
682 void execute(String[] args) { | |
683 if (clientContext.level == 0) { | |
684 clientContext.displayFailReply("no active execution"); | |
685 } else { | |
686 clientContext.displayStack(); | |
687 } | |
688 } | |
689 }; | |
690 | |
691 private final REPLCommand evalCommand = new REPLRemoteCommand("eval", null, "Evaluate a string, in context of the current frame if any") { | |
692 | |
693 private int evalCounter = 0; | |
694 | |
695 @Override | |
696 public REPLMessage createRequest(REPLClientContext context, String[] args) { | |
697 if (args.length > 1) { | |
698 final String code = args[1]; | |
699 if (!code.isEmpty()) { | |
700 // Create a fake entry in the file maps and cache, based on this unique name | |
701 final String fakeFileName = "<eval" + ++evalCounter + ">"; | |
702 Source.fromNamedText(fakeFileName, code); | |
703 final REPLMessage request = new REPLMessage(); | |
704 request.put(REPLMessage.OP, REPLMessage.EVAL); | |
705 request.put(REPLMessage.CODE, code); | |
706 request.put(REPLMessage.SOURCE_NAME, fakeFileName); | |
707 if (clientContext.level > 0) { | |
708 // Specify a requested execution context, if one exists; otherwise top level | |
709 request.put(REPLMessage.FRAME_NUMBER, Integer.toString(context.getSelectedFrameNumber())); | |
710 } | |
711 return request; | |
712 } | |
713 } | |
714 return null; | |
715 } | |
716 }; | |
717 | |
718 private final REPLCommand fileCommand = new REPLRemoteCommand("file", null, "Set/display current file for viewing") { | |
719 | |
720 final String[] help = {"file: display current file path", "file <filename>: Set file to be current file for viewing"}; | |
721 | |
722 @Override | |
723 public String[] getHelp() { | |
724 return help; | |
725 } | |
726 | |
727 @Override | |
728 public REPLMessage createRequest(REPLClientContext context, String[] args) { | |
729 if (args.length == 1) { | |
730 final Source source = clientContext.getSelectedSource(); | |
731 if (source == null) { | |
732 clientContext.displayFailReply("no file currently selected"); | |
733 } else { | |
734 clientContext.displayReply(source.getPath()); | |
735 } | |
736 return null; | |
737 } | |
738 final REPLMessage request = new REPLMessage(); | |
739 request.put(REPLMessage.OP, REPLMessage.FILE); | |
740 request.put(REPLMessage.SOURCE_NAME, args[1]); | |
741 return request; | |
742 } | |
743 | |
744 @Override | |
745 void processReply(REPLClientContext context, REPLMessage[] replies) { | |
746 REPLMessage firstReply = replies[0]; | |
747 | |
748 if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { | |
749 final String result = firstReply.get(REPLMessage.DISPLAY_MSG); | |
750 clientContext.displayFailReply(result != null ? result : firstReply.toString()); | |
751 return; | |
752 } | |
753 final String fileName = firstReply.get(REPLMessage.SOURCE_NAME); | |
754 final String path = firstReply.get(REPLMessage.FILE_PATH); | |
755 clientContext.selectSource(path == null ? fileName : path); | |
756 clientContext.displayReply(clientContext.getSelectedSource().getPath()); | |
757 | |
758 for (int i = 1; i < replies.length; i++) { | |
759 REPLMessage reply = replies[i]; | |
760 final String result = reply.get(REPLMessage.DISPLAY_MSG); | |
761 clientContext.displayInfo(result != null ? result : reply.toString()); | |
762 } | |
763 } | |
764 | |
765 }; | |
766 | |
767 private final REPLCommand helpCommand = new REPLLocalCommand("help", null, "Describe commands") { | |
768 | |
769 final String[] help = {"help: list available commands", "help <command>: additional information about <command>"}; | |
770 | |
771 @Override | |
772 public String[] getHelp() { | |
773 return help; | |
774 } | |
775 | |
776 @Override | |
777 public void execute(String[] args) { | |
778 | |
779 if (args.length == 1) { | |
780 clientContext.displayReply("Available commands:"); | |
781 for (String commandName : commandNames) { | |
782 final REPLCommand command = commandMap.get(commandName); | |
783 if (command == null) { | |
784 clientContext.displayInfo(commandName + ": Error, no implementation for command"); | |
785 } else { | |
786 final String abbrev = command.getAbbreviation(); | |
787 if (abbrev == null) { | |
788 clientContext.displayInfo(commandName + ": " + command.getDescription()); | |
789 } else { | |
790 clientContext.displayInfo(commandName + "(" + abbrev + "): " + command.getDescription()); | |
791 } | |
792 } | |
793 } | |
794 } else { | |
795 final String cmdName = args[1]; | |
796 final REPLCommand cmd = commandMap.get(cmdName); | |
797 if (cmd == null) { | |
798 clientContext.displayReply("command \"" + cmdName + "\" not recognized"); | |
799 } else { | |
800 final String[] helpLines = cmd.getHelp(); | |
801 if (helpLines == null) { | |
802 clientContext.displayReply("\"" + cmdName + "\":"); | |
803 } else if (helpLines.length == 1) { | |
804 clientContext.displayInfo(helpLines[0]); | |
805 } else { | |
806 clientContext.displayReply("\"" + cmdName + "\":"); | |
807 for (String line : helpLines) { | |
808 clientContext.displayInfo(line); | |
809 } | |
810 } | |
811 } | |
812 } | |
813 } | |
814 }; | |
815 | |
816 private final REPLIndirectCommand infoCommand = new REPLIndirectCommand(REPLMessage.INFO, null, "Additional information on topics") { | |
817 | |
818 // "Info" commands | |
819 private final Map<String, REPLCommand> infoCommandMap = new HashMap<>(); | |
820 private final Collection<String> infoCommandNames = new TreeSet<>(); | |
821 | |
822 @Override | |
823 public String[] getHelp() { | |
824 final ArrayList<String> lines = new ArrayList<>(); | |
825 for (String infoCommandName : infoCommandNames) { | |
826 final REPLCommand cmd = infoCommandMap.get(infoCommandName); | |
827 if (cmd == null) { | |
828 lines.add("\"" + REPLMessage.INFO + " " + infoCommandName + "\" not implemented"); | |
829 } else { | |
830 lines.add("\"" + REPLMessage.INFO + " " + infoCommandName + "\": " + cmd.getDescription()); | |
831 } | |
832 } | |
833 return lines.toArray(new String[0]); | |
834 } | |
835 | |
836 @Override | |
837 void addCommand(REPLCommand replCommand) { | |
838 final String commandName = replCommand.getCommand(); | |
839 final String abbreviation = replCommand.getAbbreviation(); | |
840 | |
841 infoCommandNames.add(commandName); | |
842 infoCommandMap.put(commandName, replCommand); | |
843 if (abbreviation != null) { | |
844 infoCommandMap.put(abbreviation, replCommand); | |
845 } | |
846 } | |
847 | |
848 @Override | |
849 REPLCommand getCommand(String[] args) { | |
850 if (args.length == 1) { | |
851 clientContext.displayFailReply("info topic not specified; try \"help info\""); | |
852 return null; | |
853 } | |
854 final String topic = args[1]; | |
855 REPLCommand command = infoCommandMap.get(topic); | |
856 if (command == null) { | |
857 clientContext.displayFailReply("topic \"" + topic + "\" not recognized"); | |
858 return null; | |
859 } | |
860 | |
861 return command; | |
862 } | |
863 | |
864 }; | |
865 | |
866 private final REPLCommand infoBreakCommand = new REPLRemoteCommand("breakpoint", "break", "info about breakpoints") { | |
867 | |
868 @Override | |
869 public REPLMessage createRequest(REPLClientContext context, String[] args) { | |
870 final REPLMessage request = new REPLMessage(); | |
871 request.put(REPLMessage.OP, REPLMessage.BREAKPOINT_INFO); | |
872 return request; | |
873 } | |
874 | |
875 @Override | |
876 void processReply(REPLClientContext context, REPLMessage[] replies) { | |
877 if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { | |
878 clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG)); | |
879 } else { | |
880 Arrays.sort(replies, new Comparator<REPLMessage>() { | |
881 | |
882 public int compare(REPLMessage o1, REPLMessage o2) { | |
883 try { | |
884 final int n1 = Integer.parseInt(o1.get(REPLMessage.BREAKPOINT_ID)); | |
885 final int n2 = Integer.parseInt(o2.get(REPLMessage.BREAKPOINT_ID)); | |
886 return Integer.compare(n1, n2); | |
887 } catch (Exception ex) { | |
888 } | |
889 return 0; | |
890 } | |
891 | |
892 }); | |
893 clientContext.displayReply("Breakpoints set:"); | |
894 for (REPLMessage message : replies) { | |
895 final StringBuilder sb = new StringBuilder(); | |
896 | |
897 sb.append(Integer.parseInt(message.get(REPLMessage.BREAKPOINT_ID)) + ": "); | |
898 sb.append("@" + message.get(REPLMessage.INFO_VALUE)); | |
899 sb.append(" (state=" + message.get(REPLMessage.BREAKPOINT_STATE)); | |
900 if (verboseBreakpointInfoOption.getBool()) { | |
901 sb.append(", group=" + Integer.parseInt(message.get(REPLMessage.BREAKPOINT_GROUP_ID))); | |
902 sb.append(", hits=" + Integer.parseInt(message.get(REPLMessage.BREAKPOINT_HIT_COUNT))); | |
903 sb.append(", ignore=" + Integer.parseInt(message.get(REPLMessage.BREAKPOINT_IGNORE_COUNT))); | |
904 } | |
905 final String condition = message.get(REPLMessage.BREAKPOINT_CONDITION); | |
906 if (condition != null) { | |
907 sb.append(", condition=\"" + condition + "\""); | |
908 } | |
909 sb.append(")"); | |
910 clientContext.displayInfo(sb.toString()); | |
911 } | |
912 } | |
913 } | |
914 }; | |
915 | |
916 private final REPLCommand infoLanguageCommand = new REPLRemoteCommand("language", "lang", "language and implementation details") { | |
917 | |
918 final String[] help = {"info language: list details about the language implementation"}; | |
919 | |
920 @Override | |
921 public String[] getHelp() { | |
922 return help; | |
923 } | |
924 | |
925 @Override | |
926 public REPLMessage createRequest(REPLClientContext context, String[] args) { | |
927 final REPLMessage request = new REPLMessage(); | |
928 request.put(REPLMessage.OP, REPLMessage.INFO); | |
929 request.put(REPLMessage.TOPIC, REPLMessage.LANGUAGE); | |
930 return request; | |
931 } | |
932 | |
933 @Override | |
934 void processReply(REPLClientContext context, REPLMessage[] replies) { | |
935 if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { | |
936 clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG)); | |
937 } else { | |
938 clientContext.displayReply("Language info:"); | |
939 for (REPLMessage message : replies) { | |
940 final StringBuilder sb = new StringBuilder(); | |
941 sb.append(message.get(REPLMessage.INFO_KEY)); | |
942 sb.append(": "); | |
943 sb.append(message.get(REPLMessage.INFO_VALUE)); | |
944 clientContext.displayInfo(sb.toString()); | |
945 } | |
946 } | |
947 } | |
948 }; | |
949 | |
950 private final REPLCommand infoSetCommand = new REPLLocalCommand("set", null, "info about settings") { | |
951 | |
952 final String[] help = {"info sets: list local options that can be set"}; | |
953 | |
954 @Override | |
955 public String[] getHelp() { | |
956 return help; | |
957 } | |
958 | |
959 @Override | |
960 public void execute(String[] args) { | |
961 | |
962 clientContext.displayReply("Settable options:"); | |
963 | |
964 for (String optionName : optionNames) { | |
965 final LocalOption localOption = localOptions.get(optionName); | |
966 if (localOption == null) { | |
967 clientContext.displayInfo(localOption + ": Error, no implementation for option"); | |
968 } else { | |
969 clientContext.displayInfo(optionName + "=" + localOption.getValue() + ": " + localOption.getDescription()); | |
970 } | |
971 } | |
972 } | |
973 }; | |
974 | |
975 private final REPLCommand listCommand = new REPLLocalCommand("list", null, "Display selected source file") { | |
976 | |
977 final String[] help = {"list: list <listsize> lines of selected file (see option \"listsize\")", "list all: list all lines", "list <n>: list <listsize> lines centered around line <n>"}; | |
978 | |
979 private Source lastListedSource = null; | |
980 | |
981 private int nextLineToList = 1; | |
982 | |
983 @Override | |
984 public String[] getHelp() { | |
985 return help; | |
986 } | |
987 | |
988 @Override | |
989 public void execute(String[] args) { | |
990 final Source source = clientContext.getSelectedSource(); | |
991 if (source == null) { | |
992 clientContext.displayFailReply("No selected file"); | |
993 reset(); | |
994 return; | |
995 } | |
996 final int listSize = listSizeOption.getInt(); | |
997 | |
998 if (args.length == 1) { | |
999 if (!source.equals(lastListedSource)) { | |
1000 reset(); | |
1001 } else if (nextLineToList > source.getLineCount()) { | |
1002 reset(); | |
1003 } | |
1004 final int lastListedLine = printLines(source, nextLineToList, listSize); | |
1005 lastListedSource = source; | |
1006 nextLineToList = lastListedLine > source.getLineCount() ? 1 : lastListedLine + 1; | |
1007 } else if (args.length == 2) { | |
1008 reset(); | |
1009 if (args[1].equals("all")) { | |
1010 printLines(source, 1, source.getLineCount()); | |
1011 } else { | |
1012 try { | |
1013 final int line = Integer.parseInt(args[1]); | |
1014 final int halfListSize = listSize / 2; | |
1015 final int start = Math.max(1, line - halfListSize); | |
1016 final int count = Math.min(source.getLineCount() + 1 - start, listSize); | |
1017 printLines(source, start, count); | |
1018 } catch (NumberFormatException e) { | |
1019 clientContext.displayFailReply("\"" + args[1] + "\" not recognized"); | |
1020 } | |
1021 | |
1022 } | |
1023 } | |
1024 } | |
1025 | |
1026 private int printLines(Source printSource, int start, int listSize) { | |
1027 | |
1028 clientContext.displayReply(printSource.getShortName() + ":"); | |
1029 final int lastLineNumber = Math.min(start + listSize - 1, printSource.getLineCount()); | |
1030 for (int line = start; line <= lastLineNumber; line++) { | |
1031 writer.format(CODE_LINE_FORMAT, line, printSource.getCode(line)); | |
1032 } | |
1033 return lastLineNumber; | |
1034 } | |
1035 | |
1036 /** | |
1037 * Forget where we were in a sequence of list commands with no arguments | |
1038 */ | |
1039 private void reset() { | |
1040 lastListedSource = clientContext.getSelectedSource(); | |
1041 nextLineToList = 1; | |
1042 } | |
1043 }; | |
1044 | |
1045 private final REPLCommand quitCommand = new REPLRemoteCommand("quit", "q", "Quit execution and REPL") { | |
1046 | |
1047 @Override | |
1048 public REPLMessage createRequest(REPLClientContext context, String[] args) { | |
1049 final REPLMessage request = new REPLMessage(); | |
1050 request.put(REPLMessage.OP, REPLMessage.QUIT); | |
1051 return request; | |
1052 } | |
1053 | |
1054 }; | |
1055 | |
1056 private final REPLCommand setCommand = new REPLLocalCommand("set", null, "set <option>=<value>") { | |
1057 | |
1058 @Override | |
1059 public String[] getHelp() { | |
1060 return new String[]{"Sets an option \"set <option-name>=<value>\"; see also \"info set\""}; | |
1061 } | |
1062 | |
1063 @Override | |
1064 public void execute(String[] args) { | |
1065 REPLMessage request = null; | |
1066 if (args.length == 1) { | |
1067 clientContext.displayFailReply("No option specified, try \"help set\""); | |
1068 } else if (args.length == 2) { | |
1069 String[] split = new String[0]; | |
1070 try { | |
1071 split = args[1].split("="); | |
1072 } catch (Exception ex) { | |
1073 } | |
1074 if (split.length == 0) { | |
1075 clientContext.displayFailReply("Arguments not understood, try \"help set\""); | |
1076 } else if (split.length == 1) { | |
1077 clientContext.displayFailReply("No option value specified, try \"help set\""); | |
1078 } else if (split.length > 2) { | |
1079 clientContext.displayFailReply("Arguments not understood, try \"help set\""); | |
1080 } else { | |
1081 final String optionName = split[0]; | |
1082 final String newValue = split[1]; | |
1083 final LocalOption localOption = localOptions.get(optionName); | |
1084 if (localOption != null) { | |
1085 if (!localOption.setValue(newValue)) { | |
1086 clientContext.displayFailReply("Invalid option value \"" + newValue + "\""); | |
1087 } | |
1088 clientContext.displayInfo(localOption.name + " = " + localOption.getValue()); | |
1089 } else { | |
1090 request = new REPLMessage(); | |
1091 request.put(REPLMessage.OP, REPLMessage.SET); | |
1092 request.put(REPLMessage.OPTION, optionName); | |
1093 request.put(REPLMessage.VALUE, newValue); | |
1094 } | |
1095 } | |
1096 } else { | |
1097 clientContext.displayFailReply("Arguments not understood, try \"help set\""); | |
1098 } | |
1099 } | |
1100 }; | |
1101 | |
1102 private final REPLIndirectCommand truffleCommand = new REPLIndirectCommand(REPLMessage.TRUFFLE, "t", "Access to Truffle internals") { | |
1103 | |
1104 // "Truffle" commands | |
1105 private final Map<String, REPLCommand> truffleCommandMap = new HashMap<>(); | |
1106 private final Collection<String> truffleCommandNames = new TreeSet<>(); | |
1107 | |
1108 @Override | |
1109 public String[] getHelp() { | |
1110 final ArrayList<String> lines = new ArrayList<>(); | |
1111 for (String truffleCommandName : truffleCommandNames) { | |
1112 final REPLCommand cmd = truffleCommandMap.get(truffleCommandName); | |
1113 if (cmd == null) { | |
1114 lines.add("\"" + REPLMessage.TRUFFLE + " " + truffleCommandName + "\" not implemented"); | |
1115 } else { | |
1116 for (String line : cmd.getHelp()) { | |
1117 lines.add(line); | |
1118 } | |
1119 } | |
1120 } | |
1121 return lines.toArray(new String[0]); | |
1122 } | |
1123 | |
1124 @Override | |
1125 void addCommand(REPLCommand replCommand) { | |
1126 final String commandName = replCommand.getCommand(); | |
1127 final String abbreviation = replCommand.getAbbreviation(); | |
1128 | |
1129 truffleCommandNames.add(commandName); | |
1130 truffleCommandMap.put(commandName, replCommand); | |
1131 if (abbreviation != null) { | |
1132 truffleCommandMap.put(abbreviation, replCommand); | |
1133 } | |
1134 } | |
1135 | |
1136 @Override | |
1137 REPLCommand getCommand(String[] args) { | |
1138 if (args.length == 1) { | |
1139 clientContext.displayFailReply("truffle request not specified; try \"help truffle\""); | |
1140 return null; | |
1141 } | |
1142 final String topic = args[1]; | |
1143 REPLCommand command = truffleCommandMap.get(topic); | |
1144 if (command == null) { | |
1145 clientContext.displayFailReply("truffle request \"" + topic + "\" not recognized"); | |
1146 return null; | |
1147 } | |
1148 return command; | |
1149 } | |
1150 }; | |
1151 | |
1152 private final REPLRemoteCommand truffleASTCommand = new REPLRemoteCommand("ast", null, "print the AST that contains the current node") { | |
1153 | |
1154 final String[] help = {"truffle ast: print the AST subtree that contains current node (see \"set treedepth\")", | |
1155 "truffle ast <n>: print the AST subtree that contains current node to a maximum depth of <n>"}; | |
1156 | |
1157 @Override | |
1158 public String[] getHelp() { | |
1159 return help; | |
1160 } | |
1161 | |
1162 @Override | |
1163 public REPLMessage createRequest(REPLClientContext context, String[] args) { | |
1164 if (clientContext.level() == 0) { | |
1165 context.displayFailReply("no active execution"); | |
1166 return null; | |
1167 } | |
1168 | |
1169 final REPLMessage request = new REPLMessage(); | |
1170 request.put(REPLMessage.OP, REPLMessage.TRUFFLE); | |
1171 request.put(REPLMessage.TOPIC, REPLMessage.AST); | |
1172 | |
1173 int astDepth = astDepthOption.getInt(); | |
1174 if (args.length > 2) { | |
1175 final String depthText = args[2]; | |
1176 try { | |
1177 astDepth = Integer.parseInt(depthText); | |
1178 } catch (NumberFormatException e) { | |
1179 } | |
1180 } | |
1181 request.put(REPLMessage.AST_DEPTH, Integer.toString(astDepth)); | |
1182 return request; | |
1183 } | |
1184 | |
1185 @Override | |
1186 void processReply(REPLClientContext context, REPLMessage[] replies) { | |
1187 if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { | |
1188 clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG)); | |
1189 } else { | |
1190 clientContext.displayReply("AST containing the Current Node:"); | |
1191 for (REPLMessage message : replies) { | |
1192 for (String line : message.get(REPLMessage.DISPLAY_MSG).split("\n")) { | |
1193 clientContext.displayInfo(line); | |
1194 } | |
1195 } | |
1196 } | |
1197 } | |
1198 }; | |
1199 | |
1200 private void displayTruffleAST(String text) { | |
1201 clientContext.displayReply("AST containing Current Node:"); | |
1202 for (String line : text.split("\n")) { | |
1203 clientContext.displayInfo(line); | |
1204 } | |
1205 } | |
1206 | |
1207 private final REPLRemoteCommand truffleNodeCommand = new REPLRemoteCommand("node", null, "describe current AST node") { | |
1208 | |
1209 final String[] help = {"truffle node: describe the AST node at the current execution context"}; | |
1210 | |
1211 @Override | |
1212 public String[] getHelp() { | |
1213 return help; | |
1214 } | |
1215 | |
1216 @Override | |
1217 public REPLMessage createRequest(REPLClientContext context, String[] args) { | |
1218 if (clientContext.level() == 0) { | |
1219 context.displayFailReply("no active execution"); | |
1220 return null; | |
1221 } | |
1222 final REPLMessage request = new REPLMessage(); | |
1223 request.put(REPLMessage.OP, REPLMessage.TRUFFLE_NODE); | |
1224 return request; | |
1225 } | |
1226 | |
1227 @Override | |
1228 void processReply(REPLClientContext context, REPLMessage[] replies) { | |
1229 if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { | |
1230 clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG)); | |
1231 } else { | |
1232 displayTruffleNode(replies[0].get(REPLMessage.DISPLAY_MSG)); | |
1233 } | |
1234 } | |
1235 }; | |
1236 | |
1237 private void displayTruffleNode(String nodeString) { | |
1238 clientContext.displayReply("Current Node: " + nodeString); | |
1239 } | |
1240 | |
1241 private final REPLRemoteCommand truffleSubtreeCommand = new REPLRemoteCommand("subtree", "sub", "print the AST subtree rooted at the current node") { | |
1242 | |
1243 final String[] help = {"truffle sub: print the AST subtree at the current node (see \"set treedepth\")", "truffle sub <n>: print the AST subtree at the current node to maximum depth <n>", | |
1244 "truffle subtree: print the AST subtree at the current node (see \"set treedepth\")", "truffle sub <n>: print the AST subtree at the current node to maximum depth <n>"}; | |
1245 | |
1246 @Override | |
1247 public String[] getHelp() { | |
1248 return help; | |
1249 } | |
1250 | |
1251 @Override | |
1252 public REPLMessage createRequest(REPLClientContext context, String[] args) { | |
1253 if (clientContext.level() == 0) { | |
1254 context.displayFailReply("no active execution"); | |
1255 return null; | |
1256 } | |
1257 | |
1258 final REPLMessage request = new REPLMessage(); | |
1259 request.put(REPLMessage.OP, REPLMessage.TRUFFLE); | |
1260 request.put(REPLMessage.TOPIC, REPLMessage.SUBTREE); | |
1261 | |
1262 int astDepth = astDepthOption.getInt(); | |
1263 if (args.length > 2) { | |
1264 final String depthText = args[2]; | |
1265 try { | |
1266 astDepth = Integer.parseInt(depthText); | |
1267 } catch (NumberFormatException e) { | |
1268 } | |
1269 } | |
1270 request.put(REPLMessage.AST_DEPTH, Integer.toString(astDepth)); | |
1271 return request; | |
1272 } | |
1273 | |
1274 @Override | |
1275 void processReply(REPLClientContext context, REPLMessage[] replies) { | |
1276 if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { | |
1277 clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG)); | |
1278 } else { | |
1279 clientContext.displayReply("AST subtree at Current Node:"); | |
1280 for (REPLMessage message : replies) { | |
1281 for (String line : message.get(REPLMessage.DISPLAY_MSG).split("\n")) { | |
1282 clientContext.displayInfo(line); | |
1283 } | |
1284 } | |
1285 } | |
1286 } | |
1287 }; | |
1288 | |
1289 private void displayTruffleSubtree(String text) { | |
1290 clientContext.displayReply("AST subtree at Current Node:"); | |
1291 for (String line : text.split("\n")) { | |
1292 clientContext.displayInfo(line); | |
1293 } | |
1294 } | |
1295 | |
1296 private final REPLCommand whereCommand = new REPLLocalCommand("where", null, "Show code around current break location") { | |
1297 | |
1298 @Override | |
1299 public void execute(String[] args) { | |
1300 clientContext.displayWhere(); | |
1301 } | |
1302 }; | |
1303 | |
1304 private abstract static class LocalOption { | |
1305 private final String name; | |
1306 private final String description; | |
1307 | |
1308 protected LocalOption(String name, String description) { | |
1309 this.name = name; | |
1310 this.description = description; | |
1311 } | |
1312 | |
1313 public String getName() { | |
1314 return name; | |
1315 } | |
1316 | |
1317 public String getDescription() { | |
1318 return description; | |
1319 } | |
1320 | |
1321 public abstract boolean setValue(String newValue); | |
1322 | |
1323 public boolean getBool() { | |
1324 assert false; | |
1325 return false; | |
1326 } | |
1327 | |
1328 public int getInt() { | |
1329 assert false; | |
1330 return 0; | |
1331 } | |
1332 | |
1333 public abstract String getValue(); | |
1334 } | |
1335 | |
1336 private static final class BooleanOption extends LocalOption { | |
1337 | |
1338 private Boolean value; | |
1339 | |
1340 public BooleanOption(boolean value, String name, String description) { | |
1341 super(name, description); | |
1342 this.value = value; | |
1343 } | |
1344 | |
1345 @Override | |
1346 public boolean setValue(String newValue) { | |
1347 final Boolean valueOf = Boolean.valueOf(newValue); | |
1348 if (valueOf == null) { | |
1349 return false; | |
1350 } | |
1351 value = valueOf; | |
1352 return true; | |
1353 } | |
1354 | |
1355 @Override | |
1356 public boolean getBool() { | |
1357 return value; | |
1358 } | |
1359 | |
1360 @Override | |
1361 public String getValue() { | |
1362 return value.toString(); | |
1363 } | |
1364 } | |
1365 | |
1366 private static final class IntegerOption extends LocalOption { | |
1367 | |
1368 private Integer value; | |
1369 | |
1370 public IntegerOption(int value, String name, String description) { | |
1371 super(name, description); | |
1372 this.value = value; | |
1373 } | |
1374 | |
1375 @Override | |
1376 public boolean setValue(String newValue) { | |
1377 Integer valueOf; | |
1378 try { | |
1379 valueOf = Integer.valueOf(newValue); | |
1380 } catch (NumberFormatException e) { | |
1381 return false; | |
1382 } | |
1383 value = valueOf; | |
1384 return true; | |
1385 } | |
1386 | |
1387 @Override | |
1388 public int getInt() { | |
1389 return value; | |
1390 } | |
1391 | |
1392 @Override | |
1393 public String getValue() { | |
1394 return value.toString(); | |
1395 } | |
1396 | |
1397 } | |
1398 | |
1399 } |