Improve parsing to remove need for debrace everywhere.

This commit is contained in:
Condorra 2024-09-20 22:59:00 +10:00
parent f619aa4770
commit f6486498f6
4 changed files with 282 additions and 56 deletions

View File

@ -5,7 +5,7 @@ use itertools::{join, Itertools};
pub fn debrace(inp: &str) -> &str { pub fn debrace(inp: &str) -> &str {
let v = inp.trim(); let v = inp.trim();
if v.starts_with("{") && v.ends_with("}") { if v.starts_with('{') && v.ends_with('}') {
&v[1..(v.len() - 1)] &v[1..(v.len() - 1)]
} else { } else {
v v
@ -26,7 +26,7 @@ fn reentrant_command_handler(
Some((cmd, rest)) => { Some((cmd, rest)) => {
if let ("#", command_rest) = cmd.split_at(1) { if let ("#", command_rest) = cmd.split_at(1) {
if cmd == "##" { if cmd == "##" {
match lua_state.execute(debrace(&join(rest.arguments.iter(), " "))) { match lua_state.execute(&rest.forget_guards().to_string()) {
Ok(()) => (), Ok(()) => (),
Err(msg) => { Err(msg) => {
echo_to_term_frame(globals, term_frame, &format!("{}\r\n", msg)) echo_to_term_frame(globals, term_frame, &format!("{}\r\n", msg))
@ -43,7 +43,7 @@ fn reentrant_command_handler(
); );
} }
} else { } else {
match lua_state.execute_command(command_rest, &rest.arguments) { match lua_state.execute_command(command_rest, &rest) {
Ok(()) => (), Ok(()) => (),
Err(msg) => { Err(msg) => {
echo_to_term_frame(globals, term_frame, &format!("{}\r\n", msg)) echo_to_term_frame(globals, term_frame, &format!("{}\r\n", msg))

View File

@ -7,8 +7,9 @@ use piccolo::{
}; };
use yew::UseStateSetter; use yew::UseStateSetter;
use crate::{id_intern::intern_id, GlobalLayoutCell, GlobalMemoCell, TermFrame}; use crate::{
use std::collections::VecDeque; id_intern::intern_id, parsing::ParsedCommand, GlobalLayoutCell, GlobalMemoCell, TermFrame,
};
pub struct LuaState { pub struct LuaState {
pub interp: Lua, pub interp: Lua,
@ -63,7 +64,7 @@ impl LuaState {
pub fn execute_command( pub fn execute_command(
&mut self, &mut self,
command: &str, command: &str,
arguments: &VecDeque<&str>, arguments: &ParsedCommand<'_>,
) -> Result<(), String> { ) -> Result<(), String> {
self.interp self.interp
.try_enter(|ctx| { .try_enter(|ctx| {
@ -78,8 +79,9 @@ impl LuaState {
command_fn, command_fn,
Variadic( Variadic(
arguments arguments
.arguments
.iter() .iter()
.map(|s| ctx.intern(s.as_bytes()).into()) .map(|s| ctx.intern(s.text.as_bytes()).into())
.collect::<Vec<Value>>(), .collect::<Vec<Value>>(),
), ),
); );

View File

@ -47,7 +47,7 @@ pub fn echo_frame<'gc>(
let all_parts: Vec<String> = stack let all_parts: Vec<String> = stack
.consume::<Variadic<Vec<Value>>>(ctx)? .consume::<Variadic<Vec<Value>>>(ctx)?
.into_iter() .into_iter()
.map(|v| format!("{:?}", v)) .map(|v| format!("{}", v.display()))
.collect(); .collect();
stack.push_front(frame_no); stack.push_front(frame_no);
let message = ctx.intern((all_parts.join(" ") + "\r\n").as_bytes()); let message = ctx.intern((all_parts.join(" ") + "\r\n").as_bytes());

View File

@ -1,11 +1,11 @@
use std::collections::VecDeque; use std::{collections::VecDeque, fmt::Display};
use nom::{ use nom::{
branch::alt, branch::alt,
character::complete::{anychar, char, none_of}, character::complete::{anychar, char, none_of},
combinator::{eof, map, recognize, value}, combinator::{eof, map, recognize, value},
multi::{many0_count, separated_list0}, multi::{many0_count, separated_list0},
sequence::{preceded, separated_pair}, sequence::{delimited, preceded, separated_pair},
IResult, IResult,
}; };
@ -14,9 +14,82 @@ pub struct ParseResult<'l> {
pub commands: Vec<ParsedCommand<'l>>, pub commands: Vec<ParsedCommand<'l>>,
} }
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug, Clone)]
pub enum ArgumentGuard {
Paren, // {}
DoubleQuote, // ""
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ParsedArgument<'l> {
pub guard: Option<ArgumentGuard>,
pub text: &'l str,
}
impl<'l> ParsedArgument<'l> {
pub fn forget_guard(&self) -> Self {
Self {
guard: None,
..self.clone()
}
}
}
impl<'a> Display for ParsedArgument<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.guard {
None => {}
Some(ArgumentGuard::Paren) => {
f.write_str("{")?;
}
Some(ArgumentGuard::DoubleQuote) => {
f.write_str("\"")?;
}
}
f.write_str(self.text)?;
match self.guard {
None => {}
Some(ArgumentGuard::Paren) => {
f.write_str("}")?;
}
Some(ArgumentGuard::DoubleQuote) => {
f.write_str("\"")?;
}
}
Ok(())
}
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ParsedCommand<'l> { pub struct ParsedCommand<'l> {
pub arguments: VecDeque<&'l str>, pub arguments: VecDeque<ParsedArgument<'l>>,
}
impl<'l> ParsedCommand<'l> {
pub fn forget_guards(&self) -> Self {
Self {
arguments: self.arguments.iter().map(|v| v.forget_guard()).collect(),
..self.clone()
}
}
}
impl<'a> Display for ParsedCommand<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut it = self.arguments.iter();
match it.next() {
None => {}
Some(head) => {
head.fmt(f)?;
for v in it {
f.write_str(" ")?;
v.fmt(f)?;
}
}
}
Ok(())
}
} }
impl ParsedCommand<'_> { impl ParsedCommand<'_> {
@ -26,7 +99,7 @@ impl ParsedCommand<'_> {
match tmp_arguments.pop_front() { match tmp_arguments.pop_front() {
None => return None, None => return None,
Some(head) => { Some(head) => {
let trimmed_cmd = head.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,
@ -77,8 +150,24 @@ fn parse_parenthetical(input: &str) -> IResult<&str, ()> {
)(input) )(input)
} }
fn parse_argument(input: &str) -> IResult<&str, ()> { fn parse_argument(input: &str) -> IResult<&str, ParsedArgument> {
value( alt((
map(
delimited(char('{'), recognize(parse_parenthetical), char('}')),
|v| ParsedArgument {
text: v,
guard: Some(ArgumentGuard::Paren),
},
),
map(
delimited(char('"'), recognize(parse_string), char('"')),
|v| ParsedArgument {
text: v,
guard: Some(ArgumentGuard::DoubleQuote),
},
),
map(
recognize(value(
(), (),
many0_count(alt(( many0_count(alt((
value((), preceded(char('\\'), anychar)), value((), preceded(char('\\'), anychar)),
@ -100,16 +189,21 @@ fn parse_argument(input: &str) -> IResult<&str, ()> {
), ),
value((), none_of(" ;")), value((), none_of(" ;")),
))), ))),
)(input) )),
|v| ParsedArgument {
text: v,
guard: None,
},
),
))(input)
} }
fn parse_command(input: &str) -> IResult<&str, ParsedCommand> { fn parse_command(input: &str) -> IResult<&str, ParsedCommand> {
map( map(separated_list0(char(' '), parse_argument), |arguments| {
separated_list0(char(' '), recognize(parse_argument)), ParsedCommand {
|arguments| ParsedCommand {
arguments: arguments.into(), arguments: arguments.into(),
}, }
)(input) })(input)
} }
pub fn parse_commands(input: &str) -> ParseResult { pub fn parse_commands(input: &str) -> ParseResult {
@ -128,7 +222,11 @@ mod tests {
parse_commands(""), parse_commands(""),
ParseResult { ParseResult {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec![""].into() arguments: vec![ParsedArgument {
text: "",
guard: None
}]
.into()
}] }]
} }
); );
@ -136,7 +234,11 @@ mod tests {
parse_commands("north"), parse_commands("north"),
ParseResult { ParseResult {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec!["north"].into() arguments: vec![ParsedArgument {
text: "north",
guard: None
}]
.into()
}] }]
} }
); );
@ -145,7 +247,17 @@ mod tests {
ParseResult { ParseResult {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
// This is deliberate, ensures we can reconstruct the input. // This is deliberate, ensures we can reconstruct the input.
arguments: vec!["north", ""].into() arguments: vec![
ParsedArgument {
text: "north",
guard: None
},
ParsedArgument {
text: "",
guard: None
}
]
.into()
}] }]
} }
); );
@ -154,10 +266,24 @@ mod tests {
ParseResult { ParseResult {
commands: vec![ commands: vec![
ParsedCommand { ParsedCommand {
arguments: vec!["#blah", "{x = 1 + 2; y = 3}"].into() arguments: vec![
ParsedArgument {
text: "#blah",
guard: None
},
ParsedArgument {
text: "x = 1 + 2; y = 3",
guard: Some(ArgumentGuard::Paren)
}
]
.into()
}, },
ParsedCommand { ParsedCommand {
arguments: vec!["#home"].into() arguments: vec![ParsedArgument {
text: "#home",
guard: None
}]
.into()
} }
] ]
} }
@ -166,7 +292,17 @@ mod tests {
parse_commands("#blah {x = 1 + 2"), parse_commands("#blah {x = 1 + 2"),
ParseResult { ParseResult {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec!["#blah", "{x = 1 + 2"].into() arguments: vec![
ParsedArgument {
text: "#blah",
guard: None
},
ParsedArgument {
text: "{x = 1 + 2",
guard: None
}
]
.into()
},] },]
} }
); );
@ -174,7 +310,21 @@ mod tests {
parse_commands("#blah {x = 1} {y = 1}"), parse_commands("#blah {x = 1} {y = 1}"),
ParseResult { ParseResult {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec!["#blah", "{x = 1}", "{y = 1}"].into() arguments: vec![
ParsedArgument {
text: "#blah",
guard: None
},
ParsedArgument {
text: "x = 1",
guard: Some(ArgumentGuard::Paren)
},
ParsedArgument {
text: "y = 1",
guard: Some(ArgumentGuard::Paren)
}
]
.into()
}] }]
} }
); );
@ -182,7 +332,21 @@ mod tests {
parse_commands("#blah \"hello\" \"world\""), parse_commands("#blah \"hello\" \"world\""),
ParseResult { ParseResult {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec!["#blah", "\"hello\"", "\"world\""].into() arguments: vec![
ParsedArgument {
text: "#blah",
guard: None
},
ParsedArgument {
text: "hello",
guard: Some(ArgumentGuard::DoubleQuote)
},
ParsedArgument {
text: "world",
guard: Some(ArgumentGuard::DoubleQuote)
},
]
.into()
}] }]
} }
); );
@ -190,7 +354,21 @@ mod tests {
parse_commands("#blah {x = \"}\"} {y = 1}"), parse_commands("#blah {x = \"}\"} {y = 1}"),
ParseResult { ParseResult {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec!["#blah", "{x = \"}\"}", "{y = 1}"].into() arguments: vec![
ParsedArgument {
text: "#blah",
guard: None
},
ParsedArgument {
text: "x = \"}\"",
guard: Some(ArgumentGuard::Paren)
},
ParsedArgument {
text: "y = 1",
guard: Some(ArgumentGuard::Paren)
}
]
.into()
}] }]
} }
); );
@ -200,14 +378,27 @@ mod tests {
commands: vec![ commands: vec![
ParsedCommand { ParsedCommand {
arguments: vec![ arguments: vec![
"#blah", ParsedArgument {
"{x = \"}\"; a = \"{\"; y = {}; z = 1;}", text: "#blah",
"{ q = 5 }" guard: None
},
ParsedArgument {
text: "x = \"}\"; a = \"{\"; y = {}; z = 1;",
guard: Some(ArgumentGuard::Paren)
},
ParsedArgument {
text: " q = 5 ",
guard: Some(ArgumentGuard::Paren)
}
] ]
.into() .into()
}, },
ParsedCommand { ParsedCommand {
arguments: vec![""].into() arguments: vec![ParsedArgument {
text: "",
guard: None
}]
.into()
} }
] ]
} }
@ -216,7 +407,21 @@ mod tests {
parse_commands("#blah {\\}\\}\\}} {y = 1}"), parse_commands("#blah {\\}\\}\\}} {y = 1}"),
ParseResult { ParseResult {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec!["#blah", "{\\}\\}\\}}", "{y = 1}"].into() arguments: vec![
ParsedArgument {
text: "#blah",
guard: None
},
ParsedArgument {
text: "\\}\\}\\}",
guard: Some(ArgumentGuard::Paren)
},
ParsedArgument {
text: "y = 1",
guard: Some(ArgumentGuard::Paren)
}
]
.into()
}] }]
} }
); );
@ -224,9 +429,28 @@ mod tests {
parse_commands("#blah \"This is a \\\"test\\\"\""), parse_commands("#blah \"This is a \\\"test\\\"\""),
ParseResult { ParseResult {
commands: vec![ParsedCommand { commands: vec![ParsedCommand {
arguments: vec!["#blah", "\"This is a \\\"test\\\"\""].into() arguments: vec![
ParsedArgument {
text: "#blah",
guard: None
},
ParsedArgument {
text: "This is a \\\"test\\\"",
guard: Some(ArgumentGuard::DoubleQuote)
}
]
.into()
}] }]
} }
); );
} }
#[test]
fn test_parse_roundtrip() {
let input = "#hello {to the} \"world\" I say! ";
assert_eq!(
parse_command(input).map(|c| c.1.to_string()),
Ok(input.to_owned())
);
}
} }