use crate::input::{MouseButton, MouseWheelDirection, Key, KeyModifier, KeyCode};
#[derive(Debug)]
pub(super) struct ParserError {
error: String,
line: String,
start: Option<usize>,
end: Option<usize>,
}
impl ParserError {
pub(super) fn from_parser(error: &str, line: &str, start: Option<usize>, end: Option<usize>) -> Self {
Self {
error: String::from(error),
line: String::from(line),
start,
end,
}
}
pub(super) fn new(error: &str) -> Self {
Self {
error: String::from(error),
line: String::new(),
start: None,
end: None,
}
}
pub(super) fn get_error(&self) -> &str {
self.error.as_str()
}
pub(super) fn as_string(&self) -> String {
let mut err = String::with_capacity(256);
err.push_str("Command parsing error: ");
err.push_str(self.get_error());
err.push_str("\n for line: \"");
err.push_str(self.line.as_str());
err.push_str("\"\n");
if let (Some(s), Some(e)) = (self.start, self.end) {
for _ in 0..12 {
err.push(' ');
}
for (index, _) in self.line.as_str().char_indices() {
if (index >= s) && (index < e) {
err.push('^');
} else {
err.push(' ');
}
}
err.push('\n');
}
err
}
}
#[derive(Debug, Default)]
pub(super) struct CommandParser<'a> {
command: &'a str,
params: [&'a str; 4],
count: usize,
}
impl<'a> CommandParser<'a> {
pub(super) fn new(command: &'a str) -> Result<Self, ParserError> {
let mut cp = Self {
command: "",
params: ["", "", "", ""],
count: 0,
};
cp.parse(command)?;
Ok(cp)
}
fn is_space(value: u8) -> bool {
(value == b' ') || (value == b'\t')
}
fn is_command(value: u8) -> bool {
matches!(value, b'a'..=b'z' | b'A'..=b'Z' | b'.')
}
fn is_word(value: u8) -> bool {
matches!(value, b'a'..=b'z' | b'A'..=b'Z' | b'.' | b'_' | b'-' | b'+' | b'0'..=b'9')
}
fn skip(buf: &[u8], start: usize, f: fn(u8) -> bool) -> usize {
let len = buf.len();
let mut start = start;
while (start < len) && (f(buf[start])) {
start += 1;
}
start
}
pub(super) fn get_command(&self) -> &str {
self.command
}
pub(super) fn get_params_count(&self) -> usize {
self.count
}
pub(super) fn get_string(&self, index: usize)->Option<String> {
if index >= self.count {
return None;
}
let s = String::from(self.params[index]).replace("\\n", "\n").replace("\\t", "\t").replace("\\\\", "\\");
Some(s)
}
pub(super) fn get_param(&self, index: usize) -> Option<&'a str> {
if index >= self.count {
return None;
}
Some(self.params[index])
}
pub(super) fn get_bool(&self, index: usize) -> Option<bool> {
if index >= self.count {
return None;
}
match self.params[index] {
"true" => Some(true),
"false" => Some(false),
_ => None,
}
}
pub(super) fn get_keymodifier(&self, index: usize) -> Option<KeyModifier> {
if index >= self.count {
return None;
}
let mut k = KeyModifier::None;
for part in self.params[index].split('+') {
match part {
"Ctrl" => k |= KeyModifier::Ctrl,
"Alt" => k |= KeyModifier::Alt,
"Shift" => k |= KeyModifier::Shift,
"None" => k = KeyModifier::None,
_ => { return None; }
}
}
Some(k)
}
pub(super) fn get_key(&self, index: usize) -> Option<Key> {
if index >= self.count {
return None;
}
let mut k = Key::default();
for part in self.params[index].split('+') {
match part {
"Ctrl" => k.modifier |= KeyModifier::Ctrl,
"Alt" => k.modifier |= KeyModifier::Alt,
"Shift" => k.modifier |= KeyModifier::Shift,
"F1" => k.code = KeyCode::F1,
"F2" => k.code = KeyCode::F2,
"F3" => k.code = KeyCode::F3,
"F4" => k.code = KeyCode::F4,
"F5" => k.code = KeyCode::F5,
"F6" => k.code = KeyCode::F6,
"F7" => k.code = KeyCode::F7,
"F8" => k.code = KeyCode::F8,
"F9" => k.code = KeyCode::F9,
"F10" => k.code = KeyCode::F10,
"F11" => k.code = KeyCode::F11,
"F12" => k.code = KeyCode::F12,
"Enter" => k.code = KeyCode::Enter,
"Escape" => k.code = KeyCode::Escape,
"Insert" => k.code = KeyCode::Insert,
"Delete" => k.code = KeyCode::Delete,
"Backspace" => k.code = KeyCode::Backspace,
"Tab" => k.code = KeyCode::Tab,
"Left" => k.code = KeyCode::Left,
"Up" => k.code = KeyCode::Up,
"Down" => k.code = KeyCode::Down,
"Right" => k.code = KeyCode::Right,
"PageUp" => k.code = KeyCode::PageUp,
"PageDown" => k.code = KeyCode::PageDown,
"Home" => k.code = KeyCode::Home,
"End" => k.code = KeyCode::End,
"Space" => k.code = KeyCode::Space,
"A" => k.code = KeyCode::A,
"B" => k.code = KeyCode::B,
"C" => k.code = KeyCode::C,
"D" => k.code = KeyCode::D,
"E" => k.code = KeyCode::E,
"F" => k.code = KeyCode::F,
"G" => k.code = KeyCode::G,
"H" => k.code = KeyCode::H,
"I" => k.code = KeyCode::I,
"J" => k.code = KeyCode::J,
"K" => k.code = KeyCode::K,
"L" => k.code = KeyCode::L,
"M" => k.code = KeyCode::M,
"N" => k.code = KeyCode::N,
"O" => k.code = KeyCode::O,
"P" => k.code = KeyCode::P,
"Q" => k.code = KeyCode::Q,
"R" => k.code = KeyCode::R,
"S" => k.code = KeyCode::S,
"T" => k.code = KeyCode::T,
"U" => k.code = KeyCode::U,
"V" => k.code = KeyCode::V,
"W" => k.code = KeyCode::W,
"X" => k.code = KeyCode::X,
"Y" => k.code = KeyCode::Y,
"Z" => k.code = KeyCode::Z,
"0" => k.code = KeyCode::N0,
"1" => k.code = KeyCode::N1,
"2" => k.code = KeyCode::N2,
"3" => k.code = KeyCode::N3,
"4" => k.code = KeyCode::N4,
"5" => k.code = KeyCode::N5,
"6" => k.code = KeyCode::N6,
"7" => k.code = KeyCode::N7,
"8" => k.code = KeyCode::N8,
"9" => k.code = KeyCode::N9,
_ => { return None; }
}
}
Some(k)
}
pub(super) fn get_mouse_button(&self, index: usize) -> Option<MouseButton> {
if index >= self.count {
return None;
}
match self.params[index] {
"left" => Some(MouseButton::Left),
"right" => Some(MouseButton::Right),
"center" => Some(MouseButton::Center),
_ => None,
}
}
pub(super) fn get_mouse_wheel(&self, index: usize) -> Option<MouseWheelDirection> {
if index >= self.count {
return None;
}
match self.params[index] {
"left" => Some(MouseWheelDirection::Left),
"right" => Some(MouseWheelDirection::Right),
"up" => Some(MouseWheelDirection::Up),
"down" => Some(MouseWheelDirection::Down),
_ => None,
}
}
pub(super) fn get_i32(&self, index: usize) -> Option<i32> {
if index >= self.count {
return None;
}
if let Ok(value) = self.params[index].parse::<i32>() {
return Some(value);
}
None
}
pub(super) fn get_hash(&self, index: usize) -> Option<u64> {
if index >= self.count {
return None;
}
let txt = self.params[index];
if !txt.starts_with("0x") {
return None;
}
if let Ok(value) = u64::from_str_radix(&txt[2..],16) {
return Some(value);
}
None
}
pub(super) fn parse(&mut self, command: &'a str) -> Result<(), ParserError> {
let buf = command.as_bytes();
let len = buf.len();
let mut poz = 0usize;
self.command = "";
self.count = 0;
self.params[0] = "";
self.params[1] = "";
self.params[2] = "";
self.params[3] = "";
poz = CommandParser::skip(buf, poz, CommandParser::is_space);
if poz >= len {
return Err(ParserError::from_parser(
"Expecting a valid command (not an empty line)",
command,
None,
None,
));
}
let next = CommandParser::skip(buf, poz, CommandParser::is_command);
self.command = &command[poz..next];
poz = CommandParser::skip(buf, next, CommandParser::is_space);
if poz >= len {
return Ok(());
}
if buf[poz] != b'(' {
return Err(ParserError::from_parser(
"Expecting '(' after the command !",
command,
Some(poz),
Some(poz + 1),
));
}
let parantheze_poz = poz;
poz += 1;
loop {
poz = CommandParser::skip(buf, poz, CommandParser::is_space);
if poz >= len {
return Err(ParserError::from_parser(
"Expecting ')' after the '(' ",
command,
Some(parantheze_poz),
Some(parantheze_poz + 1),
));
}
match buf[poz] {
b')' => {
return Ok(());
}
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'+' => {
if self.count >= 4 {
return Err(ParserError::from_parser(
"Too many parameters (max allowed is 4)",
command,
None,
None,
));
}
let next = CommandParser::skip(buf, poz, CommandParser::is_word);
self.params[self.count] = &command[poz..next];
poz = next;
self.count += 1;
}
b',' => {
return Err(ParserError::from_parser(
"Expecting a word but found ',' separator !",
command,
Some(poz),
Some(poz + 1),
));
}
b'"' | b'\'' => {
let string_char = buf[poz];
let mut next = poz + 1;
while (next < len) && (buf[next] != string_char) {
next += 1;
}
if next >= len {
return Err(ParserError::from_parser(
"Invalid string (no ending '\"' character found)",
command,
Some(poz),
Some(len),
));
}
if self.count >= 4 {
return Err(ParserError::from_parser(
"Too many parameters (max allowed is 4)",
command,
None,
None,
));
}
self.params[self.count] = &command[poz + 1..next];
poz = next + 1;
self.count += 1;
}
_ => {
return Err(ParserError::from_parser(
"Invalid character (expecting a word)",
command,
Some(poz),
Some(poz + 1),
));
}
}
poz = CommandParser::skip(buf, poz, CommandParser::is_space);
if (poz < len) && (buf[poz] == b',') {
poz += 1;
}
}
}
}