From f6486498f637378a41e3a2b4bb7d928818e153f8 Mon Sep 17 00:00:00 2001 From: Condorra Date: Fri, 20 Sep 2024 22:59:00 +1000 Subject: [PATCH] Improve parsing to remove need for debrace everywhere. --- src/command_handler.rs | 6 +- src/lua_engine.rs | 10 +- src/lua_engine/frames.rs | 2 +- src/parsing.rs | 320 +++++++++++++++++++++++++++++++++------ 4 files changed, 282 insertions(+), 56 deletions(-) diff --git a/src/command_handler.rs b/src/command_handler.rs index 10a9ac6..ecbd3e3 100644 --- a/src/command_handler.rs +++ b/src/command_handler.rs @@ -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)) diff --git a/src/lua_engine.rs b/src/lua_engine.rs index 4f2c131..4820dc6 100644 --- a/src/lua_engine.rs +++ b/src/lua_engine.rs @@ -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::>(), ), ); diff --git a/src/lua_engine/frames.rs b/src/lua_engine/frames.rs index 881cd5c..5d423b6 100644 --- a/src/lua_engine/frames.rs +++ b/src/lua_engine/frames.rs @@ -47,7 +47,7 @@ pub fn echo_frame<'gc>( let all_parts: Vec = stack .consume::>>(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()); diff --git a/src/parsing.rs b/src/parsing.rs index 17bf444..12f8e73 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -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>, } -#[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, + 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>, +} + +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()) + ); + } }