in debugger/src/main/scala/org.apache.daffodil.debugger.dap/Parse.scala [640:766]
def debugee(args: Debugee.LaunchArgs.Manual): Resource[IO, DAPodil.Debugee] =
for {
data <- Resource.eval(Queue.bounded[IO, Option[DAPodil.Data]](10))
state <- Resource.eval(Queue.bounded[IO, Option[DAPodil.Debugee.State]](10))
dapEvents <- Resource.eval(Queue.bounded[IO, Option[Events.DebugEvent]](10))
breakpoints <- Resource.eval(Breakpoints())
infoset <- Resource.eval(
Queue.bounded[IO, Option[String]](10)
) // TODO: it's a bit incongruous to have a separate channel for infoset changes, vs. streaming Parse.Event values
control <- Resource.eval(Control.stopped())
latestData <- Stream
.fromQueueNoneTerminated(data)
.holdResource(DAPodil.Data.empty)
latestInfoset <- Resource.eval(SignallingRef[IO, String](""))
infosetChanges = Stream
.fromQueueNoneTerminated(infoset)
.evalTap(latestInfoset.set)
.evalTap(content =>
dapEvents.offer(
Some(
InfosetEvent(
content,
args.infosetFormat match {
case "xml" => "text/xml"
case "json" => "application/json"
}
)
)
)
)
.onFinalizeCase(ec => Logger[IO].debug(s"infosetChanges (orig): $ec"))
events <- Resource.eval(Queue.bounded[IO, Option[Event]](10))
debugger <- DaffodilDebugger
.resource(state, events, breakpoints, control, infoset, args.infosetFormat)
parse <- Resource.eval(
args.data.flatMap(in => Parse(args.schemaPath, in, debugger, args.infosetFormat, args.variables, args.tunables))
)
parsing = args.infosetOutput match {
case Debugee.LaunchArgs.InfosetOutput.None =>
parse.run().drain
case Debugee.LaunchArgs.InfosetOutput.Console =>
parse
.run()
.through(text.utf8.decode)
.foldMonoid
.evalTap(xml =>
Logger[IO].debug("done collecting infoset XML output") *>
dapEvents.offer(Some(Events.OutputEvent.createConsoleOutput(xml)))
)
case Debugee.LaunchArgs.InfosetOutput.File(path) =>
parse
.run()
.through(fs2.io.file.Files[IO].writeAll(fs2.io.file.Path.fromNioPath(path)))
}
nextFrameId <- Resource.eval(
Next.int.map(_.map(DAPodil.Frame.Id.apply)).flatTap(_.next())
) // `.flatTap(_.next())`: ids start at 1
nextRef <- Resource.eval(
Next.int.map(_.map(DAPodil.VariablesReference.apply)).flatTap(_.next())
) // `.flatTap(_.next())`: ids start at 1
// convert Parse.Event values to DAPodil.Data values
deliverParseData = Stream
.fromQueueNoneTerminated(events)
.evalTap {
case start: Event.StartElement if start.isStepping =>
dapEvents.offer(Some(DataEvent(start.state.currentLocation.bytePos1b)))
case _ => IO.unit
}
.through(fromParse(nextFrameId, nextRef))
.enqueueNoneTerminated(data)
debugee = new Debugee(
DAPodil.Source(args.schemaPath, None),
DAPodil.Source(args.dataPath, None),
latestData,
Stream.fromQueueNoneTerminated(state),
state,
Stream.fromQueueNoneTerminated(dapEvents),
breakpoints,
control
)
startup = dapEvents.offer(Some(ConfigEvent(args))) *>
(if (args.stopOnEntry)
control.step() *> state.offer(
Some(
DAPodil.Debugee.State
.Stopped(DAPodil.Debugee.State.Stopped.Reason.Entry)
)
) // don't use debugee.step as we need to send Stopped.Reason.Entry, not Stopped.Reason.Step
else debugee.continue())
_ <- Stream
.emit(debugee)
.concurrently(
Stream(
Stream.eval(startup),
// ensure dapEvents is terminated when the parse is terminated
parsing
.onFinalizeCase {
case Resource.ExitCase.Errored(e: Parse.Exception) =>
// TODO: when Parse.Exception has source coordinates, include it into a more structured OutputEvent
dapEvents.offer(Some(Events.OutputEvent.createConsoleOutput(e.getMessage()))) *>
dapEvents.offer(None)
case _ => dapEvents.offer(None)
}
.onFinalizeCase(ec => Logger[IO].debug(s"parsing: $ec")),
deliverParseData.onFinalizeCase {
case ec @ Resource.ExitCase.Errored(e) =>
Logger[IO].warn(e)(s"deliverParseData: $ec")
case ec => Logger[IO].debug(s"deliverParseData: $ec")
},
infosetChanges
).parJoinUnbounded
)
.compile
.resource
.lastOrError
_ <- Resource.onFinalize(Logger[IO].debug("signalling a stop") *> parse.close())
} yield debugee