JEP 222: jshell: The Java Shell (Read-Eval-Print Loop)

OwnerRobert Field
TypeFeature
ScopeJDK
StatusClosed / Delivered
Release9
Componenttools / jshell
Discussionkulla dash dev at openjdk dot java dot net
EffortL
DurationL
Reviewed byAlex Buckley, Brian Goetz
Endorsed byBrian Goetz
Created2014/05/16 23:13
Updated2017/06/09 04:21
Issue8043364

Summary

Provide an interactive tool to evaluate declarations, statements, and expressions of the Java programming language, together with an API so that other applications can leverage this functionality.

Goals

The JShell API and tool will provide a way to interactively evaluate declarations, statements, and expressions of the Java programming language within the JShell state. The JShell state includes an evolving code and execution state. To facilitate rapid investigation and coding, statements and expressions need not occur within a method, and variables and method need not occur within a class.

The jshell tool will be a command-line tool with features to ease interaction including: a history with editing, tab-completion, automatic addition of needed terminal semicolons, and configurable predefined imports and definitions.

Non-Goals

A new interactive language is not the goal: All accepted input must match grammar productions in the Java Language Specification (JLS). Further, within an appropriate surrounding context, all accepted input must be valid Java code (JShell will automatically provide that surrounding context -- the "wrapping"). That is, if X is an input that JShell accepts (as opposed to rejects with error) then there is an A and B such that AXB is a valid program in the Java programming language.

Out of scope are graphical interfaces and debugger support. The JShell API is intended to allow JShell functionality in IDEs and other tools, but the jshell tool is not intended to be an IDE.

Motivation

Immediate feedback is important when learning a programming language and its APIs. The number one reason schools cite for moving away from Java as a teaching language is that other languages have a "REPL" and have far lower bars to an initial "Hello, world!" program. A Read-Eval-Print Loop (REPL) is an interactive programming tool which loops, continually reading user input, evaluating the input, and printing the value of the input or a description of the state change the input caused. Scala, Ruby, JavaScript, Haskell, Clojure, and Python all have REPLs and all allow small initial programs. JShell adds REPL functionality to the Java platform.

Exploration of coding options is also important for developers prototyping code or investigating a new API. Interactive evaluation is vastly more efficient in this regard than edit/compile/execute and System.out.println.

Without the ceremony of class Foo { public static void main(String[] args) { ... } }, learning and exploration is streamlined.

Description

Functionality

The JShell API will provide all of JShell's evaluation functionality. The code fragments that are input to the API are referred to as "snippets". The jshell tool will also use the JShell completion API to determine when input is incomplete (and the user must be prompted for more), when it would be complete if a semicolon were added (in which case the tool will append the semicolon) and also how to complete input when completion is requested with a tab. The tool will have a set of commands for query, saving and restoring work, and configuration. Commands will be distinguished from snippets by a leading slash.

Documentation

The JShell module API Specifications can found here:

Which includes the primary JShell API (jdk.jshell package) Specification:

The jshell tool reference:

is part of the Java Platform, Standard Edition Tools Reference:

Terms

In this document, the term “class” is meant in the sense used in the Java Virtual Machine Specification (JVMS), which includes Java Language Specification (JLS) classes, interfaces, enums, and annotation types. The text makes it clear if a different meaning is intended.

Snippets

A snippet must correspond to one of the following JLS syntax productions:

In JShell, a "variable" is a storage location and has an associated type. A variable is created explicitly with a FieldDeclaration snippet:

int a = 42;

or implicitly by an expression (see below). Variables have a small amount of field semantics/syntax (for example, the volatile modifier is allowed). However, variables have no user-visible class enclosing them and will be generally viewed and used like local variables.

All expressions are accepted as snippets. This includes expressions without side effects, such as constants, variable accesses, and lambda expressions:

1
a
2+2
Math.PI
x -> x+1
(String s) -> s.length()

as well as expressions with side effects, such as assignments and method invocations:

a = 1
System.out.println("Hello world");
new BufferedReader(new InputStreamReader(System.in))

Some forms of expression snippet implicitly create a variable to store the expression's value so it can be referred to later by other snippets. By default, an implicitly created variable has the name $X where X is the snippet identifier. A variable is not implicitly created if the expression is void (the println example), or if the value of the expression can already be referred to by a simple name (as in the case of 'a' and 'a=1' above; all other examples have variables implicitly created for them).

All statements are accepted as snippets, except for 'break', 'continue', and 'return'. However, a snippet may contain 'break', 'continue', or 'return' statements where they meet the usual rules of the Java programming language for enclosing context. For example, the return statement in this snippet is valid because it is enclosed in a lambda expression:

() -> { return 42; }

A declaration snippet (ClassDeclaration, InterfaceDeclaration, MethodDeclaration, or FieldDeclaration) is a snippet that explicitly introduces a name that can be referred to by other snippets. A declaration snippet is subject to the following rules:

All snippets, except for those of the form ImportDeclaration, may contain nested declarations. For example, a snippet that is a class instance creation expression may specify an anonymous class body with nested method declarations. The usual rules of the Java programming language apply to modifiers on nested declarations, rather than the rules above. For example, the class snippet below is accepted, and the private modifier on the nested method declaration is respected, so the snippet "new C().secret()" would not be accepted:

class C {
  int answer() { return 2 * secret(); }
  private int secret() { return 21; }
}

A snippet may not declare a package or a module. All JShell code is placed in a single package in an unnamed module. The name of the package is controlled by JShell.

Within the jshell tool, the terminal semicolon of a snippet can be omitted if that semicolon would be the last character of the input (excluding whitespace and comments).

State

The JShell state is held in an instance of JShell. A snippet is evaluated in a JShell with the eval(...) method, producing an error, declaring code, or executing a statement or expression. In the case of a variable with an initializer, both declaration and execution occur. An instance of JShell contains previously-defined and modified variables, methods, and classes, previously-defined import declarations, the side-effects of previously-entered statements and expressions (including variable initializers), and external code bases.

Modification

Since the desired use is exploration, the declarations (variables, methods, and classes) must be able to evolve over time while, at the same time, preserving evaluated data. One choice would be to make a changed declaration a new additional entity in some or all cases, but that is certain to be confusing and does not play well with exploring the interaction between declarations. In JShell, each unique declaration key has exactly one declaration at any given time. For variables and classes the unique declaration key is the name, and, the unique declaration key for methods is the name and the parameter types (to allow for overloading). As this is Java, variable, methods, and classes each have their own name spaces.

Forward reference

In the Java programming language, within the body of a class, references to members which will appear later can appear; this is a forward reference. As code is entered and evaluated sequentially in JShell, these references will be temporarily unresolved. In some cases, for example mutual recursion, forward reference is required. This can also occur in exploratory programming while entering code, for example, realizing that another (so far unwritten) method should be called. JShell supports forward references in method bodies, return type, and parameter types, in variable type, and, within a class. Since the semantics requires them to be immediately executed, forward references in variable initializers is not supported.

Snippet dependencies

The code state is kept up-to-date and consistent; that is, when a snippet is evaluated, any changes to dependent snippets are immediately propagated.

When a snippet is successfully declared, the declaration will be one of three kinds: Added, Modified, or Replaced. A snippet is Added if it is the first declaration with that key. A snippet is Replaced if its key matches a previous snippet, but their signatures differ. A snippet is Modified if its key matches a previous snippet and their signatures match; in this case, no dependent snippets are impacted. In both the Modified and Replaced cases the previous snippet is no longer part of the code state.

When a snippet is Added it may be providing an unresolved reference. When a snippet is Replaced it may update an existing snippet. For example, if a method's return type is declared to be of class C and then class C is Replaced then the method's signature has changed and the method must be Replaced. Note: This can cause previously-valid methods or classes to become invalid.

The desire is that user data persist whenever possible. This is attained except in the case of variable Replace. When a variable is replaced, either directly by the user or indirectly via a dependency update, the variable is set to its default value (null since this can only occur with reference variables).

When a declaration is invalid, either because of a forward-reference or becoming invalid through an update, the declaration is "corralled". A corralled declaration can be used in other declarations and code, however, if an attempt is made to execute it a runtime exception will occur which will explain the unresolved references or other issues.

Wrapping

In the Java programming language, variables, methods, statements, and expressions must be nested within other constructs, ultimately a class. When the implementation of JShell compiles a variable, method, statement, and expression snippet as Java code an artificial context is needed, as follows:

This wrapping also enables snippet update, so, note that a snippet class is also wrapped in a synthetic class.

Modular Environment Configuration

The jshell tool has the following options for controlling the modular environment:

The modular environment can also be configured by direct addition to the compiler and runtime options. Compiler flags may be added with the -C option. Runtime flags may be added with the -R option.

All jshell tool options are documented in the Tool Reference (see above).

The modular environment can be configured at the API level with the compilerOptions and remoteVMOptions methods on JShell.Builder.

The set of modules read by JShell's unnamed module is the same as the default set of root modules for the unnamed module, as established by JEP 261 "Root modules":

Naming

Alternatives

A simpler alternative is just to provide a batch scripting wrapper without interactive/update support.

Another alternative is to maintain the status quo: Use another language or use a third-party REPL such as BeanShell, though that particular REPL has been dormant for many years, is based on JDK 1.3, and makes arbitrary changes to the language.

Many IDEs, for example the NetBeans debugger and BlueJ's CodePad, provide mechanisms to interactively evaluate expressions. Preserved context and code remains class-based, and method granularity is not supported. They use specially crafted parsers/interpreters.

Testing

The API facilitates detailed point testing. A test framework makes writing tests straight-forward.

Because the evaluation and query functionality of the tool is built on the API, most testing is of the API. Command testing and sanity testing of the tool is, however, also needed. The tool is built with hooks for a testing harness, which is used for tool testing.

Tests are comprised of three parts:

  1. Tests for the API. These tests cover both positive and negative cases. Each public method must be covered by tests which include adding variables, methods, and class, redefining them, etc.

  2. Testing of the jshell tool. These tests check that jshell commands and compilation, and execution of Java code, have correct behavior.

  3. Stress testing. To ensure that JShell can compile all allowed Java snippets, correct Java code from the JDK itself will be used. These tests parse the sources, feed code chunks to the API, and test the behavior of the API.

Dependences

The implementation will make every effort to leverage the accuracy and engineering effort of the existing language support in the JDK. The JShell state is modeled as a JVM instance. Code analysis and the production of executable code (jdk.jshell API) will be performed by the Java Compiler (javac) through the Compiler API. Code replacement (jdk.jshell.execution) will use the Java Debug Interface (JDI).

Parsing of raw snippets (i.e., snippets that have not been wrapped) will be done using the Compiler API with a small subclassing of the parser to allow raw snippets. The resulting information will be used to wrap the snippet into a valid compilation unit including a class declaration with imports for previously evaluated code. Further analysis and generation of the class file will be done with unmodified instances of the Java compiler. Generated class files will be kept in memory and never written to storage. jdk.jshell.spi SPI exists to configure the execution engine. The default execution engine behaves as follows. Class files will be sent over a socket to the remote process. A remote agent will handle loading and execution. Replacement will be done via the JDI VirtualMachine.redefineClasses() facility.

Tab-completion analysis (jdk.jshell API) will also use the Compiler API. Completion detection will use the javac lexer, custom and table-driven code.

The jshell tool (jdk.internal.jshell.tool) will use 'jline2' for console input, editing, and history. jline2 has been, privately, rolled into the JDK.