mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 21:01:51 +03:00
Repl & Debugger (#345)
This commit is contained in:
parent
9f3fda1b42
commit
427e784663
14
build.sbt
14
build.sbt
@ -309,11 +309,14 @@ val truffleRunOptionsSettings = Seq(
|
|||||||
)
|
)
|
||||||
|
|
||||||
lazy val runtime = (project in file("engine/runtime"))
|
lazy val runtime = (project in file("engine/runtime"))
|
||||||
|
.configs(Benchmark)
|
||||||
.settings(
|
.settings(
|
||||||
version := "0.1",
|
version := "0.1",
|
||||||
commands += WithDebugCommand.withDebug,
|
commands += WithDebugCommand.withDebug,
|
||||||
inConfig(Compile)(truffleRunOptionsSettings),
|
inConfig(Compile)(truffleRunOptionsSettings),
|
||||||
inConfig(Test)(truffleRunOptionsSettings),
|
inConfig(Test)(truffleRunOptionsSettings),
|
||||||
|
inConfig(Benchmark)(Defaults.testSettings),
|
||||||
|
inConfig(Benchmark)(truffleRunOptionsSettings),
|
||||||
parallelExecution in Test := false,
|
parallelExecution in Test := false,
|
||||||
logBuffered in Test := false,
|
logBuffered in Test := false,
|
||||||
libraryDependencies ++= jmh ++ Seq(
|
libraryDependencies ++= jmh ++ Seq(
|
||||||
@ -330,9 +333,9 @@ lazy val runtime = (project in file("engine/runtime"))
|
|||||||
"org.scalacheck" %% "scalacheck" % "1.14.0" % Test,
|
"org.scalacheck" %% "scalacheck" % "1.14.0" % Test,
|
||||||
"org.scalactic" %% "scalactic" % "3.0.8" % Test,
|
"org.scalactic" %% "scalactic" % "3.0.8" % Test,
|
||||||
"org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test,
|
"org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test,
|
||||||
|
"org.graalvm.truffle" % "truffle-api" % graalVersion % Benchmark,
|
||||||
"org.typelevel" %% "cats-core" % "2.0.0-M4"
|
"org.typelevel" %% "cats-core" % "2.0.0-M4"
|
||||||
),
|
)
|
||||||
libraryDependencies ++= jmh
|
|
||||||
)
|
)
|
||||||
.settings(
|
.settings(
|
||||||
(Compile / javacOptions) ++= Seq(
|
(Compile / javacOptions) ++= Seq(
|
||||||
@ -345,11 +348,8 @@ lazy val runtime = (project in file("engine/runtime"))
|
|||||||
.dependsOn(Def.task { (Compile / sourceManaged).value.mkdirs })
|
.dependsOn(Def.task { (Compile / sourceManaged).value.mkdirs })
|
||||||
.value
|
.value
|
||||||
)
|
)
|
||||||
.configs(Benchmark)
|
|
||||||
.settings(
|
.settings(
|
||||||
logBuffered := false,
|
logBuffered := false,
|
||||||
inConfig(Benchmark)(Defaults.testSettings),
|
|
||||||
inConfig(Benchmark)(truffleRunOptionsSettings),
|
|
||||||
bench := (test in Benchmark).tag(Exclusive).value,
|
bench := (test in Benchmark).tag(Exclusive).value,
|
||||||
benchOnly := Def.inputTaskDyn {
|
benchOnly := Def.inputTaskDyn {
|
||||||
import complete.Parsers.spaceDelimited
|
import complete.Parsers.spaceDelimited
|
||||||
@ -385,8 +385,10 @@ lazy val language_server = project
|
|||||||
inConfig(Compile)(truffleRunOptionsSettings),
|
inConfig(Compile)(truffleRunOptionsSettings),
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"org.graalvm.sdk" % "polyglot-tck" % graalVersion % "provided",
|
"org.graalvm.sdk" % "polyglot-tck" % graalVersion % "provided",
|
||||||
|
"org.graalvm.truffle" % "truffle-api" % graalVersion % "provided",
|
||||||
"commons-cli" % "commons-cli" % "1.4",
|
"commons-cli" % "commons-cli" % "1.4",
|
||||||
"io.github.spencerpark" % "jupyter-jvm-basekernel" % "2.3.0"
|
"io.github.spencerpark" % "jupyter-jvm-basekernel" % "2.3.0",
|
||||||
|
"org.jline" % "jline" % "3.1.3"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.settings(
|
.settings(
|
||||||
|
@ -4,6 +4,7 @@ import java.io.InputStream
|
|||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
import org.enso.interpreter.Constants
|
import org.enso.interpreter.Constants
|
||||||
|
import org.enso.interpreter.instrument.ReplDebuggerInstrument
|
||||||
import org.enso.interpreter.runtime.RuntimeOptions
|
import org.enso.interpreter.runtime.RuntimeOptions
|
||||||
import org.graalvm.polyglot.Context
|
import org.graalvm.polyglot.Context
|
||||||
|
|
||||||
@ -18,14 +19,16 @@ class ContextFactory {
|
|||||||
* @param packagesPath Enso packages path
|
* @param packagesPath Enso packages path
|
||||||
* @param in the input stream for standard in
|
* @param in the input stream for standard in
|
||||||
* @param out the output stream for standard out
|
* @param out the output stream for standard out
|
||||||
|
* @param repl the Repl manager to use for this context
|
||||||
* @return configured Context instance
|
* @return configured Context instance
|
||||||
*/
|
*/
|
||||||
def create(
|
def create(
|
||||||
packagesPath: String = "",
|
packagesPath: String = "",
|
||||||
in: InputStream = System.in,
|
in: InputStream,
|
||||||
out: OutputStream = System.out
|
out: OutputStream,
|
||||||
): Context =
|
repl: Repl
|
||||||
Context
|
): Context = {
|
||||||
|
val context = Context
|
||||||
.newBuilder(Constants.LANGUAGE_ID)
|
.newBuilder(Constants.LANGUAGE_ID)
|
||||||
.allowExperimentalOptions(true)
|
.allowExperimentalOptions(true)
|
||||||
.allowAllAccess(true)
|
.allowAllAccess(true)
|
||||||
@ -33,4 +36,10 @@ class ContextFactory {
|
|||||||
.out(out)
|
.out(out)
|
||||||
.in(in)
|
.in(in)
|
||||||
.build
|
.build
|
||||||
|
val instrument = context.getEngine.getInstruments
|
||||||
|
.get(ReplDebuggerInstrument.INSTRUMENT_ID)
|
||||||
|
.lookup(classOf[ReplDebuggerInstrument])
|
||||||
|
instrument.setSessionManager(repl)
|
||||||
|
context
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,12 @@ import org.graalvm.polyglot.Context
|
|||||||
*/
|
*/
|
||||||
class JupyterKernel extends BaseKernel {
|
class JupyterKernel extends BaseKernel {
|
||||||
private val context: Context =
|
private val context: Context =
|
||||||
new ContextFactory().create("", getIO.in, getIO.out)
|
new ContextFactory().create(
|
||||||
|
"",
|
||||||
|
getIO.in,
|
||||||
|
getIO.out,
|
||||||
|
Repl(SimpleReplIO(getIO.in, getIO.out))
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates Enso code in the context of Jupyter request
|
* Evaluates Enso code in the context of Jupyter request
|
||||||
|
@ -13,6 +13,7 @@ object Main {
|
|||||||
private val RUN_OPTION = "run"
|
private val RUN_OPTION = "run"
|
||||||
private val HELP_OPTION = "help"
|
private val HELP_OPTION = "help"
|
||||||
private val NEW_OPTION = "new"
|
private val NEW_OPTION = "new"
|
||||||
|
private val REPL_OPTION = "repl"
|
||||||
private val JUPYTER_OPTION = "jupyter-kernel"
|
private val JUPYTER_OPTION = "jupyter-kernel"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,6 +27,10 @@ object Main {
|
|||||||
.longOpt(HELP_OPTION)
|
.longOpt(HELP_OPTION)
|
||||||
.desc("Displays this message.")
|
.desc("Displays this message.")
|
||||||
.build
|
.build
|
||||||
|
val repl = Option.builder
|
||||||
|
.longOpt(REPL_OPTION)
|
||||||
|
.desc("Runs the Enso REPL.")
|
||||||
|
.build
|
||||||
val run = Option.builder
|
val run = Option.builder
|
||||||
.hasArg(true)
|
.hasArg(true)
|
||||||
.numberOfArgs(1)
|
.numberOfArgs(1)
|
||||||
@ -50,6 +55,7 @@ object Main {
|
|||||||
val options = new Options
|
val options = new Options
|
||||||
options
|
options
|
||||||
.addOption(help)
|
.addOption(help)
|
||||||
|
.addOption(repl)
|
||||||
.addOption(run)
|
.addOption(run)
|
||||||
.addOption(newOpt)
|
.addOption(newOpt)
|
||||||
.addOption(jupyterOption)
|
.addOption(jupyterOption)
|
||||||
@ -105,12 +111,28 @@ object Main {
|
|||||||
}
|
}
|
||||||
mainLocation = main.get
|
mainLocation = main.get
|
||||||
}
|
}
|
||||||
val context = new ContextFactory().create(packagePath)
|
val context = new ContextFactory().create(
|
||||||
val source = Source.newBuilder(Constants.LANGUAGE_ID, mainLocation).build
|
packagePath,
|
||||||
|
System.in,
|
||||||
|
System.out,
|
||||||
|
Repl(TerminalIO())
|
||||||
|
)
|
||||||
|
val source = Source.newBuilder(Constants.LANGUAGE_ID, mainLocation).build
|
||||||
context.eval(source)
|
context.eval(source)
|
||||||
exitSuccess()
|
exitSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the `--repl` CLI option
|
||||||
|
*/
|
||||||
|
private def runRepl(): Unit = {
|
||||||
|
val dummySourceToTriggerRepl = "@{ @breakpoint[@Debug] }"
|
||||||
|
val context =
|
||||||
|
new ContextFactory().create("", System.in, System.out, Repl(TerminalIO()))
|
||||||
|
context.eval(Constants.LANGUAGE_ID, dummySourceToTriggerRepl)
|
||||||
|
exitSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main entry point for the CLI program.
|
* Main entry point for the CLI program.
|
||||||
*
|
*
|
||||||
@ -134,6 +156,9 @@ object Main {
|
|||||||
if (line.hasOption(RUN_OPTION)) {
|
if (line.hasOption(RUN_OPTION)) {
|
||||||
run(line.getOptionValue(RUN_OPTION))
|
run(line.getOptionValue(RUN_OPTION))
|
||||||
}
|
}
|
||||||
|
if (line.hasOption(REPL_OPTION)) {
|
||||||
|
runRepl()
|
||||||
|
}
|
||||||
if (line.hasOption(JUPYTER_OPTION)) {
|
if (line.hasOption(JUPYTER_OPTION)) {
|
||||||
new JupyterKernel().run(line.getOptionValue(JUPYTER_OPTION))
|
new JupyterKernel().run(line.getOptionValue(JUPYTER_OPTION))
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
package org.enso.languageserver
|
||||||
|
|
||||||
|
import java.io.{InputStream, OutputStream, PrintStream}
|
||||||
|
import java.util.Scanner
|
||||||
|
|
||||||
|
import org.enso.interpreter.instrument.ReplDebuggerInstrument
|
||||||
|
import org.jline.reader.{LineReader, LineReaderBuilder}
|
||||||
|
import org.jline.terminal.{Terminal, TerminalBuilder}
|
||||||
|
|
||||||
|
import collection.JavaConverters._
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents user console input.
|
||||||
|
*/
|
||||||
|
sealed trait UserInput
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End of user input.
|
||||||
|
*/
|
||||||
|
case object EndOfInput extends UserInput
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A normal line of user input.
|
||||||
|
* @param line the contents of the input line
|
||||||
|
*/
|
||||||
|
case class Line(line: String) extends UserInput
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract representation of Repl IO operations
|
||||||
|
*/
|
||||||
|
trait ReplIO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask user for a line of input, using given prompt
|
||||||
|
* @param prompt the prompt to display to the user
|
||||||
|
* @return the user-provided input
|
||||||
|
*/
|
||||||
|
def readLine(prompt: String): UserInput
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a line to the REPL.
|
||||||
|
* @param contents contents of the line to print
|
||||||
|
*/
|
||||||
|
def println(contents: String): Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A barebones implementation of [[ReplIO]] based on standard input / output operations.
|
||||||
|
* @param in input stream to use
|
||||||
|
* @param out output stream to use
|
||||||
|
*/
|
||||||
|
case class SimpleReplIO(in: InputStream, out: OutputStream) extends ReplIO {
|
||||||
|
private val scanner: Scanner = new Scanner(in)
|
||||||
|
private val printer: PrintStream = new PrintStream(out)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask user for a line of input, using given prompt
|
||||||
|
* @param prompt the prompt to display to the user
|
||||||
|
* @return the user-provided input
|
||||||
|
*/
|
||||||
|
override def readLine(prompt: String): UserInput = {
|
||||||
|
printer.print(prompt)
|
||||||
|
Try(scanner.nextLine()).map(Line).getOrElse(EndOfInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a line to the REPL.
|
||||||
|
* @param contents contents of the line to print
|
||||||
|
*/
|
||||||
|
override def println(contents: String): Unit = printer.println(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of [[ReplIO]] using system terminal capabilities.
|
||||||
|
*/
|
||||||
|
case class TerminalIO() extends ReplIO {
|
||||||
|
private val terminal: Terminal =
|
||||||
|
TerminalBuilder.builder().system(true).build()
|
||||||
|
private val lineReader: LineReader =
|
||||||
|
LineReaderBuilder.builder().terminal(terminal).build()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask user for a line of input, using given prompt
|
||||||
|
* @param prompt the prompt to display to the user
|
||||||
|
* @return the user-provided input
|
||||||
|
*/
|
||||||
|
override def readLine(prompt: String): UserInput =
|
||||||
|
Try(lineReader.readLine(prompt)).map(Line).getOrElse(EndOfInput)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a line to the REPL.
|
||||||
|
* @param contents contents of the line to print
|
||||||
|
*/
|
||||||
|
override def println(contents: String): Unit =
|
||||||
|
terminal.writer().println(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Repl logic to inject into runtime instrumentation framework.
|
||||||
|
*
|
||||||
|
* @param replIO the IO implementation to use with this Repl
|
||||||
|
*/
|
||||||
|
case class Repl(replIO: ReplIO) extends ReplDebuggerInstrument.SessionManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the Repl session by asking for user input and performing
|
||||||
|
* the requested action with the execution node.
|
||||||
|
*
|
||||||
|
* End of input causes exit from the Repl.
|
||||||
|
*
|
||||||
|
* @param executionNode the execution node capable of performing
|
||||||
|
* language-level operations
|
||||||
|
*/
|
||||||
|
override def startSession(
|
||||||
|
executionNode: ReplDebuggerInstrument.ReplExecutionEventNode
|
||||||
|
): Unit = {
|
||||||
|
while (true) {
|
||||||
|
val input = replIO.readLine("> ")
|
||||||
|
input match {
|
||||||
|
case EndOfInput =>
|
||||||
|
executionNode.exit()
|
||||||
|
return
|
||||||
|
case Line(line) =>
|
||||||
|
if (line == ":list" || line == ":l") {
|
||||||
|
val bindings = executionNode.listBindings().asScala
|
||||||
|
bindings.foreach {
|
||||||
|
case (varName, value) =>
|
||||||
|
replIO.println(s"$varName = $value")
|
||||||
|
}
|
||||||
|
} else if (line == ":quit" || line == ":q") {
|
||||||
|
executionNode.exit()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
replIO.println(s">>> ${executionNode.evaluate(line)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package org.enso.interpreter;
|
|||||||
import com.oracle.truffle.api.CallTarget;
|
import com.oracle.truffle.api.CallTarget;
|
||||||
import com.oracle.truffle.api.Truffle;
|
import com.oracle.truffle.api.Truffle;
|
||||||
import com.oracle.truffle.api.TruffleLanguage;
|
import com.oracle.truffle.api.TruffleLanguage;
|
||||||
|
import com.oracle.truffle.api.debug.DebuggerTags;
|
||||||
import com.oracle.truffle.api.instrumentation.ProvidedTags;
|
import com.oracle.truffle.api.instrumentation.ProvidedTags;
|
||||||
import com.oracle.truffle.api.instrumentation.StandardTags;
|
import com.oracle.truffle.api.instrumentation.StandardTags;
|
||||||
import com.oracle.truffle.api.nodes.RootNode;
|
import com.oracle.truffle.api.nodes.RootNode;
|
||||||
@ -33,6 +34,7 @@ import org.graalvm.options.OptionDescriptors;
|
|||||||
contextPolicy = TruffleLanguage.ContextPolicy.SHARED,
|
contextPolicy = TruffleLanguage.ContextPolicy.SHARED,
|
||||||
fileTypeDetectors = FileDetector.class)
|
fileTypeDetectors = FileDetector.class)
|
||||||
@ProvidedTags({
|
@ProvidedTags({
|
||||||
|
DebuggerTags.AlwaysHalt.class,
|
||||||
StandardTags.CallTag.class,
|
StandardTags.CallTag.class,
|
||||||
StandardTags.ExpressionTag.class,
|
StandardTags.ExpressionTag.class,
|
||||||
StandardTags.RootTag.class,
|
StandardTags.RootTag.class,
|
||||||
|
@ -0,0 +1,203 @@
|
|||||||
|
package org.enso.interpreter.instrument;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.debug.DebuggerTags;
|
||||||
|
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
|
import com.oracle.truffle.api.instrumentation.*;
|
||||||
|
import org.enso.interpreter.Language;
|
||||||
|
import org.enso.interpreter.node.expression.debug.CaptureResultScopeNode;
|
||||||
|
import org.enso.interpreter.node.expression.debug.EvalNode;
|
||||||
|
import org.enso.interpreter.runtime.Builtins;
|
||||||
|
import org.enso.interpreter.runtime.callable.CallerInfo;
|
||||||
|
import org.enso.interpreter.runtime.callable.function.Function;
|
||||||
|
import org.enso.interpreter.runtime.scope.FramePointer;
|
||||||
|
import org.enso.interpreter.runtime.state.Stateful;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** The Instrument implementation for the interactive debugger REPL. */
|
||||||
|
@TruffleInstrument.Registration(
|
||||||
|
id = ReplDebuggerInstrument.INSTRUMENT_ID,
|
||||||
|
services = ReplDebuggerInstrument.class)
|
||||||
|
public class ReplDebuggerInstrument extends TruffleInstrument {
|
||||||
|
/** This instrument's registration id. */
|
||||||
|
public static final String INSTRUMENT_ID = "enso-repl";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal reference type to store session manager and get the current version on each execution
|
||||||
|
* of this instrument.
|
||||||
|
*/
|
||||||
|
private static class SessionManagerReference {
|
||||||
|
private SessionManager sessionManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instanc of this class
|
||||||
|
*
|
||||||
|
* @param sessionManager the session manager to initially store
|
||||||
|
*/
|
||||||
|
private SessionManagerReference(SessionManager sessionManager) {
|
||||||
|
this.sessionManager = sessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current session manager
|
||||||
|
*
|
||||||
|
* @return the current session manager
|
||||||
|
*/
|
||||||
|
private SessionManager get() {
|
||||||
|
return sessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a new session manager for subsequent {@link #get()} calls.
|
||||||
|
*
|
||||||
|
* @param sessionManager the new session manager
|
||||||
|
*/
|
||||||
|
private void set(SessionManager sessionManager) {
|
||||||
|
this.sessionManager = sessionManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An object controlling the execution of REPL. */
|
||||||
|
public interface SessionManager {
|
||||||
|
/**
|
||||||
|
* Starts a new session with the provided execution node.
|
||||||
|
*
|
||||||
|
* @param executionNode the execution node that should be used for the duration of this session.
|
||||||
|
*/
|
||||||
|
void startSession(ReplExecutionEventNode executionNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SessionManagerReference sessionManagerReference =
|
||||||
|
new SessionManagerReference(ReplExecutionEventNode::exit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by Truffle when this instrument is installed.
|
||||||
|
*
|
||||||
|
* @param env the instrumentation environment
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Env env) {
|
||||||
|
SourceSectionFilter filter =
|
||||||
|
SourceSectionFilter.newBuilder().tagIs(DebuggerTags.AlwaysHalt.class).build();
|
||||||
|
Instrumenter instrumenter = env.getInstrumenter();
|
||||||
|
env.registerService(this);
|
||||||
|
instrumenter.attachExecutionEventFactory(
|
||||||
|
filter, ctx -> new ReplExecutionEventNode(ctx, sessionManagerReference));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the session manager to use whenever this instrument is activated.
|
||||||
|
*
|
||||||
|
* @param sessionManager the session manager to use
|
||||||
|
*/
|
||||||
|
public void setSessionManager(SessionManager sessionManager) {
|
||||||
|
this.sessionManagerReference.set(sessionManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The actual node that's installed as a probe on any node the instrument was launched for. */
|
||||||
|
public static class ReplExecutionEventNode extends ExecutionEventNode {
|
||||||
|
private @Child EvalNode evalNode = EvalNode.buildWithResultScopeCapture();
|
||||||
|
|
||||||
|
private Object lastReturn;
|
||||||
|
private Object lastState;
|
||||||
|
private CallerInfo lastScope;
|
||||||
|
|
||||||
|
private EventContext eventContext;
|
||||||
|
private SessionManagerReference sessionManagerReference;
|
||||||
|
|
||||||
|
private ReplExecutionEventNode(
|
||||||
|
EventContext eventContext, SessionManagerReference sessionManagerReference) {
|
||||||
|
this.eventContext = eventContext;
|
||||||
|
this.sessionManagerReference = sessionManagerReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getValue(MaterializedFrame frame, FramePointer ptr) {
|
||||||
|
return getProperFrame(frame, ptr).getValue(ptr.getFrameSlot());
|
||||||
|
}
|
||||||
|
|
||||||
|
private MaterializedFrame getProperFrame(MaterializedFrame frame, FramePointer ptr) {
|
||||||
|
MaterializedFrame currentFrame = frame;
|
||||||
|
for (int i = 0; i < ptr.getParentLevel(); i++) {
|
||||||
|
currentFrame = Function.ArgumentsHelper.getLocalScope(currentFrame.getArguments());
|
||||||
|
}
|
||||||
|
return currentFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all the bindings available in the current execution scope.
|
||||||
|
*
|
||||||
|
* @return a map, where keys are variable names and values are current values of variables.
|
||||||
|
*/
|
||||||
|
public Map<String, Object> listBindings() {
|
||||||
|
Map<String, FramePointer> flatScope = lastScope.getLocalScope().flatten();
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
for (Map.Entry<String, FramePointer> entry : flatScope.entrySet()) {
|
||||||
|
result.put(entry.getKey(), getValue(lastScope.getFrame(), entry.getValue()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates an arbitrary expression in the current execution context.
|
||||||
|
*
|
||||||
|
* @param expression the expression to evaluate
|
||||||
|
* @return the result of evaluating the expression
|
||||||
|
*/
|
||||||
|
public Object evaluate(String expression) {
|
||||||
|
try {
|
||||||
|
Stateful result = evalNode.execute(lastScope, lastState, expression);
|
||||||
|
lastState = result.getState();
|
||||||
|
CaptureResultScopeNode.WithCallerInfo payload =
|
||||||
|
(CaptureResultScopeNode.WithCallerInfo) result.getValue();
|
||||||
|
lastScope = payload.getCallerInfo();
|
||||||
|
lastReturn = payload.getResult();
|
||||||
|
return lastReturn;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminates this REPL session.
|
||||||
|
*
|
||||||
|
* <p>The last result of {@link #evaluate(String)} (or {@link Builtins#unit()} if {@link
|
||||||
|
* #evaluate(String)} was not called before) will be returned from the instrumented node.
|
||||||
|
*
|
||||||
|
* <p>This function must always be called at the end of REPL session, as otherwise the program
|
||||||
|
* will never resume. It's forbidden to use this object after exit has been called.
|
||||||
|
*/
|
||||||
|
public void exit() {
|
||||||
|
throw eventContext.createUnwind(lastReturn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by Truffle whenever this node starts execution.
|
||||||
|
*
|
||||||
|
* @param frame current execution frame
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onEnter(VirtualFrame frame) {
|
||||||
|
lastScope = Function.ArgumentsHelper.getCallerInfo(frame.getArguments());
|
||||||
|
lastReturn = lookupContextReference(Language.class).get().getUnit().newInstance();
|
||||||
|
lastState = lastScope.getFrame().getValue(lastScope.getLocalScope().getStateFrameSlot());
|
||||||
|
sessionManagerReference.get().startSession(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by Truffle whenever an unwind {@see {@link EventContext#createUnwind(Object)}} was
|
||||||
|
* thrown in the course of REPL execution.
|
||||||
|
*
|
||||||
|
* <p>We use this mechanism to inject the REPL-returned value back into caller code.
|
||||||
|
*
|
||||||
|
* @param frame current execution frame
|
||||||
|
* @param info The unwind's payload. Currently unused.
|
||||||
|
* @return the object that will become the instrumented node's return value
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Object onUnwind(VirtualFrame frame, Object info) {
|
||||||
|
return new Stateful(lastState, lastReturn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,13 +15,10 @@ import org.enso.interpreter.runtime.scope.ModuleScope;
|
|||||||
public abstract class EnsoRootNode extends RootNode {
|
public abstract class EnsoRootNode extends RootNode {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final SourceSection sourceSection;
|
private final SourceSection sourceSection;
|
||||||
private final FrameSlot stateFrameSlot;
|
|
||||||
private final LocalScope localScope;
|
private final LocalScope localScope;
|
||||||
private final ModuleScope moduleScope;
|
private final ModuleScope moduleScope;
|
||||||
private @CompilerDirectives.CompilationFinal TruffleLanguage.ContextReference<Context>
|
private @CompilerDirectives.CompilationFinal TruffleLanguage.ContextReference<Context>
|
||||||
contextReference;
|
contextReference;
|
||||||
private @CompilerDirectives.CompilationFinal TruffleLanguage.LanguageReference<Language>
|
|
||||||
languageReference;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs the root node.
|
* Constructs the root node.
|
||||||
@ -43,8 +40,6 @@ public abstract class EnsoRootNode extends RootNode {
|
|||||||
this.localScope = localScope;
|
this.localScope = localScope;
|
||||||
this.moduleScope = moduleScope;
|
this.moduleScope = moduleScope;
|
||||||
this.sourceSection = sourceSection;
|
this.sourceSection = sourceSection;
|
||||||
this.stateFrameSlot =
|
|
||||||
localScope.getFrameDescriptor().findOrAddFrameSlot("<<state>>", FrameSlotKind.Object);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,7 +79,7 @@ public abstract class EnsoRootNode extends RootNode {
|
|||||||
* @return the state frame slot
|
* @return the state frame slot
|
||||||
*/
|
*/
|
||||||
public FrameSlot getStateFrameSlot() {
|
public FrameSlot getStateFrameSlot() {
|
||||||
return this.stateFrameSlot;
|
return localScope.getStateFrameSlot();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
package org.enso.interpreter.node.expression.builtin.debug;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
|
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||||
|
import org.enso.interpreter.Constants;
|
||||||
|
import org.enso.interpreter.Language;
|
||||||
|
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
|
||||||
|
import org.enso.interpreter.node.expression.debug.BreakpointNode;
|
||||||
|
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
|
||||||
|
import org.enso.interpreter.runtime.callable.function.Function;
|
||||||
|
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
|
||||||
|
import org.enso.interpreter.runtime.state.Stateful;
|
||||||
|
|
||||||
|
/** Root of the builtin Debug.breakpoint function. */
|
||||||
|
@NodeInfo(
|
||||||
|
shortName = "Debug.breakpoint",
|
||||||
|
description = "Root of the builtin Debug.breakpoint function.")
|
||||||
|
public class DebugBreakpointNode extends BuiltinRootNode {
|
||||||
|
private @Child BreakpointNode instrumentableNode = BreakpointNode.build();
|
||||||
|
|
||||||
|
private DebugBreakpointNode(Language language) {
|
||||||
|
super(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes this node by delegating to its instrumentable child.
|
||||||
|
*
|
||||||
|
* @param frame current execution frame
|
||||||
|
* @return the result of running the instrumentable node
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Stateful execute(VirtualFrame frame) {
|
||||||
|
Object state = Function.ArgumentsHelper.getState(frame.getArguments());
|
||||||
|
return instrumentableNode.execute(frame, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps this node in a 1-argument function.
|
||||||
|
*
|
||||||
|
* @param language current language instance
|
||||||
|
* @return the function wrapper for this node
|
||||||
|
*/
|
||||||
|
public static Function makeFunction(Language language) {
|
||||||
|
return Function.fromBuiltinRootNodeWithCallerFrameAccess(
|
||||||
|
new DebugBreakpointNode(language),
|
||||||
|
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
|
||||||
|
new ArgumentDefinition(
|
||||||
|
0, Constants.THIS_ARGUMENT_NAME, ArgumentDefinition.ExecutionMode.EXECUTE));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package org.enso.interpreter.node.expression.debug;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.debug.DebuggerTags;
|
||||||
|
import com.oracle.truffle.api.dsl.CachedContext;
|
||||||
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
|
import com.oracle.truffle.api.instrumentation.GenerateWrapper;
|
||||||
|
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
|
||||||
|
import com.oracle.truffle.api.instrumentation.ProbeNode;
|
||||||
|
import com.oracle.truffle.api.instrumentation.Tag;
|
||||||
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
|
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||||
|
import org.enso.interpreter.Language;
|
||||||
|
import org.enso.interpreter.runtime.Builtins;
|
||||||
|
import org.enso.interpreter.runtime.Context;
|
||||||
|
import org.enso.interpreter.runtime.state.Stateful;
|
||||||
|
|
||||||
|
/** A base node serving as an instrumentable marker. */
|
||||||
|
@NodeInfo(description = "Instrumentation marker node.")
|
||||||
|
@GenerateWrapper
|
||||||
|
public abstract class BreakpointNode extends Node implements InstrumentableNode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells Truffle this node is instrumentable.
|
||||||
|
*
|
||||||
|
* @return {@code true} – this node is always instrumentable.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isInstrumentable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute this node. Does not do anything interesting, the default implementation returns {@link
|
||||||
|
* Builtins#unit()}. The behavior and return value of this node are assumed to be injected by
|
||||||
|
* attached instruments.
|
||||||
|
*
|
||||||
|
* @param frame current execution frame
|
||||||
|
* @param state current value of the monadic state
|
||||||
|
* @return the result of executing this node
|
||||||
|
*/
|
||||||
|
public abstract Stateful execute(VirtualFrame frame, Object state);
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
Stateful execute(
|
||||||
|
VirtualFrame frame, Object state, @CachedContext(Language.class) Context context) {
|
||||||
|
return new Stateful(state, context.getUnit().newInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of this node.
|
||||||
|
*
|
||||||
|
* @return a new instance of this node
|
||||||
|
*/
|
||||||
|
public static BreakpointNode build() {
|
||||||
|
return BreakpointNodeGen.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs Truffle about the provided tags.
|
||||||
|
*
|
||||||
|
* <p>This node only provides the {@link DebuggerTags.AlwaysHalt} tag.
|
||||||
|
*
|
||||||
|
* @param tag the tag to verify
|
||||||
|
* @return {@code true} if the tag is {@link DebuggerTags.AlwaysHalt}, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean hasTag(Class<? extends Tag> tag) {
|
||||||
|
return tag == DebuggerTags.AlwaysHalt.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instrumentable wrapper node for this node.
|
||||||
|
*
|
||||||
|
* @param probeNode the probe node to wrap
|
||||||
|
* @return the wrapper instance wrapping both this and the probe node
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public WrapperNode createWrapper(ProbeNode probeNode) {
|
||||||
|
return new BreakpointNodeWrapper(this, probeNode);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package org.enso.interpreter.node.expression.debug;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
|
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||||
|
import org.enso.interpreter.node.ExpressionNode;
|
||||||
|
import org.enso.interpreter.node.callable.CaptureCallerInfoNode;
|
||||||
|
import org.enso.interpreter.runtime.callable.CallerInfo;
|
||||||
|
|
||||||
|
/** Node capturing the runtime execution scope of its child. */
|
||||||
|
@NodeInfo(description = "Captures the child's execution scope.")
|
||||||
|
public class CaptureResultScopeNode extends ExpressionNode {
|
||||||
|
|
||||||
|
/** Value object wrapping the expression return value and the execution scope. */
|
||||||
|
public static class WithCallerInfo {
|
||||||
|
private final CallerInfo callerInfo;
|
||||||
|
private final Object result;
|
||||||
|
|
||||||
|
private WithCallerInfo(CallerInfo callerInfo, Object result) {
|
||||||
|
this.callerInfo = callerInfo;
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the attached caller info object.
|
||||||
|
*
|
||||||
|
* @return the caller info (execution scope) captured by this node
|
||||||
|
*/
|
||||||
|
public CallerInfo getCallerInfo() {
|
||||||
|
return callerInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the attached result object.
|
||||||
|
*
|
||||||
|
* @return the result captured by this node
|
||||||
|
*/
|
||||||
|
public Object getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Child ExpressionNode expression;
|
||||||
|
private @Child CaptureCallerInfoNode captureCallerInfoNode = CaptureCallerInfoNode.build();
|
||||||
|
|
||||||
|
private CaptureResultScopeNode(ExpressionNode expression) {
|
||||||
|
this.expression = expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of this node.
|
||||||
|
*
|
||||||
|
* @param expressionNode the child of this node, the return value of which should be captured
|
||||||
|
* @return an instance of this node
|
||||||
|
*/
|
||||||
|
public static CaptureResultScopeNode build(ExpressionNode expressionNode) {
|
||||||
|
return new CaptureResultScopeNode(expressionNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the node by running the child expression and capturing the caller info object
|
||||||
|
* containing current scope information.
|
||||||
|
*
|
||||||
|
* @param frame the stack frame for execution
|
||||||
|
* @return the captured caller info and the return value of the child expression
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public WithCallerInfo executeGeneric(VirtualFrame frame) {
|
||||||
|
return new WithCallerInfo(
|
||||||
|
captureCallerInfoNode.execute(frame), expression.executeGeneric(frame));
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,11 @@ import org.enso.interpreter.runtime.state.Stateful;
|
|||||||
/** Node running Enso expressions passed to it as strings. */
|
/** Node running Enso expressions passed to it as strings. */
|
||||||
@NodeInfo(description = "Evaluates code passed to it as string")
|
@NodeInfo(description = "Evaluates code passed to it as string")
|
||||||
public abstract class EvalNode extends BaseNode {
|
public abstract class EvalNode extends BaseNode {
|
||||||
|
private final boolean shouldCaptureResultScope;
|
||||||
|
|
||||||
|
EvalNode(boolean shouldCaptureResultScope) {
|
||||||
|
this.shouldCaptureResultScope = shouldCaptureResultScope;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of this node.
|
* Creates an instance of this node.
|
||||||
@ -27,7 +32,16 @@ public abstract class EvalNode extends BaseNode {
|
|||||||
* @return an instance of this node
|
* @return an instance of this node
|
||||||
*/
|
*/
|
||||||
public static EvalNode build() {
|
public static EvalNode build() {
|
||||||
return EvalNodeGen.create();
|
return EvalNodeGen.create(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of this node, with frame capture enabled.
|
||||||
|
*
|
||||||
|
* @return an instance of this node
|
||||||
|
*/
|
||||||
|
public static EvalNode buildWithResultScopeCapture() {
|
||||||
|
return EvalNodeGen.create(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,6 +62,9 @@ public abstract class EvalNode extends BaseNode {
|
|||||||
.get()
|
.get()
|
||||||
.compiler()
|
.compiler()
|
||||||
.runInline(expression, language, localScope, moduleScope);
|
.runInline(expression, language, localScope, moduleScope);
|
||||||
|
if (shouldCaptureResultScope) {
|
||||||
|
expr = CaptureResultScopeNode.build(expr);
|
||||||
|
}
|
||||||
ClosureRootNode framedNode =
|
ClosureRootNode framedNode =
|
||||||
new ClosureRootNode(
|
new ClosureRootNode(
|
||||||
lookupLanguageReference(Language.class).get(),
|
lookupLanguageReference(Language.class).get(),
|
||||||
|
@ -2,6 +2,7 @@ package org.enso.interpreter.runtime;
|
|||||||
|
|
||||||
import org.enso.interpreter.Language;
|
import org.enso.interpreter.Language;
|
||||||
import org.enso.interpreter.node.expression.builtin.IfZeroNode;
|
import org.enso.interpreter.node.expression.builtin.IfZeroNode;
|
||||||
|
import org.enso.interpreter.node.expression.builtin.debug.DebugBreakpointNode;
|
||||||
import org.enso.interpreter.node.expression.builtin.debug.DebugEvalNode;
|
import org.enso.interpreter.node.expression.builtin.debug.DebugEvalNode;
|
||||||
import org.enso.interpreter.node.expression.builtin.error.CatchErrorNode;
|
import org.enso.interpreter.node.expression.builtin.error.CatchErrorNode;
|
||||||
import org.enso.interpreter.node.expression.builtin.error.CatchPanicNode;
|
import org.enso.interpreter.node.expression.builtin.error.CatchPanicNode;
|
||||||
@ -64,6 +65,7 @@ public class Builtins {
|
|||||||
scope.registerMethod(state, "run", RunStateNode.makeFunction(language));
|
scope.registerMethod(state, "run", RunStateNode.makeFunction(language));
|
||||||
|
|
||||||
scope.registerMethod(debug, "eval", DebugEvalNode.makeFunction(language));
|
scope.registerMethod(debug, "eval", DebugEvalNode.makeFunction(language));
|
||||||
|
scope.registerMethod(debug, "breakpoint", DebugBreakpointNode.makeFunction(language));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,6 +2,7 @@ package org.enso.interpreter.runtime.scope;
|
|||||||
|
|
||||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||||
import com.oracle.truffle.api.frame.FrameSlot;
|
import com.oracle.truffle.api.frame.FrameSlot;
|
||||||
|
import com.oracle.truffle.api.frame.FrameSlotKind;
|
||||||
import org.enso.interpreter.runtime.error.VariableRedefinitionException;
|
import org.enso.interpreter.runtime.error.VariableRedefinitionException;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -14,9 +15,10 @@ import java.util.Optional;
|
|||||||
* frames.
|
* frames.
|
||||||
*/
|
*/
|
||||||
public class LocalScope {
|
public class LocalScope {
|
||||||
public final Map<String, FrameSlot> items;
|
|
||||||
private final FrameDescriptor frameDescriptor;
|
private final FrameDescriptor frameDescriptor;
|
||||||
public final LocalScope parent;
|
private final LocalScope parent;
|
||||||
|
private final Map<String, FrameSlot> items;
|
||||||
|
private final FrameSlot stateFrameSlot;
|
||||||
|
|
||||||
/** Creates a root local scope. */
|
/** Creates a root local scope. */
|
||||||
public LocalScope() {
|
public LocalScope() {
|
||||||
@ -29,9 +31,11 @@ public class LocalScope {
|
|||||||
* @param parent the parent scope
|
* @param parent the parent scope
|
||||||
*/
|
*/
|
||||||
public LocalScope(LocalScope parent) {
|
public LocalScope(LocalScope parent) {
|
||||||
items = new HashMap<>();
|
this.items = new HashMap<>();
|
||||||
frameDescriptor = new FrameDescriptor();
|
this.frameDescriptor = new FrameDescriptor();
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
|
this.stateFrameSlot =
|
||||||
|
frameDescriptor.findOrAddFrameSlot("<<monadic_state>>", FrameSlotKind.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,4 +101,31 @@ public class LocalScope {
|
|||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the monadic state frame slot for this local scope.
|
||||||
|
*
|
||||||
|
* @return the frame slot containing monadic state
|
||||||
|
*/
|
||||||
|
public FrameSlot getStateFrameSlot() {
|
||||||
|
return stateFrameSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, FramePointer> flattenWithLevel(int level) {
|
||||||
|
Map<String, FramePointer> parentResult =
|
||||||
|
parent == null ? new HashMap<>() : getParent().flattenWithLevel(level + 1);
|
||||||
|
for (Map.Entry<String, FrameSlot> entry : items.entrySet()) {
|
||||||
|
parentResult.put(entry.getKey(), new FramePointer(level, entry.getValue()));
|
||||||
|
}
|
||||||
|
return parentResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a flat representation of the scope, including variables from all parent lexical scopes.
|
||||||
|
*
|
||||||
|
* @return a flat representation of this scope
|
||||||
|
*/
|
||||||
|
public Map<String, FramePointer> flatten() {
|
||||||
|
return flattenWithLevel(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package org.enso.interpreter.test
|
|||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
import org.enso.interpreter.Constants
|
import org.enso.interpreter.Constants
|
||||||
|
import org.enso.interpreter.instrument.ReplDebuggerInstrument
|
||||||
import org.graalvm.polyglot.{Context, Value}
|
import org.graalvm.polyglot.{Context, Value}
|
||||||
import org.scalatest.{FlatSpec, Matchers}
|
import org.scalatest.{FlatSpec, Matchers}
|
||||||
|
|
||||||
@ -29,6 +30,12 @@ trait InterpreterRunner {
|
|||||||
|
|
||||||
def parse(code: String): Value =
|
def parse(code: String): Value =
|
||||||
InterpreterException.rethrowPolyglot(eval(code))
|
InterpreterException.rethrowPolyglot(eval(code))
|
||||||
|
|
||||||
|
def getReplInstrument: ReplDebuggerInstrument = {
|
||||||
|
ctx.getEngine.getInstruments
|
||||||
|
.get(ReplDebuggerInstrument.INSTRUMENT_ID)
|
||||||
|
.lookup(classOf[ReplDebuggerInstrument])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait InterpreterTest
|
trait InterpreterTest
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
package org.enso.interpreter.test.instrument
|
||||||
|
|
||||||
|
import org.enso.interpreter.test.InterpreterTest
|
||||||
|
import collection.JavaConverters._
|
||||||
|
|
||||||
|
class ReplTest extends InterpreterTest {
|
||||||
|
"Repl" should "be able to list local variables in its scope" in {
|
||||||
|
val code =
|
||||||
|
"""
|
||||||
|
|@{
|
||||||
|
| x = 10;
|
||||||
|
| y = 20;
|
||||||
|
| z = x + y;
|
||||||
|
| @breakpoint[@Debug]
|
||||||
|
|}
|
||||||
|
|""".stripMargin
|
||||||
|
var scopeResult: Map[String, AnyRef] = Map()
|
||||||
|
getReplInstrument.setSessionManager { executor =>
|
||||||
|
scopeResult = executor.listBindings.asScala.toMap
|
||||||
|
executor.exit()
|
||||||
|
}
|
||||||
|
eval(code)
|
||||||
|
scopeResult shouldEqual Map("x" -> 10, "y" -> 20, "z" -> 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
"Repl" should "be able to execute arbitrary code in the caller scope" in {
|
||||||
|
val code =
|
||||||
|
"""
|
||||||
|
|@{
|
||||||
|
| x = 1;
|
||||||
|
| y = 2;
|
||||||
|
| @breakpoint[@Debug]
|
||||||
|
|}
|
||||||
|
|""".stripMargin
|
||||||
|
var evalResult: AnyRef = null
|
||||||
|
getReplInstrument.setSessionManager { executor =>
|
||||||
|
evalResult = executor.evaluate("x + y")
|
||||||
|
executor.exit()
|
||||||
|
}
|
||||||
|
eval(code)
|
||||||
|
evalResult shouldEqual 3
|
||||||
|
}
|
||||||
|
|
||||||
|
"Repl" should "return the last evaluated value back to normal execution flow" in {
|
||||||
|
val code =
|
||||||
|
"""
|
||||||
|
|@{
|
||||||
|
| a = 5;
|
||||||
|
| b = 6;
|
||||||
|
| c = @breakpoint[@Debug];
|
||||||
|
| c * a
|
||||||
|
|}
|
||||||
|
|""".stripMargin
|
||||||
|
getReplInstrument.setSessionManager { executor =>
|
||||||
|
executor.evaluate("a + b")
|
||||||
|
executor.exit()
|
||||||
|
}
|
||||||
|
eval(code) shouldEqual 55
|
||||||
|
}
|
||||||
|
|
||||||
|
"Repl" should "be able to define its local variables" in {
|
||||||
|
val code =
|
||||||
|
"""
|
||||||
|
|@{
|
||||||
|
| x = 10;
|
||||||
|
| @breakpoint[@Debug]
|
||||||
|
|}
|
||||||
|
|""".stripMargin
|
||||||
|
getReplInstrument.setSessionManager { executor =>
|
||||||
|
executor.evaluate("y = x + 1")
|
||||||
|
executor.evaluate("z = y * x")
|
||||||
|
executor.evaluate("z")
|
||||||
|
executor.exit()
|
||||||
|
}
|
||||||
|
eval(code) shouldEqual 110
|
||||||
|
}
|
||||||
|
|
||||||
|
"Repl" should "access and modify monadic state" in {
|
||||||
|
val code =
|
||||||
|
"""
|
||||||
|
|@{
|
||||||
|
| @put[@State, 10];
|
||||||
|
| @breakpoint[@Debug];
|
||||||
|
| @get[@State]
|
||||||
|
|}
|
||||||
|
|""".stripMargin
|
||||||
|
getReplInstrument.setSessionManager { executor =>
|
||||||
|
executor.evaluate("x = @get[@State]")
|
||||||
|
executor.evaluate("@put[@State, x+1]")
|
||||||
|
executor.exit()
|
||||||
|
}
|
||||||
|
eval(code) shouldEqual 11
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user