///|
priv suberror BreakpointSourceError {
BreakpointSourceMissing(String)
BreakpointSourceIsNotAFile(String)
} derive(Debug)
///|
priv struct CommandBreak {
target : BreakCommandTarget
}
///|
priv enum BreakCommandTarget {
SourceBreakTarget(String, Int)
CurrentSourceBreakTarget(Int)
FunctionBreakTarget(@debugger.FunctionBreakpointRequest)
}
///|
fn parse_function_break_target(arguments : StringView) -> CommandParseResult {
let request = @debugger.FunctionBreakpointRequest::parse(arguments) catch {
@debugger.FunctionNameError(message) => return invalid_command(message)
error => return invalid_command(@debug.to_string(error))
}
parsed_command(CommandBreak::{ target: FunctionBreakTarget(request), })
}
///|
fn parse_break_command(
_invoked_name : StringView,
arguments : StringView,
) -> CommandParseResult {
guard arguments != "" else {
return invalid_command(
"break requires , :, or ",
)
}
guard arguments.rev_find(":") is Some(separator) else {
if arguments is ['0'..='9' | '+' | '-', ..] {
guard arguments.all(character => character is ('0'..='9')) else {
return invalid_command(
"breakpoint line must be a positive decimal integer",
)
}
let line = Some(@string.parse_int(arguments, base=10)) catch { _ => None }
guard line is Some(line) && line > 0 else {
return invalid_command(
"breakpoint line must be a positive decimal integer",
)
}
return parsed_command(CommandBreak::{
target: CurrentSourceBreakTarget(line),
})
}
return parse_function_break_target(arguments)
}
let source = arguments[:separator].trim(char_set=" \t")
let line_text = arguments[separator + 1:].trim(char_set=" \t")
guard source != "" else {
return invalid_command("breakpoint file cannot be empty")
}
guard line_text != "" else {
return invalid_command("breakpoint line cannot be empty")
}
let parsed_line = Some(@string.parse_int(line_text, base=10)) catch {
_ => None
}
guard parsed_line is Some(line) && line > 0 else {
return invalid_command("breakpoint line must be a positive integer")
}
parsed_command(CommandBreak::{
target: SourceBreakTarget(source.to_owned(), line),
})
}
///|
fn break_command_spec() -> CommandSpec {
CommandSpec::new(
"break",
["b"],
"break ",
"Set a breakpoint at line (selected frame), file:line, function, or @alias.function",
parse_break_command,
)
}
///|
fn source_breakpoint_presentation(
breakpoint : @debugger.BreakpointSnapshot,
requested_source : String,
) -> PresentationEvent {
match breakpoint.status() {
@debugger.Pending =>
BreakpointPendingPresentation(
breakpoint.id(),
requested_source,
breakpoint.requested_line(),
)
@debugger.Verified | @debugger.Rejected(_) =>
BreakpointUpdatePresentation(breakpoint)
}
}
///|
fn function_breakpoint_presentation(
breakpoint : @debugger.FunctionBreakpointSnapshot,
) -> PresentationEvent {
match breakpoint.status() {
@debugger.Pending =>
FunctionBreakpointPendingPresentation(
breakpoint.id(),
breakpoint.display_name(),
)
@debugger.Verified | @debugger.Rejected(_) =>
FunctionBreakpointUpdatePresentation(breakpoint)
}
}
///|
async fn resolve_breakpoint_source(path : String) -> String {
guard @fs.exists(path) else { raise BreakpointSourceMissing(path) }
guard @fs.kind(path) is Regular else {
raise BreakpointSourceIsNotAFile(path)
}
@fs.realpath(path)
}
///|
async fn set_source_breakpoint(
session : @debugger.DebugSession,
source : String,
line : Int,
) -> PresentationEvent {
let canonical_source = resolve_breakpoint_source(source)
let breakpoint = session.add_source_breakpoint(
canonical_source,
line,
display_path=source,
)
source_breakpoint_presentation(breakpoint, source)
}
///|
impl ReplCommand for CommandBreak with fn execute(self, context) {
let event = try {
match self.target {
SourceBreakTarget(source, line) =>
set_source_breakpoint(context.session, source, line)
CurrentSourceBreakTarget(line) => {
guard context.session.selected_frame_snapshot() is Some(frame) else {
return CommandResult::continuing(events=[
BreakpointErrorPresentation(BreakpointCurrentFrameMissing),
])
}
guard frame.source_path() is Some(source) && source != "" else {
return CommandResult::continuing(events=[
BreakpointErrorPresentation(BreakpointCurrentFrameSourceMissing),
])
}
set_source_breakpoint(context.session, source, line)
}
FunctionBreakTarget(request) => {
let breakpoint = context.session.add_function_breakpoint(request)
function_breakpoint_presentation(breakpoint)
}
}
} catch {
BreakpointSourceMissing(path) =>
BreakpointErrorPresentation(BreakpointSourceNotFound(path))
BreakpointSourceIsNotAFile(path) =>
BreakpointErrorPresentation(BreakpointSourceNotAFile(path))
@debugger.OperationNotAllowed(state~, action~) =>
BreakpointErrorPresentation(BreakpointOperationNotAllowed(state, action))
@debugger.MissingCurrentPackage =>
BreakpointErrorPresentation(BreakpointFunctionContextMissing)
@debugger.UnknownPackageAlias(package_alias) =>
BreakpointErrorPresentation(BreakpointUnknownPackageAlias(package_alias))
@debugger.AdapterFailure(message) => AdapterFailurePresentation(message)
error =>
BreakpointErrorPresentation(
BreakpointUnexpectedFailure(@debug.to_string(error)),
)
}
CommandResult::continuing(events=[event])
}