Skip to content

Commit 6e966e8

Browse files
committed
update readme v1
1 parent 7643b87 commit 6e966e8

File tree

4 files changed

+88
-15
lines changed

4 files changed

+88
-15
lines changed

README.md

+85-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# scala-debug-adapter
2+
23
[![build-badge][]][build]
34
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/ch.epfl.scala/sbt-debug-adapter/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ch.epfl.scala/sbt-debug-adapter)
45

@@ -9,11 +10,12 @@ The scala-debug-adapter is a server-side implementation of the [Debug Adapter Pr
910
It is based on and extends the [microsoft/java-debug](https://github.com/microsoft/java-debug) implementation.
1011

1112
The project originated in the [Bloop](https://github.com/scalacenter/bloop) repository, it is now released independently so that it can be used in other build tools of the Scala ecosystem.
12-
For instance, the [sbt-debug-adapter](#sbt-debug-adapter) is an sbt plugin that provides sbt with the debug adapter capability.
13+
For instance, the [sbt-debug-adapter](#sbt-debug-adapter plugin) is an sbt plugin that provides sbt with the debug adapter capability.
1314

1415
## Usage
1516

1617
You can add the `scala-debug-adapter` as a dependency in your `build.sbt`:
18+
1719
```scala
1820
// build.sbt
1921
scalaVersion := "2.12.16",
@@ -28,7 +30,7 @@ While it is always the case with Java 9, you probably want to use the `sbt-jdi-t
2830
addSbtPlugin("org.scala-debugger" % "sbt-jdi-tools" % "1.1.1")
2931
```
3032

31-
You can start a debug server by providing your own intance of `Debuggee`, `DebugToolsResolver` and `Logger`.
33+
You can start a debug server by providing your own instance of `Debuggee`, `DebugToolsResolver` and `Logger`.
3234

3335
### The `Debuggee`
3436

@@ -37,6 +39,7 @@ The `Debuggee` is a trait that describes the program that we want to debug.
3739
It contains the list of modules of the current project, the list of libraries, the list of unmanaged entries and the java runtime.
3840

3941
There are a few difference between a module, a library and an unmanaged entry:
42+
4043
- `Module`: We know its exact scala version, and its compiler options.
4144
They will be used by the debugger to evaluate expressions in the module.
4245
- `Library`: We only know its binary version.
@@ -51,11 +54,12 @@ The sbt version of `Debuggee` can be found [here](https://github.com/scalacenter
5154

5255
### The `DebugToolsResolver`
5356

54-
Depending on the Scala versions specified in the `Debuggee`, the debugger will need to resolve some additionnal tools:
57+
Depending on the Scala versions specified in the `Debuggee`, the debugger will need to resolve some additional tools:
58+
5559
- The expression compiler: to compile the Scala expression that the user wants to evaluate
56-
- The step filter: to filter the intermediate steps of execution that are generated by the compiler, among which the mixin-forwarders, the bridges, the getters and setters, the synthetized methods.
60+
- The step filter (renamed to `unpickler`): to filter the intermediate steps of execution that are generated by the compiler, among which the mixin-forwarders, the bridges, the getters and setters, the synthetized methods.
5761

58-
The role of the `DebugToolsResolver` is to resovle the debug tools and their dependencies, and to load them in a class-loader.
62+
The role of the `DebugToolsResolver` is to resolve the debug tools and their dependencies, and to load them in a class-loader.
5963
The debugger takes care of reusing those class-loaders as often as possible: it should call the `DebugToolsResolver` only once by Scala version.
6064

6165
To implement the `DebugToolsResolver` you can use the Maven coordinates of the required tools in `ch.epfl.scala.debugadapter.BuildInfo`.
@@ -74,21 +78,21 @@ val address = new DebugServer.Address()
7478
val tools = DebugTools(debuggee, resolver, logger)
7579

7680
// create and start the debug server
77-
val server = DebugServer(debugge, tools, logger, address)
81+
val server = DebugServer(debuggee, tools, logger, address)
7882
server.start()
7983

8084
// return address.uri for the DAP client to connect
8185
address.uri
8286
```
8387

84-
# sbt-debug-adapter
88+
## sbt-debug-adapter plugin
8589

8690
The `sbt-debug-adapter` is an sbt plugin compatible with sbt `1.4.0` or greater.
8791
It provides the sbt server with the BSP `debugSession/start` endpoint to start a Scala DAP server.
8892

8993
The specification of the `debugSession/start` endpoint can be found in the [Bloop documentation](https://scalacenter.github.io/bloop/docs/debugging-reference).
9094

91-
## Usage
95+
### Plugin usage
9296

9397
As a global plugin use `~/.sbt/1.0/plugins/plugins.sbt`, otherwise add to your local project (e.g. `project/plugins.sbt`):
9498

@@ -105,19 +109,86 @@ To do so you can use the `sbt-jdi-tools` plugin in the meta project (it goes to
105109
addSbtPlugin("org.scala-debugger" % "sbt-jdi-tools" % "1.1.1")
106110
```
107111

108-
# Development
112+
## Development
113+
114+
### expression-compiler
115+
116+
The [`expression-compiler`](https://github.com/scalacenter/scala-debug-adapter/tree/main/modules/expression-compiler) is a module used to compile expression from the debug console. It will insert the expression in the source file, compile it, and return the result of the expression.
117+
118+
To do so, it adds 2 new phases to the Scala compiler:
119+
120+
- `extract-expression`: extract the expression from the source file in a new class from where we can evaluate it. To keep the context (local variables), we import them in the new class via a `Map`
121+
- `resolve-reflect-eval`: use reflection to resolve some symbols of the expression
109122

110-
## tests
123+
Debugging the `expression-compiler` requires to enable some logs to show the trees generated by the compiler and the expression compiler. To do so, you need to uncomment the following lines:
124+
125+
- `println(messageAndPos(...))` in `ExpressionReporter.scala`
126+
- For Scala 3.1+: [file](https://github.com/iusildra/scala-debug-adapter/blob/main/modules/expression-compiler/src/main/scala-3.1+/dotty/tools/dotc/ExpressionReporter.scala)
127+
- For Scala 3.0: [file](https://github.com/iusildra/scala-debug-adapter/blob/main/modules/expression-compiler/src/main/scala-3.0/dotty/tools/dotc/ExpressionReporter.scala)
128+
- `// "-Vprint:typer,extract-expression,resolve-reflect-eval"` in [`ExpressionCompilerBridge.scala`](https://github.com/iusildra/scala-debug-adapter/blob/main/modules/expression-compiler/src/main/scala-3/dotty/tools/dotc/ExpressionCompilerBridge.scala)
129+
130+
### Runtime evaluator
131+
132+
#### Idea
133+
134+
The runtime evaluator is a fully reflection-based evaluation mode designed for simple expressions (without implicits, generics, etc.) written in the debug console. It is less precise than the expression compiler (that can evaluate any kind of expression), but it is much faster (since it does not need to compile). Also, because it is reflection-based, we can access runtime types and private members, which is not possible with the expression compiler. For instance:
135+
136+
```scala
137+
class A { private val x = 64 }
138+
class B extends A { val y = 42 }
139+
140+
val a: A = new A
141+
val b: A = new B
142+
143+
// a.x returns 64
144+
// b.y returns 42
145+
```
146+
147+
#### Constraints
148+
149+
Because it is less precise than the expression compiler, it might fail while evaluating an expression (and placing the program in an invalid state), or evaluate it incorrectly (bad overload resolution for instance). To overcome this, a validation step is performed before any evaluation and an order of priority has been defined to choose between the expression compiler and the runtime evaluator:
150+
151+
![evaluation mode priorities](./doc/evaluation-provider.svg)
152+
153+
#### Implementation
154+
155+
The evaluator is implemented in a 3-steps process:
156+
157+
- parse the expression with [scalameta](https://scalameta.org/) to get the tree representing the expression
158+
- validation: traverse the tree and recursively check that the expression and its sub-expressions can be evaluated at runtime (type-checking, polymorphism overloads resolution, type resolution, etc.). Transform the AST from scalameta to a new AST containing all the information needed for evaluation (see [RuntimeTree.scala](https://github.com/scalacenter/scala-debug-adapter/blob/main/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala))
159+
- evaluation: consume the new AST and evaluate the expression
160+
161+
The validation AST is separated in 2 main parts:
162+
163+
- `RuntimeEvaluableTree`: these nodes are valid expressions by themselves (e.g. a literal, a method call, a module, etc.)
164+
- `RuntimeValidationTree`: theses nodes are not a valid expression by themselves (e.g. a class name), but they can be contained within an evaluable node (e.g. static member access)
165+
166+
The validation is modeled with a [Validation](https://github.com/scalacenter/scala-debug-adapter/blob/main/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/Validation.scala) monad:
167+
168+
- `Valid`: the expression is valid
169+
- `Recoverable`: the expression is not valid, but we might be able to recover from it (e.g. when validating `foo`, we might not find a local variable `foo`, but we might find a field, a method, etc. that we can use instead)
170+
- `CompilerRecoverable` when information at runtime is not enough to validate the information (e.g. overloads ambiguity at runtime), the expression compiler might be able to validate it
171+
- `Fatal` for errors that cannot be recovered (e.g. parsing error, unexpected exception, etc.). *Abort the whole evaluation process*
172+
173+
#### Pre evaluation
174+
175+
Pre-evaluation is a validation mode that first validate an expression, and evaluate it does not have side effects / fail (only a few nodes can be pre-evaluated). It is used to access the runtime type of an expression to get more information about it. ([RuntimePreEvaluationValidator.scala](https://github.com/scalacenter/scala-debug-adapter/blob/main/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala))
176+
177+
### tests
111178

112179
The [`tests`](https://github.com/scalacenter/scala-debug-adapter/tree/main/modules/tests/src/test/scala/ch/epfl/scala/debugadapter) module contains the tests of the Scala Debug Server.
113180
It uses the [TestingDebugClient](https://github.com/scalacenter/scala-debug-adapter/blob/main/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingDebugClient.scala), a minimal debug client that is used to communicate with the debug server via a socket.
114181

115-
# References
182+
### Show logs
183+
184+
To print the logs during the tests, you must change the logger in `DebugTestSuite#getDebugServer` and `DebugTestSuite#startDebugServer` from `NoopLogger` to `PrintLogger`
185+
186+
## References
116187

117-
- [Bloop Debugging Referece](https://scalacenter.github.io/bloop/docs/debugging-reference)
188+
- [Bloop Debugging Reference](https://scalacenter.github.io/bloop/docs/debugging-reference)
118189
- [Microsoft DAP for Java](https://github.com/microsoft/vscode-java-debug)
119190
- [DebugAdapterProvider](https://github.com/build-server-protocol/build-server-protocol/issues/145)
120191

121-
# History
192+
## History
122193

123-
- [Origial project discussion](https://github.com/scalameta/metals-feature-requests/issues/168)
194+
- [Original project discussion](https://github.com/scalameta/metals-feature-requests/issues/168)

0 commit comments

Comments
 (0)