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

View File

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

View File

@ -47,7 +47,7 @@ pub fn echo_frame<'gc>(
let all_parts: Vec<String> = stack
.consume::<Variadic<Vec<Value>>>(ctx)?
.into_iter()
.map(|v| format!("{:?}", v))
.map(|v| format!("{}", v.display()))
.collect();
stack.push_front(frame_no);
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::{
branch::alt,
character::complete::{anychar, char, none_of},
combinator::{eof, map, recognize, value},
multi::{many0_count, separated_list0},
sequence::{preceded, separated_pair},
sequence::{delimited, preceded, separated_pair},
IResult,
};
@ -14,9 +14,82 @@ pub struct ParseResult<'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 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<'_> {
@ -26,7 +99,7 @@ impl ParsedCommand<'_> {
match tmp_arguments.pop_front() {
None => return None,
Some(head) => {
let trimmed_cmd = head.trim();
let trimmed_cmd = head.text.trim();
if !trimmed_cmd.is_empty() {
return Some((
trimmed_cmd,
@ -77,39 +150,60 @@ fn parse_parenthetical(input: &str) -> IResult<&str, ()> {
)(input)
}
fn parse_argument(input: &str) -> IResult<&str, ()> {
value(
(),
many0_count(alt((
value((), preceded(char('\\'), anychar)),
value(
fn parse_argument(input: &str) -> IResult<&str, ParsedArgument> {
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(
(),
separated_pair(
char('{'),
parse_parenthetical,
alt((value((), eof), value((), char('}')))),
),
),
value(
(),
separated_pair(
char('"'),
parse_string,
alt((value((), eof), value((), char('"')))),
),
),
value((), none_of(" ;")),
))),
)(input)
many0_count(alt((
value((), preceded(char('\\'), anychar)),
value(
(),
separated_pair(
char('{'),
parse_parenthetical,
alt((value((), eof), value((), char('}')))),
),
),
value(
(),
separated_pair(
char('"'),
parse_string,
alt((value((), eof), value((), char('"')))),
),
),
value((), none_of(" ;")),
))),
)),
|v| ParsedArgument {
text: v,
guard: None,
},
),
))(input)
}
fn parse_command(input: &str) -> IResult<&str, ParsedCommand> {
map(
separated_list0(char(' '), recognize(parse_argument)),
|arguments| ParsedCommand {
map(separated_list0(char(' '), parse_argument), |arguments| {
ParsedCommand {
arguments: arguments.into(),
},
)(input)
}
})(input)
}
pub fn parse_commands(input: &str) -> ParseResult {
@ -128,7 +222,11 @@ mod tests {
parse_commands(""),
ParseResult {
commands: vec![ParsedCommand {
arguments: vec![""].into()
arguments: vec![ParsedArgument {
text: "",
guard: None
}]
.into()
}]
}
);
@ -136,7 +234,11 @@ mod tests {
parse_commands("north"),
ParseResult {
commands: vec![ParsedCommand {
arguments: vec!["north"].into()
arguments: vec![ParsedArgument {
text: "north",
guard: None
}]
.into()
}]
}
);
@ -145,7 +247,17 @@ mod tests {
ParseResult {
commands: vec![ParsedCommand {
// 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 {
commands: vec![
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 {
arguments: vec!["#home"].into()
arguments: vec![ParsedArgument {
text: "#home",
guard: None
}]
.into()
}
]
}
@ -166,7 +292,17 @@ mod tests {
parse_commands("#blah {x = 1 + 2"),
ParseResult {
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}"),
ParseResult {
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\""),
ParseResult {
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}"),
ParseResult {
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![
ParsedCommand {
arguments: vec![
"#blah",
"{x = \"}\"; a = \"{\"; y = {}; z = 1;}",
"{ q = 5 }"
ParsedArgument {
text: "#blah",
guard: None
},
ParsedArgument {
text: "x = \"}\"; a = \"{\"; y = {}; z = 1;",
guard: Some(ArgumentGuard::Paren)
},
ParsedArgument {
text: " q = 5 ",
guard: Some(ArgumentGuard::Paren)
}
]
.into()
},
ParsedCommand {
arguments: vec![""].into()
arguments: vec![ParsedArgument {
text: "",
guard: None
}]
.into()
}
]
}
@ -216,7 +407,21 @@ mod tests {
parse_commands("#blah {\\}\\}\\}} {y = 1}"),
ParseResult {
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\\\"\""),
ParseResult {
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())
);
}
}