def debugee()

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