From 7f3b4d80a7bda13460c376cca5bac97969a71d68 Mon Sep 17 00:00:00 2001 From: Seth Pellegrino Date: Tue, 11 Apr 2023 12:13:43 -0700 Subject: [PATCH 1/4] wip: squash of initial prototypes A combination of: wip: doesn't even build lol wip: everything compiles now, so it must work --- src/ansi.rs | 819 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 796 insertions(+), 23 deletions(-) diff --git a/src/ansi.rs b/src/ansi.rs index 6b0d9e9..704f6c9 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -57,7 +57,7 @@ use alloc::{ vec, vec::Vec, }; -use core::{fmt::Debug, str::FromStr}; +use core::{fmt::Debug, marker::PhantomData, num::ParseIntError, str::FromStr}; use esp_println::println; use nom::{IResult, Parser}; @@ -129,25 +129,15 @@ type TextOpResult<'a> = IResult<&'a str, TextOp>; trait StrParseFnMut<'a, O> = FnMut(&'a str) -> IResult<&'a str, O>; -fn start_with_char<'a, O, P: StrParser<'a, O>>( - start: char, - mut parser: P, -) -> impl StrParseFnMut<'a, O> { - move |input: &'a str| { - nom::sequence::preceded(nom::character::streaming::char(start), |x: &'a str| { - parser.parse(x) - }) - .parse(input) - } +fn start_with_char<'a, O, P: StrParser<'a, O>>(start: char, parser: P) -> impl StrParser<'a, O> { + nom::sequence::preceded(nom::character::streaming::char(start), parser) } /// Recognize ESC, and then parses via the P parser. If P fails, this parser will return /// the Failure variant (by using nom `cut`). If the this parser does not recognize ESC /// it will return with the nom Error variant. -fn start_with_esc<'a, O, P: StrParser<'a, O>>(mut parser: P) -> impl StrParseFnMut<'a, O> { - move |input: &'a str| { - start_with_char(ESC, nom::combinator::cut(|x: &'a str| parser.parse(x)))(input) - } +fn start_with_esc<'a, O, P: StrParser<'a, O>>(parser: P) -> impl StrParser<'a, O> { + start_with_char(ESC, nom::combinator::cut(parser)) } // This will parse "...P... " for some char ending and parsed sequence P @@ -206,13 +196,15 @@ where ::Err: Debug, { move |input: &str| { - let params = nom::sequence::separated_pair( - nom::character::streaming::digit1, - nom::character::streaming::char(';'), - nom::character::streaming::digit1, - ); - sequence_with_ending(params, ending)(input) - .map(|(rest, (a, b))| (rest, (N::from_str(a).unwrap(), N::from_str(b).unwrap()))) + nom::sequence::terminated( + nom::sequence::separated_pair( + nom::character::streaming::digit1, + nom::character::streaming::char(';'), + nom::character::streaming::digit1, + ), + nom::character::streaming::char(ending), + )(input) + .map(|(rest, (a, b))| (rest, (N::from_str(a).unwrap(), N::from_str(b).unwrap()))) } } @@ -476,6 +468,786 @@ fn set_text_mode(input: &str) -> OpResult { .map(|(rest, found)| (rest, Op::TextOp(found))) } +// struct Seq<'a, 'b, P, O>(&'a [P], &'b dyn FnOnce((&'a str, &'a str)) -> O) +// where +// P: StrParser<'a, O>; + +// fn parse2<'a>(input: &'a str) -> OpResult { +// let q = [ +// Seq(&[ESC, '7'], &|(rest, _)| (rest, Op::SaveCursorPos)), +// Seq(&[ESC, '8'], &|(rest, _)| (rest, Op::RestoreCursorPos)), +// Seq(&[ESC, 'H'], &|(rest, _)| { +// (rest, Op::MoveCursorAbs { x: 0, y: 0 }) +// }), +// Seq(&[ESC, arg::, ';', arg::, 'H'], &|( +// rest, +// _, +// )| { +// ( +// rest, +// Op::MoveCursorAbs { +// x: b.saturating_sub(1), +// y: a.saturating_sub(1), +// }, +// ) +// }), +// ]; + +// todo!() +// } + +fn parse4<'a>(input: &'a str) -> OpResult { + use nom::Err; + // struct Seq(&'static [P1], P2, F); + // struct Seq(char, char, F) + // where + // F: FnOnce(char) -> Op; + + fn seq<'a>(s: &'a [char], f: &'a impl Fn(char) -> Op) -> impl StrParser<'a, Op> { + let (req, rest) = match s { + &[req, ref rest @ ..] if !rest.is_empty() => (req, rest), + _ => panic!("bad sequence, needed at least two characters (i.e. [ESC, X] for some X)"), + }; + + nom::combinator::map(Chars { req, rest }, f) + } + + // parse! { + // [ESC, ']', 'H'] => Op::MoveCursorAbs + // [ESC, ']', (a,b) @ params::<(usize,usize)>, 'H'] => |a, b| Op::MoveCursorAbs + // } + + fn seqz<'a, O>( + (s, p): (&'a [char], impl StrParser<'a, O>), + f: &'a impl Fn(O) -> Op, + ) -> impl StrParser<'a, Op> { + let (req, rest) = match s { + &[req, ref rest @ ..] if !rest.is_empty() => (req, rest), + _ => panic!("bad sequence, needed at least two characters (i.e. [ESC, X] for some X)"), + }; + + nom::sequence::preceded(Chars { req, rest }, nom::combinator::map(p, f)) + } + + struct Chars<'a> { + req: char, + rest: &'a [char], + } + impl<'a> nom::Parser<&'a str, char, nom::error::Error<&'a str>> for Chars<'a> { + fn parse(&mut self, input: &'a str) -> IResult<&'a str, char, nom::error::Error<&'a str>> { + let (input, ch) = + nom::combinator::cut(nom::character::streaming::char(self.req))(input)?; + self.rest.iter().try_fold((input, ch), |(input, _), &ch| { + nom::character::streaming::char(ch)(input) + }) + } + } + + trait Params<'a>: Sized { + type O; + type Err; + fn recognize(input: &'a str) -> IResult<&'a str, Self::O>; + fn map(o: Self::O) -> Result; + } + impl<'a> Params<'a> for (usize, usize) { + type O = (&'a str, &'a str); + type Err = ParseIntError; + + fn recognize(input: &'a str) -> IResult<&'a str, (&'a str, &'a str)> { + nom::sequence::separated_pair( + nom::character::streaming::digit1, + nom::character::streaming::char(';'), + nom::character::streaming::digit1, + )(input) + } + + fn map((a, b): (&'a str, &'a str)) -> Result { + Ok((usize::from_str(a)?, usize::from_str(b)?)) + } + } + // impl<'a> Params<'a> for isize { + + // } + + fn param<'a, P: Params<'a>, Any>(tail: impl StrParser<'a, Any>) -> impl StrParser<'a, P> { + nom::sequence::terminated(nom::combinator::map_res(P::recognize, P::map), tail) + } + + fn opt_param<'a, P: Params<'a>, Any>( + tail: impl StrParser<'a, Any>, + default: P, + ) -> impl StrParser<'a, P> { + move |input| todo!() + } + + use nom::branch::alt; + use nom::character::streaming::char as ch; + + // enum Foo { + // Ch(char), + // P(StrParser) + // } + // TODO: how to make this transparent again to the optimizer? + for opt in [ + &mut seq(&[ESC, '7'], &|_| Op::SaveCursorPos), + &mut seq(&[ESC, '8'], &|_| Op::RestoreCursorPos), + &mut seq(&[ESC, '[', 'H'], &|_| Op::MoveCursorAbs { x: 0, y: 0 }), + &mut seqz( + ( + &[ESC, '['], + param::<(usize, usize), _>(alt((ch('H'), ch('f')))), + ), + &|(a, b)| Op::MoveCursorAbs { + x: b.saturating_sub(1), + y: a.saturating_sub(1), + }, + ), + ] as [&mut dyn StrParser<'a, Op>; 4] + { + let res = opt.parse(input); + if let Err(Err::Error(_)) = res { + continue; + } + return res; + } + + Err(nom::Err::Incomplete(nom::Needed::Unknown)) +} + +// fn parse7(input: &str) -> OpResult { +// static SEQUENCES = [ +// "ESC [ ; ? H", +// ]; + +// fn make_parser(seq: &[&str]) -> impl FnMut() -> GenericSequence { + +// } + +// // SEQUENCES +// } + +pub fn parse6(input: &str) -> Result { + // ESC [ ;? H + + // "ESC 7" -> Op::SaveCursorPos, + // "ESC 8" + // "ESC [ ;? H" -> ([]) + + // generic parser: + // s => ESC [ `[4, 6]` H + // enum GenSeq<'a> { + // Lit(char) + // Param() + // } + // let foo = [""; 6]; + + // let gen_parse = |input| start_with_esc(nom::multi::fold_many_m_n(0, 6, parse, init, fold)); + + fn gen_parse<'a, 'str, const M: usize>( + input: &'str str, + q: &'a mut [&'str str; M], + ) -> IResult<&'str str, &'a [&'str str]> { + let (input, start) = nom::combinator::cut(nom::combinator::recognize( + nom::character::streaming::one_of("\u{1b}\u{9b}"), + )) + .parse(input)?; + + q[0] = start; + // c0 + match nom::combinator::cond( + start == "\x1b", + nom::sequence::tuple(( + nom::combinator::opt(nom::sequence::tuple(( + nom::combinator::recognize(nom::character::streaming::char('\x21')), + nom::combinator::recognize(nom::character::streaming::char('\x40')), + ))), + nom::combinator::recognize(nom::character::streaming::satisfy(|ch| { + '\x00' < ch && ch < '\x1f' + // TODO: what set do these belong to ? any? + || ch == '7' || ch == '8' + })), + )), + ) + .parse(input) + { + // collapse the two intro sequences to one + Ok((rest, Some((Some(_), n)))) | Ok((rest, Some((None, n)))) => { + q[1] = n; + return Ok((rest, &q[..=1])); + } + Err(err @ nom::Err::Failure(_)) | Err(err @ nom::Err::Incomplete(_)) => { + return Err(err) + } + // We didn't match a c0 sequence, nothing to return yet + Err(nom::Err::Error(_)) | Ok((_, None)) => {} + }; + + // TODO: c1 set + + // control sequences + let input = if start == "\x1b" { + let (input, _) = nom::character::streaming::char('[').parse(input)?; + // map everything to this particular CSI + q[0] = "\u{9b}"; + input + } else { + input + }; + + // CSI P ... P I ... I F + // + // where + // P ... P are Parameter Bytes, which, if present, consist of bit combinations from 03/00 (\x30) to 03/15 (\x3f) + // I ... I are Intermediate Bytes, which, if present, consist of bit combinations from 02/00 (\x20) to 02/15 (\x2f) + // F is the Final Byte; it consists of a bit combination from 04/00 (\x40) to 07/14 (\x7e) + // + // NB: the ECMA-43/48 standards use `nibble/nibble`, in decimal, to represent a 7- or 8-bit number. + // For example, `01/02` can be either 7- or 8-bit in their notation, and is equivalent to a more + // familiar hex notation `0x12`. Similarly, `15/15` (which is necessarily 8-bit) is equivalent to `0xff`. + // + // cf. https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf#page=24 + // and https://www.ecma-international.org/wp-content/uploads/ECMA-43_3rd_edition_december_1991.pdf#page=14 + + let params = nom::branch::alt(( + nom::bytes::streaming::is_a("0123456789:;<=>?"), + nom::combinator::success(""), + )); + let intermediate = nom::branch::alt(( + nom::bytes::streaming::is_a(concat!(" ", "!\"#$%&'()*+,/")), + nom::combinator::success(""), + )); + let fin = nom::combinator::recognize(nom::character::streaming::satisfy(|ch| { + '\x40' < ch && ch < '\x7e' + })); + + let (rest, ((params, intermediate), fin)) = + params.and(intermediate).and(fin).parse(input)?; + + q[1] = params; + q[2] = intermediate; + q[3] = fin; + + Ok((rest, &q[..=3])) + } + + // TODO: other kinds of non-digit-y things? + fn sep<'a, 'str, const M: usize>( + input: &'str str, + q: &'a mut [&'str str; M], + ) -> Result<&'a [&'str str], nom::Err>> { + let (_, i) = nom::combinator::all_consuming(nom::multi::fold_many_m_n( + 0, + M, + nom::sequence::terminated( + nom::bytes::complete::take_while1(nom::AsChar::is_dec_digit), + // TODO: this is wrong: it's not optional, unless it's in the last position + nom::combinator::opt(nom::character::complete::char(';')), + ), + || 0, + |i, p| { + q[i] = p; + i + 1 + }, + )) + .parse(input)?; + + Ok(&q[..i]) + } + + const ESC: &str = "\u{1b}"; + const CSI: &str = "\u{9b}"; + + let z = match *gen_parse(input, &mut [""; 4]) + .map_err(|e| alloc::format!("{:?}", e))? + .1 + { + [ESC, "7"] => Op::SaveCursorPos, + [ESC, "8"] => Op::RestoreCursorPos, + + [CSI, params, "", "H"] | [CSI, params, "", "f"] => { + esp_println::println!("{params:?}"); + match *sep(params, &mut [""; 2]).map_err(|e| alloc::format!("{:?}", e))? { + [] => Op::MoveCursorAbs { x: 0, y: 0 }, + [a, b] => Op::MoveCursorAbs { + x: usize::from_str(b) + .map_err(|e| alloc::format!("{:?}", e))? + .saturating_sub(1), + y: usize::from_str(a) + .map_err(|e| alloc::format!("{:?}", e))? + .saturating_sub(1), + }, + _ => { + return Err(alloc::format!( + "Bad number of params got {:?} wanted 0 or 2", + params + )) + } + } + } + + _ => todo!(), + }; + + Ok(z) +} + +fn parse3(input: &str) -> OpResult { + use self::start_with_esc as esc; + use nom::branch::alt; + use nom::character::streaming::char as ch; + use nom::combinator::map; + + alt(( + alt(( + esc(map(ch('7'), |_| Op::SaveCursorPos)), + esc(map(ch('8'), |_| Op::RestoreCursorPos)), + )), + esc(nom::sequence::preceded( + ch('['), + map(ch('H'), |_| Op::MoveCursorAbs { x: 0, y: 0 }), + )), + esc(nom::sequence::preceded( + ch('['), + map(dual_int_parameter_sequence::('H'), |(a, b)| { + Op::MoveCursorAbs { + x: b.saturating_sub(1), + y: a.saturating_sub(1), + } + }), + )), + esc(nom::sequence::preceded( + ch('['), + map(dual_int_parameter_sequence::('f'), |(a, b)| { + Op::MoveCursorAbs { + x: b.saturating_sub(1), + y: a.saturating_sub(1), + } + }), + )), + ))(input) +} + +fn parse_all<'a>(input: &'a str) -> OpResult { + { + let mut parser = nom::branch::alt(( + |input| { + nom::bytes::streaming::tag("\u{1B}7")(input) + .map(|(rest, _)| (rest, Op::SaveCursorPos)) + }, + |input| { + nom::bytes::streaming::tag("\u{1B}8")(input) + .map(|(rest, _)| (rest, Op::RestoreCursorPos)) + }, + { + let mut parser = nom::branch::alt(( + |input| { + nom::character::streaming::char('H')(input) + .map(|(rest, _)| (rest, Op::MoveCursorAbs { x: 0, y: 0 })) + }, + move |input| { + nom::branch::alt(( + move |input: &'a str| { + let params = nom::sequence::separated_pair( + nom::character::streaming::digit1, + nom::character::streaming::char(';'), + nom::character::streaming::digit1, + ); + { + let mut parser = params; + move |input: &'a str| { + nom::sequence::terminated( + |x: &'a str| parser.parse(x), + nom::character::streaming::char('H'), + )(input) + } + } + .parse(input) + .map(move |(rest, (a, b))| { + ( + rest, + ( + ::from_str(a).unwrap(), + ::from_str(b).unwrap(), + ), + ) + }) + }, + move |input: &'a str| { + let params = nom::sequence::separated_pair( + nom::character::streaming::digit1, + nom::character::streaming::char(';'), + nom::character::streaming::digit1, + ); + { + let mut parser = params; + move |input: &'a str| { + nom::sequence::terminated( + |x: &'a str| parser.parse(x), + nom::character::streaming::char('f'), + )(input) + } + } + .parse(input) + .map(|(rest, (a, b))| { + ( + rest, + ( + ::from_str(a).unwrap(), + ::from_str(b).unwrap(), + ), + ) + }) + }, + )) + .parse(input) + .map(|(rest, (a, b))| { + ( + rest, + Op::MoveCursorAbs { + x: b.saturating_sub(1), + y: a.saturating_sub(1), + }, + ) + }) + }, + |input| { + { + move |input: &'a str| { + { + { + let mut parser = + nom::combinator::opt(nom::character::streaming::digit1); + move |input: &'a str| { + nom::sequence::terminated( + |x: &'a str| parser.parse(x), + nom::character::streaming::char('A'), + ) + .parse(input) + } + } + .parse(input) + .map(|(rest, n)| { + ( + rest, + match n { + None => 1, + Some(n) => ::from_str(n).unwrap(), + }, + ) + }) + } + .map(|(rest, n)| (rest, Op::MoveCursorDelta { dx: 0, dy: -n })) + } + } + .parse(input) + }, + |input| { + { + move |input: &'a str| { + { + { + let mut parser = + nom::combinator::opt(nom::character::streaming::digit1); + move |input: &'a str| { + nom::sequence::terminated( + |x: &'a str| parser.parse(x), + nom::character::streaming::char('B'), + )(input) + } + } + .parse(input) + .map(|(rest, n)| { + ( + rest, + match n { + None => 1, + Some(n) => ::from_str(n).unwrap(), + }, + ) + }) + } + .map(|(rest, n)| (rest, Op::MoveCursorDelta { dx: 0, dy: n })) + } + } + .parse(input) + }, + |input| { + { + move |input: &'a str| { + { + sequence_with_ending( + nom::combinator::opt(nom::character::streaming::digit1), + 'D', + ) + .parse(input) + .map(|(rest, n)| { + ( + rest, + match n { + None => 1, + Some(n) => ::from_str(n).unwrap(), + }, + ) + }) + } + .map(|(rest, n)| (rest, Op::MoveCursorDelta { dx: -n, dy: 0 })) + } + } + .parse(input) + }, + |input| { + { + move |input: &'a str| { + { + { + let mut parser = + nom::combinator::opt(nom::character::streaming::digit1); + move |input: &'a str| { + nom::sequence::terminated( + |x: &'a str| parser.parse(x), + nom::character::streaming::char('C'), + ) + .parse(input) + } + } + .parse(input) + .map(|(rest, n)| { + ( + rest, + match n { + None => 1, + Some(n) => ::from_str(n).unwrap(), + }, + ) + }) + } + .map(|(rest, n)| (rest, Op::MoveCursorDelta { dx: n, dy: 0 })) + } + } + .parse(input) + }, + |input| { + { + move |input: &'a str| { + { + sequence_with_ending( + nom::combinator::opt(nom::character::streaming::digit1), + 'G', + ) + .parse(input) + .map(|(rest, n)| { + ( + rest, + match n { + None => 0, + Some(n) => ::from_str(n).unwrap(), + }, + ) + }) + } + .map(|(rest, n)| { + ( + rest, + Op::MoveCursorAbsCol { + x: n.saturating_sub(1), + }, + ) + }) + } + } + .parse(input) + }, + |input| { + { + move |input: &'a str| { + { + { + sequence_with_ending( + nom::combinator::opt(nom::character::streaming::digit1), + 'E', + ) + .parse(input) + .map( + |(rest, n)| { + ( + rest, + match n { + None => 1, + Some(n) => ::from_str(n).unwrap(), + }, + ) + }, + ) + } + .map(|(rest, n)| { + (rest, Op::MoveCursorBeginningAndLine { dy: n }) + }) + } + } + } + .parse(input) + }, + |input| { + { + move |input: &'a str| { + { + { + let mut parser = + nom::combinator::opt(nom::character::streaming::digit1); + move |input: &'a str| { + nom::sequence::terminated( + |x: &'a str| parser.parse(x), + nom::character::streaming::char('F'), + ) + .parse(input) + } + } + .parse(input) + .map(|(rest, n)| { + ( + rest, + match n { + None => 1, + Some(n) => ::from_str(n).unwrap(), + }, + ) + }) + } + .map(|(rest, n)| (rest, Op::MoveCursorBeginningAndLine { dy: -n })) + } + } + .parse(input) + }, + |input| { + nom::bytes::streaming::tag("3~") + .parse(input) + .map(|(rest, _)| (rest, Op::InPlaceDelete)) + }, + |input| { + { + let mut parser = + nom::combinator::opt(nom::character::streaming::one_of("012")); + move |input: &'a str| { + nom::sequence::terminated( + |x: &'a str| parser.parse(x), + nom::character::streaming::char('J'), + ) + .parse(input) + } + } + .parse(input) + .map(|(rest, arg)| { + ( + rest, + match arg { + None => Op::EraseScreen(EraseMode::FromCursor), + Some('0') => Op::EraseScreen(EraseMode::FromCursor), + Some('1') => Op::EraseScreen(EraseMode::ToCursor), + Some('2') => Op::EraseScreen(EraseMode::All), + Some(_) => unreachable!(), + }, + ) + }) + }, + |input| { + { + let mut parser = + nom::combinator::opt(nom::character::streaming::one_of("012")); + move |input: &'a str| { + nom::sequence::terminated( + |x: &'a str| parser.parse(x), + nom::character::streaming::char('K'), + ) + .parse(input) + } + } + .parse(input) + .map(|(rest, arg)| { + ( + rest, + match arg { + None => Op::EraseLine(EraseMode::FromCursor), + Some('0') => Op::EraseLine(EraseMode::FromCursor), + Some('1') => Op::EraseLine(EraseMode::ToCursor), + Some('2') => Op::EraseLine(EraseMode::All), + Some(_) => unreachable!(), + }, + ) + }) + }, + |input| { + { + let mut parser = nom::character::streaming::char('6'); + move |input: &'a str| { + nom::sequence::terminated( + |x: &'a str| parser.parse(x), + nom::character::streaming::char('n'), + ) + .parse(input) + } + } + .parse(input) + .map(|(rest, _)| (rest, Op::RequstCursorPos)) + }, + |input| { + nom::sequence::terminated( + nom::multi::separated_list0( + nom::character::streaming::char(';'), + nom::branch::alt(( + set_basic_color_atom, + set_bg_256_color_atom, + set_fg_256_color_atom, + set_text_mode_atom, + )), + ), + nom::character::streaming::char('m'), + ) + .parse(input) + .map(|(rest, found)| (rest, Op::TextOp(found))) + }, + |input: &'a str| { + nom::sequence::tuple(( + nom::character::streaming::char('?'), + nom::character::streaming::digit0, + nom::character::streaming::char('h'), + )) + .parse(input) + .map(|(rest, (_, b, _))| (rest, Op::DecPrivateSet(b.to_owned()))) + }, + |input: &'a str| { + nom::sequence::tuple(( + nom::character::streaming::char('?'), + nom::character::streaming::digit0, + nom::character::streaming::char('l'), + )) + .parse(input) + .map(|(rest, (_, b, _))| (rest, Op::DecPrivateReset(b.to_owned()))) + }, + )); + move |input: &'a str| { + nom::sequence::preceded(nom::character::streaming::char('['), |x: &'a str| { + parser.parse(x) + }) + .parse(input) + } + }, + )); + move |input: &'a str| { + { + let mut parser = nom::combinator::cut(|x: &'a str| parser.parse(x)); + move |input: &'a str| { + nom::sequence::preceded(nom::character::streaming::char(ESC), |x: &'a str| { + parser.parse(x) + }) + .parse(input) + } + } + .parse(input) + } + } + .parse(input) +} + fn parse(input: &str) -> OpResult { start_with_esc(nom::branch::alt(( save_cursor_position, @@ -504,7 +1276,8 @@ fn parse(input: &str) -> OpResult { reset_private_sequence, )), ), - )))(input) + ))) + .parse(input) } pub enum OpChar { From bd8243586bbfafd648e49b6252f55bcfa1200e0f Mon Sep 17 00:00:00 2001 From: Seth Pellegrino Date: Thu, 13 Apr 2023 12:02:12 -0700 Subject: [PATCH 2/4] wip: parse3 example --- src/ansi.rs | 126 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 102 insertions(+), 24 deletions(-) diff --git a/src/ansi.rs b/src/ansi.rs index 704f6c9..e41d428 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -573,11 +573,17 @@ fn parse4<'a>(input: &'a str) -> OpResult { nom::sequence::terminated(nom::combinator::map_res(P::recognize, P::map), tail) } - fn opt_param<'a, P: Params<'a>, Any>( + fn opt_param<'a, P: Params<'a> + Copy, Any>( tail: impl StrParser<'a, Any>, default: P, ) -> impl StrParser<'a, P> { - move |input| todo!() + nom::sequence::terminated( + nom::combinator::map_res(nom::combinator::opt(P::recognize), move |opt| match opt { + None => Ok(default), + Some(o) => P::map(o), + }), + tail, + ) } use nom::branch::alt; @@ -717,7 +723,7 @@ pub fn parse6(input: &str) -> Result { nom::combinator::success(""), )); let fin = nom::combinator::recognize(nom::character::streaming::satisfy(|ch| { - '\x40' < ch && ch < '\x7e' + ('\x40'..='\x7e').contains(&ch) })); let (rest, ((params, intermediate), fin)) = @@ -792,38 +798,110 @@ pub fn parse6(input: &str) -> Result { } fn parse3(input: &str) -> OpResult { - use self::start_with_esc as esc; use nom::branch::alt; use nom::character::streaming::char as ch; - use nom::combinator::map; + use nom::combinator::{cut, fail}; + use nom::sequence::terminated as term; + + // can't use trait aliases in the return type, see: https://github.com/rust-lang/rust-analyzer/issues/13410 + fn esc<'a, P: StrParser<'a, O>, O>( + parser: P, + ) -> impl nom::Parser<&'a str, O, nom::error::Error<&'a str>> { + nom::sequence::preceded(ch('\x1b'), parser) + } + + // can't use trait aliases in the return type, see: https://github.com/rust-lang/rust-analyzer/issues/13410 + fn csi<'a, P: StrParser<'a, O>, O>( + parser: P, + ) -> impl nom::Parser<&'a str, O, nom::error::Error<&'a str>> { + nom::sequence::preceded( + alt(( + nom::combinator::recognize(ch('\u{9b}')), + nom::combinator::recognize(nom::bytes::streaming::tag("\x1b[")), + )), + parser, + ) + } + + trait Params<'a>: Sized { + type O; + type Err; + fn recognize(input: &'a str) -> IResult<&'a str, Self::O>; + fn map(o: Self::O) -> Result; + } + impl<'a> Params<'a> for (usize, usize) { + type O = (&'a str, &'a str); + type Err = ParseIntError; + + fn recognize(input: &'a str) -> IResult<&'a str, (&'a str, &'a str)> { + nom::sequence::separated_pair( + nom::character::streaming::digit1, + nom::character::streaming::char(';'), + nom::character::streaming::digit1, + )(input) + } + + fn map((a, b): (&'a str, &'a str)) -> Result { + Ok((usize::from_str(a)?, usize::from_str(b)?)) + } + } + + fn param<'a, P: Params<'a>>() -> impl StrParser<'a, P> { + nom::combinator::map_res(P::recognize, P::map) + } + + fn final_char>(i: I) -> IResult + where + I: nom::Slice> + nom::InputIter, + ::Item: nom::AsChar, + { + nom::character::streaming::satisfy(|ch| ('\x40'..='\x7e').contains(&ch)).parse(i) + } + + fn non_final>(i: I) -> IResult + where + I: nom::Slice> + + nom::Slice> + + nom::InputIter + + Clone + + nom::Offset + + nom::InputLength, + ::Item: nom::AsChar, + { + nom::combinator::recognize(nom::multi::many0_count(nom::character::streaming::satisfy( + |ch| !('\x40'..='\x7e').contains(&ch), + ))) + .parse(i) + } + + fn bail>(i: I) -> IResult { + cut(fail)(i) + } alt(( alt(( - esc(map(ch('7'), |_| Op::SaveCursorPos)), - esc(map(ch('8'), |_| Op::RestoreCursorPos)), - )), - esc(nom::sequence::preceded( - ch('['), - map(ch('H'), |_| Op::MoveCursorAbs { x: 0, y: 0 }), - )), - esc(nom::sequence::preceded( - ch('['), - map(dual_int_parameter_sequence::('H'), |(a, b)| { - Op::MoveCursorAbs { - x: b.saturating_sub(1), - y: a.saturating_sub(1), - } - }), + esc(ch('7')).map(|_| Op::SaveCursorPos), + esc(ch('8')).map(|_| Op::RestoreCursorPos), )), - esc(nom::sequence::preceded( - ch('['), - map(dual_int_parameter_sequence::('f'), |(a, b)| { + csi(alt((alt(( + alt((ch('H'), ch('f'))).map(|_| Op::MoveCursorAbs { x: 0, y: 0 }), + term(param::<(usize, usize)>(), alt((ch('H'), ch('f')))).map(|(a, b)| { Op::MoveCursorAbs { x: b.saturating_sub(1), y: a.saturating_sub(1), } }), - )), + term(non_final, alt((ch('H'), ch('f')))).and_then(bail), + )),))), + // unknown sequence + nom::combinator::recognize(nom::sequence::preceded( + alt(( + esc(nom::combinator::success(())), + csi(nom::combinator::success(())), + )), + term(non_final, final_char), + )) + .and_then(bail), ))(input) } From 5e933edf78f165270615d0c4f646da42cf3d9cfd Mon Sep 17 00:00:00 2001 From: Seth Pellegrino Date: Sun, 16 Apr 2023 08:58:28 -0700 Subject: [PATCH 3/4] wip: committing to parse6 --- src/ansi.rs | 787 +++--------------------------------------------- src/terminal.rs | 2 +- 2 files changed, 43 insertions(+), 746 deletions(-) diff --git a/src/ansi.rs b/src/ansi.rs index e41d428..eb0cf73 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -12,8 +12,8 @@ //! ESC [ F => Cursor to beginning of prev line, n lines up //! ESC [ G => Cursor to column n //! ESC [ S => Scroll up n lines -//! ESC [ T => SCroll down n lines -//! ESC [ 6 n => Request cursor postion, as `ESC [ ; R` at row r and column c +//! ESC [ T => Scroll down n lines +//! ESC [ 6 n => Request cursor position, as `ESC [ ; R` at row r and column c //! ESC 7 => Save cursor position //! ESC 8 => Restore cursor position //! ESC [ 3 > ~ => Delete @@ -57,7 +57,7 @@ use alloc::{ vec, vec::Vec, }; -use core::{fmt::Debug, marker::PhantomData, num::ParseIntError, str::FromStr}; +use core::{fmt::Debug, str::FromStr}; use esp_println::println; use nom::{IResult, Parser}; @@ -70,7 +70,7 @@ pub enum Op { MoveCursorAbsCol { x: usize }, MoveCursorBeginningAndLine { dy: isize }, Scroll { delta: isize }, - RequstCursorPos, + RequestCursorPos, SaveCursorPos, RestoreCursorPos, EraseScreen(EraseMode), @@ -293,9 +293,9 @@ fn scroll_down(input: &str) -> OpResult { // Request Cursor Position // ESC [ 6 n -fn request_cursor_postion(input: &str) -> OpResult { +fn request_cursor_position(input: &str) -> OpResult { sequence_with_ending(nom::character::streaming::char('6'), 'n')(input) - .map(|(rest, _)| (rest, Op::RequstCursorPos)) + .map(|(rest, _)| (rest, Op::RequestCursorPos)) } // ESC 7 => Save cursor position @@ -468,190 +468,10 @@ fn set_text_mode(input: &str) -> OpResult { .map(|(rest, found)| (rest, Op::TextOp(found))) } -// struct Seq<'a, 'b, P, O>(&'a [P], &'b dyn FnOnce((&'a str, &'a str)) -> O) -// where -// P: StrParser<'a, O>; - -// fn parse2<'a>(input: &'a str) -> OpResult { -// let q = [ -// Seq(&[ESC, '7'], &|(rest, _)| (rest, Op::SaveCursorPos)), -// Seq(&[ESC, '8'], &|(rest, _)| (rest, Op::RestoreCursorPos)), -// Seq(&[ESC, 'H'], &|(rest, _)| { -// (rest, Op::MoveCursorAbs { x: 0, y: 0 }) -// }), -// Seq(&[ESC, arg::, ';', arg::, 'H'], &|( -// rest, -// _, -// )| { -// ( -// rest, -// Op::MoveCursorAbs { -// x: b.saturating_sub(1), -// y: a.saturating_sub(1), -// }, -// ) -// }), -// ]; - -// todo!() -// } - -fn parse4<'a>(input: &'a str) -> OpResult { - use nom::Err; - // struct Seq(&'static [P1], P2, F); - // struct Seq(char, char, F) - // where - // F: FnOnce(char) -> Op; - - fn seq<'a>(s: &'a [char], f: &'a impl Fn(char) -> Op) -> impl StrParser<'a, Op> { - let (req, rest) = match s { - &[req, ref rest @ ..] if !rest.is_empty() => (req, rest), - _ => panic!("bad sequence, needed at least two characters (i.e. [ESC, X] for some X)"), - }; - - nom::combinator::map(Chars { req, rest }, f) - } - - // parse! { - // [ESC, ']', 'H'] => Op::MoveCursorAbs - // [ESC, ']', (a,b) @ params::<(usize,usize)>, 'H'] => |a, b| Op::MoveCursorAbs - // } - - fn seqz<'a, O>( - (s, p): (&'a [char], impl StrParser<'a, O>), - f: &'a impl Fn(O) -> Op, - ) -> impl StrParser<'a, Op> { - let (req, rest) = match s { - &[req, ref rest @ ..] if !rest.is_empty() => (req, rest), - _ => panic!("bad sequence, needed at least two characters (i.e. [ESC, X] for some X)"), - }; - - nom::sequence::preceded(Chars { req, rest }, nom::combinator::map(p, f)) - } - - struct Chars<'a> { - req: char, - rest: &'a [char], - } - impl<'a> nom::Parser<&'a str, char, nom::error::Error<&'a str>> for Chars<'a> { - fn parse(&mut self, input: &'a str) -> IResult<&'a str, char, nom::error::Error<&'a str>> { - let (input, ch) = - nom::combinator::cut(nom::character::streaming::char(self.req))(input)?; - self.rest.iter().try_fold((input, ch), |(input, _), &ch| { - nom::character::streaming::char(ch)(input) - }) - } - } - - trait Params<'a>: Sized { - type O; - type Err; - fn recognize(input: &'a str) -> IResult<&'a str, Self::O>; - fn map(o: Self::O) -> Result; - } - impl<'a> Params<'a> for (usize, usize) { - type O = (&'a str, &'a str); - type Err = ParseIntError; - - fn recognize(input: &'a str) -> IResult<&'a str, (&'a str, &'a str)> { - nom::sequence::separated_pair( - nom::character::streaming::digit1, - nom::character::streaming::char(';'), - nom::character::streaming::digit1, - )(input) - } - - fn map((a, b): (&'a str, &'a str)) -> Result { - Ok((usize::from_str(a)?, usize::from_str(b)?)) - } - } - // impl<'a> Params<'a> for isize { - - // } - - fn param<'a, P: Params<'a>, Any>(tail: impl StrParser<'a, Any>) -> impl StrParser<'a, P> { - nom::sequence::terminated(nom::combinator::map_res(P::recognize, P::map), tail) - } - - fn opt_param<'a, P: Params<'a> + Copy, Any>( - tail: impl StrParser<'a, Any>, - default: P, - ) -> impl StrParser<'a, P> { - nom::sequence::terminated( - nom::combinator::map_res(nom::combinator::opt(P::recognize), move |opt| match opt { - None => Ok(default), - Some(o) => P::map(o), - }), - tail, - ) - } - - use nom::branch::alt; - use nom::character::streaming::char as ch; - - // enum Foo { - // Ch(char), - // P(StrParser) - // } - // TODO: how to make this transparent again to the optimizer? - for opt in [ - &mut seq(&[ESC, '7'], &|_| Op::SaveCursorPos), - &mut seq(&[ESC, '8'], &|_| Op::RestoreCursorPos), - &mut seq(&[ESC, '[', 'H'], &|_| Op::MoveCursorAbs { x: 0, y: 0 }), - &mut seqz( - ( - &[ESC, '['], - param::<(usize, usize), _>(alt((ch('H'), ch('f')))), - ), - &|(a, b)| Op::MoveCursorAbs { - x: b.saturating_sub(1), - y: a.saturating_sub(1), - }, - ), - ] as [&mut dyn StrParser<'a, Op>; 4] - { - let res = opt.parse(input); - if let Err(Err::Error(_)) = res { - continue; - } - return res; - } - - Err(nom::Err::Incomplete(nom::Needed::Unknown)) -} - -// fn parse7(input: &str) -> OpResult { -// static SEQUENCES = [ -// "ESC [ ; ? H", -// ]; - -// fn make_parser(seq: &[&str]) -> impl FnMut() -> GenericSequence { - -// } - -// // SEQUENCES -// } - -pub fn parse6(input: &str) -> Result { - // ESC [ ;? H - - // "ESC 7" -> Op::SaveCursorPos, - // "ESC 8" - // "ESC [ ;? H" -> ([]) - - // generic parser: - // s => ESC [ `[4, 6]` H - // enum GenSeq<'a> { - // Lit(char) - // Param() - // } - // let foo = [""; 6]; - - // let gen_parse = |input| start_with_esc(nom::multi::fold_many_m_n(0, 6, parse, init, fold)); - - fn gen_parse<'a, 'str, const M: usize>( +pub fn parse6(input: &str) -> OpResult { + fn gen_parse<'a, 'str>( input: &'str str, - q: &'a mut [&'str str; M], + q: &'a mut [&'str str; 4], ) -> IResult<&'str str, &'a [&'str str]> { let (input, start) = nom::combinator::cut(nom::combinator::recognize( nom::character::streaming::one_of("\u{1b}\u{9b}"), @@ -736,57 +556,63 @@ pub fn parse6(input: &str) -> Result { Ok((rest, &q[..=3])) } - // TODO: other kinds of non-digit-y things? - fn sep<'a, 'str, const M: usize>( - input: &'str str, - q: &'a mut [&'str str; M], - ) -> Result<&'a [&'str str], nom::Err>> { + trait Params<'a>: Sized { + fn parse(input: &'a str) -> IResult<&'a str, Self>; + } + impl<'a> Params<'a> for usize { + fn parse(input: &'a str) -> IResult<&'a str, Self> { + nom::combinator::map_res(nom::character::streaming::digit1, usize::from_str) + .parse(input) + } + } + + /// kind of like [nom::multi::fill], but for up to N repeats rather than exactly N + fn param<'s, 'p, P: Params<'s>, const N: usize>( + input: &'s str, + params: &'p mut [P; N], + ) -> Result<&'p [P], nom::Err>> { let (_, i) = nom::combinator::all_consuming(nom::multi::fold_many_m_n( 0, - M, + N, nom::sequence::terminated( - nom::bytes::complete::take_while1(nom::AsChar::is_dec_digit), + P::parse, // TODO: this is wrong: it's not optional, unless it's in the last position nom::combinator::opt(nom::character::complete::char(';')), ), || 0, |i, p| { - q[i] = p; + params[i] = p; i + 1 }, )) .parse(input)?; - Ok(&q[..i]) + Ok(¶ms[..i]) } const ESC: &str = "\u{1b}"; const CSI: &str = "\u{9b}"; - let z = match *gen_parse(input, &mut [""; 4]) - .map_err(|e| alloc::format!("{:?}", e))? - .1 - { + let mut seq = [""; 4]; + let (rest, seq) = gen_parse(input, &mut seq)?; + + let op = match *seq { [ESC, "7"] => Op::SaveCursorPos, [ESC, "8"] => Op::RestoreCursorPos, [CSI, params, "", "H"] | [CSI, params, "", "f"] => { - esp_println::println!("{params:?}"); - match *sep(params, &mut [""; 2]).map_err(|e| alloc::format!("{:?}", e))? { + match *param(params, &mut [usize::default(); 2])? { [] => Op::MoveCursorAbs { x: 0, y: 0 }, [a, b] => Op::MoveCursorAbs { - x: usize::from_str(b) - .map_err(|e| alloc::format!("{:?}", e))? - .saturating_sub(1), - y: usize::from_str(a) - .map_err(|e| alloc::format!("{:?}", e))? - .saturating_sub(1), + x: b.saturating_sub(1), + y: a.saturating_sub(1), }, _ => { - return Err(alloc::format!( - "Bad number of params got {:?} wanted 0 or 2", - params - )) + todo!("return Err(Failure(..)) with appropriate context") + // return Err(alloc::format!( + // "Bad number of params got {:?} wanted 0 or 2", + // params + // )) } } } @@ -794,536 +620,7 @@ pub fn parse6(input: &str) -> Result { _ => todo!(), }; - Ok(z) -} - -fn parse3(input: &str) -> OpResult { - use nom::branch::alt; - use nom::character::streaming::char as ch; - use nom::combinator::{cut, fail}; - use nom::sequence::terminated as term; - - // can't use trait aliases in the return type, see: https://github.com/rust-lang/rust-analyzer/issues/13410 - fn esc<'a, P: StrParser<'a, O>, O>( - parser: P, - ) -> impl nom::Parser<&'a str, O, nom::error::Error<&'a str>> { - nom::sequence::preceded(ch('\x1b'), parser) - } - - // can't use trait aliases in the return type, see: https://github.com/rust-lang/rust-analyzer/issues/13410 - fn csi<'a, P: StrParser<'a, O>, O>( - parser: P, - ) -> impl nom::Parser<&'a str, O, nom::error::Error<&'a str>> { - nom::sequence::preceded( - alt(( - nom::combinator::recognize(ch('\u{9b}')), - nom::combinator::recognize(nom::bytes::streaming::tag("\x1b[")), - )), - parser, - ) - } - - trait Params<'a>: Sized { - type O; - type Err; - fn recognize(input: &'a str) -> IResult<&'a str, Self::O>; - fn map(o: Self::O) -> Result; - } - impl<'a> Params<'a> for (usize, usize) { - type O = (&'a str, &'a str); - type Err = ParseIntError; - - fn recognize(input: &'a str) -> IResult<&'a str, (&'a str, &'a str)> { - nom::sequence::separated_pair( - nom::character::streaming::digit1, - nom::character::streaming::char(';'), - nom::character::streaming::digit1, - )(input) - } - - fn map((a, b): (&'a str, &'a str)) -> Result { - Ok((usize::from_str(a)?, usize::from_str(b)?)) - } - } - - fn param<'a, P: Params<'a>>() -> impl StrParser<'a, P> { - nom::combinator::map_res(P::recognize, P::map) - } - - fn final_char>(i: I) -> IResult - where - I: nom::Slice> + nom::InputIter, - ::Item: nom::AsChar, - { - nom::character::streaming::satisfy(|ch| ('\x40'..='\x7e').contains(&ch)).parse(i) - } - - fn non_final>(i: I) -> IResult - where - I: nom::Slice> - + nom::Slice> - + nom::InputIter - + Clone - + nom::Offset - + nom::InputLength, - ::Item: nom::AsChar, - { - nom::combinator::recognize(nom::multi::many0_count(nom::character::streaming::satisfy( - |ch| !('\x40'..='\x7e').contains(&ch), - ))) - .parse(i) - } - - fn bail>(i: I) -> IResult { - cut(fail)(i) - } - - alt(( - alt(( - esc(ch('7')).map(|_| Op::SaveCursorPos), - esc(ch('8')).map(|_| Op::RestoreCursorPos), - )), - csi(alt((alt(( - alt((ch('H'), ch('f'))).map(|_| Op::MoveCursorAbs { x: 0, y: 0 }), - term(param::<(usize, usize)>(), alt((ch('H'), ch('f')))).map(|(a, b)| { - Op::MoveCursorAbs { - x: b.saturating_sub(1), - y: a.saturating_sub(1), - } - }), - term(non_final, alt((ch('H'), ch('f')))).and_then(bail), - )),))), - // unknown sequence - nom::combinator::recognize(nom::sequence::preceded( - alt(( - esc(nom::combinator::success(())), - csi(nom::combinator::success(())), - )), - term(non_final, final_char), - )) - .and_then(bail), - ))(input) -} - -fn parse_all<'a>(input: &'a str) -> OpResult { - { - let mut parser = nom::branch::alt(( - |input| { - nom::bytes::streaming::tag("\u{1B}7")(input) - .map(|(rest, _)| (rest, Op::SaveCursorPos)) - }, - |input| { - nom::bytes::streaming::tag("\u{1B}8")(input) - .map(|(rest, _)| (rest, Op::RestoreCursorPos)) - }, - { - let mut parser = nom::branch::alt(( - |input| { - nom::character::streaming::char('H')(input) - .map(|(rest, _)| (rest, Op::MoveCursorAbs { x: 0, y: 0 })) - }, - move |input| { - nom::branch::alt(( - move |input: &'a str| { - let params = nom::sequence::separated_pair( - nom::character::streaming::digit1, - nom::character::streaming::char(';'), - nom::character::streaming::digit1, - ); - { - let mut parser = params; - move |input: &'a str| { - nom::sequence::terminated( - |x: &'a str| parser.parse(x), - nom::character::streaming::char('H'), - )(input) - } - } - .parse(input) - .map(move |(rest, (a, b))| { - ( - rest, - ( - ::from_str(a).unwrap(), - ::from_str(b).unwrap(), - ), - ) - }) - }, - move |input: &'a str| { - let params = nom::sequence::separated_pair( - nom::character::streaming::digit1, - nom::character::streaming::char(';'), - nom::character::streaming::digit1, - ); - { - let mut parser = params; - move |input: &'a str| { - nom::sequence::terminated( - |x: &'a str| parser.parse(x), - nom::character::streaming::char('f'), - )(input) - } - } - .parse(input) - .map(|(rest, (a, b))| { - ( - rest, - ( - ::from_str(a).unwrap(), - ::from_str(b).unwrap(), - ), - ) - }) - }, - )) - .parse(input) - .map(|(rest, (a, b))| { - ( - rest, - Op::MoveCursorAbs { - x: b.saturating_sub(1), - y: a.saturating_sub(1), - }, - ) - }) - }, - |input| { - { - move |input: &'a str| { - { - { - let mut parser = - nom::combinator::opt(nom::character::streaming::digit1); - move |input: &'a str| { - nom::sequence::terminated( - |x: &'a str| parser.parse(x), - nom::character::streaming::char('A'), - ) - .parse(input) - } - } - .parse(input) - .map(|(rest, n)| { - ( - rest, - match n { - None => 1, - Some(n) => ::from_str(n).unwrap(), - }, - ) - }) - } - .map(|(rest, n)| (rest, Op::MoveCursorDelta { dx: 0, dy: -n })) - } - } - .parse(input) - }, - |input| { - { - move |input: &'a str| { - { - { - let mut parser = - nom::combinator::opt(nom::character::streaming::digit1); - move |input: &'a str| { - nom::sequence::terminated( - |x: &'a str| parser.parse(x), - nom::character::streaming::char('B'), - )(input) - } - } - .parse(input) - .map(|(rest, n)| { - ( - rest, - match n { - None => 1, - Some(n) => ::from_str(n).unwrap(), - }, - ) - }) - } - .map(|(rest, n)| (rest, Op::MoveCursorDelta { dx: 0, dy: n })) - } - } - .parse(input) - }, - |input| { - { - move |input: &'a str| { - { - sequence_with_ending( - nom::combinator::opt(nom::character::streaming::digit1), - 'D', - ) - .parse(input) - .map(|(rest, n)| { - ( - rest, - match n { - None => 1, - Some(n) => ::from_str(n).unwrap(), - }, - ) - }) - } - .map(|(rest, n)| (rest, Op::MoveCursorDelta { dx: -n, dy: 0 })) - } - } - .parse(input) - }, - |input| { - { - move |input: &'a str| { - { - { - let mut parser = - nom::combinator::opt(nom::character::streaming::digit1); - move |input: &'a str| { - nom::sequence::terminated( - |x: &'a str| parser.parse(x), - nom::character::streaming::char('C'), - ) - .parse(input) - } - } - .parse(input) - .map(|(rest, n)| { - ( - rest, - match n { - None => 1, - Some(n) => ::from_str(n).unwrap(), - }, - ) - }) - } - .map(|(rest, n)| (rest, Op::MoveCursorDelta { dx: n, dy: 0 })) - } - } - .parse(input) - }, - |input| { - { - move |input: &'a str| { - { - sequence_with_ending( - nom::combinator::opt(nom::character::streaming::digit1), - 'G', - ) - .parse(input) - .map(|(rest, n)| { - ( - rest, - match n { - None => 0, - Some(n) => ::from_str(n).unwrap(), - }, - ) - }) - } - .map(|(rest, n)| { - ( - rest, - Op::MoveCursorAbsCol { - x: n.saturating_sub(1), - }, - ) - }) - } - } - .parse(input) - }, - |input| { - { - move |input: &'a str| { - { - { - sequence_with_ending( - nom::combinator::opt(nom::character::streaming::digit1), - 'E', - ) - .parse(input) - .map( - |(rest, n)| { - ( - rest, - match n { - None => 1, - Some(n) => ::from_str(n).unwrap(), - }, - ) - }, - ) - } - .map(|(rest, n)| { - (rest, Op::MoveCursorBeginningAndLine { dy: n }) - }) - } - } - } - .parse(input) - }, - |input| { - { - move |input: &'a str| { - { - { - let mut parser = - nom::combinator::opt(nom::character::streaming::digit1); - move |input: &'a str| { - nom::sequence::terminated( - |x: &'a str| parser.parse(x), - nom::character::streaming::char('F'), - ) - .parse(input) - } - } - .parse(input) - .map(|(rest, n)| { - ( - rest, - match n { - None => 1, - Some(n) => ::from_str(n).unwrap(), - }, - ) - }) - } - .map(|(rest, n)| (rest, Op::MoveCursorBeginningAndLine { dy: -n })) - } - } - .parse(input) - }, - |input| { - nom::bytes::streaming::tag("3~") - .parse(input) - .map(|(rest, _)| (rest, Op::InPlaceDelete)) - }, - |input| { - { - let mut parser = - nom::combinator::opt(nom::character::streaming::one_of("012")); - move |input: &'a str| { - nom::sequence::terminated( - |x: &'a str| parser.parse(x), - nom::character::streaming::char('J'), - ) - .parse(input) - } - } - .parse(input) - .map(|(rest, arg)| { - ( - rest, - match arg { - None => Op::EraseScreen(EraseMode::FromCursor), - Some('0') => Op::EraseScreen(EraseMode::FromCursor), - Some('1') => Op::EraseScreen(EraseMode::ToCursor), - Some('2') => Op::EraseScreen(EraseMode::All), - Some(_) => unreachable!(), - }, - ) - }) - }, - |input| { - { - let mut parser = - nom::combinator::opt(nom::character::streaming::one_of("012")); - move |input: &'a str| { - nom::sequence::terminated( - |x: &'a str| parser.parse(x), - nom::character::streaming::char('K'), - ) - .parse(input) - } - } - .parse(input) - .map(|(rest, arg)| { - ( - rest, - match arg { - None => Op::EraseLine(EraseMode::FromCursor), - Some('0') => Op::EraseLine(EraseMode::FromCursor), - Some('1') => Op::EraseLine(EraseMode::ToCursor), - Some('2') => Op::EraseLine(EraseMode::All), - Some(_) => unreachable!(), - }, - ) - }) - }, - |input| { - { - let mut parser = nom::character::streaming::char('6'); - move |input: &'a str| { - nom::sequence::terminated( - |x: &'a str| parser.parse(x), - nom::character::streaming::char('n'), - ) - .parse(input) - } - } - .parse(input) - .map(|(rest, _)| (rest, Op::RequstCursorPos)) - }, - |input| { - nom::sequence::terminated( - nom::multi::separated_list0( - nom::character::streaming::char(';'), - nom::branch::alt(( - set_basic_color_atom, - set_bg_256_color_atom, - set_fg_256_color_atom, - set_text_mode_atom, - )), - ), - nom::character::streaming::char('m'), - ) - .parse(input) - .map(|(rest, found)| (rest, Op::TextOp(found))) - }, - |input: &'a str| { - nom::sequence::tuple(( - nom::character::streaming::char('?'), - nom::character::streaming::digit0, - nom::character::streaming::char('h'), - )) - .parse(input) - .map(|(rest, (_, b, _))| (rest, Op::DecPrivateSet(b.to_owned()))) - }, - |input: &'a str| { - nom::sequence::tuple(( - nom::character::streaming::char('?'), - nom::character::streaming::digit0, - nom::character::streaming::char('l'), - )) - .parse(input) - .map(|(rest, (_, b, _))| (rest, Op::DecPrivateReset(b.to_owned()))) - }, - )); - move |input: &'a str| { - nom::sequence::preceded(nom::character::streaming::char('['), |x: &'a str| { - parser.parse(x) - }) - .parse(input) - } - }, - )); - move |input: &'a str| { - { - let mut parser = nom::combinator::cut(|x: &'a str| parser.parse(x)); - move |input: &'a str| { - nom::sequence::preceded(nom::character::streaming::char(ESC), |x: &'a str| { - parser.parse(x) - }) - .parse(input) - } - } - .parse(input) - } - } - .parse(input) + Ok((rest, op)) } fn parse(input: &str) -> OpResult { @@ -1348,7 +645,7 @@ fn parse(input: &str) -> OpResult { delete, erase_screen, erase_line, - request_cursor_postion, + request_cursor_position, set_text_mode, set_private_sequence, reset_private_sequence, diff --git a/src/terminal.rs b/src/terminal.rs index 827f025..20966b0 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -296,7 +296,7 @@ impl TextField { MoveCursorBeginningAndLine { dy } => { self.move_cursor(dy, -(self.cursor.pos.col() as isize)); } - RequstCursorPos => {} + RequestCursorPos => {} SaveCursorPos => {} RestoreCursorPos => {} EraseScreen(erase) => { From a5d231f21daa3dd0e0f78a0d64fe4c5649fb755d Mon Sep 17 00:00:00 2001 From: Seth Pellegrino Date: Fri, 28 Apr 2023 08:21:48 -0700 Subject: [PATCH 4/4] refactor: more explicit terminal sequence parsing Introduces parse_new, which takes advantage of a generalized sequence parser and slice patterns to (hopefully) make the mapping between a byte pattern and the corresponding terminal behavior more explicit. This initial implementation is running in "dark launch" mode: we run both parse_new and parse_classic (always taking the latter's output as canonical), and print a warning when the two don't match. This has resulted in a number of "bug-compatible" changes to the parser to match the old behavior precisely, both in an effort to ensure that we're faithfully reproducing the semantics of the old parser and to give us the opportunity to directly compare the relative merits of the old & new approaches. In terms of completeness, this current implementation has a few limitations: * Most notably, we don't handle the "redraw all" request given (currently) by the sequence "\u{1b}[VxD": per the standard, that parses as `[CSI, .., "V"]` followed by the unrelated bytes `xD`. We can either extend the parser to recognize this sequence, or, as I would prefer, change the sequence to "fit" within the standard. * The sequence enumeration and handling feels pretty good to me, both in terms of how the existing sequences are handled and ease of adding new ones (including, as `set_text_mode` demonstrates, the flexibility to integrate external combinatorial parsers if necessary), especially with respect to the parameter parsing. However, the parser itself is a mess: the standard proved less helpful in recognizing the set of sequences we've encountered in the wild" than I'd hoped, so I think we could definitely do better if we revisit it with fresh eyes. * Not all of the error cases are exactly the same when given "weird" sequences like "\u{1b}[?m"; they both produce an Err::Failure, but marking slightly different portions of the input. I believe this to be roughly acceptable (we'd still make progress towards parsing the entire input, just while printing out slightly different results for the unrecognized sequences). * I based the current general parser on the ECMA standard rather than ANSI's, despite being in `ansi.rs`. So that's potential for some comedy maybe. And some work that remains entirely untouched is: * Integrating a utf-8 parser so we can correctly handle multi-byte sequences. * Collapsing the Text/Op hierarchy so the parser can more directly split incoming inputs rather than "leaking" those details into parse_str_tail. * Critically evaluating the efficiency and throughpout of the parser, especially with an eye towards reducing the number of times we scan over the whole input. * Replacing the allocating branches (TextOp, DecPrivate*) with iterators. --- Cargo.toml | 5 +- examples/escape_seq.rs | 36 ++-- src/ansi.rs | 389 +++++++++++++++++++++++++++++++++++------ src/bin/vgaterm.rs | 9 +- src/display.rs | 1 - src/terminal.rs | 31 +++- 6 files changed, 389 insertions(+), 82 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7629961..9ae9534 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,10 @@ unroll = "0.1.5" [dev-dependencies] [features] -default = ["perf_log"] +default = ["background"] +background = [] + +op_log = [] perf_log = [] [patch.crates-io] diff --git a/examples/escape_seq.rs b/examples/escape_seq.rs index e251261..561d081 100644 --- a/examples/escape_seq.rs +++ b/examples/escape_seq.rs @@ -11,7 +11,9 @@ use esp32c3_hal::{ timer::TimerGroup, Rtc, IO, }; -use esp_println::{print, println}; +use esp_backtrace as _; +use esp_println::println; +use nom::Parser; use riscv::asm::wfi; use vgaterm::ansi; @@ -33,22 +35,6 @@ fn init_heap() { } } -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - print!("Aborting: "); - if let Some(p) = info.location() { - println!( - "line {}, file {}: {}", - p.line(), - p.file(), - info.message().unwrap() - ); - } else { - println!("no information available."); - } - stop(); -} - #[no_mangle] extern "C" fn stop() -> ! { loop { @@ -96,8 +82,20 @@ fn main() -> ! { riscv::interrupt::enable(); } - let r = ansi::parse_esc_str("abcd\u{1B}[XYZ\u{1B}["); - println!("{:?}", r); + // let r = ansi::parse_esc_str("abcd\u{1B}[XYZ\u{1B}["); + // println!("{:?}\n", r); + // let r = ansi::parse_esc_str("\u{1B}["); + // println!("{:?}\n", r); + // let r = ansi::parse_esc_str("\u{1B}8"); + // println!("{:?}\n", r); + + println!("{:?}", ansi::parse("\u{1B}[;")); + println!("{:?}", ansi::parse("\u{1B}[1;;")); + println!("{:?}", ansi::parse("\u{1B}[m")); + println!("{:?}", ansi::parse("\u{1B}[1;2m")); + println!("{:?}", ansi::parse("\u{1b}[?m")); + + println!("{:?}", ansi::parse("\u{1B}[1;")); // match escape.push_str("abcd\u{1B}[5") { // ParseRes::InSequence(s) => { diff --git a/src/ansi.rs b/src/ansi.rs index eb0cf73..3c3d3f3 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -59,11 +59,11 @@ use alloc::{ }; use core::{fmt::Debug, str::FromStr}; use esp_println::println; -use nom::{IResult, Parser}; +use nom::{combinator::fail, error::context, IResult, Parser}; const ESC: char = '\u{1B}'; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum Op { MoveCursorDelta { dx: isize, dy: isize }, MoveCursorAbs { x: usize, y: usize }, @@ -82,7 +82,7 @@ pub enum Op { Vgaterm(Vgaterm), } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum TextOp { SetBGBasic { bg: u8 }, SetFGBasic { fg: u8 }, @@ -92,7 +92,7 @@ pub enum TextOp { SetTextMode(SetUnset, Style), } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum Style { Bold, Dim, @@ -103,20 +103,20 @@ pub enum Style { Inverse, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum SetUnset { Set, Unset, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum EraseMode { FromCursor, ToCursor, All, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum Vgaterm { Redraw, } @@ -468,36 +468,35 @@ fn set_text_mode(input: &str) -> OpResult { .map(|(rest, found)| (rest, Op::TextOp(found))) } -pub fn parse6(input: &str) -> OpResult { +pub fn parse_new(input: &str) -> OpResult { fn gen_parse<'a, 'str>( input: &'str str, q: &'a mut [&'str str; 4], ) -> IResult<&'str str, &'a [&'str str]> { - let (input, start) = nom::combinator::cut(nom::combinator::recognize( - nom::character::streaming::one_of("\u{1b}\u{9b}"), - )) - .parse(input)?; - - q[0] = start; - // c0 - match nom::combinator::cond( - start == "\x1b", - nom::sequence::tuple(( - nom::combinator::opt(nom::sequence::tuple(( - nom::combinator::recognize(nom::character::streaming::char('\x21')), - nom::combinator::recognize(nom::character::streaming::char('\x40')), - ))), - nom::combinator::recognize(nom::character::streaming::satisfy(|ch| { - '\x00' < ch && ch < '\x1f' - // TODO: what set do these belong to ? any? - || ch == '7' || ch == '8' - })), - )), + let (input, start) = + nom::combinator::recognize(nom::character::streaming::one_of("\u{1b}\u{9b}")) + .parse(input)?; + + match context( + "c0", + nom::combinator::cond( + start == "\x1b", + nom::sequence::tuple(( + nom::combinator::opt(nom::sequence::tuple(( + nom::combinator::recognize(nom::character::complete::char('\x21')), + nom::combinator::recognize(nom::character::complete::char('\x40')), + ))), + nom::combinator::recognize(nom::character::streaming::satisfy(|ch| { + '\x00' < ch && ch < '\x1f' + })), + )), + ), ) .parse(input) { // collapse the two intro sequences to one Ok((rest, Some((Some(_), n)))) | Ok((rest, Some((None, n)))) => { + q[0] = start; q[1] = n; return Ok((rest, &q[..=1])); } @@ -509,15 +508,61 @@ pub fn parse6(input: &str) -> OpResult { }; // TODO: c1 set + match context( + // cf. https://github.com/fusesource/jansi/issues/226 + "vt100 (non-standard)", + nom::combinator::cond( + start == "\x1b", + nom::combinator::recognize(nom::character::streaming::satisfy(|ch| { + ch == '7' || ch == '8' + })), + ), + ) + .parse(input) + { + Ok((rest, Some(n))) => { + q[0] = start; + q[1] = n; + return Ok((rest, &q[..=1])); + } + Err(err @ nom::Err::Failure(_)) | Err(err @ nom::Err::Incomplete(_)) => { + return Err(err) + } + // We didn't match a non-standard VT100 sequence, nothing to return yet + Err(nom::Err::Error(_)) | Ok((_, None)) => {} + } + + match context( + // catch-all + "errybody else (non-standard)", + nom::combinator::cond( + start == "\x1b", + // TODO: (can't do this right now because it prevents us from recognizing CSIs and would need to come "later") + // nom::combinator::recognize(nom::character::streaming::anychar), + nom::combinator::recognize(nom::character::streaming::none_of("[")), + ), + ) + .parse(input) + { + Ok((rest, Some(n))) => { + q[0] = start; + q[1] = n; + return Ok((rest, &q[..=1])); + } + Err(err @ nom::Err::Failure(_)) | Err(err @ nom::Err::Incomplete(_)) => { + return Err(err) + } + // We didn't match a non-standard VT100 sequence, nothing to return yet + Err(nom::Err::Error(_)) | Ok((_, None)) => {} + } // control sequences - let input = if start == "\x1b" { + let (input, start) = if start == "\x1b" { let (input, _) = nom::character::streaming::char('[').parse(input)?; // map everything to this particular CSI - q[0] = "\u{9b}"; - input + (input, "\u{9b}") } else { - input + (input, start) }; // CSI P ... P I ... I F @@ -535,42 +580,100 @@ pub fn parse6(input: &str) -> OpResult { // and https://www.ecma-international.org/wp-content/uploads/ECMA-43_3rd_edition_december_1991.pdf#page=14 let params = nom::branch::alt(( - nom::bytes::streaming::is_a("0123456789:;<=>?"), + nom::bytes::complete::is_a("0123456789:;<=>?"), nom::combinator::success(""), )); let intermediate = nom::branch::alt(( - nom::bytes::streaming::is_a(concat!(" ", "!\"#$%&'()*+,/")), + nom::bytes::complete::is_a(concat!(" ", "!\"#$%&'()*+,/")), nom::combinator::success(""), )); - let fin = nom::combinator::recognize(nom::character::streaming::satisfy(|ch| { + let mut fin = nom::combinator::recognize(nom::character::streaming::satisfy(|ch| { ('\x40'..='\x7e').contains(&ch) })); - let (rest, ((params, intermediate), fin)) = - params.and(intermediate).and(fin).parse(input)?; + // TODO? + // let (rest, ((params, intermediate), fin)) = + // params.and(intermediate).and(fin).parse(input)?; + // bug-compat: + // currently, we bail out on sequences like "\u{1b}[;" with an error (even though it's reasonably considered incomplete, as we report without this check) + let (rest, (params, intermediate)) = params.and(intermediate).parse(input)?; + // but the trick is to avoid bailing on sequences like "\u{1b}[1;", which the old code considers "incomplete" + // this + if params.split(';').rev().skip(1).any(str::is_empty) { + return Err(nom::Err::Failure(nom::error::Error { + input: params, + code: nom::error::ErrorKind::Char, + })); + } + let (rest, fin) = fin.parse(rest)?; + // TODO: collapse params & intr to "mid" w/ recognize(params.and(alt((inter, nonstandard)))) ? + q[0] = start; q[1] = params; q[2] = intermediate; q[3] = fin; - Ok((rest, &q[..=3])) + Ok((rest, &q[..])) } trait Params<'a>: Sized { fn parse(input: &'a str) -> IResult<&'a str, Self>; } - impl<'a> Params<'a> for usize { + + trait FromDigits: core::str::FromStr {} + + impl<'a, T> Params<'a> for T + where + T: FromDigits, + { fn parse(input: &'a str) -> IResult<&'a str, Self> { - nom::combinator::map_res(nom::character::streaming::digit1, usize::from_str) + nom::combinator::map_res(nom::character::complete::digit1, Self::from_str).parse(input) + } + } + impl FromDigits for usize {} + impl FromDigits for isize {} + + trait AllConsuming: nom::Parser + Sized + where + I: nom::InputLength, + E: nom::error::ParseError, + { + fn parse_all(self, input: I) -> Result> { + nom::combinator::cut(nom::combinator::all_consuming(self)) .parse(input) + .map(|(_, o)| o) } } + impl AllConsuming for T + where + T: nom::Parser, + I: nom::InputLength, + E: nom::error::ParseError, + { + } + + fn bail(input: &str) -> IResult<&str, O> { + nom::combinator::cut(nom::combinator::fail).parse(input) + } + /// kind of like [nom::multi::fill], but for up to N repeats rather than exactly N - fn param<'s, 'p, P: Params<'s>, const N: usize>( + // TODO: can this be a parser? we could use .parse_all then + // TODO? + // g) If the parameter string starts with the bit combination 03/11, an empty parameter sub-string is + // assumed preceding the separator; if the parameter string terminates with the bit combination 03/11, + // an empty parameter sub-string is assumed following the separator; if the parameter string contains + // successive bit combinations 03/11, empty parameter sub-strings are assumed between the separators. + // + // h) If the control function has more than one parameter, and some parameter sub-strings are empty, the + // separators (bit combination 03/11) must still be present. However, if the last parameter sub-string(s) + // is empty, the separator preceding it may be omitted, see B.2 in annex B. + // — https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf#page=26 + fn many_param<'s, 'p, P: Params<'s>, const N: usize>( input: &'s str, params: &'p mut [P; N], ) -> Result<&'p [P], nom::Err>> { + // todo: check first byte for '\x30'..=\x3b ([0-9:;]); else we're in Special Params-land let (_, i) = nom::combinator::all_consuming(nom::multi::fold_many_m_n( 0, N, @@ -590,40 +693,166 @@ pub fn parse6(input: &str) -> OpResult { Ok(¶ms[..i]) } + fn param<'a, P: Params<'a> + Clone>( + default: P, + ) -> impl nom::Parser<&'a str, P, nom::error::Error<&'a str>> { + nom::branch::alt(( + P::parse, + nom::combinator::eof.and_then(nom::combinator::success(default)), + )) + } + const ESC: &str = "\u{1b}"; const CSI: &str = "\u{9b}"; - let mut seq = [""; 4]; - let (rest, seq) = gen_parse(input, &mut seq)?; + let mut buf = [""; 4]; + let (rest, seq) = gen_parse(input, &mut buf)?; + + println!("{:?}", seq); + + let back_compat_err = move |err| match err { + nom::Err::Error(_) => nom::Err::Error(nom::error::Error { + input: &input[2..], + code: nom::error::ErrorKind::Fail, + }), + nom::Err::Failure(_) => nom::Err::Failure(nom::error::Error { + input: &input[2..], + code: nom::error::ErrorKind::Fail, + }), + nom::Err::Incomplete(n) => nom::Err::Incomplete(n), + }; let op = match *seq { - [ESC, "7"] => Op::SaveCursorPos, - [ESC, "8"] => Op::RestoreCursorPos, - - [CSI, params, "", "H"] | [CSI, params, "", "f"] => { - match *param(params, &mut [usize::default(); 2])? { + // TODO: + // nom::bytes::streaming::tag("VxD")(input).map(|(rest, _)| (rest, Op::Vgaterm(Vgaterm::Redraw))) + + //TODO: + // [ESC, "7"] => Op::SaveCursorPos, + // [ESC, "8"] => Op::RestoreCursorPos, + // for now, bug-compat: + [ESC, ESC, "7"] => Op::SaveCursorPos, + [ESC, ESC, "8"] => Op::RestoreCursorPos, + + // bug-compat: + [CSI, _, _, "f"] => return bail(&input[2..]), + // TODO + // [CSI, params, intr, "H"] | [CSI, params, intr, "f"] => { + [CSI, params, intr, "H"] => { + if !intr.is_empty() { + return context("unrecognized intermediates", bail)(intr); + } + match *many_param(params, &mut [usize::default(); 2]).map_err(back_compat_err)? { [] => Op::MoveCursorAbs { x: 0, y: 0 }, [a, b] => Op::MoveCursorAbs { x: b.saturating_sub(1), y: a.saturating_sub(1), }, - _ => { - todo!("return Err(Failure(..)) with appropriate context") - // return Err(alloc::format!( - // "Bad number of params got {:?} wanted 0 or 2", - // params - // )) - } + _ => return context("expected 0 or 2 params", bail).parse(params), } } - _ => todo!(), + [CSI, params, "", "A"] => param::(1) + .parse_all(params) + .map_err(back_compat_err) + .map(|n| Op::MoveCursorDelta { dx: 0, dy: -n })?, + [CSI, params, "", "B"] => param::(1) + .parse_all(params) + .map_err(back_compat_err) + .map(|n| Op::MoveCursorDelta { dx: 0, dy: n })?, + [CSI, params, "", "C"] => param::(1) + .parse_all(params) + .map_err(back_compat_err) + .map(|n| Op::MoveCursorDelta { dx: n, dy: 0 })?, + [CSI, params, "", "D"] => param::(1) + .parse_all(params) + .map_err(back_compat_err) + .map(|n| Op::MoveCursorDelta { dx: -n, dy: 0 })?, + [CSI, params, "", "E"] => param::(1) + .parse_all(params) + .map_err(back_compat_err) + .map(|n| Op::MoveCursorBeginningAndLine { dy: n })?, + [CSI, params, "", "F"] => param::(1) + .parse_all(params) + .map_err(back_compat_err) + .map(|n| Op::MoveCursorBeginningAndLine { dy: -n })?, + [CSI, params, "", "G"] => param::(0 /* <-- TODO? */) + .parse_all(params) + .map_err(back_compat_err) + .map(|n| Op::MoveCursorAbsCol { + x: n.saturating_sub(1), + })?, + + [CSI, params, "", "J"] => match params { + "" | "0" => Op::EraseScreen(EraseMode::FromCursor), + "1" => Op::EraseScreen(EraseMode::ToCursor), + "2" => Op::EraseScreen(EraseMode::All), + _ => { + return context("invalid screen erase mode", bail) + .parse(params) + .map_err(back_compat_err); + } + }, + [CSI, params, "", "K"] => match params { + "" | "0" => Op::EraseLine(EraseMode::FromCursor), + "1" => Op::EraseLine(EraseMode::ToCursor), + "2" => Op::EraseLine(EraseMode::All), + _ => { + return context("invalid line erase mode", bail) + .parse(params) + .map_err(back_compat_err); + } + }, + + [CSI, "3", "", "~"] => Op::InPlaceDelete, + + [CSI, "6", "", "n"] => Op::RequestCursorPos, + [CSI, params, "", "m"] => { + nom::multi::separated_list0( + nom::character::complete::char(';'), + nom::combinator::complete(any_text_mode), + ) + // back-compat (for using `any_text_mode`) + // we need some sort of terminator for the streaming digit parsers to recognize the last item + // so, let's keep it classic and pick '\0' + .and(nom::character::complete::char('\0')) + .map(|(r, _)| r) + .parse_all(alloc::format!("{}\0", params).as_str()) + // TODO: + // .parse_all(params) + .map(Op::TextOp) + .map_err(back_compat_err)? + } + + [CSI, params, "", "h"] => nom::sequence::preceded( + nom::character::complete::char('?'), + nom::character::complete::digit0, + ) + .map(|s: &str| Op::DecPrivateSet(s.to_owned())) + .parse_all(params) + .map_err(back_compat_err)?, + [CSI, params, "", "l"] => nom::sequence::preceded( + nom::character::complete::char('?'), + nom::character::complete::digit0, + ) + .map(|s: &str| Op::DecPrivateReset(s.to_owned())) + .parse_all(params) + .map_err(back_compat_err)?, + + // TODO: + // [ESC, ..] | [CSI, ..] => return bail(input), + // _ => return fail(input), // `fail` is (confusingly) not Failure, but Error + // for now (back-compat): + [ESC, ..] => return bail(&input[1..]), + [CSI, ..] => return bail(&input[2..]), + _ => { + return nom::sequence::preceded(nom::bytes::complete::tag("\u{1b}"), fail).parse(input) + } }; Ok((rest, op)) } -fn parse(input: &str) -> OpResult { +fn parse_classic(input: &str) -> OpResult { start_with_esc(nom::branch::alt(( save_cursor_position, restore_cursor_position, @@ -655,6 +884,52 @@ fn parse(input: &str) -> OpResult { .parse(input) } +pub fn parse(input: &str) -> OpResult { + match (parse_classic(input), parse_new(input)) { + (Ok(classic), Ok(new)) if classic == new => Ok(classic), + ( + r @ Err(nom::Err::Error(..)), + Err(nom::Err::Error(nom::error::Error { + input: ref new, + code: _, + })), + ) if { + let Err(nom::Err::Error(nom::error::Error { + input: ref classic, + code: _, + })) = r else { unreachable!() }; + classic + } == new => + { + r + } + ( + r @ Err(nom::Err::Failure(..)), + Err(nom::Err::Failure(nom::error::Error { + input: ref new, + code: _, + })), + ) if { + let Err(nom::Err::Failure(nom::error::Error { + input: ref classic, + code: _, + })) = r else { unreachable!() }; + classic + } == new => + { + r + } + (r @ Err(nom::Err::Incomplete(..)), Err(nom::Err::Incomplete(_))) => r, + (r, new) => { + println!( + "whuh oh! wanted: {:?}\n got: {:?}\nfor input: {:?}", + r, new, input + ); + r + } + } +} + pub enum OpChar { Char(char), Op(Op), diff --git a/src/bin/vgaterm.rs b/src/bin/vgaterm.rs index 6ed51ea..c539647 100644 --- a/src/bin/vgaterm.rs +++ b/src/bin/vgaterm.rs @@ -10,7 +10,7 @@ use esp32c3_hal::prelude::*; use esp32c3_hal::timer::TimerGroup; use esp32c3_hal::{gpio::IO, peripherals::Peripherals, Rtc}; use esp_backtrace as _; -use vgaterm::{self, perf, video}; +use vgaterm::{self, perf}; use vgaterm::{interrupt::Priority, usb_keyboard::US_ENGLISH, Work}; use core::fmt::Write; @@ -132,8 +132,11 @@ fn main() -> ! { 40_000_000, ); - let image = include_bytes!("../../image.bin"); - video::load_from_slice(image); + #[cfg(feature = "background")] + { + let image = include_bytes!("../../image.bin"); + vgaterm::video::load_from_slice(image); + } let mut display = vgaterm::display::Display::new(); diff --git a/src/display.rs b/src/display.rs index a5c8984..0481551 100644 --- a/src/display.rs +++ b/src/display.rs @@ -112,7 +112,6 @@ impl DrawTarget for Display { // print!("{: ^5}", c); unsafe { video::BUFFER[i] = c }; } - // println!(); offset += screen_width; } // }); diff --git a/src/terminal.rs b/src/terminal.rs index 20966b0..8991c85 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -273,7 +273,36 @@ impl TextField { fn handle_op(&mut self, op: Op) { use Op::*; - // println!("{:?}", op); + #[cfg(feature = "op_log")] + { + use core::cmp::min; + use esp_println::println; + match &op { + TextOp(v) => println!( + "TextOp({:?}{}) [{}]", + &v[..min(v.len(), 5)], + if v.len() > 5 { "..." } else { "" }, + v.len() + ), + DecPrivateSet(v) => { + println!( + "DecPrivateSet({:?}{}) [{}]", + &v[..min(v.len(), 5)], + if v.len() > 5 { "..." } else { "" }, + v.len() + ) + } + DecPrivateReset(v) => { + println!( + "DecPrivateReset({:?}{}) [{}]", + &v[..min(v.len(), 5)], + if v.len() > 5 { "..." } else { "" }, + v.len() + ) + } + _ => println!("{:?}", op), + } + } match op { MoveCursorAbs { x, y } => { self.move_cursor(