///|
/// User-visible debugger phase owned by DebugSession.
///
/// Adapter initialization and protocol configuration states intentionally do
/// not appear here.
pub enum DebugSessionState {
Ready
Running
Stopped
Exited
Failed
} derive(Debug, Eq)
///|
pub extend DebugSessionState with @debug.Debug::{to_repr}
///|
pub extend DebugSessionState with Eq::{equal, not_equal}
///|
pub suberror DebugSessionError {
OperationNotAllowed(state~ : DebugSessionState, action~ : String)
FrameNotLoaded(Int)
FrameNotFound(Int)
NoFrameSelected
NoVisibleCaller(Int)
NoVisibleCallee(Int)
AdapterFailure(String)
BreakpointNotFound(Int)
BreakpointManagementRejected(String)
} derive(Debug)
///|
pub extend DebugSessionError with @debug.Debug::{to_repr}
///|
/// Identifies one launch attempt within a DebugSession. Values are monotonic
/// and contain no adapter execution identifier.
pub struct ExecutionEpoch {
priv number : Int
} derive(Debug, Eq)
///|
pub extend ExecutionEpoch with @debug.Debug::{to_repr}
///|
pub extend ExecutionEpoch with Eq::{equal, not_equal}
///|
fn ExecutionEpoch::new(number : Int) -> ExecutionEpoch {
{ number, }
}
///|
pub fn ExecutionEpoch::number(self : ExecutionEpoch) -> Int {
self.number
}
///|
/// Identifies one stable stop inside an application execution epoch. The stop
/// number restarts at one for each execution.
pub struct StopEpoch {
priv execution : ExecutionEpoch
priv number : Int
} derive(Debug, Eq)
///|
pub extend StopEpoch with @debug.Debug::{to_repr}
///|
pub extend StopEpoch with Eq::{equal, not_equal}
///|
fn StopEpoch::new(execution : ExecutionEpoch, number : Int) -> StopEpoch {
{ execution, number, }
}
///|
pub fn StopEpoch::execution(self : StopEpoch) -> ExecutionEpoch {
self.execution
}
///|
pub fn StopEpoch::number(self : StopEpoch) -> Int {
self.number
}
///|
/// Application-owned state for the current stable stop. Thread and frame
/// selections are logical domain values; an adapter maps them to temporary
/// protocol identifiers for the active execution.
priv struct StableStopState {
epoch : StopEpoch
snapshot : StopSnapshot
thread : ThreadSelection
mut stack : StackSnapshot
mut frame : FrameSelection?
}
///|
/// Mutable application state kept behind DebugSession. Protocol lifecycle and
/// adapter identifiers must not be added to this type.
priv struct DebugApplicationState {
mut phase : DebugSessionState
mut execution_number : Int
mut stop_number : Int
mut stable_stop : StableStopState?
}
///|
fn DebugApplicationState::new() -> DebugApplicationState {
{ phase: Ready, execution_number: 0, stop_number: 0, stable_stop: None, }
}
///|
fn DebugApplicationState::execution_epoch(
self : DebugApplicationState,
) -> ExecutionEpoch? {
guard self.execution_number > 0 else { return None }
Some(ExecutionEpoch::new(self.execution_number))
}
///|
fn DebugApplicationState::stop_epoch(
self : DebugApplicationState,
) -> StopEpoch? {
self.stable_stop.map(stop => stop.epoch)
}
///|
fn DebugApplicationState::last_stop_snapshot(
self : DebugApplicationState,
) -> StopSnapshot? {
self.stable_stop.map(stop => stop.snapshot)
}
///|
fn DebugApplicationState::selected_thread(
self : DebugApplicationState,
) -> ThreadSelection? {
self.stable_stop.map(stop => stop.thread)
}
///|
fn DebugApplicationState::selected_frame(
self : DebugApplicationState,
) -> FrameSelection? {
match self.stable_stop {
Some(stop) => stop.frame
None => None
}
}
///|
fn DebugApplicationState::stack_snapshot(
self : DebugApplicationState,
) -> StackSnapshot? {
self.stable_stop.map(stop => stop.stack)
}
///|
fn DebugApplicationState::selected_frame_snapshot(
self : DebugApplicationState,
) -> StackFrameSnapshot? {
guard self.stable_stop is Some(stop) && stop.frame is Some(selection) else {
return None
}
stop.stack.frame(selection.index())
}
///|
fn DebugApplicationState::update_stack(
self : DebugApplicationState,
stack : StackSnapshot,
) -> Unit raise DebugSessionError {
guard self.phase is Stopped && self.stable_stop is Some(stop) else {
raise OperationNotAllowed(state=self.phase, action="load stack frames")
}
stop.stack = stack
if stop.frame is None {
stop.frame = stack.default_selection()
}
}
///|
fn DebugApplicationState::begin_execution(self : DebugApplicationState) -> Unit {
self.execution_number += 1
self.stop_number = 0
self.stable_stop = None
self.phase = Running
}
///|
fn DebugApplicationState::begin_resume(self : DebugApplicationState) -> Unit {
self.stable_stop = None
self.phase = Running
}
///|
fn DebugApplicationState::accept_stopped(
self : DebugApplicationState,
snapshot : StopSnapshot,
stack : StackSnapshot,
) -> Unit {
self.stop_number += 1
let execution = ExecutionEpoch::new(self.execution_number)
self.stable_stop = Some({
epoch: StopEpoch::new(execution, self.stop_number),
snapshot,
thread: stack.thread(),
stack,
frame: stack.default_selection(),
})
self.phase = Stopped
}
///|
fn DebugApplicationState::accept_exit(self : DebugApplicationState) -> Unit {
self.stable_stop = None
self.phase = Ready
}
///|
fn DebugApplicationState::select_frame(
self : DebugApplicationState,
physical_index : Int,
) -> StackFrameSnapshot raise DebugSessionError {
guard self.phase is Stopped && self.stable_stop is Some(stop) else {
raise OperationNotAllowed(state=self.phase, action="select a frame")
}
guard stop.stack.is_loaded(physical_index) else {
raise FrameNotLoaded(physical_index)
}
guard stop.stack.frame(physical_index) is Some(frame) else {
raise FrameNotFound(physical_index)
}
stop.frame = Some(FrameSelection::at(physical_index))
frame
}
///|
fn DebugApplicationState::select_visible_callee(
self : DebugApplicationState,
) -> StackFrameSnapshot raise DebugSessionError {
guard self.phase is Stopped &&
self.stable_stop is Some(stop) &&
stop.frame is Some(selection) else {
if self.phase is Stopped {
raise NoFrameSelected
}
raise OperationNotAllowed(state=self.phase, action="select a callee frame")
}
match stop.stack.visible_callee(selection) {
FrameAvailable(frame) => self.select_frame(frame.index())
MoreFramesRequired(start_frame~) => raise FrameNotLoaded(start_frame)
VisibleFrameBoundary => raise NoVisibleCallee(selection.index())
}
}
///|
fn DebugApplicationState::transition_without_stop(
self : DebugApplicationState,
phase : DebugSessionState,
) -> Unit {
self.stable_stop = None
self.phase = phase
}