#[cfg(test)] mod tests; use crate::AnsiSequence; use heapless::Vec; use nom::branch::alt; use nom::bytes::complete::tag; use nom::character::complete::{digit0, digit1}; use nom::combinator::{map, map_res, opt, value}; use nom::sequence::{delimited, preceded}; use nom::IResult; use nom::Parser; macro_rules! tag_parser { ($sig:ident, $tag:expr, $ret:expr) => { fn $sig(input: &str) -> IResult<&str, AnsiSequence> { value($ret, tag($tag)).parse(input) } }; } fn parse_u32(input: &str) -> IResult<&str, u32> { map_res(digit1, |s: &str| s.parse::()).parse(input) } fn parse_u8(input: &str) -> IResult<&str, u8> { map_res(digit1, |s: &str| s.parse::()).parse(input) } // TODO kind of ugly, would prefer to pass in the default so we could use it for // all escapes with defaults (not just those that default to 1). fn parse_def_cursor_int(input: &str) -> IResult<&str, u32> { map(digit0, |s: &str| s.parse::().unwrap_or(1)).parse(input) } fn cursor_pos(input: &str) -> IResult<&str, AnsiSequence> { map( ( tag("["), parse_def_cursor_int, opt(tag(";")), parse_def_cursor_int, alt((tag("H"), tag("f"))), ), |(_, x, _, y, _)| AnsiSequence::CursorPos(x, y), ) .parse(input) } fn escape(input: &str) -> IResult<&str, AnsiSequence> { value(AnsiSequence::Escape, tag("\u{1b}")).parse(input) } fn cursor_up(input: &str) -> IResult<&str, AnsiSequence> { map(delimited(tag("["), parse_def_cursor_int, tag("A")), |am| { AnsiSequence::CursorUp(am) }) .parse(input) } fn cursor_down(input: &str) -> IResult<&str, AnsiSequence> { map(delimited(tag("["), parse_def_cursor_int, tag("B")), |am| { AnsiSequence::CursorDown(am) }) .parse(input) } fn cursor_right(input: &str) -> IResult<&str, AnsiSequence> { map(delimited(tag("["), parse_def_cursor_int, tag("C")), |am| { AnsiSequence::CursorRight(am) }) .parse(input) } fn cursor_left(input: &str) -> IResult<&str, AnsiSequence> { map(delimited(tag("["), parse_def_cursor_int, tag("D")), |am| { AnsiSequence::CursorLeft(am) }) .parse(input) } fn graphics_mode1(input: &str) -> IResult<&str, AnsiSequence> { map(delimited(tag("["), parse_u8, tag("m")), |val| { let mode = Vec::from_slice(&[val]).expect("Vec::from_slice should allocate sufficient size"); AnsiSequence::SetGraphicsMode(mode) }) .parse(input) } fn graphics_mode2(input: &str) -> IResult<&str, AnsiSequence> { map( (tag("["), parse_u8, tag(";"), parse_u8, tag("m")), |(_, val1, _, val2, _)| { let mode = Vec::from_slice(&[val1, val2]) .expect("Vec::from_slice should allocate sufficient size"); AnsiSequence::SetGraphicsMode(mode) }, ) .parse(input) } fn graphics_mode3(input: &str) -> IResult<&str, AnsiSequence> { map( ( tag("["), parse_u8, tag(";"), parse_u8, tag(";"), parse_u8, tag("m"), ), |(_, val1, _, val2, _, val3, _)| { let mode = Vec::from_slice(&[val1, val2, val3]) .expect("Vec::from_slice should allocate sufficient size"); AnsiSequence::SetGraphicsMode(mode) }, ) .parse(input) } fn graphics_mode4(input: &str) -> IResult<&str, AnsiSequence> { value(AnsiSequence::SetGraphicsMode(Vec::new()), tag("[m")).parse(input) } fn graphics_mode5(input: &str) -> IResult<&str, AnsiSequence> { map( ( tag("["), parse_u8, tag(";"), parse_u8, tag(";"), parse_u8, tag(";"), parse_u8, tag(";"), parse_u8, tag("m"), ), |(_, val1, _, val2, _, val3, _, val4, _, val5, _)| { let mode = Vec::from_slice(&[val1, val2, val3, val4, val5]) .expect("Vec::from_slice should allocate sufficient size"); AnsiSequence::SetGraphicsMode(mode) }, ) .parse(input) } fn graphics_mode(input: &str) -> IResult<&str, AnsiSequence> { alt(( graphics_mode1, graphics_mode2, graphics_mode3, graphics_mode4, graphics_mode5, )) .parse(input) } fn set_mode(input: &str) -> IResult<&str, AnsiSequence> { map(delimited(tag("[="), parse_u8, tag("h")), |val| { AnsiSequence::SetMode(val) }) .parse(input) } fn reset_mode(input: &str) -> IResult<&str, AnsiSequence> { map(delimited(tag("[="), parse_u8, tag("l")), |val| { AnsiSequence::ResetMode(val) }) .parse(input) } fn set_top_and_bottom(input: &str) -> IResult<&str, AnsiSequence> { map( (tag("["), parse_u32, tag(";"), parse_u32, tag("r")), |(_, x, _, y, _)| AnsiSequence::SetTopAndBottom(x, y), ) .parse(input) } fn erase_line_to_end(input: &str) -> IResult<&str, AnsiSequence> { map(alt((tag("[K"), tag("[0K"))), |_| { AnsiSequence::EraseLineToEnd }) .parse(input) } fn erase_screen_to_end(input: &str) -> IResult<&str, AnsiSequence> { map(alt((tag("[J"), tag("[0J"))), |_| { AnsiSequence::EraseScreenToEnd }) .parse(input) } tag_parser!( erase_screen_from_start, "[1J", AnsiSequence::EraseScreenFromStart ); tag_parser!( erase_line_from_start, "[1K", AnsiSequence::EraseLineFromStart ); tag_parser!(cursor_up_scroll, "[ M", AnsiSequence::CursorUpScroll); tag_parser!(erase_line, "[2K", AnsiSequence::EraseLine); tag_parser!(cursor_home, "[H", AnsiSequence::CursorHome); tag_parser!(request_cursor_pos, "[6n", AnsiSequence::RequestCursorPos); tag_parser!(cursor_save, "[s", AnsiSequence::CursorSave); tag_parser!(cursor_restore, "[u", AnsiSequence::CursorRestore); tag_parser!(erase_screen, "[2J", AnsiSequence::EraseScreen); tag_parser!(hide_cursor, "[?25l", AnsiSequence::HideCursor); tag_parser!(show_cursor, "[?25h", AnsiSequence::ShowCursor); tag_parser!(cursor_to_app, "[?1h", AnsiSequence::CursorToApp); tag_parser!(set_new_line_mode, "[20h", AnsiSequence::SetNewLineMode); tag_parser!(set_col_132, "[?3h", AnsiSequence::SetCol132); tag_parser!(set_smooth_scroll, "[?4h", AnsiSequence::SetSmoothScroll); tag_parser!(set_reverse_video, "[?5h", AnsiSequence::SetReverseVideo); tag_parser!(set_origin_rel, "[?6h", AnsiSequence::SetOriginRelative); tag_parser!(set_auto_wrap, "[?7h", AnsiSequence::SetAutoWrap); tag_parser!(set_auto_repeat, "[?8h", AnsiSequence::SetAutoRepeat); tag_parser!(set_interlacing, "[?9h", AnsiSequence::SetInterlacing); tag_parser!(set_linefeed, "[20l", AnsiSequence::SetLineFeedMode); tag_parser!(set_cursorkey, "[?1l", AnsiSequence::SetCursorKeyToCursor); tag_parser!(set_vt52, "[?2l", AnsiSequence::SetVT52); tag_parser!(set_col80, "[?3l", AnsiSequence::SetCol80); tag_parser!(set_jump_scroll, "[?4l", AnsiSequence::SetJumpScrolling); tag_parser!(set_normal_video, "[?5l", AnsiSequence::SetNormalVideo); tag_parser!(set_origin_abs, "[?6l", AnsiSequence::SetOriginAbsolute); tag_parser!(reset_auto_wrap, "[?7l", AnsiSequence::ResetAutoWrap); tag_parser!(reset_auto_repeat, "[?8l", AnsiSequence::ResetAutoRepeat); tag_parser!(reset_interlacing, "[?9l", AnsiSequence::ResetInterlacing); tag_parser!(set_alternate_keypad, "=", AnsiSequence::SetAlternateKeypad); tag_parser!(set_numeric_keypad, ">", AnsiSequence::SetNumericKeypad); tag_parser!(set_uk_g0, "(A", AnsiSequence::SetUKG0); tag_parser!(set_uk_g1, ")A", AnsiSequence::SetUKG1); tag_parser!(set_us_g0, "(B", AnsiSequence::SetUSG0); tag_parser!(set_us_g1, ")B", AnsiSequence::SetUSG1); tag_parser!(set_g0_special, "(0", AnsiSequence::SetG0SpecialChars); tag_parser!(set_g1_special, ")0", AnsiSequence::SetG1SpecialChars); tag_parser!(set_g0_alternate, "(1", AnsiSequence::SetG0AlternateChar); tag_parser!(set_g1_alternate, ")1", AnsiSequence::SetG1AlternateChar); tag_parser!(set_g0_graph, "(2", AnsiSequence::SetG0AltAndSpecialGraph); tag_parser!(set_g1_graph, ")2", AnsiSequence::SetG1AltAndSpecialGraph); tag_parser!(set_single_shift2, "N", AnsiSequence::SetSingleShift2); tag_parser!(set_single_shift3, "O", AnsiSequence::SetSingleShift3); fn combined(input: &str) -> IResult<&str, AnsiSequence> { // `alt` only supports up to 21 parsers, and nom doesn't seem to // have an alternative with higher variability. // So we simply nest them. alt(( alt(( escape, cursor_pos, cursor_up, cursor_down, cursor_right, cursor_left, cursor_save, cursor_restore, erase_screen, erase_line, graphics_mode, set_mode, reset_mode, hide_cursor, show_cursor, cursor_to_app, set_new_line_mode, set_col_132, set_smooth_scroll, set_reverse_video, set_origin_rel, )), alt(( set_auto_wrap, set_auto_repeat, set_interlacing, set_linefeed, set_cursorkey, set_vt52, set_col80, set_jump_scroll, set_normal_video, set_origin_abs, reset_auto_wrap, reset_auto_repeat, reset_interlacing, set_top_and_bottom, set_alternate_keypad, set_numeric_keypad, set_uk_g0, set_uk_g1, set_us_g0, set_us_g1, set_g0_special, )), set_g1_special, set_g0_alternate, set_g1_alternate, set_g0_graph, set_g1_graph, set_single_shift2, set_single_shift3, request_cursor_pos, cursor_home, erase_line_from_start, erase_screen_from_start, erase_screen_to_end, erase_line_to_end, cursor_up_scroll, )) .parse(input) } pub fn parse_escape(input: &str) -> IResult<&str, AnsiSequence> { preceded(tag("\u{1b}"), combined).parse(input) }