///|
priv struct CommandPrint {
path : @debugger.VariablePath
}
///|
fn is_variable_name_char(character : Char, first~ : Bool) -> Bool {
if character.is_ascii() {
character.is_ascii_alphabetic() ||
character == '_' ||
(!first && character.is_ascii_digit())
} else {
character.is_printable() &&
!character.is_whitespace() &&
!character.is_control()
}
}
///|
fn is_variable_path_segment(segment : StringView) -> Bool {
guard segment is [first, .. rest] && is_variable_name_char(first, first=true) else {
return false
}
rest.all(character => is_variable_name_char(character, first=false))
}
///|
priv struct VariablePathCursor {
bytes : Bytes
mut offset : Int
}
///|
fn VariablePathCursor::new(input : StringView) -> VariablePathCursor {
{ bytes: @utf8.encode(input), offset: 0, }
}
///|
fn VariablePathCursor::peek(self : VariablePathCursor) -> Byte? {
self.bytes.get(self.offset)
}
///|
fn VariablePathCursor::is_at_end(self : VariablePathCursor) -> Bool {
self.offset == self.bytes.length()
}
///|
fn VariablePathCursor::take_identifier(self : VariablePathCursor) -> String? {
let start = self.offset
while self.peek() is Some(byte) &&
byte != b'.' &&
byte != b'[' &&
byte != b']' {
self.offset += 1
}
guard start < self.offset else { return None }
let segment = @utf8.decode(self.bytes[start:self.offset].to_owned()) catch {
_ => return None
}
guard is_variable_path_segment(segment) else { return None }
Some(segment)
}
///|
fn VariablePathCursor::take_index(self : VariablePathCursor) -> Int? {
guard self.peek() == Some(b'[') else { return None }
self.offset += 1
let start = self.offset
while self.peek() is Some(byte) && byte >= b'0' && byte <= b'9' {
self.offset += 1
}
guard start < self.offset && self.peek() == Some(b']') else { return None }
let digits = @utf8.decode(self.bytes[start:self.offset].to_owned()) catch {
_ => return None
}
self.offset += 1
let parsed = Some(@string.parse_int(digits, base=10)) catch { _ => None }
guard parsed is Some(index) && index >= 0 else { return None }
Some(index)
}
///|
fn parse_variable_path(arguments : StringView) -> @debugger.VariablePath? {
let cursor = VariablePathCursor::new(arguments)
guard cursor.take_identifier() is Some(root) else { return None }
let steps : Array[@debugger.VariablePathStep] = []
while !cursor.is_at_end() {
match cursor.peek() {
Some(b'.') => {
cursor.offset += 1
guard cursor.take_identifier() is Some(field) else { return None }
steps.push(@debugger.Field(field))
}
Some(b'[') => {
guard cursor.take_index() is Some(index) else { return None }
steps.push(@debugger.Index(index))
}
_ => return None
}
}
Some(@debugger.VariablePath::new(root, steps))
}
///|
fn parse_print_command(
_invoked_name : StringView,
arguments : StringView,
) -> CommandParseResult {
guard arguments != "" else {
return invalid_command("print requires a variable path")
}
guard parse_variable_path(arguments) is Some(path) else {
return invalid_command(
"print only accepts variable paths with fields and non-negative indices",
)
}
parsed_command(CommandPrint::{ path, })
}
///|
fn print_command_spec() -> CommandSpec {
CommandSpec::new(
"print",
["p"],
"print ",
"Show a variable, field, or indexed element path",
parse_print_command,
)
}
///|
impl ReplCommand for CommandPrint with fn execute(self, context) {
let events : Array[PresentationEvent] = []
let result = context.session.find_variable(self.path) catch {
@debugger.OperationNotAllowed(state~, action~) => {
events.push(OperationNotAllowedPresentation(state, action))
return CommandResult::continuing(events~)
}
@debugger.AdapterFailure(message) => {
append_debuggee_output(events, context.session)
events.push(AdapterFailurePresentation(message))
return CommandResult::continuing(events~)
}
error => raise error
}
append_debuggee_output(events, context.session)
match result {
@debugger.VariableFound(variable) =>
events.push(VariablePresentation(variable))
@debugger.VariableLookupFailed(failure) =>
events.push(VariableLookupFailurePresentation(failure))
}
CommandResult::continuing(events~)
}