Skip to content

Commit cc4a3e4

Browse files
committed
Implement step in and step out
- Renamed step to stepOver to make it more specific what is going on. - Implemented step in and step out logic. - Made stepIn do what step, now stepOver, was doing. Closes #5 Closes #1089
1 parent 2ae578f commit cc4a3e4

File tree

2 files changed

+154
-20
lines changed

2 files changed

+154
-20
lines changed

debugger/src/main/scala/org.apache.daffodil.debugger.dap/DAPodil.scala

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ class DAPodil(
193193
disconnect(request, args)
194194
case extract(Command.EVALUATE, args: EvaluateArguments) =>
195195
eval(request, args)
196+
case extract(Command.STEPIN, _) =>
197+
stepIn(request)
198+
case extract(Command.STEPOUT, _) =>
199+
stepOut(request)
196200
case _ =>
197201
session.abort(ErrorEvent.UnhandledRequest(show"unhandled request $request"), show"unhandled request $request")
198202
}
@@ -328,7 +332,7 @@ class DAPodil(
328332
state.get.flatMap {
329333
case DAPodil.State.Launched(debugee) =>
330334
for {
331-
_ <- debugee.step()
335+
_ <- debugee.stepIn()
332336
_ <- session.sendResponse(request.respondSuccess())
333337
} yield ()
334338
case s => DAPodil.InvalidState.raise(request, "Launched", s)
@@ -423,6 +427,26 @@ class DAPodil(
423427
} yield ()
424428
case s => DAPodil.InvalidState.raise(request, "Launched", s)
425429
}
430+
431+
def stepIn(request: Request): IO[Unit] =
432+
state.get.flatMap {
433+
case DAPodil.State.Launched(debugee) =>
434+
for {
435+
_ <- debugee.stepIn()
436+
_ <- session.sendResponse(request.respondSuccess())
437+
} yield ()
438+
case s => DAPodil.InvalidState.raise(request, "Launched", s)
439+
}
440+
441+
def stepOut(request: Request): IO[Unit] =
442+
state.get.flatMap {
443+
case DAPodil.State.Launched(debugee) =>
444+
for {
445+
_ <- debugee.stepOut()
446+
_ <- session.sendResponse(request.respondSuccess())
447+
} yield ()
448+
case s => DAPodil.InvalidState.raise(request, "Launched", s)
449+
}
426450
}
427451

428452
object DAPodil extends IOApp {
@@ -554,7 +578,9 @@ object DAPodil extends IOApp {
554578
.compile
555579
.lastOrError
556580

557-
def step(): IO[Unit]
581+
def stepOver(): IO[Unit]
582+
def stepIn(): IO[Unit]
583+
def stepOut(): IO[Unit]
558584
def continue(): IO[Unit]
559585
def pause(): IO[Unit]
560586
def setBreakpoints(uri: URI, lines: List[DAPodil.Line]): IO[Unit]
@@ -571,7 +597,9 @@ object DAPodil extends IOApp {
571597
object Stopped {
572598
def entry: Stopped = Stopped(Reason.Entry)
573599
def pause: Stopped = Stopped(Reason.Pause)
574-
def step: Stopped = Stopped(Reason.Step)
600+
def stepOver: Stopped = Stopped(Reason.StepOver)
601+
def stepIn: Stopped = Stopped(Reason.StepIn)
602+
def stepOut: Stopped = Stopped(Reason.StepOut)
575603
def breakpointHit(location: DAPodil.Location): Stopped = Stopped(Reason.BreakpointHit(location))
576604

577605
sealed trait Reason
@@ -587,7 +615,9 @@ object DAPodil extends IOApp {
587615
case object Pause extends Reason
588616

589617
/** The user requested a step, which completed, so now we are stopped again. */
590-
case object Step extends Reason
618+
case object StepOver extends Reason
619+
case object StepIn extends Reason
620+
case object StepOut extends Reason
591621

592622
/** A breakpoint was hit. */
593623
case class BreakpointHit(location: DAPodil.Location) extends Reason

debugger/src/main/scala/org.apache.daffodil.debugger.dap/Parse.scala

Lines changed: 120 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,27 @@ object Parse {
192192
def sourceChanges(): Stream[IO, DAPodil.Source] =
193193
Stream.empty
194194

195-
def step(): IO[Unit] =
196-
control.step() *> parseEvents
195+
def stepOver(): IO[Unit] =
196+
control.stepOver() *> parseEvents
197197
.send(
198198
Parse.Event
199-
.Control(DAPodil.Debugee.State.Stopped.step)
199+
.Control(DAPodil.Debugee.State.Stopped.stepOver)
200+
)
201+
.void
202+
203+
def stepIn(): IO[Unit] =
204+
control.stepIn() *> parseEvents
205+
.send(
206+
Parse.Event
207+
.Control(DAPodil.Debugee.State.Stopped.stepIn)
208+
)
209+
.void
210+
211+
def stepOut(): IO[Unit] =
212+
control.stepOut() *> parseEvents
213+
.send(
214+
Parse.Event
215+
.Control(DAPodil.Debugee.State.Stopped.stepOut)
200216
)
201217
.void
202218

@@ -748,7 +764,7 @@ object Parse {
748764

749765
startup = dapEvents.send(ConfigEvent(args)) *>
750766
(if (args.stopOnEntry)
751-
control.step() *>
767+
control.stepIn() *>
752768
events.send(Parse.Event.Control(DAPodil.Debugee.State.Stopped.entry))
753769
// don't use debugee.step as we need to send Stopped.entry, not Stopped.step
754770
else debugee.continue())
@@ -818,8 +834,12 @@ object Parse {
818834
new Events.StoppedEvent("entry", 1L)
819835
case DAPodil.Debugee.State.Stopped.Reason.Pause =>
820836
new Events.StoppedEvent("pause", 1L)
821-
case DAPodil.Debugee.State.Stopped.Reason.Step =>
837+
case DAPodil.Debugee.State.Stopped.Reason.StepOver =>
822838
new Events.StoppedEvent("step", 1L)
839+
case DAPodil.Debugee.State.Stopped.Reason.StepIn =>
840+
new Events.StoppedEvent("stepIn", 1L)
841+
case DAPodil.Debugee.State.Stopped.Reason.StepOut =>
842+
new Events.StoppedEvent("stepOut", 1L)
823843
case DAPodil.Debugee.State.Stopped.Reason.BreakpointHit(location) =>
824844
new Events.StoppedEvent("breakpoint", 1L, false, show"Breakpoint hit at $location", null)
825845
},
@@ -1248,6 +1268,16 @@ object Parse {
12481268
*/
12491269
def await(): IO[Boolean]
12501270

1271+
/** Update the control with the current element depth (set by the parser thread before awaiting). Depth is used to
1272+
* implement step/stepOut semantics.
1273+
*/
1274+
def setCurrentDepth(depth: Int): IO[Unit]
1275+
1276+
/** Indicate the kind of the upcoming await (e.g. "start" or "end"). Parser hooks should set this before calling
1277+
* await().
1278+
*/
1279+
def setAwaitingKind(kind: String): IO[Unit]
1280+
12511281
/** Start running. */
12521282
def continue(): IO[Unit]
12531283

@@ -1256,7 +1286,9 @@ object Parse {
12561286
* IMPORTANT: Shouldn't return until any work blocked by an `await` completes, otherwise the update that was
12571287
* waiting will race with the code that sees the `step` complete.
12581288
*/
1259-
def step(): IO[Unit]
1289+
def stepOver(): IO[Unit]
1290+
def stepIn(): IO[Unit]
1291+
def stepOut(): IO[Unit]
12601292

12611293
/** Stop running. */
12621294
def pause(): IO[Unit]
@@ -1275,6 +1307,9 @@ object Parse {
12751307
for {
12761308
waiterArrived <- Deferred[IO, Unit]
12771309
state <- Ref[IO].of[State](AwaitingFirstAwait(waiterArrived))
1310+
currentDepth <- Ref[IO].of[Int](0)
1311+
awaitingKind <- Ref[IO].of[String]("")
1312+
stopTarget <- Ref[IO].of[Option[(Int, String)]](None)
12781313
} yield new Control {
12791314
def await(): IO[Boolean] =
12801315
for {
@@ -1286,34 +1321,75 @@ object Parse {
12861321
.complete(()) *> nextContinue.get.as(true)
12871322
case Running => Running -> IO.pure(false)
12881323
case s @ Stopped(whenContinued, nextAwaitStarted) =>
1289-
s -> nextAwaitStarted.complete(()) *> // signal next await happened
1290-
whenContinued.get.as(true) // block
1324+
s -> nextAwaitStarted.complete(()) *>
1325+
// Decide whether to block or let parser continue
1326+
stopTarget.get.flatMap {
1327+
case None => whenContinued.get.as(true)
1328+
case Some((targetDepth, mode)) =>
1329+
for {
1330+
cur <- currentDepth.get
1331+
kind <- awaitingKind.get
1332+
res <- mode match {
1333+
// Allow running while deeper than target.
1334+
// When at or above target and we're at an end-element, block once then clear target
1335+
case "stepOut" =>
1336+
if (cur > targetDepth) IO.pure(false)
1337+
else if (kind == "end") whenContinued.get.flatMap(_ => stopTarget.set(None).as(true))
1338+
else IO.pure(false)
1339+
1340+
// Allow running until we reach a deeper depth, then pause at the start-element and clear target
1341+
case "stepIn" =>
1342+
if (cur < targetDepth) IO.pure(false)
1343+
else if (kind == "start") whenContinued.get.flatMap(_ => stopTarget.set(None).as(true))
1344+
else IO.pure(false)
1345+
1346+
// Block once and clear the stopTarget so subsequent awaits don't re-trigger
1347+
case _ => whenContinued.get.flatMap(_ => stopTarget.set(None).as(true))
1348+
}
1349+
} yield res
1350+
}
12911351
}.flatten
12921352
} yield awaited
12931353

1294-
def step(): IO[Unit] =
1354+
def performStep(stepType: String, addedDepth: Int): IO[Unit] =
12951355
for {
12961356
nextContinue <- Deferred[IO, Unit]
12971357
nextAwaitStarted <- Deferred[IO, Unit]
12981358
_ <- state.modify {
12991359
case s @ AwaitingFirstAwait(waiterArrived) =>
1300-
s -> waiterArrived.get *> step()
1360+
s -> waiterArrived.get *> (stepType match {
1361+
case "stepOver" => stepOver()
1362+
case "stepIn" => stepIn()
1363+
case "stepOut" => stepOut()
1364+
})
13011365
case Running => Running -> IO.unit
13021366
case Stopped(whenContinued, _) =>
13031367
Stopped(nextContinue, nextAwaitStarted) -> (
1304-
whenContinued.complete(()) *> // wake up await-ers
1305-
nextAwaitStarted.get // block until next await is invoked
1368+
for {
1369+
d <- currentDepth.get
1370+
_ <- stopTarget.set(Some((d + addedDepth, stepType)))
1371+
_ <- whenContinued.complete(())
1372+
_ <- nextAwaitStarted.get
1373+
} yield ()
13061374
)
13071375
}.flatten
13081376
} yield ()
13091377

1378+
def stepOver(): IO[Unit] = performStep("stepOver", 0)
1379+
def stepIn(): IO[Unit] = performStep("stepIn", 1)
1380+
def stepOut(): IO[Unit] = performStep("stepOut", -1)
1381+
1382+
def setCurrentDepth(depth: Int): IO[Unit] = currentDepth.set(depth)
1383+
1384+
def setAwaitingKind(kind: String): IO[Unit] = awaitingKind.set(kind)
1385+
13101386
def continue(): IO[Unit] =
13111387
state.modify {
13121388
case s @ AwaitingFirstAwait(waiterArrived) =>
13131389
s -> waiterArrived.get *> continue()
1314-
case Running => Running -> IO.unit
1390+
case Running => Running -> IO.unit
13151391
case Stopped(whenContinued, _) =>
1316-
Running -> whenContinued.complete(()).void // wake up await-ers
1392+
Running -> (stopTarget.set(None) *> whenContinued.complete(())).void // wake up await-ers
13171393
}.flatten
13181394

13191395
def pause(): IO[Unit] =
@@ -1370,7 +1446,21 @@ object Parse {
13701446

13711447
for {
13721448
_ <- logger.debug("pre-control await")
1373-
isStepping <- control.await() // may block until external control says to unblock, for stepping behavior
1449+
_ <- control.setCurrentDepth(
1450+
pstate.currentNode.toOption
1451+
.map { node =>
1452+
var depth = 0
1453+
var n = node
1454+
while (n.diParent != null) {
1455+
depth += 1
1456+
n = n.diParent
1457+
}
1458+
depth
1459+
}
1460+
.getOrElse(0)
1461+
)
1462+
_ <- control.setAwaitingKind("start")
1463+
isStepping <- control.await()
13741464
_ <- logger.debug("post-control await")
13751465
location = createLocation(pstate.schemaFileLocation)
13761466
shouldBreak <- breakpoints.shouldBreak(location)
@@ -1403,7 +1493,21 @@ object Parse {
14031493

14041494
override def endElement(pstate: PState, processor: Parser): Unit =
14051495
dispatcher.unsafeRunSync {
1406-
control.await() *> // ensure no events while debugger is paused
1496+
val depth = pstate.currentNode.toOption
1497+
.map { node =>
1498+
var d = 0
1499+
var n = node
1500+
while (n.diParent != null) {
1501+
d += 1
1502+
n = n.diParent
1503+
}
1504+
d
1505+
}
1506+
.getOrElse(0)
1507+
1508+
control.setCurrentDepth(depth) *>
1509+
control.setAwaitingKind("end") *>
1510+
control.await() *>
14071511
events.send(Event.EndElement(pstate.copyStateForDebugger)).void
14081512
}
14091513
}

0 commit comments

Comments
 (0)