JShellTool.java revision 3062:15bdc18525ff
1/* 2 * Copyright (c) 2014, 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 26package jdk.internal.jshell.tool; 27 28import java.io.BufferedWriter; 29import java.io.ByteArrayInputStream; 30import java.io.File; 31import java.io.FileNotFoundException; 32import java.io.FileReader; 33import java.io.IOException; 34import java.io.InputStream; 35import java.io.PrintStream; 36import java.io.Reader; 37import java.io.StringReader; 38import java.nio.charset.Charset; 39import java.nio.file.AccessDeniedException; 40import java.nio.file.FileSystems; 41import java.nio.file.Files; 42import java.nio.file.NoSuchFileException; 43import java.nio.file.Path; 44import java.nio.file.Paths; 45import java.util.ArrayList; 46import java.util.Arrays; 47import java.util.Collections; 48import java.util.Iterator; 49import java.util.LinkedHashMap; 50import java.util.LinkedHashSet; 51import java.util.List; 52import java.util.Map; 53import java.util.Map.Entry; 54import java.util.Scanner; 55import java.util.Set; 56import java.util.function.Consumer; 57import java.util.function.Predicate; 58import java.util.prefs.Preferences; 59import java.util.regex.Matcher; 60import java.util.regex.Pattern; 61import java.util.stream.Collectors; 62import java.util.stream.Stream; 63import java.util.stream.StreamSupport; 64 65import jdk.internal.jshell.debug.InternalDebugControl; 66import jdk.internal.jshell.tool.IOContext.InputInterruptedException; 67import jdk.jshell.Diag; 68import jdk.jshell.EvalException; 69import jdk.jshell.JShell; 70import jdk.jshell.Snippet; 71import jdk.jshell.DeclarationSnippet; 72import jdk.jshell.TypeDeclSnippet; 73import jdk.jshell.MethodSnippet; 74import jdk.jshell.PersistentSnippet; 75import jdk.jshell.VarSnippet; 76import jdk.jshell.ExpressionSnippet; 77import jdk.jshell.Snippet.Status; 78import jdk.jshell.SourceCodeAnalysis; 79import jdk.jshell.SourceCodeAnalysis.CompletionInfo; 80import jdk.jshell.SourceCodeAnalysis.Suggestion; 81import jdk.jshell.SnippetEvent; 82import jdk.jshell.UnresolvedReferenceException; 83import jdk.jshell.Snippet.SubKind; 84import jdk.jshell.JShell.Subscription; 85 86import static java.nio.file.StandardOpenOption.CREATE; 87import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; 88import static java.nio.file.StandardOpenOption.WRITE; 89import java.util.MissingResourceException; 90import java.util.ResourceBundle; 91import static java.util.stream.Collectors.toList; 92 93/** 94 * Command line REPL tool for Java using the JShell API. 95 * @author Robert Field 96 */ 97public class JShellTool { 98 99 private static final Pattern LINEBREAK = Pattern.compile("\\R"); 100 private static final Pattern HISTORY_ALL_FILENAME = Pattern.compile( 101 "((?<cmd>(all|history))(\\z|\\p{javaWhitespace}+))?(?<filename>.*)"); 102 103 final InputStream cmdin; 104 final PrintStream cmdout; 105 final PrintStream cmderr; 106 final PrintStream console; 107 final InputStream userin; 108 final PrintStream userout; 109 final PrintStream usererr; 110 111 /** 112 * The constructor for the tool (used by tool launch via main and by test 113 * harnesses to capture ins and outs. 114 * @param cmdin command line input -- snippets and commands 115 * @param cmdout command line output, feedback including errors 116 * @param cmderr start-up errors and debugging info 117 * @param console console control interaction 118 * @param userin code execution input (not yet functional) 119 * @param userout code execution output -- System.out.printf("hi") 120 * @param usererr code execution error stream -- System.err.printf("Oops") 121 */ 122 public JShellTool(InputStream cmdin, PrintStream cmdout, PrintStream cmderr, 123 PrintStream console, 124 InputStream userin, PrintStream userout, PrintStream usererr) { 125 this.cmdin = cmdin; 126 this.cmdout = cmdout; 127 this.cmderr = cmderr; 128 this.console = console; 129 this.userin = userin; 130 this.userout = userout; 131 this.usererr = usererr; 132 } 133 134 private IOContext input = null; 135 private boolean regenerateOnDeath = true; 136 private boolean live = false; 137 138 SourceCodeAnalysis analysis; 139 JShell state = null; 140 Subscription shutdownSubscription = null; 141 142 private boolean debug = false; 143 private boolean displayPrompt = true; 144 public boolean testPrompt = false; 145 private Feedback feedback = Feedback.Default; 146 private String cmdlineClasspath = null; 147 private String cmdlineStartup = null; 148 private String editor = null; 149 150 static final Preferences PREFS = Preferences.userRoot().node("tool/REPL"); 151 152 static final String STARTUP_KEY = "STARTUP"; 153 154 static final String DEFAULT_STARTUP = 155 "\n" + 156 "import java.util.*;\n" + 157 "import java.io.*;\n" + 158 "import java.math.*;\n" + 159 "import java.net.*;\n" + 160 "import java.util.concurrent.*;\n" + 161 "import java.util.prefs.*;\n" + 162 "import java.util.regex.*;\n" + 163 "void printf(String format, Object... args) { System.out.printf(format, args); }\n"; 164 165 // Tool id (tid) mapping 166 NameSpace mainNamespace; 167 NameSpace startNamespace; 168 NameSpace errorNamespace; 169 NameSpace currentNameSpace; 170 Map<Snippet,SnippetInfo> mapSnippet; 171 172 void debug(String format, Object... args) { 173 if (debug) { 174 cmderr.printf(format + "\n", args); 175 } 176 } 177 178 /** 179 * For more verbose feedback modes 180 * @param format printf format 181 * @param args printf args 182 */ 183 void fluff(String format, Object... args) { 184 if (feedback() != Feedback.Off && feedback() != Feedback.Concise) { 185 hard(format, args); 186 } 187 } 188 189 /** 190 * For concise feedback mode only 191 * @param format printf format 192 * @param args printf args 193 */ 194 void concise(String format, Object... args) { 195 if (feedback() == Feedback.Concise) { 196 hard(format, args); 197 } 198 } 199 200 /** 201 * For all feedback modes -- must show 202 * @param format printf format 203 * @param args printf args 204 */ 205 void hard(String format, Object... args) { 206 cmdout.printf("| " + format + "\n", args); 207 } 208 209 /** 210 * Trim whitespace off end of string 211 * @param s 212 * @return 213 */ 214 static String trimEnd(String s) { 215 int last = s.length() - 1; 216 int i = last; 217 while (i >= 0 && Character.isWhitespace(s.charAt(i))) { 218 --i; 219 } 220 if (i != last) { 221 return s.substring(0, i + 1); 222 } else { 223 return s; 224 } 225 } 226 227 /** 228 * Normal start entry point 229 * @param args 230 * @throws Exception 231 */ 232 public static void main(String[] args) throws Exception { 233 new JShellTool(System.in, System.out, System.err, System.out, 234 new ByteArrayInputStream(new byte[0]), System.out, System.err) 235 .start(args); 236 } 237 238 public void start(String[] args) throws Exception { 239 List<String> loadList = processCommandArgs(args); 240 if (loadList == null) { 241 // Abort 242 return; 243 } 244 try (IOContext in = new ConsoleIOContext(this, cmdin, console)) { 245 start(in, loadList); 246 } 247 } 248 249 private void start(IOContext in, List<String> loadList) { 250 resetState(); // Initialize 251 252 for (String loadFile : loadList) { 253 cmdOpen(loadFile); 254 } 255 256 if (regenerateOnDeath) { 257 fluff("Welcome to JShell -- Version %s", version()); 258 fluff("Type /help for help"); 259 } 260 261 try { 262 while (regenerateOnDeath) { 263 if (!live) { 264 resetState(); 265 } 266 run(in); 267 } 268 } finally { 269 closeState(); 270 } 271 } 272 273 /** 274 * Process the command line arguments. 275 * Set options. 276 * @param args the command line arguments 277 * @return the list of files to be loaded 278 */ 279 private List<String> processCommandArgs(String[] args) { 280 List<String> loadList = new ArrayList<>(); 281 Iterator<String> ai = Arrays.asList(args).iterator(); 282 while (ai.hasNext()) { 283 String arg = ai.next(); 284 if (arg.startsWith("-")) { 285 switch (arg) { 286 case "-classpath": 287 case "-cp": 288 if (cmdlineClasspath != null) { 289 cmderr.printf("Conflicting -classpath option.\n"); 290 return null; 291 } 292 if (ai.hasNext()) { 293 cmdlineClasspath = ai.next(); 294 } else { 295 cmderr.printf("Argument to -classpath missing.\n"); 296 return null; 297 } 298 break; 299 case "-help": 300 printUsage(); 301 return null; 302 case "-version": 303 cmdout.printf("jshell %s\n", version()); 304 return null; 305 case "-fullversion": 306 cmdout.printf("jshell %s\n", fullVersion()); 307 return null; 308 case "-startup": 309 if (cmdlineStartup != null) { 310 cmderr.printf("Conflicting -startup or -nostartup option.\n"); 311 return null; 312 } 313 if (ai.hasNext()) { 314 String filename = ai.next(); 315 try { 316 byte[] encoded = Files.readAllBytes(Paths.get(filename)); 317 cmdlineStartup = new String(encoded); 318 } catch (AccessDeniedException e) { 319 hard("File '%s' for start-up is not accessible.", filename); 320 } catch (NoSuchFileException e) { 321 hard("File '%s' for start-up is not found.", filename); 322 } catch (Exception e) { 323 hard("Exception while reading start-up file: %s", e); 324 } 325 } else { 326 cmderr.printf("Argument to -startup missing.\n"); 327 return null; 328 } 329 break; 330 case "-nostartup": 331 if (cmdlineStartup != null && !cmdlineStartup.isEmpty()) { 332 cmderr.printf("Conflicting -startup option.\n"); 333 return null; 334 } 335 cmdlineStartup = ""; 336 break; 337 default: 338 cmderr.printf("Unknown option: %s\n", arg); 339 printUsage(); 340 return null; 341 } 342 } else { 343 loadList.add(arg); 344 } 345 } 346 return loadList; 347 } 348 349 private void printUsage() { 350 cmdout.printf("Usage: jshell <options> <load files>\n"); 351 cmdout.printf("where possible options include:\n"); 352 cmdout.printf(" -classpath <path> Specify where to find user class files\n"); 353 cmdout.printf(" -cp <path> Specify where to find user class files\n"); 354 cmdout.printf(" -startup <file> One run replacement for the start-up definitions\n"); 355 cmdout.printf(" -nostartup Do not run the start-up definitions\n"); 356 cmdout.printf(" -help Print a synopsis of standard options\n"); 357 cmdout.printf(" -version Version information\n"); 358 } 359 360 private void resetState() { 361 closeState(); 362 363 // Initialize tool id mapping 364 mainNamespace = new NameSpace("main", ""); 365 startNamespace = new NameSpace("start", "s"); 366 errorNamespace = new NameSpace("error", "e"); 367 mapSnippet = new LinkedHashMap<>(); 368 currentNameSpace = startNamespace; 369 370 state = JShell.builder() 371 .in(userin) 372 .out(userout) 373 .err(usererr) 374 .tempVariableNameGenerator(()-> "$" + currentNameSpace.tidNext()) 375 .idGenerator((sn, i) -> (currentNameSpace == startNamespace || state.status(sn).isActive) 376 ? currentNameSpace.tid(sn) 377 : errorNamespace.tid(sn)) 378 .build(); 379 analysis = state.sourceCodeAnalysis(); 380 shutdownSubscription = state.onShutdown((JShell deadState) -> { 381 if (deadState == state) { 382 hard("State engine terminated. See /history"); 383 live = false; 384 } 385 }); 386 live = true; 387 388 if (cmdlineClasspath != null) { 389 state.addToClasspath(cmdlineClasspath); 390 } 391 392 393 String start; 394 if (cmdlineStartup == null) { 395 start = PREFS.get(STARTUP_KEY, "<nada>"); 396 if (start.equals("<nada>")) { 397 start = DEFAULT_STARTUP; 398 PREFS.put(STARTUP_KEY, DEFAULT_STARTUP); 399 } 400 } else { 401 start = cmdlineStartup; 402 } 403 try (IOContext suin = new FileScannerIOContext(new StringReader(start))) { 404 run(suin); 405 } catch (Exception ex) { 406 hard("Unexpected exception reading start-up: %s\n", ex); 407 } 408 currentNameSpace = mainNamespace; 409 } 410 411 private void closeState() { 412 live = false; 413 JShell oldState = state; 414 if (oldState != null) { 415 oldState.unsubscribe(shutdownSubscription); // No notification 416 oldState.close(); 417 } 418 } 419 420 /** 421 * Main loop 422 * @param in the line input/editing context 423 */ 424 private void run(IOContext in) { 425 IOContext oldInput = input; 426 input = in; 427 try { 428 String incomplete = ""; 429 while (live) { 430 String prompt; 431 if (in.interactiveOutput() && displayPrompt) { 432 prompt = testPrompt 433 ? incomplete.isEmpty() 434 ? "\u0005" //ENQ 435 : "\u0006" //ACK 436 : incomplete.isEmpty() 437 ? feedback() == Feedback.Concise 438 ? "-> " 439 : "\n-> " 440 : ">> " 441 ; 442 } else { 443 prompt = ""; 444 } 445 String raw; 446 try { 447 raw = in.readLine(prompt, incomplete); 448 } catch (InputInterruptedException ex) { 449 //input interrupted - clearing current state 450 incomplete = ""; 451 continue; 452 } 453 if (raw == null) { 454 //EOF 455 if (in.interactiveOutput()) { 456 // End after user ctrl-D 457 regenerateOnDeath = false; 458 } 459 break; 460 } 461 String trimmed = trimEnd(raw); 462 if (!trimmed.isEmpty()) { 463 String line = incomplete + trimmed; 464 465 // No commands in the middle of unprocessed source 466 if (incomplete.isEmpty() && line.startsWith("/") && !line.startsWith("//") && !line.startsWith("/*")) { 467 processCommand(line.trim()); 468 } else { 469 incomplete = processSourceCatchingReset(line); 470 } 471 } 472 } 473 } catch (IOException ex) { 474 hard("Unexpected exception: %s\n", ex); 475 } finally { 476 input = oldInput; 477 } 478 } 479 480 private String processSourceCatchingReset(String src) { 481 try { 482 input.beforeUserCode(); 483 return processSource(src); 484 } catch (IllegalStateException ex) { 485 hard("Resetting..."); 486 live = false; // Make double sure 487 return ""; 488 } finally { 489 input.afterUserCode(); 490 } 491 } 492 493 private void processCommand(String cmd) { 494 try { 495 //handle "/[number]" 496 cmdUseHistoryEntry(Integer.parseInt(cmd.substring(1))); 497 return ; 498 } catch (NumberFormatException ex) { 499 //ignore 500 } 501 String arg = ""; 502 int idx = cmd.indexOf(' '); 503 if (idx > 0) { 504 arg = cmd.substring(idx + 1).trim(); 505 cmd = cmd.substring(0, idx); 506 } 507 Command command = commands.get(cmd); 508 if (command == null || command.kind == CommandKind.HELP_ONLY) { 509 hard("No such command: %s", cmd); 510 fluff("Type /help for help."); 511 } else { 512 command.run.accept(arg); 513 } 514 } 515 516 private static Path toPathResolvingUserHome(String pathString) { 517 if (pathString.replace(File.separatorChar, '/').startsWith("~/")) 518 return Paths.get(System.getProperty("user.home"), pathString.substring(2)); 519 else 520 return Paths.get(pathString); 521 } 522 523 static final class Command { 524 public final String[] aliases; 525 public final String params; 526 public final String description; 527 public final Consumer<String> run; 528 public final CompletionProvider completions; 529 public final CommandKind kind; 530 531 public Command(String command, String alias, String params, String description, Consumer<String> run, CompletionProvider completions) { 532 this(command, alias, params, description, run, completions, CommandKind.NORMAL); 533 } 534 535 public Command(String command, String alias, String params, String description, Consumer<String> run, CompletionProvider completions, CommandKind kind) { 536 this.aliases = alias != null ? new String[] {command, alias} : new String[] {command}; 537 this.params = params; 538 this.description = description; 539 this.run = run; 540 this.completions = completions; 541 this.kind = kind; 542 } 543 544 } 545 546 interface CompletionProvider { 547 List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor); 548 } 549 550 enum CommandKind { 551 NORMAL, 552 HIDDEN, 553 HELP_ONLY; 554 } 555 556 static final class FixedCompletionProvider implements CompletionProvider { 557 558 private final String[] alternatives; 559 560 public FixedCompletionProvider(String... alternatives) { 561 this.alternatives = alternatives; 562 } 563 564 @Override 565 public List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor) { 566 List<Suggestion> result = new ArrayList<>(); 567 568 for (String alternative : alternatives) { 569 if (alternative.startsWith(input)) { 570 result.add(new Suggestion(alternative, false)); 571 } 572 } 573 574 anchor[0] = 0; 575 576 return result; 577 } 578 579 } 580 581 private static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider(); 582 private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true); 583 private final Map<String, Command> commands = new LinkedHashMap<>(); 584 private void registerCommand(Command cmd) { 585 for (String str : cmd.aliases) { 586 commands.put(str, cmd); 587 } 588 } 589 private static CompletionProvider fileCompletions(Predicate<Path> accept) { 590 return (code, cursor, anchor) -> { 591 int lastSlash = code.lastIndexOf('/'); 592 String path = code.substring(0, lastSlash + 1); 593 String prefix = lastSlash != (-1) ? code.substring(lastSlash + 1) : code; 594 Path current = toPathResolvingUserHome(path); 595 List<Suggestion> result = new ArrayList<>(); 596 try (Stream<Path> dir = Files.list(current)) { 597 dir.filter(f -> accept.test(f) && f.getFileName().toString().startsWith(prefix)) 598 .map(f -> new Suggestion(f.getFileName() + (Files.isDirectory(f) ? "/" : ""), false)) 599 .forEach(result::add); 600 } catch (IOException ex) { 601 //ignore... 602 } 603 if (path.isEmpty()) { 604 StreamSupport.stream(FileSystems.getDefault().getRootDirectories().spliterator(), false) 605 .filter(root -> accept.test(root) && root.toString().startsWith(prefix)) 606 .map(root -> new Suggestion(root.toString(), false)) 607 .forEach(result::add); 608 } 609 anchor[0] = path.length(); 610 return result; 611 }; 612 } 613 614 private static CompletionProvider classPathCompletion() { 615 return fileCompletions(p -> Files.isDirectory(p) || 616 p.getFileName().toString().endsWith(".zip") || 617 p.getFileName().toString().endsWith(".jar")); 618 } 619 620 private CompletionProvider editCompletion() { 621 return (prefix, cursor, anchor) -> { 622 anchor[0] = 0; 623 return state.snippets() 624 .stream() 625 .flatMap(k -> (k instanceof DeclarationSnippet) 626 ? Stream.of(String.valueOf(k.id()), ((DeclarationSnippet) k).name()) 627 : Stream.of(String.valueOf(k.id()))) 628 .filter(k -> k.startsWith(prefix)) 629 .map(k -> new Suggestion(k, false)) 630 .collect(Collectors.toList()); 631 }; 632 } 633 634 private static CompletionProvider saveCompletion() { 635 CompletionProvider keyCompletion = new FixedCompletionProvider("all ", "history "); 636 return (code, cursor, anchor) -> { 637 List<Suggestion> result = new ArrayList<>(); 638 int space = code.indexOf(' '); 639 if (space == (-1)) { 640 result.addAll(keyCompletion.completionSuggestions(code, cursor, anchor)); 641 } 642 result.addAll(FILE_COMPLETION_PROVIDER.completionSuggestions(code.substring(space + 1), cursor - space - 1, anchor)); 643 anchor[0] += space + 1; 644 return result; 645 }; 646 } 647 648 // Table of commands -- with command forms, argument kinds, help message, implementation, ... 649 650 { 651 registerCommand(new Command("/list", "/l", "[all]", "list the source you have typed", 652 arg -> cmdList(arg), 653 new FixedCompletionProvider("all"))); 654 registerCommand(new Command("/seteditor", null, "<executable>", "set the external editor command to use", 655 arg -> cmdSetEditor(arg), 656 EMPTY_COMPLETION_PROVIDER)); 657 registerCommand(new Command("/edit", "/e", "<name or id>", "edit a source entry referenced by name or id", 658 arg -> cmdEdit(arg), 659 editCompletion())); 660 registerCommand(new Command("/drop", "/d", "<name or id>", "delete a source entry referenced by name or id", 661 arg -> cmdDrop(arg), 662 editCompletion())); 663 registerCommand(new Command("/save", "/s", "[all|history] <file>", "save the source you have typed", 664 arg -> cmdSave(arg), 665 saveCompletion())); 666 registerCommand(new Command("/open", "/o", "<file>", "open a file as source input", 667 arg -> cmdOpen(arg), 668 FILE_COMPLETION_PROVIDER)); 669 registerCommand(new Command("/vars", "/v", null, "list the declared variables and their values", 670 arg -> cmdVars(), 671 EMPTY_COMPLETION_PROVIDER)); 672 registerCommand(new Command("/methods", "/m", null, "list the declared methods and their signatures", 673 arg -> cmdMethods(), 674 EMPTY_COMPLETION_PROVIDER)); 675 registerCommand(new Command("/classes", "/c", null, "list the declared classes", 676 arg -> cmdClasses(), 677 EMPTY_COMPLETION_PROVIDER)); 678 registerCommand(new Command("/exit", "/x", null, "exit the REPL", 679 arg -> cmdExit(), 680 EMPTY_COMPLETION_PROVIDER)); 681 registerCommand(new Command("/reset", "/r", null, "reset everything in the REPL", 682 arg -> cmdReset(), 683 EMPTY_COMPLETION_PROVIDER)); 684 registerCommand(new Command("/feedback", "/f", "<level>", "feedback information: off, concise, normal, verbose, default, or ?", 685 arg -> cmdFeedback(arg), 686 new FixedCompletionProvider("off", "concise", "normal", "verbose", "default", "?"))); 687 registerCommand(new Command("/prompt", "/p", null, "toggle display of a prompt", 688 arg -> cmdPrompt(), 689 EMPTY_COMPLETION_PROVIDER)); 690 registerCommand(new Command("/classpath", "/cp", "<path>", "add a path to the classpath", 691 arg -> cmdClasspath(arg), 692 classPathCompletion())); 693 registerCommand(new Command("/history", "/h", null, "history of what you have typed", 694 arg -> cmdHistory(), 695 EMPTY_COMPLETION_PROVIDER)); 696 registerCommand(new Command("/setstart", null, "<file>", "read file and set as the new start-up definitions", 697 arg -> cmdSetStart(arg), 698 FILE_COMPLETION_PROVIDER)); 699 registerCommand(new Command("/savestart", null, "<file>", "save the default start-up definitions to the file", 700 arg -> cmdSaveStart(arg), 701 FILE_COMPLETION_PROVIDER)); 702 registerCommand(new Command("/debug", "/db", "", "toggle debugging of the REPL", 703 arg -> cmdDebug(arg), 704 EMPTY_COMPLETION_PROVIDER, 705 CommandKind.HIDDEN)); 706 registerCommand(new Command("/help", "/?", "", "this help message", 707 arg -> cmdHelp(), 708 EMPTY_COMPLETION_PROVIDER)); 709 registerCommand(new Command("/!", null, "", "re-run last snippet", 710 arg -> cmdUseHistoryEntry(-1), 711 EMPTY_COMPLETION_PROVIDER)); 712 registerCommand(new Command("/<n>", null, "", "re-run n-th snippet", 713 arg -> { throw new IllegalStateException(); }, 714 EMPTY_COMPLETION_PROVIDER, 715 CommandKind.HELP_ONLY)); 716 registerCommand(new Command("/-<n>", null, "", "re-run n-th previous snippet", 717 arg -> { throw new IllegalStateException(); }, 718 EMPTY_COMPLETION_PROVIDER, 719 CommandKind.HELP_ONLY)); 720 } 721 722 public List<Suggestion> commandCompletionSuggestions(String code, int cursor, int[] anchor) { 723 String prefix = code.substring(0, cursor); 724 int space = prefix.indexOf(' '); 725 Stream<Suggestion> result; 726 727 if (space == (-1)) { 728 result = commands.values() 729 .stream() 730 .distinct() 731 .filter(cmd -> cmd.kind != CommandKind.HIDDEN && cmd.kind != CommandKind.HELP_ONLY) 732 .map(cmd -> cmd.aliases[0]) 733 .filter(key -> key.startsWith(prefix)) 734 .map(key -> new Suggestion(key + " ", false)); 735 anchor[0] = 0; 736 } else { 737 String arg = prefix.substring(space + 1); 738 String cmd = prefix.substring(0, space); 739 Command command = commands.get(cmd); 740 if (command != null) { 741 result = command.completions.completionSuggestions(arg, cursor - space, anchor).stream(); 742 anchor[0] += space + 1; 743 } else { 744 result = Stream.empty(); 745 } 746 } 747 748 return result.sorted((s1, s2) -> s1.continuation.compareTo(s2.continuation)) 749 .collect(Collectors.toList()); 750 } 751 752 public String commandDocumentation(String code, int cursor) { 753 code = code.substring(0, cursor); 754 int space = code.indexOf(' '); 755 756 if (space != (-1)) { 757 String cmd = code.substring(0, space); 758 Command command = commands.get(cmd); 759 if (command != null) { 760 return command.description; 761 } 762 } 763 764 return null; 765 } 766 767 // --- Command implementations --- 768 769 void cmdSetEditor(String arg) { 770 if (arg.isEmpty()) { 771 hard("/seteditor requires a path argument"); 772 } else { 773 editor = arg; 774 fluff("Editor set to: %s", arg); 775 } 776 } 777 778 void cmdClasspath(String arg) { 779 if (arg.isEmpty()) { 780 hard("/classpath requires a path argument"); 781 } else { 782 state.addToClasspath(toPathResolvingUserHome(arg).toString()); 783 fluff("Path %s added to classpath", arg); 784 } 785 } 786 787 void cmdDebug(String arg) { 788 if (arg.isEmpty()) { 789 debug = !debug; 790 InternalDebugControl.setDebugFlags(state, debug ? InternalDebugControl.DBG_GEN : 0); 791 fluff("Debugging %s", debug ? "on" : "off"); 792 } else { 793 int flags = 0; 794 for (char ch : arg.toCharArray()) { 795 switch (ch) { 796 case '0': 797 flags = 0; 798 debug = false; 799 fluff("Debugging off"); 800 break; 801 case 'r': 802 debug = true; 803 fluff("REPL tool debugging on"); 804 break; 805 case 'g': 806 flags |= InternalDebugControl.DBG_GEN; 807 fluff("General debugging on"); 808 break; 809 case 'f': 810 flags |= InternalDebugControl.DBG_FMGR; 811 fluff("File manager debugging on"); 812 break; 813 case 'c': 814 flags |= InternalDebugControl.DBG_COMPA; 815 fluff("Completion analysis debugging on"); 816 break; 817 case 'd': 818 flags |= InternalDebugControl.DBG_DEP; 819 fluff("Dependency debugging on"); 820 break; 821 case 'e': 822 flags |= InternalDebugControl.DBG_EVNT; 823 fluff("Event debugging on"); 824 break; 825 default: 826 hard("Unknown debugging option: %c", ch); 827 fluff("Use: 0 r g f c d"); 828 break; 829 } 830 } 831 InternalDebugControl.setDebugFlags(state, flags); 832 } 833 } 834 835 private void cmdExit() { 836 regenerateOnDeath = false; 837 live = false; 838 fluff("Goodbye\n"); 839 } 840 841 private void cmdFeedback(String arg) { 842 switch (arg) { 843 case "": 844 case "d": 845 case "default": 846 feedback = Feedback.Default; 847 break; 848 case "o": 849 case "off": 850 feedback = Feedback.Off; 851 break; 852 case "c": 853 case "concise": 854 feedback = Feedback.Concise; 855 break; 856 case "n": 857 case "normal": 858 feedback = Feedback.Normal; 859 break; 860 case "v": 861 case "verbose": 862 feedback = Feedback.Verbose; 863 break; 864 default: 865 hard("Follow /feedback with of the following:"); 866 hard(" off (errors and critical output only)"); 867 hard(" concise"); 868 hard(" normal"); 869 hard(" verbose"); 870 hard(" default"); 871 hard("You may also use just the first letter, for example: /f c"); 872 hard("In interactive mode 'default' is the same as 'normal', from a file it is the same as 'off'"); 873 return; 874 } 875 fluff("Feedback mode: %s", feedback.name().toLowerCase()); 876 } 877 878 void cmdHelp() { 879 int synopsisLen = 0; 880 Map<String, String> synopsis2Description = new LinkedHashMap<>(); 881 for (Command cmd : new LinkedHashSet<>(commands.values())) { 882 if (cmd.kind == CommandKind.HIDDEN) 883 continue; 884 StringBuilder synopsis = new StringBuilder(); 885 if (cmd.aliases.length > 1) { 886 synopsis.append(String.format("%-3s or ", cmd.aliases[1])); 887 } else { 888 synopsis.append(" "); 889 } 890 synopsis.append(cmd.aliases[0]); 891 if (cmd.params != null) 892 synopsis.append(" ").append(cmd.params); 893 synopsis2Description.put(synopsis.toString(), cmd.description); 894 synopsisLen = Math.max(synopsisLen, synopsis.length()); 895 } 896 cmdout.println("Type a Java language expression, statement, or declaration."); 897 cmdout.println("Or type one of the following commands:\n"); 898 for (Entry<String, String> e : synopsis2Description.entrySet()) { 899 cmdout.print(String.format("%-" + synopsisLen + "s", e.getKey())); 900 cmdout.print(" -- "); 901 cmdout.println(e.getValue()); 902 } 903 cmdout.println(); 904 cmdout.println("Supported shortcuts include:"); 905 cmdout.println("<tab> -- show possible completions for the current text"); 906 cmdout.println("Shift-<tab> -- for current method or constructor invocation, show a synopsis of the method/constructor"); 907 } 908 909 private void cmdHistory() { 910 cmdout.println(); 911 for (String s : input.currentSessionHistory()) { 912 // No number prefix, confusing with snippet ids 913 cmdout.printf("%s\n", s); 914 } 915 } 916 917 /** 918 * Convert a user argument to a list of snippets referenced by that 919 * argument (or lack of argument). 920 * @param arg The user's argument to the command 921 * @return a list of referenced snippets 922 */ 923 private List<Snippet> argToSnippets(String arg) { 924 List<Snippet> snippets = new ArrayList<>(); 925 if (arg.isEmpty()) { 926 // Default is all user snippets 927 for (Snippet sn : state.snippets()) { 928 if (notInStartUp(sn)) { 929 snippets.add(sn); 930 } 931 } 932 } else { 933 // Look for all declarations with matching names 934 for (Snippet key : state.snippets()) { 935 switch (key.kind()) { 936 case METHOD: 937 case VAR: 938 case TYPE_DECL: 939 if (((DeclarationSnippet) key).name().equals(arg)) { 940 snippets.add(key); 941 } 942 break; 943 } 944 } 945 // If no declarations found, look for an id of this name 946 if (snippets.isEmpty()) { 947 for (Snippet sn : state.snippets()) { 948 if (sn.id().equals(arg)) { 949 snippets.add(sn); 950 break; 951 } 952 } 953 } 954 // If still no matches found, give an error 955 if (snippets.isEmpty()) { 956 hard("No definition or id named %s found. See /classes /methods /vars or /list", arg); 957 return null; 958 } 959 } 960 return snippets; 961 } 962 963 private void cmdDrop(String arg) { 964 if (arg.isEmpty()) { 965 hard("In the /drop argument, please specify an import, variable, method, or class to drop."); 966 hard("Specify by id or name. Use /list to see ids. Use /reset to reset all state."); 967 return; 968 } 969 List<Snippet> snippetSet = argToSnippets(arg); 970 if (snippetSet == null) { 971 return; 972 } 973 snippetSet = snippetSet.stream() 974 .filter(sn -> state.status(sn).isActive) 975 .collect(toList()); 976 snippetSet.removeIf(sn -> !(sn instanceof PersistentSnippet)); 977 if (snippetSet.isEmpty()) { 978 hard("The argument did not specify an import, variable, method, or class to drop."); 979 return; 980 } 981 if (snippetSet.size() > 1) { 982 hard("The argument references more than one import, variable, method, or class."); 983 hard("Try again with one of the ids below:"); 984 for (Snippet sn : snippetSet) { 985 cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n ")); 986 } 987 return; 988 } 989 PersistentSnippet psn = (PersistentSnippet) snippetSet.iterator().next(); 990 state.drop(psn).forEach(this::handleEvent); 991 } 992 993 private void cmdEdit(String arg) { 994 List<Snippet> snippetSet = argToSnippets(arg); 995 if (snippetSet == null) { 996 return; 997 } 998 Set<String> srcSet = new LinkedHashSet<>(); 999 for (Snippet key : snippetSet) { 1000 String src = key.source(); 1001 switch (key.subKind()) { 1002 case VAR_VALUE_SUBKIND: 1003 break; 1004 case ASSIGNMENT_SUBKIND: 1005 case OTHER_EXPRESSION_SUBKIND: 1006 case TEMP_VAR_EXPRESSION_SUBKIND: 1007 if (!src.endsWith(";")) { 1008 src = src + ";"; 1009 } 1010 srcSet.add(src); 1011 break; 1012 default: 1013 srcSet.add(src); 1014 break; 1015 } 1016 } 1017 StringBuilder sb = new StringBuilder(); 1018 for (String s : srcSet) { 1019 sb.append(s); 1020 sb.append('\n'); 1021 } 1022 String src = sb.toString(); 1023 Consumer<String> saveHandler = new SaveHandler(src, srcSet); 1024 Consumer<String> errorHandler = s -> hard("Edit Error: %s", s); 1025 if (editor == null) { 1026 EditPad.edit(errorHandler, src, saveHandler); 1027 } else { 1028 ExternalEditor.edit(editor, errorHandler, src, saveHandler, input); 1029 } 1030 } 1031 //where 1032 // receives editor requests to save 1033 private class SaveHandler implements Consumer<String> { 1034 1035 String src; 1036 Set<String> currSrcs; 1037 1038 SaveHandler(String src, Set<String> ss) { 1039 this.src = src; 1040 this.currSrcs = ss; 1041 } 1042 1043 @Override 1044 public void accept(String s) { 1045 if (!s.equals(src)) { // quick check first 1046 src = s; 1047 try { 1048 Set<String> nextSrcs = new LinkedHashSet<>(); 1049 boolean failed = false; 1050 while (true) { 1051 CompletionInfo an = analysis.analyzeCompletion(s); 1052 if (!an.completeness.isComplete) { 1053 break; 1054 } 1055 String tsrc = trimNewlines(an.source); 1056 if (!failed && !currSrcs.contains(tsrc)) { 1057 failed = processCompleteSource(tsrc); 1058 } 1059 nextSrcs.add(tsrc); 1060 if (an.remaining.isEmpty()) { 1061 break; 1062 } 1063 s = an.remaining; 1064 } 1065 currSrcs = nextSrcs; 1066 } catch (IllegalStateException ex) { 1067 hard("Resetting..."); 1068 resetState(); 1069 currSrcs = new LinkedHashSet<>(); // re-process everything 1070 } 1071 } 1072 } 1073 1074 private String trimNewlines(String s) { 1075 int b = 0; 1076 while (b < s.length() && s.charAt(b) == '\n') { 1077 ++b; 1078 } 1079 int e = s.length() -1; 1080 while (e >= 0 && s.charAt(e) == '\n') { 1081 --e; 1082 } 1083 return s.substring(b, e + 1); 1084 } 1085 } 1086 1087 private void cmdList(String arg) { 1088 boolean all = false; 1089 switch (arg) { 1090 case "all": 1091 all = true; 1092 break; 1093 case "history": 1094 cmdHistory(); 1095 return; 1096 case "": 1097 break; 1098 default: 1099 hard("Invalid /list argument: %s", arg); 1100 return; 1101 } 1102 boolean hasOutput = false; 1103 for (Snippet sn : state.snippets()) { 1104 if (all || (notInStartUp(sn) && state.status(sn).isActive)) { 1105 if (!hasOutput) { 1106 cmdout.println(); 1107 hasOutput = true; 1108 } 1109 cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n ")); 1110 1111 } 1112 } 1113 } 1114 1115 private void cmdOpen(String filename) { 1116 if (filename.isEmpty()) { 1117 hard("The /open command requires a filename argument."); 1118 } else { 1119 try { 1120 run(new FileScannerIOContext(toPathResolvingUserHome(filename).toString())); 1121 } catch (FileNotFoundException e) { 1122 hard("File '%s' is not found: %s", filename, e.getMessage()); 1123 } catch (Exception e) { 1124 hard("Exception while reading file: %s", e); 1125 } 1126 } 1127 } 1128 1129 private void cmdPrompt() { 1130 displayPrompt = !displayPrompt; 1131 fluff("Prompt will %sdisplay. Use /prompt to toggle.", displayPrompt ? "" : "NOT "); 1132 concise("Prompt: %s", displayPrompt ? "on" : "off"); 1133 } 1134 1135 private void cmdReset() { 1136 live = false; 1137 fluff("Resetting state."); 1138 } 1139 1140 private void cmdSave(String arg_filename) { 1141 Matcher mat = HISTORY_ALL_FILENAME.matcher(arg_filename); 1142 if (!mat.find()) { 1143 hard("Malformed argument to the /save command: %s", arg_filename); 1144 return; 1145 } 1146 boolean useHistory = false; 1147 boolean saveAll = false; 1148 String cmd = mat.group("cmd"); 1149 if (cmd != null) switch (cmd) { 1150 case "all": 1151 saveAll = true; 1152 break; 1153 case "history": 1154 useHistory = true; 1155 break; 1156 } 1157 String filename = mat.group("filename"); 1158 if (filename == null ||filename.isEmpty()) { 1159 hard("The /save command requires a filename argument."); 1160 return; 1161 } 1162 try (BufferedWriter writer = Files.newBufferedWriter(toPathResolvingUserHome(filename), 1163 Charset.defaultCharset(), 1164 CREATE, TRUNCATE_EXISTING, WRITE)) { 1165 if (useHistory) { 1166 for (String s : input.currentSessionHistory()) { 1167 writer.write(s); 1168 writer.write("\n"); 1169 } 1170 } else { 1171 for (Snippet sn : state.snippets()) { 1172 if (saveAll || notInStartUp(sn)) { 1173 writer.write(sn.source()); 1174 writer.write("\n"); 1175 } 1176 } 1177 } 1178 } catch (FileNotFoundException e) { 1179 hard("File '%s' for save is not accessible: %s", filename, e.getMessage()); 1180 } catch (Exception e) { 1181 hard("Exception while saving: %s", e); 1182 } 1183 } 1184 1185 private void cmdSetStart(String filename) { 1186 if (filename.isEmpty()) { 1187 hard("The /setstart command requires a filename argument."); 1188 } else { 1189 try { 1190 byte[] encoded = Files.readAllBytes(toPathResolvingUserHome(filename)); 1191 String init = new String(encoded); 1192 PREFS.put(STARTUP_KEY, init); 1193 } catch (AccessDeniedException e) { 1194 hard("File '%s' for /setstart is not accessible.", filename); 1195 } catch (NoSuchFileException e) { 1196 hard("File '%s' for /setstart is not found.", filename); 1197 } catch (Exception e) { 1198 hard("Exception while reading start set file: %s", e); 1199 } 1200 } 1201 } 1202 1203 private void cmdSaveStart(String filename) { 1204 if (filename.isEmpty()) { 1205 hard("The /savestart command requires a filename argument."); 1206 } else { 1207 try { 1208 Files.write(toPathResolvingUserHome(filename), DEFAULT_STARTUP.getBytes()); 1209 } catch (AccessDeniedException e) { 1210 hard("File '%s' for /savestart is not accessible.", filename); 1211 } catch (NoSuchFileException e) { 1212 hard("File '%s' for /savestart cannot be located.", filename); 1213 } catch (Exception e) { 1214 hard("Exception while saving default startup file: %s", e); 1215 } 1216 } 1217 } 1218 1219 private void cmdVars() { 1220 for (VarSnippet vk : state.variables()) { 1221 String val = state.status(vk) == Status.VALID 1222 ? state.varValue(vk) 1223 : "(not-active)"; 1224 hard(" %s %s = %s", vk.typeName(), vk.name(), val); 1225 } 1226 } 1227 1228 private void cmdMethods() { 1229 for (MethodSnippet mk : state.methods()) { 1230 hard(" %s %s", mk.name(), mk.signature()); 1231 } 1232 } 1233 1234 private void cmdClasses() { 1235 for (TypeDeclSnippet ck : state.types()) { 1236 String kind; 1237 switch (ck.subKind()) { 1238 case INTERFACE_SUBKIND: 1239 kind = "interface"; 1240 break; 1241 case CLASS_SUBKIND: 1242 kind = "class"; 1243 break; 1244 case ENUM_SUBKIND: 1245 kind = "enum"; 1246 break; 1247 case ANNOTATION_TYPE_SUBKIND: 1248 kind = "@interface"; 1249 break; 1250 default: 1251 assert false : "Wrong kind" + ck.subKind(); 1252 kind = "class"; 1253 break; 1254 } 1255 hard(" %s %s", kind, ck.name()); 1256 } 1257 } 1258 1259 private void cmdUseHistoryEntry(int index) { 1260 List<Snippet> keys = state.snippets(); 1261 if (index < 0) 1262 index += keys.size(); 1263 else 1264 index--; 1265 if (index >= 0 && index < keys.size()) { 1266 String source = keys.get(index).source(); 1267 cmdout.printf("%s\n", source); 1268 input.replaceLastHistoryEntry(source); 1269 processSourceCatchingReset(source); 1270 } else { 1271 hard("Cannot find snippet %d", index + 1); 1272 } 1273 } 1274 1275 /** 1276 * Filter diagnostics for only errors (no warnings, ...) 1277 * @param diagnostics input list 1278 * @return filtered list 1279 */ 1280 List<Diag> errorsOnly(List<Diag> diagnostics) { 1281 return diagnostics.stream() 1282 .filter(d -> d.isError()) 1283 .collect(toList()); 1284 } 1285 1286 void printDiagnostics(String source, List<Diag> diagnostics, boolean embed) { 1287 String padding = embed? " " : ""; 1288 for (Diag diag : diagnostics) { 1289 //assert diag.getSource().equals(source); 1290 1291 if (!embed) { 1292 if (diag.isError()) { 1293 hard("Error:"); 1294 } else { 1295 hard("Warning:"); 1296 } 1297 } 1298 1299 for (String line : diag.getMessage(null).split("\\r?\\n")) { 1300 if (!line.trim().startsWith("location:")) { 1301 hard("%s%s", padding, line); 1302 } 1303 } 1304 1305 int pstart = (int) diag.getStartPosition(); 1306 int pend = (int) diag.getEndPosition(); 1307 Matcher m = LINEBREAK.matcher(source); 1308 int pstartl = 0; 1309 int pendl = -2; 1310 while (m.find(pstartl)) { 1311 pendl = m.start(); 1312 if (pendl >= pstart) { 1313 break; 1314 } else { 1315 pstartl = m.end(); 1316 } 1317 } 1318 if (pendl < pstart) { 1319 pendl = source.length(); 1320 } 1321 fluff("%s%s", padding, source.substring(pstartl, pendl)); 1322 1323 StringBuilder sb = new StringBuilder(); 1324 int start = pstart - pstartl; 1325 for (int i = 0; i < start; ++i) { 1326 sb.append(' '); 1327 } 1328 sb.append('^'); 1329 boolean multiline = pend > pendl; 1330 int end = (multiline ? pendl : pend) - pstartl - 1; 1331 if (end > start) { 1332 for (int i = start + 1; i < end; ++i) { 1333 sb.append('-'); 1334 } 1335 if (multiline) { 1336 sb.append("-..."); 1337 } else { 1338 sb.append('^'); 1339 } 1340 } 1341 fluff("%s%s", padding, sb.toString()); 1342 1343 debug("printDiagnostics start-pos = %d ==> %d -- wrap = %s", diag.getStartPosition(), start, this); 1344 debug("Code: %s", diag.getCode()); 1345 debug("Pos: %d (%d - %d)", diag.getPosition(), 1346 diag.getStartPosition(), diag.getEndPosition()); 1347 } 1348 } 1349 1350 private String processSource(String srcInput) throws IllegalStateException { 1351 while (true) { 1352 CompletionInfo an = analysis.analyzeCompletion(srcInput); 1353 if (!an.completeness.isComplete) { 1354 return an.remaining; 1355 } 1356 boolean failed = processCompleteSource(an.source); 1357 if (failed || an.remaining.isEmpty()) { 1358 return ""; 1359 } 1360 srcInput = an.remaining; 1361 } 1362 } 1363 //where 1364 private boolean processCompleteSource(String source) throws IllegalStateException { 1365 debug("Compiling: %s", source); 1366 boolean failed = false; 1367 List<SnippetEvent> events = state.eval(source); 1368 for (SnippetEvent e : events) { 1369 failed |= handleEvent(e); 1370 } 1371 return failed; 1372 } 1373 1374 private boolean handleEvent(SnippetEvent ste) { 1375 Snippet sn = ste.snippet(); 1376 if (sn == null) { 1377 debug("Event with null key: %s", ste); 1378 return false; 1379 } 1380 List<Diag> diagnostics = state.diagnostics(sn); 1381 String source = sn.source(); 1382 if (ste.causeSnippet() == null) { 1383 // main event 1384 printDiagnostics(source, diagnostics, false); 1385 if (ste.status().isActive) { 1386 if (ste.exception() != null) { 1387 if (ste.exception() instanceof EvalException) { 1388 printEvalException((EvalException) ste.exception()); 1389 return true; 1390 } else if (ste.exception() instanceof UnresolvedReferenceException) { 1391 printUnresolved((UnresolvedReferenceException) ste.exception()); 1392 } else { 1393 hard("Unexpected execution exception: %s", ste.exception()); 1394 return true; 1395 } 1396 } else { 1397 displayDeclarationAndValue(ste, false, ste.value()); 1398 } 1399 } else if (ste.status() == Status.REJECTED) { 1400 if (diagnostics.isEmpty()) { 1401 hard("Failed."); 1402 } 1403 return true; 1404 } 1405 } else if (ste.status() == Status.REJECTED) { 1406 //TODO -- I don't believe updates can cause failures any more 1407 hard("Caused failure of dependent %s --", ((DeclarationSnippet) sn).name()); 1408 printDiagnostics(source, diagnostics, true); 1409 } else { 1410 // Update 1411 SubKind subkind = sn.subKind(); 1412 if (sn instanceof DeclarationSnippet 1413 && (feedback() == Feedback.Verbose 1414 || ste.status() == Status.OVERWRITTEN 1415 || subkind == SubKind.VAR_DECLARATION_SUBKIND 1416 || subkind == SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND)) { 1417 // Under the conditions, display update information 1418 displayDeclarationAndValue(ste, true, null); 1419 List<Diag> other = errorsOnly(diagnostics); 1420 if (other.size() > 0) { 1421 printDiagnostics(source, other, true); 1422 } 1423 } 1424 } 1425 return false; 1426 } 1427 1428 @SuppressWarnings("fallthrough") 1429 private void displayDeclarationAndValue(SnippetEvent ste, boolean update, String value) { 1430 Snippet key = ste.snippet(); 1431 String declared; 1432 Status status = ste.status(); 1433 switch (status) { 1434 case VALID: 1435 case RECOVERABLE_DEFINED: 1436 case RECOVERABLE_NOT_DEFINED: 1437 if (ste.previousStatus().isActive) { 1438 declared = ste.isSignatureChange() 1439 ? "Replaced" 1440 : "Modified"; 1441 } else { 1442 declared = "Added"; 1443 } 1444 break; 1445 case OVERWRITTEN: 1446 declared = "Overwrote"; 1447 break; 1448 case DROPPED: 1449 declared = "Dropped"; 1450 break; 1451 case REJECTED: 1452 declared = "Rejected"; 1453 break; 1454 case NONEXISTENT: 1455 default: 1456 // Should not occur 1457 declared = ste.previousStatus().toString() + "=>" + status.toString(); 1458 } 1459 if (update) { 1460 declared = " Update " + declared.toLowerCase(); 1461 } 1462 String however; 1463 if (key instanceof DeclarationSnippet && (status == Status.RECOVERABLE_DEFINED || status == Status.RECOVERABLE_NOT_DEFINED)) { 1464 String cannotUntil = (status == Status.RECOVERABLE_NOT_DEFINED) 1465 ? " cannot be referenced until" 1466 : " cannot be invoked until"; 1467 however = (update? " which" : ", however, it") + cannotUntil + unresolved((DeclarationSnippet) key); 1468 } else { 1469 however = ""; 1470 } 1471 switch (key.subKind()) { 1472 case CLASS_SUBKIND: 1473 fluff("%s class %s%s", declared, ((TypeDeclSnippet) key).name(), however); 1474 break; 1475 case INTERFACE_SUBKIND: 1476 fluff("%s interface %s%s", declared, ((TypeDeclSnippet) key).name(), however); 1477 break; 1478 case ENUM_SUBKIND: 1479 fluff("%s enum %s%s", declared, ((TypeDeclSnippet) key).name(), however); 1480 break; 1481 case ANNOTATION_TYPE_SUBKIND: 1482 fluff("%s annotation interface %s%s", declared, ((TypeDeclSnippet) key).name(), however); 1483 break; 1484 case METHOD_SUBKIND: 1485 fluff("%s method %s(%s)%s", declared, ((MethodSnippet) key).name(), 1486 ((MethodSnippet) key).parameterTypes(), however); 1487 break; 1488 case VAR_DECLARATION_SUBKIND: 1489 if (!update) { 1490 VarSnippet vk = (VarSnippet) key; 1491 if (status == Status.RECOVERABLE_NOT_DEFINED) { 1492 fluff("%s variable %s%s", declared, vk.name(), however); 1493 } else { 1494 fluff("%s variable %s of type %s%s", declared, vk.name(), vk.typeName(), however); 1495 } 1496 break; 1497 } 1498 // Fall through 1499 case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND: { 1500 VarSnippet vk = (VarSnippet) key; 1501 if (status == Status.RECOVERABLE_NOT_DEFINED) { 1502 if (!update) { 1503 fluff("%s variable %s%s", declared, vk.name(), however); 1504 break; 1505 } 1506 } else if (update) { 1507 if (ste.isSignatureChange()) { 1508 hard("%s variable %s, reset to null", declared, vk.name()); 1509 } 1510 } else { 1511 fluff("%s variable %s of type %s with initial value %s", 1512 declared, vk.name(), vk.typeName(), value); 1513 concise("%s : %s", vk.name(), value); 1514 } 1515 break; 1516 } 1517 case TEMP_VAR_EXPRESSION_SUBKIND: { 1518 VarSnippet vk = (VarSnippet) key; 1519 if (update) { 1520 hard("%s temporary variable %s, reset to null", declared, vk.name()); 1521 } else { 1522 fluff("Expression value is: %s", (value)); 1523 fluff(" assigned to temporary variable %s of type %s", vk.name(), vk.typeName()); 1524 concise("%s : %s", vk.name(), value); 1525 } 1526 break; 1527 } 1528 case OTHER_EXPRESSION_SUBKIND: 1529 fluff("Expression value is: %s", (value)); 1530 break; 1531 case VAR_VALUE_SUBKIND: { 1532 ExpressionSnippet ek = (ExpressionSnippet) key; 1533 fluff("Variable %s of type %s has value %s", ek.name(), ek.typeName(), (value)); 1534 concise("%s : %s", ek.name(), value); 1535 break; 1536 } 1537 case ASSIGNMENT_SUBKIND: { 1538 ExpressionSnippet ek = (ExpressionSnippet) key; 1539 fluff("Variable %s has been assigned the value %s", ek.name(), (value)); 1540 concise("%s : %s", ek.name(), value); 1541 break; 1542 } 1543 } 1544 } 1545 //where 1546 void printStackTrace(StackTraceElement[] stes) { 1547 for (StackTraceElement ste : stes) { 1548 StringBuilder sb = new StringBuilder(); 1549 String cn = ste.getClassName(); 1550 if (!cn.isEmpty()) { 1551 int dot = cn.lastIndexOf('.'); 1552 if (dot > 0) { 1553 sb.append(cn.substring(dot + 1)); 1554 } else { 1555 sb.append(cn); 1556 } 1557 sb.append("."); 1558 } 1559 if (!ste.getMethodName().isEmpty()) { 1560 sb.append(ste.getMethodName()); 1561 sb.append(" "); 1562 } 1563 String fileName = ste.getFileName(); 1564 int lineNumber = ste.getLineNumber(); 1565 String loc = ste.isNativeMethod() 1566 ? "Native Method" 1567 : fileName == null 1568 ? "Unknown Source" 1569 : lineNumber >= 0 1570 ? fileName + ":" + lineNumber 1571 : fileName; 1572 hard(" at %s(%s)", sb, loc); 1573 1574 } 1575 } 1576 //where 1577 void printUnresolved(UnresolvedReferenceException ex) { 1578 MethodSnippet corralled = ex.getMethodSnippet(); 1579 List<Diag> otherErrors = errorsOnly(state.diagnostics(corralled)); 1580 StringBuilder sb = new StringBuilder(); 1581 if (otherErrors.size() > 0) { 1582 if (state.unresolvedDependencies(corralled).size() > 0) { 1583 sb.append(" and"); 1584 } 1585 if (otherErrors.size() == 1) { 1586 sb.append(" this error is addressed --"); 1587 } else { 1588 sb.append(" these errors are addressed --"); 1589 } 1590 } else { 1591 sb.append("."); 1592 } 1593 1594 hard("Attempted to call %s which cannot be invoked until%s", corralled.name(), 1595 unresolved(corralled), sb.toString()); 1596 if (otherErrors.size() > 0) { 1597 printDiagnostics(corralled.source(), otherErrors, true); 1598 } 1599 } 1600 //where 1601 void printEvalException(EvalException ex) { 1602 if (ex.getMessage() == null) { 1603 hard("%s thrown", ex.getExceptionClassName()); 1604 } else { 1605 hard("%s thrown: %s", ex.getExceptionClassName(), ex.getMessage()); 1606 } 1607 printStackTrace(ex.getStackTrace()); 1608 } 1609 //where 1610 String unresolved(DeclarationSnippet key) { 1611 List<String> unr = state.unresolvedDependencies(key); 1612 StringBuilder sb = new StringBuilder(); 1613 int fromLast = unr.size(); 1614 if (fromLast > 0) { 1615 sb.append(" "); 1616 } 1617 for (String u : unr) { 1618 --fromLast; 1619 sb.append(u); 1620 if (fromLast == 0) { 1621 // No suffix 1622 } else if (fromLast == 1) { 1623 sb.append(", and "); 1624 } else { 1625 sb.append(", "); 1626 } 1627 } 1628 switch (unr.size()) { 1629 case 0: 1630 break; 1631 case 1: 1632 sb.append(" is declared"); 1633 break; 1634 default: 1635 sb.append(" are declared"); 1636 break; 1637 } 1638 return sb.toString(); 1639 } 1640 1641 enum Feedback { 1642 Default, 1643 Off, 1644 Concise, 1645 Normal, 1646 Verbose 1647 } 1648 1649 Feedback feedback() { 1650 if (feedback == Feedback.Default) { 1651 return input == null || input.interactiveOutput() ? Feedback.Normal : Feedback.Off; 1652 } 1653 return feedback; 1654 } 1655 1656 boolean notInStartUp(Snippet sn) { 1657 return mapSnippet.get(sn).space != startNamespace; 1658 } 1659 1660 /** The current version number as a string. 1661 */ 1662 static String version() { 1663 return version("release"); // mm.nn.oo[-milestone] 1664 } 1665 1666 /** The current full version number as a string. 1667 */ 1668 static String fullVersion() { 1669 return version("full"); // mm.mm.oo[-milestone]-build 1670 } 1671 1672 private static final String versionRBName = "jdk.internal.jshell.tool.resources.version"; 1673 private static ResourceBundle versionRB; 1674 1675 private static String version(String key) { 1676 if (versionRB == null) { 1677 try { 1678 versionRB = ResourceBundle.getBundle(versionRBName); 1679 } catch (MissingResourceException e) { 1680 return "(version info not available)"; 1681 } 1682 } 1683 try { 1684 return versionRB.getString(key); 1685 } 1686 catch (MissingResourceException e) { 1687 return "(version info not available)"; 1688 } 1689 } 1690 1691 class NameSpace { 1692 final String spaceName; 1693 final String prefix; 1694 private int nextNum; 1695 1696 NameSpace(String spaceName, String prefix) { 1697 this.spaceName = spaceName; 1698 this.prefix = prefix; 1699 this.nextNum = 1; 1700 } 1701 1702 String tid(Snippet sn) { 1703 String tid = prefix + nextNum++; 1704 mapSnippet.put(sn, new SnippetInfo(sn, this, tid)); 1705 return tid; 1706 } 1707 1708 String tidNext() { 1709 return prefix + nextNum; 1710 } 1711 } 1712 1713 static class SnippetInfo { 1714 final Snippet snippet; 1715 final NameSpace space; 1716 final String tid; 1717 1718 SnippetInfo(Snippet snippet, NameSpace space, String tid) { 1719 this.snippet = snippet; 1720 this.space = space; 1721 this.tid = tid; 1722 } 1723 } 1724} 1725 1726class ScannerIOContext extends IOContext { 1727 1728 private final Scanner scannerIn; 1729 private final PrintStream pStream; 1730 1731 public ScannerIOContext(Scanner scannerIn, PrintStream pStream) { 1732 this.scannerIn = scannerIn; 1733 this.pStream = pStream; 1734 } 1735 1736 @Override 1737 public String readLine(String prompt, String prefix) { 1738 if (pStream != null && prompt != null) { 1739 pStream.print(prompt); 1740 } 1741 if (scannerIn.hasNextLine()) { 1742 return scannerIn.nextLine(); 1743 } else { 1744 return null; 1745 } 1746 } 1747 1748 @Override 1749 public boolean interactiveOutput() { 1750 return true; 1751 } 1752 1753 @Override 1754 public Iterable<String> currentSessionHistory() { 1755 return Collections.emptyList(); 1756 } 1757 1758 @Override 1759 public void close() { 1760 scannerIn.close(); 1761 } 1762 1763 @Override 1764 public boolean terminalEditorRunning() { 1765 return false; 1766 } 1767 1768 @Override 1769 public void suspend() { 1770 } 1771 1772 @Override 1773 public void resume() { 1774 } 1775 1776 @Override 1777 public void beforeUserCode() { 1778 } 1779 1780 @Override 1781 public void afterUserCode() { 1782 } 1783 1784 @Override 1785 public void replaceLastHistoryEntry(String source) { 1786 } 1787} 1788 1789class FileScannerIOContext extends ScannerIOContext { 1790 1791 public FileScannerIOContext(String fn) throws FileNotFoundException { 1792 this(new FileReader(fn)); 1793 } 1794 1795 public FileScannerIOContext(Reader rdr) throws FileNotFoundException { 1796 super(new Scanner(rdr), null); 1797 } 1798 1799 @Override 1800 public boolean interactiveOutput() { 1801 return false; 1802 } 1803} 1804 1805