pub mod expression;
use super::r#break::BreakpointIdentity;
use super::{
Command, CommandError, r#async, call, frame, memory, print, register, source_code, thread,
trigger, watch,
};
use super::{CommandResult, r#break};
use crate::debugger::register::debug::BreakCondition;
use crate::debugger::variable::dqe::Dqe;
use crate::debugger::variable::dqe::Selector;
use crate::ui::command::watch::WatchpointIdentity;
use ariadne::{Color, Fmt, Label, Report, ReportKind, Source};
use chumsky::error::{Rich, RichPattern, RichReason};
use chumsky::prelude::{any, choice, end, just, one_of};
use chumsky::text::whitespace;
use chumsky::{Boxed, IterParser, Parser, extra, text};
use itertools::Itertools;
pub const VAR_COMMAND: &str = "var";
pub const VAR_DEBUG_COMMAND: &str = "vard";
pub const VAR_LOCAL_KEY: &str = "locals";
pub const ARG_COMMAND: &str = "arg";
pub const ARG_DEBUG_COMMAND: &str = "argd";
pub const ARG_ALL_KEY: &str = "all";
pub const BACKTRACE_COMMAND: &str = "backtrace";
pub const BACKTRACE_COMMAND_SHORT: &str = "bt";
pub const BACKTRACE_ALL_SUBCOMMAND: &str = "all";
pub const CONTINUE_COMMAND: &str = "continue";
pub const CONTINUE_COMMAND_SHORT: &str = "c";
pub const FRAME_COMMAND: &str = "frame";
pub const FRAME_COMMAND_SHORT: &str = "f";
pub const FRAME_COMMAND_INFO_SUBCOMMAND: &str = "info";
pub const FRAME_COMMAND_SWITCH_SUBCOMMAND: &str = "switch";
pub const RUN_COMMAND: &str = "run";
pub const RUN_COMMAND_SHORT: &str = "r";
pub const STEP_INSTRUCTION_COMMAND: &str = "stepi";
pub const STEP_INTO_COMMAND: &str = "stepinto";
pub const STEP_INTO_COMMAND_SHORT: &str = "step";
pub const STEP_OUT_COMMAND: &str = "stepout";
pub const STEP_OUT_COMMAND_SHORT: &str = "finish";
pub const STEP_OVER_COMMAND: &str = "stepover";
pub const STEP_OVER_COMMAND_SHORT: &str = "next";
pub const SYMBOL_COMMAND: &str = "symbol";
pub const BREAK_COMMAND: &str = "break";
pub const BREAK_COMMAND_SHORT: &str = "b";
pub const BREAK_REMOVE_SUBCOMMAND: &str = "remove";
pub const BREAK_REMOVE_SUBCOMMAND_SHORT: &str = "r";
pub const BREAK_INFO_SUBCOMMAND: &str = "info";
pub const WATCH_COMMAND: &str = "watch";
pub const WATCH_COMMAND_SHORT: &str = "w";
pub const WATCH_REMOVE_SUBCOMMAND: &str = "remove";
pub const WATCH_REMOVE_SUBCOMMAND_SHORT: &str = "r";
pub const WATCH_INFO_SUBCOMMAND: &str = "info";
pub const MEMORY_COMMAND: &str = "memory";
pub const MEMORY_COMMAND_SHORT: &str = "mem";
pub const MEMORY_COMMAND_READ_SUBCOMMAND: &str = "read";
pub const MEMORY_COMMAND_WRITE_SUBCOMMAND: &str = "write";
pub const REGISTER_COMMAND: &str = "register";
pub const REGISTER_COMMAND_SHORT: &str = "reg";
pub const REGISTER_COMMAND_READ_SUBCOMMAND: &str = "read";
pub const REGISTER_COMMAND_WRITE_SUBCOMMAND: &str = "write";
pub const REGISTER_COMMAND_INFO_SUBCOMMAND: &str = "info";
pub const THREAD_COMMAND: &str = "thread";
pub const THREAD_COMMAND_INFO_SUBCOMMAND: &str = "info";
pub const THREAD_COMMAND_SWITCH_SUBCOMMAND: &str = "switch";
pub const THREAD_COMMAND_CURRENT_SUBCOMMAND: &str = "current";
pub const SHARED_LIB_COMMAND: &str = "sharedlib";
pub const SHARED_LIB_COMMAND_INFO_SUBCOMMAND: &str = "info";
pub const SOURCE_COMMAND: &str = "source";
pub const SOURCE_COMMAND_DISASM_SUBCOMMAND: &str = "asm";
pub const SOURCE_COMMAND_FUNCTION_SUBCOMMAND: &str = "fn";
pub const ORACLE_COMMAND: &str = "oracle";
pub const ASYNC_COMMAND: &str = "async";
pub const ASYNC_COMMAND_BACKTRACE_SUBCOMMAND: &str = "backtrace";
pub const ASYNC_COMMAND_BACKTRACE_SUBCOMMAND_SHORT: &str = "bt";
pub const ASYNC_COMMAND_TASK_SUBCOMMAND: &str = "task";
pub const ASYNC_COMMAND_STEP_INTO_SUBCOMMAND: &str = "stepinto";
pub const ASYNC_COMMAND_STEP_INTO_SUBCOMMAND_SHORT: &str = "step";
pub const ASYNC_COMMAND_STEP_OVER_SUBCOMMAND: &str = "stepover";
pub const ASYNC_COMMAND_STEP_OVER_SUBCOMMAND_SHORT: &str = "next";
pub const ASYNC_COMMAND_STEP_OUT_SUBCOMMAND: &str = "stepout";
pub const ASYNC_COMMAND_STEP_OUT_SUBCOMMAND_SHORT: &str = "finish";
pub const TRIGGER_COMMAND: &str = "trigger";
pub const TRIGGER_COMMAND_ANY_TRIGGER_SUBCOMMAND: &str = "any";
pub const TRIGGER_COMMAND_BRKPT_TRIGGER_SUBCOMMAND: &str = "b";
pub const TRIGGER_COMMAND_WP_TRIGGER_SUBCOMMAND: &str = "w";
pub const TRIGGER_COMMAND_INFO_SUBCOMMAND: &str = "info";
pub const CALL_COMMAND: &str = "call";
pub const HELP_COMMAND: &str = "help";
pub const HELP_COMMAND_SHORT: &str = "h";
type Err<'a> = extra::Err<Rich<'a, char>>;
pub fn hex<'a>() -> impl chumsky::Parser<'a, &'a str, usize, Err<'a>> + Clone {
let prefix = just("0x").or(just("0X"));
prefix
.ignore_then(
text::digits(16)
.at_least(1)
.to_slice()
.map(|s: &str| usize::from_str_radix(s, 16).unwrap()),
)
.padded()
.labelled("hexidecimal number")
}
pub fn rust_identifier<'a>() -> impl chumsky::Parser<'a, &'a str, &'a str, Err<'a>> + Clone {
text::ascii::ident()
.separated_by(just("::"))
.allow_leading()
.at_least(1)
.to_slice()
.padded()
.labelled("rust identifier")
}
pub fn brkpt_at_addr_parser<'a>() -> impl chumsky::Parser<'a, &'a str, BreakpointIdentity, Err<'a>>
{
hex().map(BreakpointIdentity::Address)
}
pub fn brkpt_at_line_parser<'a>() -> impl chumsky::Parser<'a, &'a str, BreakpointIdentity, Err<'a>>
{
any()
.filter(|c: &char| *c != ':')
.repeated()
.to_slice()
.then_ignore(just(':'))
.then(text::int(10).from_str().unwrapped())
.map(|(file, line): (&str, u64)| BreakpointIdentity::Line(file.trim().to_string(), line))
.padded()
}
pub fn brkpt_number<'a>() -> impl chumsky::Parser<'a, &'a str, BreakpointIdentity, Err<'a>> {
text::int(10)
.from_str()
.unwrapped()
.map(|number: u32| BreakpointIdentity::Number(number))
.padded()
}
pub fn brkpt_at_fn<'a>() -> impl chumsky::Parser<'a, &'a str, BreakpointIdentity, Err<'a>> {
any()
.repeated()
.to_slice()
.map(|fn_name: &str| BreakpointIdentity::Function(fn_name.trim().to_string()))
}
pub fn watchpoint_cond<'a>() -> impl chumsky::Parser<'a, &'a str, BreakCondition, Err<'a>> {
let ws_req = whitespace().at_least(1);
let ws_req_or_end = ws_req.or(end());
let op = |sym| whitespace().then(just(sym)).then(ws_req_or_end);
op("+rw")
.to(BreakCondition::DataReadsWrites)
.or(op("+w").to(BreakCondition::DataWrites))
.or(text::whitespace().to(BreakCondition::DataWrites))
}
pub fn watchpoint_at_dqe<'a>() -> impl chumsky::Parser<'a, &'a str, WatchpointIdentity, Err<'a>> {
let source_rewind_parser = any::<_, Err>().repeated().to_slice().rewind();
source_rewind_parser
.then(expression::parser().padded())
.map(|(source, dqe)| WatchpointIdentity::DQE(source.trim().to_string(), dqe))
}
pub fn watchpoint_at_address<'a>() -> impl chumsky::Parser<'a, &'a str, WatchpointIdentity, Err<'a>>
{
hex()
.then(just(':').ignore_then(one_of("1248")))
.padded()
.map(|(addr, size)| {
WatchpointIdentity::Address(addr, size.to_digit(10).expect("infallible") as u8)
})
}
fn command<'a, I>(ctx: &'static str, inner: I) -> Boxed<'a, 'a, &'a str, Command, Err<'a>>
where
I: chumsky::Parser<'a, &'a str, Command, Err<'a>> + 'a,
{
inner.then_ignore(end()).labelled(ctx).boxed()
}
impl Command {
pub fn render_errors(src: &str, errors: Vec<Rich<char>>) -> String {
let mut reports = vec![];
for e in errors {
fn generate_reports(
src: &str,
reports: &mut Vec<String>,
err: &Rich<char>,
reason: &RichReason<char>,
) {
let report = Report::build(ReportKind::Error, "<command>", err.span().start)
.with_help("try \"help\" command");
let report = match reason {
RichReason::ExpectedFound { expected, found } => report
.with_message(format!(
"{}, expected {}",
if found.is_some() {
"unexpected token in input"
} else {
"unexpected end of input"
},
if expected.is_empty() {
"something else".to_string()
} else {
expected
.iter()
.map(|e| match e {
RichPattern::Token(tok) => tok.to_string(),
RichPattern::Label(label) => label.to_string(),
RichPattern::Identifier(ident) => ident.to_string(),
RichPattern::Any => {
"anything other than the end of input".to_string()
}
RichPattern::SomethingElse => {
"something other than the provided input".to_string()
}
RichPattern::EndOfInput => "end of input".to_string(),
})
.join(", ")
}
))
.with_label(
Label::new(("<command>", err.span().into_range()))
.with_message(format!(
"unexpected token {}",
err.found()
.map(|t| t.to_string())
.unwrap_or("EOL".to_string())
.fg(Color::Red)
))
.with_color(Color::Red),
),
RichReason::Custom(msg) => report.with_message(msg).with_label(
Label::new(("<command>", err.span().into_range()))
.with_message(format!("{}", msg.fg(Color::Red)))
.with_color(Color::Red),
),
};
let mut buf = vec![];
_ = report
.finish()
.write_for_stdout(("<command>", Source::from(&src)), &mut buf);
reports.push(
std::str::from_utf8(&buf[..])
.expect("infallible")
.to_string(),
);
}
generate_reports(src, &mut reports, &e, e.reason());
}
reports.join("\n")
}
fn parser<'a>() -> impl Parser<'a, &'a str, Command, Err<'a>> {
let ws_req = whitespace().at_least(1);
let ws_req_or_end = ws_req.or(end());
let op = |sym| whitespace().then(just(sym)).then(ws_req_or_end);
let op_w_arg = |sym| whitespace().then(just(sym)).then(ws_req);
let sub_op = |sym| just(sym).then(ws_req_or_end);
let sub_op_w_arg = |sym| just(sym).then(ws_req);
let print_local_vars = choice((
op_w_arg(VAR_COMMAND).to(print::RenderMode::Builtin),
op_w_arg(VAR_DEBUG_COMMAND).to(print::RenderMode::Debug),
))
.then(sub_op(VAR_LOCAL_KEY))
.map(|(mode, _)| {
Command::Print(print::Command::Variable {
mode,
dqe: Dqe::Variable(Selector::Any),
})
});
let print_var = choice((
op_w_arg(VAR_COMMAND).to(print::RenderMode::Builtin),
op_w_arg(VAR_DEBUG_COMMAND).to(print::RenderMode::Debug),
))
.then(expression::parser())
.map(|(mode, dqe)| Command::Print(print::Command::Variable { mode, dqe }));
let print_variables = choice((print_local_vars, print_var)).boxed();
let print_all_args = choice((
op_w_arg(ARG_COMMAND).to(print::RenderMode::Builtin),
op_w_arg(ARG_DEBUG_COMMAND).to(print::RenderMode::Debug),
))
.then(sub_op(ARG_ALL_KEY))
.map(|(mode, _)| {
Command::Print(print::Command::Argument {
mode,
dqe: Dqe::Variable(Selector::Any),
})
});
let print_arg = choice((
op_w_arg(ARG_COMMAND).to(print::RenderMode::Builtin),
op_w_arg(ARG_DEBUG_COMMAND).to(print::RenderMode::Debug),
))
.then(expression::parser())
.map(|(mode, dqe)| Command::Print(print::Command::Argument { mode, dqe }));
let print_arguments = choice((print_all_args, print_arg)).boxed();
let op2 = |full, short| op(full).or(op(short));
let op2_w_arg = |full, short| op_w_arg(full).or(op_w_arg(short));
let sub_op2 = |full, short| sub_op(full).or(sub_op(short));
let sub_op2_w_arg = |full, short| sub_op_w_arg(full).or(sub_op_w_arg(short));
let r#continue = op2(CONTINUE_COMMAND, CONTINUE_COMMAND_SHORT).to(Command::Continue);
let run = op2(RUN_COMMAND, RUN_COMMAND_SHORT).to(Command::Run);
let stepi = op(STEP_INSTRUCTION_COMMAND).to(Command::StepInstruction);
let step_into = op2(STEP_INTO_COMMAND, STEP_INTO_COMMAND_SHORT).to(Command::StepInto);
let step_out = op2(STEP_OUT_COMMAND, STEP_OUT_COMMAND_SHORT).to(Command::StepOut);
let step_over = op2(STEP_OVER_COMMAND, STEP_OVER_COMMAND_SHORT).to(Command::StepOver);
let call = op_w_arg(CALL_COMMAND)
.ignore_then(
text::ident().padded().then(
expression::literal()
.padded()
.repeated()
.collect::<Vec<_>>(),
),
)
.map(|(fn_name, literals)| {
Command::Call(call::Command {
fn_name: fn_name.trim().to_string(),
args: literals.into_boxed_slice(),
})
})
.boxed();
let source_code = op_w_arg(SOURCE_COMMAND)
.ignore_then(choice((
sub_op(SOURCE_COMMAND_DISASM_SUBCOMMAND)
.to(Command::SourceCode(source_code::Command::Asm)),
sub_op(SOURCE_COMMAND_FUNCTION_SUBCOMMAND)
.to(Command::SourceCode(source_code::Command::Function)),
text::int(10)
.from_str()
.unwrapped()
.map(|num| Command::SourceCode(source_code::Command::Range(num)))
.padded(),
)))
.boxed();
let help = op2(HELP_COMMAND, HELP_COMMAND_SHORT)
.ignore_then(any().repeated().at_least(1).padded().to_slice().or_not())
.map(|s| Command::Help {
command: s.map(ToOwned::to_owned),
reason: None,
})
.padded()
.boxed();
let backtrace = op2(BACKTRACE_COMMAND, BACKTRACE_COMMAND_SHORT)
.ignore_then(sub_op(BACKTRACE_ALL_SUBCOMMAND).or_not())
.map(|all| {
if all.is_some() {
Command::PrintBacktrace(super::backtrace::Command::All)
} else {
Command::PrintBacktrace(super::backtrace::Command::CurrentThread)
}
})
.boxed();
let symbol = op_w_arg(SYMBOL_COMMAND)
.ignore_then(any().repeated().padded().to_slice())
.map(|s| Command::PrintSymbol(s.trim().to_string()))
.boxed();
let r#break = op2_w_arg(BREAK_COMMAND, BREAK_COMMAND_SHORT)
.ignore_then(choice((
sub_op2_w_arg(BREAK_REMOVE_SUBCOMMAND, BREAK_REMOVE_SUBCOMMAND_SHORT)
.ignore_then(choice((
brkpt_at_addr_parser(),
brkpt_at_line_parser(),
brkpt_number(),
brkpt_at_fn(),
)))
.map(|brkpt| Command::Breakpoint(r#break::Command::Remove(brkpt))),
sub_op(BREAK_INFO_SUBCOMMAND).to(Command::Breakpoint(r#break::Command::Info)),
choice((
brkpt_at_addr_parser(),
brkpt_at_line_parser(),
brkpt_at_fn(),
))
.map(|brkpt| Command::Breakpoint(r#break::Command::Add(brkpt))),
)))
.boxed();
let watchpoint = op2_w_arg(WATCH_COMMAND, WATCH_COMMAND_SHORT)
.ignore_then(choice((
sub_op2_w_arg(WATCH_REMOVE_SUBCOMMAND, WATCH_REMOVE_SUBCOMMAND_SHORT)
.ignore_then(choice((
text::int(10)
.from_str()
.unwrapped()
.map(|number: u32| WatchpointIdentity::Number(number))
.padded(),
watchpoint_at_address(),
watchpoint_at_dqe(),
)))
.map(|ident| Command::Watchpoint(watch::Command::Remove(ident))),
sub_op(BREAK_INFO_SUBCOMMAND).to(Command::Watchpoint(watch::Command::Info)),
watchpoint_cond()
.then(watchpoint_at_address())
.map(|(cond, ident)| Command::Watchpoint(watch::Command::Add(ident, cond))),
watchpoint_cond()
.then(watchpoint_at_dqe())
.map(|(cond, identity)| {
Command::Watchpoint(watch::Command::Add(identity, cond))
}),
)))
.boxed();
let memory = op2_w_arg(MEMORY_COMMAND, MEMORY_COMMAND_SHORT)
.ignore_then(choice((
sub_op_w_arg(MEMORY_COMMAND_READ_SUBCOMMAND)
.ignore_then(hex())
.map(|addr| Command::Memory(memory::Command::Read(addr))),
sub_op_w_arg(MEMORY_COMMAND_WRITE_SUBCOMMAND)
.ignore_then(hex().then(hex()))
.map(|(addr, val)| Command::Memory(memory::Command::Write(addr, val))),
)))
.boxed();
let register = op2_w_arg(REGISTER_COMMAND, REGISTER_COMMAND_SHORT)
.ignore_then(choice((
sub_op(REGISTER_COMMAND_INFO_SUBCOMMAND)
.to(Command::Register(register::Command::Info)),
sub_op_w_arg(REGISTER_COMMAND_READ_SUBCOMMAND)
.ignore_then(text::ident())
.map(|reg_name| {
Command::Register(register::Command::Read(reg_name.to_string()))
})
.padded(),
sub_op_w_arg(REGISTER_COMMAND_WRITE_SUBCOMMAND)
.ignore_then(text::ident().then(hex()))
.map(|(reg_name, val)| {
Command::Register(register::Command::Write(
reg_name.to_string(),
val as u64,
))
})
.padded(),
)))
.boxed();
let thread = op_w_arg(THREAD_COMMAND)
.ignore_then(choice((
sub_op(THREAD_COMMAND_INFO_SUBCOMMAND).to(Command::Thread(thread::Command::Info)),
sub_op(THREAD_COMMAND_CURRENT_SUBCOMMAND)
.to(Command::Thread(thread::Command::Current)),
sub_op_w_arg(THREAD_COMMAND_SWITCH_SUBCOMMAND)
.ignore_then(text::int(10))
.from_str()
.unwrapped()
.map(|num| Command::Thread(thread::Command::Switch(num)))
.padded(),
)))
.boxed();
let frame = op2_w_arg(FRAME_COMMAND, FRAME_COMMAND_SHORT)
.ignore_then(choice((
sub_op(FRAME_COMMAND_INFO_SUBCOMMAND).to(Command::Frame(frame::Command::Info)),
sub_op(FRAME_COMMAND_SWITCH_SUBCOMMAND)
.ignore_then(text::int(10).from_str().unwrapped())
.map(|num| Command::Frame(frame::Command::Switch(num)))
.padded(),
)))
.boxed();
let shared_lib = op_w_arg(SHARED_LIB_COMMAND)
.then(sub_op(SHARED_LIB_COMMAND_INFO_SUBCOMMAND))
.to(Command::SharedLib)
.boxed();
let r#async = op_w_arg(ASYNC_COMMAND)
.ignore_then(choice((
sub_op2(
ASYNC_COMMAND_BACKTRACE_SUBCOMMAND,
ASYNC_COMMAND_BACKTRACE_SUBCOMMAND_SHORT,
)
.ignore_then(sub_op(BACKTRACE_ALL_SUBCOMMAND).or_not())
.map(|all| {
if all.is_some() {
Command::Async(r#async::Command::FullBacktrace)
} else {
Command::Async(r#async::Command::ShortBacktrace)
}
}),
sub_op(ASYNC_COMMAND_TASK_SUBCOMMAND)
.ignore_then(any().repeated().padded().to_slice())
.map(|s| {
let s = s.trim();
if s.is_empty() {
Command::Async(r#async::Command::CurrentTask(None))
} else {
Command::Async(r#async::Command::CurrentTask(Some(s.to_string())))
}
}),
sub_op2(
ASYNC_COMMAND_STEP_OVER_SUBCOMMAND,
ASYNC_COMMAND_STEP_OVER_SUBCOMMAND_SHORT,
)
.to(Command::Async(r#async::Command::StepOver)),
sub_op2(
ASYNC_COMMAND_STEP_OUT_SUBCOMMAND,
ASYNC_COMMAND_STEP_OUT_SUBCOMMAND_SHORT,
)
.to(Command::Async(r#async::Command::StepOut)),
)))
.boxed();
let trigger = op(TRIGGER_COMMAND)
.ignore_then(choice((
choice((
sub_op(TRIGGER_COMMAND_INFO_SUBCOMMAND).to(trigger::Command::Info),
sub_op(TRIGGER_COMMAND_ANY_TRIGGER_SUBCOMMAND).to(
trigger::Command::AttachToDefined(trigger::TriggerEvent::Any),
),
sub_op(TRIGGER_COMMAND_BRKPT_TRIGGER_SUBCOMMAND)
.ignore_then(text::int(10))
.from_str()
.unwrapped()
.map(|num| {
trigger::Command::AttachToDefined(trigger::TriggerEvent::Breakpoint(
num,
))
}),
sub_op(TRIGGER_COMMAND_WP_TRIGGER_SUBCOMMAND)
.ignore_then(text::int(10))
.from_str()
.unwrapped()
.map(|num| {
trigger::Command::AttachToDefined(trigger::TriggerEvent::Watchpoint(
num,
))
}),
))
.padded(),
end()
.to(trigger::Command::AttachToPreviouslyCreated)
.padded(),
)))
.map(Command::Trigger)
.boxed();
let oracle = op_w_arg(ORACLE_COMMAND)
.ignore_then(text::ident().padded().then(text::ident().or_not()))
.map(|(name, subcmd)| {
Command::Oracle(name.trim().to_string(), subcmd.map(ToString::to_string))
})
.padded()
.boxed();
choice((
command(VAR_COMMAND, print_variables),
command(ARG_COMMAND, print_arguments),
command(CONTINUE_COMMAND, r#continue),
command(RUN_COMMAND, run),
command(STEP_INSTRUCTION_COMMAND, stepi),
command(STEP_INTO_COMMAND, step_into),
command(STEP_OUT_COMMAND, step_out),
command(STEP_OVER_COMMAND, step_over),
command(SOURCE_COMMAND, source_code),
command(HELP_COMMAND, help),
command(BACKTRACE_COMMAND, backtrace),
command(SYMBOL_COMMAND, symbol),
command(BREAK_COMMAND, r#break),
command(MEMORY_COMMAND, memory),
command(REGISTER_COMMAND, register),
command(THREAD_COMMAND, thread),
command(FRAME_COMMAND, frame),
command(SHARED_LIB_COMMAND, shared_lib),
command(ORACLE_COMMAND, oracle),
command(WATCH_COMMAND, watchpoint),
command(ASYNC_COMMAND, r#async),
command(TRIGGER_COMMAND, trigger),
command(CALL_COMMAND, call),
))
}
pub fn parse(input: &str) -> CommandResult<Command> {
Self::parser()
.parse(input)
.into_result()
.map_err(|e| CommandError::Parsing(Self::render_errors(input, e)))
}
}
#[test]
fn test_hex_parser() {
struct TestCase {
string: &'static str,
result: Result<usize, ()>,
}
let cases = vec![
TestCase {
string: "0x123AA",
result: Ok(0x123aa_usize),
},
TestCase {
string: " 0X123AA ",
result: Ok(0x123aa_usize),
},
TestCase {
string: " 0x 123AA ",
result: Err(()),
},
TestCase {
string: " 123AA ",
result: Err(()),
},
];
for tc in cases {
let expr = hex().parse(tc.string).into_result();
assert_eq!(expr.map_err(|_| ()), tc.result);
}
}
#[test]
fn test_rust_identifier_parser() {
struct TestCase {
string: &'static str,
result: Result<&'static str, ()>,
}
let cases = vec![
TestCase {
string: "some_var",
result: Ok("some_var"),
},
TestCase {
string: "_some_var",
result: Ok("_some_var"),
},
TestCase {
string: " _some_var ",
result: Ok("_some_var"),
},
TestCase {
string: "::aa::BB::_CC1",
result: Ok("::aa::BB::_CC1"),
},
TestCase {
string: "1a",
result: Err(()),
},
TestCase {
string: "aa::",
result: Err(()),
},
];
for tc in cases {
let expr = rust_identifier().parse(tc.string).into_result();
assert_eq!(expr.map_err(|_| ()), tc.result);
}
}
#[test]
fn test_parser() {
use crate::debugger::variable::dqe::Literal;
enum Expect {
Ok(Command),
Err,
}
struct TestCase {
inputs: Vec<&'static str>,
expected: Expect,
}
let cases = vec![
TestCase {
inputs: vec!["var locals"],
expected: Expect::Ok(Command::Print(print::Command::Variable {
dqe: Dqe::Variable(Selector::Any),
mode: print::RenderMode::Builtin,
})),
},
TestCase {
inputs: vec!["vard locals"],
expected: Expect::Ok(Command::Print(print::Command::Variable {
dqe: Dqe::Variable(Selector::Any),
mode: print::RenderMode::Debug,
})),
},
TestCase {
inputs: vec!["var **var1"],
expected: Expect::Ok(Command::Print(print::Command::Variable {
dqe: Dqe::Deref(
Dqe::Deref(Dqe::Variable(Selector::by_name("var1", false)).boxed()).boxed(),
),
mode: print::RenderMode::Builtin,
})),
},
TestCase {
inputs: vec!["var locals_var"],
expected: Expect::Ok(Command::Print(print::Command::Variable {
dqe: Dqe::Variable(Selector::by_name("locals_var", false)),
mode: print::RenderMode::Builtin,
})),
},
TestCase {
inputs: vec!["vard locals_var"],
expected: Expect::Ok(Command::Print(print::Command::Variable {
dqe: Dqe::Variable(Selector::by_name("locals_var", false)),
mode: print::RenderMode::Debug,
})),
},
TestCase {
inputs: vec!["var ("],
expected: Expect::Err,
},
TestCase {
inputs: vec!["das"],
expected: Expect::Err,
},
TestCase {
inputs: vec!["voo"],
expected: Expect::Err,
},
TestCase {
inputs: vec!["arg 11"],
expected: Expect::Err,
},
TestCase {
inputs: vec!["br", "arglocals"],
expected: Expect::Err,
},
TestCase {
inputs: vec!["arg all"],
expected: Expect::Ok(Command::Print(print::Command::Argument {
dqe: Dqe::Variable(Selector::Any),
mode: print::RenderMode::Builtin,
})),
},
TestCase {
inputs: vec!["argd all"],
expected: Expect::Ok(Command::Print(print::Command::Argument {
dqe: Dqe::Variable(Selector::Any),
mode: print::RenderMode::Debug,
})),
},
TestCase {
inputs: vec!["arg all_arg"],
expected: Expect::Ok(Command::Print(print::Command::Argument {
dqe: Dqe::Variable(Selector::by_name("all_arg", false)),
mode: print::RenderMode::Builtin,
})),
},
TestCase {
inputs: vec!["argd all_arg"],
expected: Expect::Ok(Command::Print(print::Command::Argument {
dqe: Dqe::Variable(Selector::by_name("all_arg", false)),
mode: print::RenderMode::Debug,
})),
},
TestCase {
inputs: vec!["bt", "backtrace"],
expected: Expect::Ok(Command::PrintBacktrace(
super::backtrace::Command::CurrentThread,
)),
},
TestCase {
inputs: vec!["bt all", "backtrace all "],
expected: Expect::Ok(Command::PrintBacktrace(super::backtrace::Command::All)),
},
TestCase {
inputs: vec!["c", "continue"],
expected: Expect::Ok(Command::Continue),
},
TestCase {
inputs: vec!["frame info ", " frame info"],
expected: Expect::Ok(Command::Frame(frame::Command::Info)),
},
TestCase {
inputs: vec!["f info ", " f info"],
expected: Expect::Ok(Command::Frame(frame::Command::Info)),
},
TestCase {
inputs: vec!["frame switch 1", " frame switch 1 "],
expected: Expect::Ok(Command::Frame(frame::Command::Switch(1))),
},
TestCase {
inputs: vec!["r", "run"],
expected: Expect::Ok(Command::Run),
},
TestCase {
inputs: vec!["symbol main", " symbol main "],
expected: Expect::Ok(Command::PrintSymbol("main".into())),
},
TestCase {
inputs: vec![" stepi"],
expected: Expect::Ok(Command::StepInstruction),
},
TestCase {
inputs: vec!["step", "stepinto"],
expected: Expect::Ok(Command::StepInto),
},
TestCase {
inputs: vec!["finish", "stepout"],
expected: Expect::Ok(Command::StepOut),
},
TestCase {
inputs: vec!["next", "stepover"],
expected: Expect::Ok(Command::StepOver),
},
TestCase {
inputs: vec!["b some_func", "break some_func", " break some_func "],
expected: Expect::Ok(Command::Breakpoint(r#break::Command::Add(
BreakpointIdentity::Function("some_func".into()),
))),
},
TestCase {
inputs: vec!["b rust_fn"],
expected: Expect::Ok(Command::Breakpoint(r#break::Command::Add(
BreakpointIdentity::Function("rust_fn".into()),
))),
},
TestCase {
inputs: vec!["b info_rust"],
expected: Expect::Ok(Command::Breakpoint(r#break::Command::Add(
BreakpointIdentity::Function("info_rust".into()),
))),
},
TestCase {
inputs: vec!["b file:123", "break file:123", " break file:123 "],
expected: Expect::Ok(Command::Breakpoint(r#break::Command::Add(
BreakpointIdentity::Line("file".into(), 123),
))),
},
TestCase {
inputs: vec!["b 0x123", "break 0x123", " break 0x0123 "],
expected: Expect::Ok(Command::Breakpoint(r#break::Command::Add(
BreakpointIdentity::Address(0x123),
))),
},
TestCase {
inputs: vec![
"b r some_func",
"break r some_func",
" break r some_func ",
],
expected: Expect::Ok(Command::Breakpoint(r#break::Command::Remove(
BreakpointIdentity::Function("some_func".into()),
))),
},
TestCase {
inputs: vec![
"b r ns1::some_func",
"break r ns1::some_func",
" break r ns1::some_func ",
],
expected: Expect::Ok(Command::Breakpoint(r#break::Command::Remove(
BreakpointIdentity::Function("ns1::some_func".into()),
))),
},
TestCase {
inputs: vec![
"b remove file:123",
"break r file:123",
" break remove file:123 ",
],
expected: Expect::Ok(Command::Breakpoint(r#break::Command::Remove(
BreakpointIdentity::Line("file".into(), 123),
))),
},
TestCase {
inputs: vec!["b remove 0x123", "break r 0x123", " break r 0x123 "],
expected: Expect::Ok(Command::Breakpoint(r#break::Command::Remove(
BreakpointIdentity::Address(0x123),
))),
},
TestCase {
inputs: vec!["b info", "break info ", " break info "],
expected: Expect::Ok(Command::Breakpoint(r#break::Command::Info)),
},
TestCase {
inputs: vec!["watch var1", "watch var1 ", " w var1 "],
expected: Expect::Ok(Command::Watchpoint(watch::Command::Add(
WatchpointIdentity::DQE(
"var1".into(),
Dqe::Variable(Selector::by_name("var1", false)),
),
BreakCondition::DataWrites,
))),
},
TestCase {
inputs: vec!["watch +rw var1", "watch +rw var1 ", " w +rw var1 "],
expected: Expect::Ok(Command::Watchpoint(watch::Command::Add(
WatchpointIdentity::DQE(
"var1".into(),
Dqe::Variable(Selector::by_name("var1", false)),
),
BreakCondition::DataReadsWrites,
))),
},
TestCase {
inputs: vec!["watch +w var1", "watch +w var1 ", " w +w var1 "],
expected: Expect::Ok(Command::Watchpoint(watch::Command::Add(
WatchpointIdentity::DQE(
"var1".into(),
Dqe::Variable(Selector::by_name("var1", false)),
),
BreakCondition::DataWrites,
))),
},
TestCase {
inputs: vec![
"watch ns1::ns2::var1",
"watch ns1::ns2::var1 ",
" w ns1::ns2::var1 ",
],
expected: Expect::Ok(Command::Watchpoint(watch::Command::Add(
WatchpointIdentity::DQE(
"ns1::ns2::var1".into(),
Dqe::Variable(Selector::by_name("ns1::ns2::var1", false)),
),
BreakCondition::DataWrites,
))),
},
TestCase {
inputs: vec!["watch 0x123:4", "watch 0x123:4 ", " w 0x123:4 "],
expected: Expect::Ok(Command::Watchpoint(watch::Command::Add(
WatchpointIdentity::Address(0x123, 4),
BreakCondition::DataWrites,
))),
},
TestCase {
inputs: vec!["watch info", "watch info ", " w info "],
expected: Expect::Ok(Command::Watchpoint(watch::Command::Info)),
},
TestCase {
inputs: vec!["watch r var1", "watch remove var1 ", " w r var1 "],
expected: Expect::Ok(Command::Watchpoint(watch::Command::Remove(
WatchpointIdentity::DQE(
"var1".into(),
Dqe::Variable(Selector::by_name("var1", false)),
),
))),
},
TestCase {
inputs: vec!["watch r 2", "watch remove 2 ", " w r 2 "],
expected: Expect::Ok(Command::Watchpoint(watch::Command::Remove(
WatchpointIdentity::Number(2),
))),
},
TestCase {
inputs: vec![
"mem read 0x123",
"memory read 0x123",
" mem read 0x123 ",
],
expected: Expect::Ok(Command::Memory(memory::Command::Read(0x123))),
},
TestCase {
inputs: vec![
"mem write 0x123 0x321",
"memory write 0x123 0x321",
" mem write 0x123 0x321 ",
],
expected: Expect::Ok(Command::Memory(memory::Command::Write(0x123, 0x321))),
},
TestCase {
inputs: vec!["reg info", "register info", " reg info "],
expected: Expect::Ok(Command::Register(register::Command::Info)),
},
TestCase {
inputs: vec!["reg read rip", "register read rip", " reg read rip "],
expected: Expect::Ok(Command::Register(register::Command::Read("rip".into()))),
},
TestCase {
inputs: vec![
"reg write rip 0x123",
"register write rip 0x123",
" reg write rip 0x123 ",
],
expected: Expect::Ok(Command::Register(register::Command::Write(
"rip".into(),
0x123,
))),
},
TestCase {
inputs: vec!["thread info", "thread info "],
expected: Expect::Ok(Command::Thread(thread::Command::Info)),
},
TestCase {
inputs: vec!["thread current", "thread current "],
expected: Expect::Ok(Command::Thread(thread::Command::Current)),
},
TestCase {
inputs: vec!["thread switch 1", " thread switch 1 "],
expected: Expect::Ok(Command::Thread(thread::Command::Switch(1))),
},
TestCase {
inputs: vec!["sharedlib info", " sharedlib info "],
expected: Expect::Ok(Command::SharedLib),
},
TestCase {
inputs: vec!["source asm", " source asm "],
expected: Expect::Ok(Command::SourceCode(source_code::Command::Asm)),
},
TestCase {
inputs: vec!["source fn", " source fn "],
expected: Expect::Ok(Command::SourceCode(source_code::Command::Function)),
},
TestCase {
inputs: vec!["source 12", " source 12 "],
expected: Expect::Ok(Command::SourceCode(source_code::Command::Range(12))),
},
TestCase {
inputs: vec!["async backtrace", " async bt "],
expected: Expect::Ok(Command::Async(r#async::Command::ShortBacktrace)),
},
TestCase {
inputs: vec!["async backtrace all", " async bt all "],
expected: Expect::Ok(Command::Async(r#async::Command::FullBacktrace)),
},
TestCase {
inputs: vec!["async task", " async task "],
expected: Expect::Ok(Command::Async(r#async::Command::CurrentTask(None))),
},
TestCase {
inputs: vec!["async stepover", " async next "],
expected: Expect::Ok(Command::Async(r#async::Command::StepOver)),
},
TestCase {
inputs: vec!["async stepout", " async finish "],
expected: Expect::Ok(Command::Async(r#async::Command::StepOut)),
},
TestCase {
inputs: vec!["async task abc.*", " async task abc.* "],
expected: Expect::Ok(Command::Async(r#async::Command::CurrentTask(Some(
"abc.*".into(),
)))),
},
TestCase {
inputs: vec!["trigger", " trigger "],
expected: Expect::Ok(Command::Trigger(
trigger::Command::AttachToPreviouslyCreated,
)),
},
TestCase {
inputs: vec!["trigger any", " trigger any "],
expected: Expect::Ok(Command::Trigger(trigger::Command::AttachToDefined(
trigger::TriggerEvent::Any,
))),
},
TestCase {
inputs: vec!["trigger info", " trigger info "],
expected: Expect::Ok(Command::Trigger(trigger::Command::Info)),
},
TestCase {
inputs: vec!["trigger b 1", " trigger b 1 "],
expected: Expect::Ok(Command::Trigger(trigger::Command::AttachToDefined(
trigger::TriggerEvent::Breakpoint(1),
))),
},
TestCase {
inputs: vec!["trigger w 2", " trigger w 2 "],
expected: Expect::Ok(Command::Trigger(trigger::Command::AttachToDefined(
trigger::TriggerEvent::Watchpoint(2),
))),
},
TestCase {
inputs: vec!["call some_fn 1", " call some_fn 1 "],
expected: Expect::Ok(Command::Call(call::Command {
fn_name: "some_fn".into(),
args: [Literal::Int(1)].into(),
})),
},
TestCase {
inputs: vec!["call some_fn 1 2 3 4 5 6"],
expected: Expect::Ok(Command::Call(call::Command {
fn_name: "some_fn".into(),
args: [
Literal::Int(1),
Literal::Int(2),
Literal::Int(3),
Literal::Int(4),
Literal::Int(5),
Literal::Int(6),
]
.into(),
})),
},
TestCase {
inputs: vec!["oracle tokio", " oracle tokio "],
expected: Expect::Ok(Command::Oracle("tokio".into(), None)),
},
TestCase {
inputs: vec!["oracle tokio all ", " oracle tokio all"],
expected: Expect::Ok(Command::Oracle("tokio".into(), Some("all".into()))),
},
];
for case in cases {
for input in case.inputs {
let result = Command::parse(input);
match case.expected {
Expect::Ok(ref expected_cmd) => {
assert!(result.is_ok());
assert_eq!(&result.unwrap(), expected_cmd);
}
Expect::Err => assert!(result.is_err()),
}
}
}
}