blob: aa39ca83679d04f3d900d646f175f9c215b1b512 [file] [log] [blame] [edit]
use paste::paste;
use crate::protocol::packet::PacketBuf;
use crate::target::Target;
pub(self) mod prelude {
pub use super::ParseCommand;
pub use crate::common::*;
pub use crate::protocol::common::*;
pub use crate::protocol::packet::PacketBuf;
pub use core::convert::{TryFrom, TryInto};
}
pub trait ParseCommand<'a>: Sized {
/// Try to parse a packet from the packet buffer.
fn from_packet(buf: PacketBuf<'a>) -> Option<Self>;
}
macro_rules! commands {
(
$(
$ext:ident $(use $lt:lifetime)? {
$($name:literal => $mod:ident::$command:ident$(<$lifetime:lifetime>)?,)*
}
)*
) => {paste! {
$($(
#[allow(non_snake_case, non_camel_case_types)]
pub mod $mod;
)*)*
pub mod ext {
$(
#[allow(non_camel_case_types)]
pub enum [<$ext:camel>] $(<$lt>)? {
$($command(super::$mod::$command<$($lifetime)?>),)*
}
)*
}
/// GDB commands
pub enum Command<'a> {
$(
[<$ext:camel>](ext::[<$ext:camel>]$(<$lt>)?),
)*
Unknown(&'a str),
}
impl<'a> Command<'a> {
pub fn from_packet(
target: &mut impl Target,
buf: PacketBuf<'a>
) -> Result<Command<'a>, CommandParseError<'a>> {
if buf.as_body().is_empty() {
return Err(CommandParseError::Empty);
}
let body = buf.as_body();
// This scoped extension trait enables using `base` as an
// `$ext`, even through the `base` method on `Target` doesn't
// return an Option.
trait Hack { fn base(&mut self) -> Option<()> { Some(()) } }
impl<T: Target> Hack for T {}
$(
if target.$ext().is_some() {
// TODO?: use tries for more efficient longest prefix matching
#[allow(clippy::string_lit_as_bytes)]
match body {
$(_ if body.starts_with($name.as_bytes()) => {
crate::__dead_code_marker!($name, "prefix_match");
let buf = buf.trim_start_body_bytes($name.len());
let cmd = $mod::$command::from_packet(buf)
.ok_or(CommandParseError::MalformedCommand($name))?;
return Ok(
Command::[<$ext:camel>](
ext::[<$ext:camel>]::$command(cmd)
)
)
})*
_ => {},
}
}
)*
Ok(Command::Unknown(buf.into_body_str()))
}
}
}};
}
/// Command parse error
// TODO?: add more granular errors to command parsing code
pub enum CommandParseError<'a> {
Empty,
/// catch-all
MalformedCommand(&'a str),
}
commands! {
base use 'a {
"?" => question_mark::QuestionMark,
"c" => _c::c<'a>,
"D" => _d_upcase::D,
"g" => _g::g,
"G" => _g_upcase::G<'a>,
"H" => _h_upcase::H,
"k" => _k::k,
"m" => _m::m<'a>,
"M" => _m_upcase::M<'a>,
"p" => _p::p,
"P" => _p_upcase::P<'a>,
"qAttached" => _qAttached::qAttached,
"qfThreadInfo" => _qfThreadInfo::qfThreadInfo,
"QStartNoAckMode" => _QStartNoAckMode::QStartNoAckMode,
"qsThreadInfo" => _qsThreadInfo::qsThreadInfo,
"qSupported" => _qSupported::qSupported<'a>,
"qXfer:features:read" => _qXfer_features_read::qXferFeaturesRead,
"s" => _s::s<'a>,
"T" => _t_upcase::T,
"vCont" => _vCont::vCont<'a>,
"vKill" => _vKill::vKill,
"z" => _z::z<'a>,
"Z" => _z_upcase::Z<'a>,
}
extended_mode use 'a {
"!" => exclamation_mark::ExclamationMark,
"QDisableRandomization" => _QDisableRandomization::QDisableRandomization,
"QEnvironmentHexEncoded" => _QEnvironmentHexEncoded::QEnvironmentHexEncoded<'a>,
"QEnvironmentReset" => _QEnvironmentReset::QEnvironmentReset,
"QEnvironmentUnset" => _QEnvironmentUnset::QEnvironmentUnset<'a>,
"QSetWorkingDir" => _QSetWorkingDir::QSetWorkingDir<'a>,
"QStartupWithShell" => _QStartupWithShell::QStartupWithShell,
"R" => _r_upcase::R,
"vAttach" => _vAttach::vAttach,
"vRun" => _vRun::vRun<'a>,
}
monitor_cmd use 'a {
"qRcmd" => _qRcmd::qRcmd<'a>,
}
section_offsets {
"qOffsets" => _qOffsets::qOffsets,
}
}