Compare commits

...

11 Commits

Author SHA1 Message Date
mtgmonkey
555b28e25a add tons of codes 2025-07-08 15:31:23 -04:00
mtgmonkey
e1082e47cb implement get_cursor_pos 2025-07-08 12:15:21 -04:00
David Bittner
6b973008e0 Merge branch 'feat/update-nom' into 'master'
Update nom to v8

See merge request davidbittner/ansi-parser!18
2025-05-06 22:59:25 +00:00
Lucas Schwiderski
7a31a7346b Update nom to v8 2025-04-22 22:06:44 +02:00
David Bittner
a431fb31f8 Merge branch 'issue/outdated-heapless' into 'master'
Update heapless to v0.8.0

See merge request davidbittner/ansi-parser!15
2024-05-17 17:33:40 +00:00
Lucas Schwiderski
bfcd268967 Bump version to v0.9.1 2024-05-16 11:02:54 +02:00
Lucas Schwiderski
af1951d169 Update heapless to v0.8.0 2024-05-15 19:10:49 +02:00
David Bittner
80b28ea6c4 Merge branch 'issue/outdated-nom' into 'master'
Update to nom@7

See merge request davidbittner/ansi-parser!13
2024-01-16 18:57:53 +00:00
Lucas Schwiderski
27beb4bc1f Update to nom@7 2023-11-07 16:46:25 +01:00
David Bittner
28001c3197 Merge branch 'master' into 'master'
update heapless create version up. to 0.6.1

See merge request davidbittner/ansi-parser!12
2022-07-15 18:53:49 +00:00
blacknon
e512d4f0f1 update heapless create version up. to 0.6.1 2022-06-12 17:41:10 +09:00
4 changed files with 298 additions and 295 deletions

View File

@@ -1,21 +1,21 @@
[package] [package]
name = "ansi-parser" authors = ["David Bittner <bittneradave@gmail.com>"]
description = "A library using nom for parsing ANSI Escape Codes" description = "A library using nom for parsing ANSI Escape Codes"
homepage = "https://gitlab.com/DavidBittner/ansi-parser" homepage = "https://gitlab.com/DavidBittner/ansi-parser"
authors = ["David Bittner <bittneradave@gmail.com>"] name = "ansi-parser"
version = "0.8.0"
license = "MPL-2.0" license = "MPL-2.0"
version = "0.9.1"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
heapless = "0.5.6" heapless = "0.8.0"
[dependencies.nom] [dependencies.nom]
version = "4.2.3"
default-features = false default-features = false
version = "8.0.0"
[features] [features]
std = ["nom/std"]
default = ["std"] default = ["std"]
std = ["nom/std"]

View File

@@ -1,24 +1,36 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use heapless::{consts::U5, Vec}; use heapless::Vec;
///The following are the implemented ANSI escape sequences. More to be added. ///The following are the implemented ANSI escape sequences. More to be added.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum AnsiSequence { pub enum AnsiSequence {
Escape, Escape,
CursorPos(u32, u32), CursorHome, // ^[[H
CursorUp(u32), CursorPos(u32, u32), // ^[[#;#H or [#;#f
CursorDown(u32), CursorUp(u32), // ^[[#A
CursorForward(u32), CursorDown(u32), // ^[[#B
CursorBackward(u32), CursorRight(u32), // ^[[#C
CursorSave, CursorLeft(u32), // ^[[#D
CursorRestore, // TODO ^[[#E
EraseDisplay, // TODO ^[[#F
EraseLine, // TODO ^[[#G
SetGraphicsMode(Vec<u8, U5>), RequestCursorPos, // ^[[6n TODO rename GetCursorPos to RequestCursorPos
SetMode(u8), CursorUpScroll, // ^[ M
ResetMode(u8), CursorSave, // ^[ 7
CursorRestore, // ^[ 8
EraseScreenToEnd, // ^[[0J or ^[[J
EraseScreenFromStart, // ^[[1J
EraseScreen, // ^[[2J
// TODO ^[[3J
EraseLineToEnd, // ^[[0K or ^[[K
EraseLineFromStart, // ^[[1K
EraseLine, // ^[[2K
SetGraphicsMode(Vec<u8, 5>), // TODO figure out graphics mode
SetMode(u8), // ^[[=#h
ResetMode(u8), // ^[[=#l
HideCursor, HideCursor,
ShowCursor, ShowCursor,
CursorToApp, CursorToApp,
@@ -65,15 +77,22 @@ impl Display for AnsiSequence {
use AnsiSequence::*; use AnsiSequence::*;
match self { match self {
Escape => write!(formatter, "\u{1b}"), Escape => write!(formatter, "\u{1b}"),
RequestCursorPos => write!(formatter, "[6n"),
CursorHome => write!(formatter, "[H"),
CursorPos(line, col) => write!(formatter, "[{};{}H", line, col), CursorPos(line, col) => write!(formatter, "[{};{}H", line, col),
CursorUp(amt) => write!(formatter, "[{}A", amt), CursorUp(amt) => write!(formatter, "[{}A", amt),
CursorDown(amt) => write!(formatter, "[{}B", amt), CursorDown(amt) => write!(formatter, "[{}B", amt),
CursorForward(amt) => write!(formatter, "[{}C", amt), CursorRight(amt) => write!(formatter, "[{}C", amt),
CursorBackward(amt) => write!(formatter, "[{}D", amt), CursorLeft(amt) => write!(formatter, "[{}D", amt),
CursorUpScroll => write!(formatter, "[ M"),
CursorSave => write!(formatter, "[s"), CursorSave => write!(formatter, "[s"),
CursorRestore => write!(formatter, "[u"), CursorRestore => write!(formatter, "[u"),
EraseDisplay => write!(formatter, "[2J"), EraseScreenToEnd => write!(formatter, "[0J"),
EraseLine => write!(formatter, "[K"), EraseScreenFromStart => write!(formatter, "[1J"),
EraseScreen => write!(formatter, "[2J"),
EraseLineToEnd => write!(formatter, "[0K"),
EraseLineFromStart => write!(formatter, "[1K"),
EraseLine => write!(formatter, "[2K"),
SetGraphicsMode(vec) => match vec.len() { SetGraphicsMode(vec) => match vec.len() {
0 => write!(formatter, "[m"), 0 => write!(formatter, "[m"),
1 => write!(formatter, "[{}m", vec[0]), 1 => write!(formatter, "[{}m", vec[0]),

View File

@@ -3,242 +3,216 @@ mod tests;
use crate::AnsiSequence; use crate::AnsiSequence;
use core::convert::TryInto;
use heapless::Vec; use heapless::Vec;
use nom::*; 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 { macro_rules! tag_parser {
($sig:ident, $tag:expr, $ret:expr) => { ($sig:ident, $tag:expr, $ret:expr) => {
named!( fn $sig(input: &str) -> IResult<&str, AnsiSequence> {
$sig<&str, AnsiSequence>, value($ret, tag($tag)).parse(input)
do_parse!( }
tag!($tag) >> };
($ret)
)
);
}
} }
named!( fn parse_u32(input: &str) -> IResult<&str, u32> {
parse_int<&str, u32>, map_res(digit1, |s: &str| s.parse::<u32>()).parse(input)
map_res!( }
nom::digit,
|s: &str| s.parse::<u32>() fn parse_u8(input: &str) -> IResult<&str, u8> {
) map_res(digit1, |s: &str| s.parse::<u8>()).parse(input)
); }
// TODO kind of ugly, would prefer to pass in the default so we could use it for // 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). // all escapes with defaults (not just those that default to 1).
named!( fn parse_def_cursor_int(input: &str) -> IResult<&str, u32> {
parse_def_cursor_int<&str, u32>, map(digit0, |s: &str| s.parse::<u32>().unwrap_or(1)).parse(input)
map!( }
nom::digit0,
|s: &str| s.parse::<u32>().unwrap_or(1)
)
);
named!( fn cursor_pos(input: &str) -> IResult<&str, AnsiSequence> {
cursor_pos<&str, AnsiSequence>, map(
do_parse!( (
tag!("[") >> tag("["),
x: parse_def_cursor_int >> parse_def_cursor_int,
opt!(tag!(";")) >> opt(tag(";")),
y: parse_def_cursor_int >> parse_def_cursor_int,
alt!( alt((tag("H"), tag("f"))),
tag!("H") | ),
tag!("f") |(_, x, _, y, _)| AnsiSequence::CursorPos(x, y),
) >>
(AnsiSequence::CursorPos(x, y))
) )
); .parse(input)
}
named!( fn escape(input: &str) -> IResult<&str, AnsiSequence> {
escape<&str, AnsiSequence>, value(AnsiSequence::Escape, tag("\u{1b}")).parse(input)
do_parse!( }
tag!("\u{1b}") >>
(AnsiSequence::Escape) 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)
}
named!( fn graphics_mode3(input: &str) -> IResult<&str, AnsiSequence> {
cursor_up<&str, AnsiSequence>, map(
do_parse!( (
tag!("[") >> tag("["),
am: parse_def_cursor_int >> parse_u8,
tag!("A") >> tag(";"),
(AnsiSequence::CursorUp(am)) 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)
}
named!( fn graphics_mode4(input: &str) -> IResult<&str, AnsiSequence> {
cursor_down<&str, AnsiSequence>, value(AnsiSequence::SetGraphicsMode(Vec::new()), tag("[m")).parse(input)
do_parse!( }
tag!("[") >>
am: parse_def_cursor_int >> fn graphics_mode5(input: &str) -> IResult<&str, AnsiSequence> {
tag!("B") >> map(
(AnsiSequence::CursorDown(am)) (
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)
}
named!( fn graphics_mode(input: &str) -> IResult<&str, AnsiSequence> {
cursor_forward<&str, AnsiSequence>, alt((
do_parse!( graphics_mode1,
tag!("[") >> graphics_mode2,
am: parse_def_cursor_int >> graphics_mode3,
tag!("C") >> graphics_mode4,
(AnsiSequence::CursorForward(am)) 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)
}
named!( fn erase_line_to_end(input: &str) -> IResult<&str, AnsiSequence> {
cursor_backward<&str, AnsiSequence>, map(alt((tag("[K"), tag("[0K"))), |_| {
do_parse!( AnsiSequence::EraseLineToEnd
tag!("[") >> })
am: parse_def_cursor_int >> .parse(input)
tag!("D") >> }
(AnsiSequence::CursorBackward(am))
)
);
named!( fn erase_screen_to_end(input: &str) -> IResult<&str, AnsiSequence> {
graphics_mode1<&str, AnsiSequence>, map(alt((tag("[J"), tag("[0J"))), |_| {
do_parse!( AnsiSequence::EraseScreenToEnd
tag!("[") >> })
val: parse_int >> .parse(input)
tag!("m") >> }
val: expr_res!(val.try_into()) >>
conv: expr_res!(Vec::from_slice(&[val])) >>
(AnsiSequence::SetGraphicsMode(conv))
)
);
named!( tag_parser!(
graphics_mode2<&str, AnsiSequence>, erase_screen_from_start,
do_parse!( "[1J",
tag!("[") >> AnsiSequence::EraseScreenFromStart
val1: parse_int >>
tag!(";") >>
val2: parse_int >>
tag!("m") >>
val1: expr_res!(val1.try_into()) >>
val2: expr_res!(val2.try_into()) >>
conv: expr_res!(Vec::from_slice(&[
val1,
val2,
])) >>
(AnsiSequence::SetGraphicsMode(conv))
)
); );
tag_parser!(
named!( erase_line_from_start,
graphics_mode3<&str, AnsiSequence>, "[1K",
do_parse!( AnsiSequence::EraseLineFromStart
tag!("[") >>
val1: parse_int >>
tag!(";") >>
val2: parse_int >>
tag!(";") >>
val3: parse_int >>
tag!("m") >>
val1: expr_res!(val1.try_into()) >>
val2: expr_res!(val2.try_into()) >>
val3: expr_res!(val3.try_into()) >>
conv: expr_res!(Vec::from_slice(&[
val1,
val2,
val3,
])) >>
(AnsiSequence::SetGraphicsMode(conv))
)
); );
tag_parser!(cursor_up_scroll, "[ M", AnsiSequence::CursorUpScroll);
named!( tag_parser!(erase_line, "[2K", AnsiSequence::EraseLine);
graphics_mode4<&str, AnsiSequence>, tag_parser!(cursor_home, "[H", AnsiSequence::CursorHome);
do_parse!( tag_parser!(request_cursor_pos, "[6n", AnsiSequence::RequestCursorPos);
tag!("[m") >>
(AnsiSequence::SetGraphicsMode(Vec::new()))
)
);
named!(
graphics_mode5<&str, AnsiSequence>,
do_parse!(
tag!("[") >>
val1: parse_int >>
tag!(";") >>
val2: parse_int >>
tag!(";") >>
val3: parse_int >>
tag!(";") >>
val4: parse_int >>
tag!(";") >>
val5: parse_int >>
tag!("m") >>
val1: expr_res!(val1.try_into()) >>
val2: expr_res!(val2.try_into()) >>
val3: expr_res!(val3.try_into()) >>
val4: expr_res!(val4.try_into()) >>
val5: expr_res!(val5.try_into()) >>
conv: expr_res!(Vec::from_slice(&[
val1,
val2,
val3,
val4,
val5,
])) >>
(AnsiSequence::SetGraphicsMode(conv))
)
);
named!(
graphics_mode<&str, AnsiSequence>,
alt!(
graphics_mode1
| graphics_mode2
| graphics_mode3
| graphics_mode4
| graphics_mode5
)
);
named!(
set_mode<&str, AnsiSequence>,
do_parse!(
tag!("[=") >>
mode: parse_int >>
conv: expr_res!(mode.try_into()) >>
tag!("h") >>
(AnsiSequence::SetMode(conv))
)
);
named!(
reset_mode<&str, AnsiSequence>,
do_parse!(
tag!("[=") >>
mode: parse_int >>
conv: expr_res!(mode.try_into()) >>
tag!("l") >>
(AnsiSequence::ResetMode(conv))
)
);
named!(
set_top_and_bottom<&str, AnsiSequence>,
do_parse!(
tag!("[") >>
x: parse_int >>
tag!(";") >>
y: parse_int >>
tag!("r") >>
(AnsiSequence::SetTopAndBottom(x, y))
)
);
tag_parser!(cursor_save, "[s", AnsiSequence::CursorSave); tag_parser!(cursor_save, "[s", AnsiSequence::CursorSave);
tag_parser!(cursor_restore, "[u", AnsiSequence::CursorRestore); tag_parser!(cursor_restore, "[u", AnsiSequence::CursorRestore);
tag_parser!(erase_display, "[2J", AnsiSequence::EraseDisplay); tag_parser!(erase_screen, "[2J", AnsiSequence::EraseScreen);
tag_parser!(erase_line, "[K", AnsiSequence::EraseLine);
tag_parser!(hide_cursor, "[?25l", AnsiSequence::HideCursor); tag_parser!(hide_cursor, "[?25l", AnsiSequence::HideCursor);
tag_parser!(show_cursor, "[?25h", AnsiSequence::ShowCursor); tag_parser!(show_cursor, "[?25h", AnsiSequence::ShowCursor);
tag_parser!(cursor_to_app, "[?1h", AnsiSequence::CursorToApp); tag_parser!(cursor_to_app, "[?1h", AnsiSequence::CursorToApp);
@@ -276,66 +250,75 @@ tag_parser!(set_g1_graph, ")2", AnsiSequence::SetG1AltAndSpecialGraph);
tag_parser!(set_single_shift2, "N", AnsiSequence::SetSingleShift2); tag_parser!(set_single_shift2, "N", AnsiSequence::SetSingleShift2);
tag_parser!(set_single_shift3, "O", AnsiSequence::SetSingleShift3); tag_parser!(set_single_shift3, "O", AnsiSequence::SetSingleShift3);
named!( fn combined(input: &str) -> IResult<&str, AnsiSequence> {
combined<&str, AnsiSequence>, // `alt` only supports up to 21 parsers, and nom doesn't seem to
alt!( // have an alternative with higher variability.
escape // So we simply nest them.
| cursor_pos alt((
| cursor_up alt((
| cursor_down escape,
| cursor_forward cursor_pos,
| cursor_backward cursor_up,
| cursor_save cursor_down,
| cursor_restore cursor_right,
| erase_display cursor_left,
| erase_line cursor_save,
| graphics_mode cursor_restore,
| set_mode erase_screen,
| reset_mode erase_line,
| hide_cursor graphics_mode,
| show_cursor set_mode,
| cursor_to_app reset_mode,
| set_new_line_mode hide_cursor,
| set_col_132 show_cursor,
| set_smooth_scroll cursor_to_app,
| set_reverse_video set_new_line_mode,
| set_origin_rel set_col_132,
| set_auto_wrap set_smooth_scroll,
| set_auto_repeat set_reverse_video,
| set_interlacing set_origin_rel,
| set_linefeed )),
| set_cursorkey alt((
| set_vt52 set_auto_wrap,
| set_col80 set_auto_repeat,
| set_jump_scroll set_interlacing,
| set_normal_video set_linefeed,
| set_origin_abs set_cursorkey,
| reset_auto_wrap set_vt52,
| reset_auto_repeat set_col80,
| reset_interlacing set_jump_scroll,
| set_top_and_bottom set_normal_video,
| set_alternate_keypad set_origin_abs,
| set_numeric_keypad reset_auto_wrap,
| set_uk_g0 reset_auto_repeat,
| set_uk_g1 reset_interlacing,
| set_us_g0 set_top_and_bottom,
| set_us_g1 set_alternate_keypad,
| set_g0_special set_numeric_keypad,
| set_g1_special set_uk_g0,
| set_g0_alternate set_uk_g1,
| set_g1_alternate set_us_g0,
| set_g0_graph set_us_g1,
| set_g1_graph set_g0_special,
| set_single_shift2 )),
| set_single_shift3 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)
}
named!( pub fn parse_escape(input: &str) -> IResult<&str, AnsiSequence> {
pub parse_escape<&str, AnsiSequence>, preceded(tag("\u{1b}"), combined).parse(input)
do_parse!( }
tag!("\u{1b}") >>
seq: combined >>
(seq)
)
);

View File

@@ -44,6 +44,7 @@ macro_rules! test_def_val_parser {
}; };
} }
test_parser!(get_cursor_pos, "\u{1b}[6n");
test_def_val_parser!(cursor_pos_default, "\u{1b}[H"); test_def_val_parser!(cursor_pos_default, "\u{1b}[H");
test_def_val_parser!(cursor_pos, "\u{1b}[10;5H"); test_def_val_parser!(cursor_pos, "\u{1b}[10;5H");
test_def_val_parser!(cursor_up_default, "\u{1b}[A"); test_def_val_parser!(cursor_up_default, "\u{1b}[A");