Unquote strings and braces to simplify command-Lua interop.

This commit is contained in:
Condorra 2024-09-21 22:11:05 +10:00
parent ba49876f80
commit 855ecfc13a
2 changed files with 107 additions and 45 deletions

View File

@ -64,7 +64,7 @@ impl LuaState {
pub fn execute_command( pub fn execute_command(
&mut self, &mut self,
command: &str, command: &str,
arguments: &ParsedCommand<'_>, arguments: &ParsedCommand,
) -> Result<(), String> { ) -> Result<(), String> {
self.interp self.interp
.try_enter(|ctx| { .try_enter(|ctx| {

View File

@ -10,8 +10,8 @@ use nom::{
}; };
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
pub struct ParseResult<'l> { pub struct ParseResult {
pub commands: Vec<ParsedCommand<'l>>, pub commands: Vec<ParsedCommand>,
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
@ -21,12 +21,13 @@ pub enum ArgumentGuard {
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
pub struct ParsedArgument<'l> { pub struct ParsedArgument {
pub guard: Option<ArgumentGuard>, pub guard: Option<ArgumentGuard>,
pub text: &'l str, pub text: String,
pub quoted_text: String,
} }
impl<'l> ParsedArgument<'l> { impl ParsedArgument {
pub fn forget_guard(&self) -> Self { pub fn forget_guard(&self) -> Self {
Self { Self {
guard: None, guard: None,
@ -35,7 +36,7 @@ impl<'l> ParsedArgument<'l> {
} }
} }
impl<'a> Display for ParsedArgument<'a> { impl Display for ParsedArgument {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.guard { match self.guard {
None => {} None => {}
@ -46,7 +47,7 @@ impl<'a> Display for ParsedArgument<'a> {
f.write_str("\"")?; f.write_str("\"")?;
} }
} }
f.write_str(self.text)?; f.write_str(&self.quoted_text)?;
match self.guard { match self.guard {
None => {} None => {}
Some(ArgumentGuard::Paren) => { Some(ArgumentGuard::Paren) => {
@ -61,11 +62,11 @@ impl<'a> Display for ParsedArgument<'a> {
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
pub struct ParsedCommand<'l> { pub struct ParsedCommand {
pub arguments: VecDeque<ParsedArgument<'l>>, pub arguments: VecDeque<ParsedArgument>,
} }
impl<'l> ParsedCommand<'l> { impl ParsedCommand {
pub fn forget_guards(&self) -> Self { pub fn forget_guards(&self) -> Self {
Self { Self {
arguments: self.arguments.iter().map(|v| v.forget_guard()).collect(), arguments: self.arguments.iter().map(|v| v.forget_guard()).collect(),
@ -74,7 +75,7 @@ impl<'l> ParsedCommand<'l> {
} }
} }
impl<'a> Display for ParsedCommand<'a> { impl Display for ParsedCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut it = self.arguments.iter(); let mut it = self.arguments.iter();
match it.next() { match it.next() {
@ -92,8 +93,8 @@ impl<'a> Display for ParsedCommand<'a> {
} }
} }
impl ParsedCommand<'_> { impl ParsedCommand {
pub fn split_out_command(&self) -> Option<(&str, Self)> { pub fn split_out_command(&self) -> Option<(String, Self)> {
let mut tmp_arguments = self.arguments.clone(); let mut tmp_arguments = self.arguments.clone();
loop { loop {
match tmp_arguments.pop_front() { match tmp_arguments.pop_front() {
@ -102,7 +103,7 @@ impl ParsedCommand<'_> {
let trimmed_cmd = head.text.trim(); let trimmed_cmd = head.text.trim();
if !trimmed_cmd.is_empty() { if !trimmed_cmd.is_empty() {
return Some(( return Some((
trimmed_cmd, trimmed_cmd.to_owned(),
Self { Self {
arguments: tmp_arguments, arguments: tmp_arguments,
}, },
@ -114,6 +115,31 @@ impl ParsedCommand<'_> {
} }
} }
fn unquote_string(input: &str) -> String {
let mut buf: String = String::new();
let mut iter = input.chars();
loop {
match iter.next() {
None => return buf,
Some('\\') => match iter.next() {
None => {
buf.push('\\');
return buf;
}
Some('\\') => buf.push('\\'),
Some('{') => buf.push('{'),
Some('}') => buf.push('}'),
Some('"') => buf.push('"'),
Some(c) => {
buf.push('\\');
buf.push(c);
}
},
Some(c) => buf.push(c),
}
}
}
fn parse_string(input: &str) -> IResult<&str, ()> { fn parse_string(input: &str) -> IResult<&str, ()> {
value( value(
(), (),
@ -155,14 +181,16 @@ fn parse_argument(input: &str) -> IResult<&str, ParsedArgument> {
map( map(
delimited(char('{'), recognize(parse_parenthetical), char('}')), delimited(char('{'), recognize(parse_parenthetical), char('}')),
|v| ParsedArgument { |v| ParsedArgument {
text: v, text: unquote_string(v),
quoted_text: v.to_owned(),
guard: Some(ArgumentGuard::Paren), guard: Some(ArgumentGuard::Paren),
}, },
), ),
map( map(
delimited(char('"'), recognize(parse_string), char('"')), delimited(char('"'), recognize(parse_string), char('"')),
|v| ParsedArgument { |v| ParsedArgument {
text: v, text: unquote_string(v),
quoted_text: v.to_owned(),
guard: Some(ArgumentGuard::DoubleQuote), guard: Some(ArgumentGuard::DoubleQuote),
}, },
), ),
@ -191,7 +219,8 @@ fn parse_argument(input: &str) -> IResult<&str, ParsedArgument> {
))), ))),
)), )),
|v| ParsedArgument { |v| ParsedArgument {
text: v, text: unquote_string(v),
quoted_text: v.to_owned(),
guard: None, guard: None,
}, },
), ),
@ -223,7 +252,8 @@ mod tests {
ParseResult { ParseResult {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec![ParsedArgument { arguments: vec![ParsedArgument {
text: "", text: "".to_owned(),
quoted_text: "".to_owned(),
guard: None guard: None
}] }]
.into() .into()
@ -235,7 +265,8 @@ mod tests {
ParseResult { ParseResult {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec![ParsedArgument { arguments: vec![ParsedArgument {
text: "north", text: "north".to_owned(),
quoted_text: "north".to_owned(),
guard: None guard: None
}] }]
.into() .into()
@ -249,11 +280,13 @@ mod tests {
// This is deliberate, ensures we can reconstruct the input. // This is deliberate, ensures we can reconstruct the input.
arguments: vec![ arguments: vec![
ParsedArgument { ParsedArgument {
text: "north", text: "north".to_owned(),
quoted_text: "north".to_owned(),
guard: None guard: None
}, },
ParsedArgument { ParsedArgument {
text: "", text: "".to_owned(),
quoted_text: "".to_owned(),
guard: None guard: None
} }
] ]
@ -268,11 +301,13 @@ mod tests {
ParsedCommand { ParsedCommand {
arguments: vec![ arguments: vec![
ParsedArgument { ParsedArgument {
text: "#blah", text: "#blah".to_owned(),
quoted_text: "#blah".to_owned(),
guard: None guard: None
}, },
ParsedArgument { ParsedArgument {
text: "x = 1 + 2; y = 3", text: "x = 1 + 2; y = 3".to_owned(),
quoted_text: "x = 1 + 2; y = 3".to_owned(),
guard: Some(ArgumentGuard::Paren) guard: Some(ArgumentGuard::Paren)
} }
] ]
@ -280,7 +315,8 @@ mod tests {
}, },
ParsedCommand { ParsedCommand {
arguments: vec![ParsedArgument { arguments: vec![ParsedArgument {
text: "#home", text: "#home".to_owned(),
quoted_text: "#home".to_owned(),
guard: None guard: None
}] }]
.into() .into()
@ -294,11 +330,13 @@ mod tests {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec![ arguments: vec![
ParsedArgument { ParsedArgument {
text: "#blah", text: "#blah".to_owned(),
quoted_text: "#blah".to_owned(),
guard: None guard: None
}, },
ParsedArgument { ParsedArgument {
text: "{x = 1 + 2", text: "{x = 1 + 2".to_owned(),
quoted_text: "{x = 1 + 2".to_owned(),
guard: None guard: None
} }
] ]
@ -312,15 +350,18 @@ mod tests {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec![ arguments: vec![
ParsedArgument { ParsedArgument {
text: "#blah", text: "#blah".to_owned(),
quoted_text: "#blah".to_owned(),
guard: None guard: None
}, },
ParsedArgument { ParsedArgument {
text: "x = 1", text: "x = 1".to_owned(),
quoted_text: "x = 1".to_owned(),
guard: Some(ArgumentGuard::Paren) guard: Some(ArgumentGuard::Paren)
}, },
ParsedArgument { ParsedArgument {
text: "y = 1", text: "y = 1".to_owned(),
quoted_text: "y = 1".to_owned(),
guard: Some(ArgumentGuard::Paren) guard: Some(ArgumentGuard::Paren)
} }
] ]
@ -334,15 +375,18 @@ mod tests {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec![ arguments: vec![
ParsedArgument { ParsedArgument {
text: "#blah", text: "#blah".to_owned(),
quoted_text: "#blah".to_owned(),
guard: None guard: None
}, },
ParsedArgument { ParsedArgument {
text: "hello", text: "hello".to_owned(),
quoted_text: "hello".to_owned(),
guard: Some(ArgumentGuard::DoubleQuote) guard: Some(ArgumentGuard::DoubleQuote)
}, },
ParsedArgument { ParsedArgument {
text: "world", text: "world".to_owned(),
quoted_text: "world".to_owned(),
guard: Some(ArgumentGuard::DoubleQuote) guard: Some(ArgumentGuard::DoubleQuote)
}, },
] ]
@ -356,15 +400,18 @@ mod tests {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec![ arguments: vec![
ParsedArgument { ParsedArgument {
text: "#blah", text: "#blah".to_owned(),
quoted_text: "#blah".to_owned(),
guard: None guard: None
}, },
ParsedArgument { ParsedArgument {
text: "x = \"}\"", text: "x = \"}\"".to_owned(),
quoted_text: "x = \"}\"".to_owned(),
guard: Some(ArgumentGuard::Paren) guard: Some(ArgumentGuard::Paren)
}, },
ParsedArgument { ParsedArgument {
text: "y = 1", text: "y = 1".to_owned(),
quoted_text: "y = 1".to_owned(),
guard: Some(ArgumentGuard::Paren) guard: Some(ArgumentGuard::Paren)
} }
] ]
@ -379,15 +426,18 @@ mod tests {
ParsedCommand { ParsedCommand {
arguments: vec![ arguments: vec![
ParsedArgument { ParsedArgument {
text: "#blah", text: "#blah".to_owned(),
quoted_text: "#blah".to_owned(),
guard: None guard: None
}, },
ParsedArgument { ParsedArgument {
text: "x = \"}\"; a = \"{\"; y = {}; z = 1;", text: "x = \"}\"; a = \"{\"; y = {}; z = 1;".to_owned(),
quoted_text: "x = \"}\"; a = \"{\"; y = {}; z = 1;".to_owned(),
guard: Some(ArgumentGuard::Paren) guard: Some(ArgumentGuard::Paren)
}, },
ParsedArgument { ParsedArgument {
text: " q = 5 ", text: " q = 5 ".to_owned(),
quoted_text: " q = 5 ".to_owned(),
guard: Some(ArgumentGuard::Paren) guard: Some(ArgumentGuard::Paren)
} }
] ]
@ -395,7 +445,8 @@ mod tests {
}, },
ParsedCommand { ParsedCommand {
arguments: vec![ParsedArgument { arguments: vec![ParsedArgument {
text: "", text: "".to_owned(),
quoted_text: "".to_owned(),
guard: None guard: None
}] }]
.into() .into()
@ -409,15 +460,18 @@ mod tests {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec![ arguments: vec![
ParsedArgument { ParsedArgument {
text: "#blah", text: "#blah".to_owned(),
quoted_text: "#blah".to_owned(),
guard: None guard: None
}, },
ParsedArgument { ParsedArgument {
text: "\\}\\}\\}", text: "}}}".to_owned(),
quoted_text: "\\}\\}\\}".to_owned(),
guard: Some(ArgumentGuard::Paren) guard: Some(ArgumentGuard::Paren)
}, },
ParsedArgument { ParsedArgument {
text: "y = 1", text: "y = 1".to_owned(),
quoted_text: "y = 1".to_owned(),
guard: Some(ArgumentGuard::Paren) guard: Some(ArgumentGuard::Paren)
} }
] ]
@ -431,11 +485,13 @@ mod tests {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec![ arguments: vec![
ParsedArgument { ParsedArgument {
text: "#blah", text: "#blah".to_owned(),
quoted_text: "#blah".to_owned(),
guard: None guard: None
}, },
ParsedArgument { ParsedArgument {
text: "This is a \\\"test\\\"", text: "This is a \"test\"".to_owned(),
quoted_text: "This is a \\\"test\\\"".to_owned(),
guard: Some(ArgumentGuard::DoubleQuote) guard: Some(ArgumentGuard::DoubleQuote)
} }
] ]
@ -452,5 +508,11 @@ mod tests {
parse_command(input).map(|c| c.1.to_string()), parse_command(input).map(|c| c.1.to_string()),
Ok(input.to_owned()) Ok(input.to_owned())
); );
let input = "{{{\"Foo \\ Bar\"}}}";
assert_eq!(
parse_command(input).map(|c| c.1.to_string()),
Ok(input.to_owned())
);
} }
} }