diff options
Diffstat (limited to 'src/color_new.rs')
-rw-r--r-- | src/color_new.rs | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/src/color_new.rs b/src/color_new.rs new file mode 100644 index 0000000..1d3e358 --- /dev/null +++ b/src/color_new.rs @@ -0,0 +1,281 @@ +#[non_exhaustive] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum Color { + Black, + DarkGrey, + Red, + DarkRed, + Green, + DarkGreen, + Yellow, + DarkYellow, + Blue, + DarkBlue, + Magenta, + DarkMagenta, + Cyan, + DarkCyan, + White, + Grey, +} + +pub trait YaxColors { + fn arithmetic_op(&self) -> Color; + fn stack_op(&self) -> Color; + fn nop_op(&self) -> Color; + fn stop_op(&self) -> Color; + fn control_flow_op(&self) -> Color; + fn data_op(&self) -> Color; + fn comparison_op(&self) -> Color; + fn invalid_op(&self) -> Color; + fn platform_op(&self) -> Color; + fn misc_op(&self) -> Color; + + fn register(&self) -> Color; + fn program_counter(&self) -> Color; + fn number(&self) -> Color; + fn zero(&self) -> Color; + fn one(&self) -> Color; + fn minus_one(&self) -> Color; + fn address(&self) -> Color; + fn symbol(&self) -> Color; + fn function(&self) -> Color; +} + +/// support for colorizing text with ANSI control sequences. +/// +/// the most useful item in this module is [`ansi::AnsiDisplaySink`], which interprets span entry +/// and exit as points at which ANSI sequences may need to be written into the output it wraps - +/// that output may be any type implementing [`crate::display::DisplaySink`], including +/// [`crate::display::FmtSink`] to adapt any implementer of `fmt::Write` such as standard out. +/// +/// ## example +/// +/// to write colored text to standard out: +/// +/// ``` +/// # #[cfg(feature="alloc")] +/// # { +/// # extern crate alloc; +/// # use alloc::string::String; +/// use yaxpeax_arch::color_new::DefaultColors; +/// use yaxpeax_arch::color_new::ansi::AnsiDisplaySink; +/// use yaxpeax_arch::display::FmtSink; +/// +/// let mut s = String::new(); +/// let mut s_sink = FmtSink::new(&mut s); +/// +/// let mut writer = AnsiDisplaySink::new(&mut s_sink, DefaultColors); +/// +/// // this might be a yaxpeax crate's `display_into`, or other library implementation code +/// mod fake_yaxpeax_crate { +/// use yaxpeax_arch::display::DisplaySink; +/// +/// pub fn format_memory_operand<T: DisplaySink>(out: &mut T) -> core::fmt::Result { +/// out.span_start_immediate(); +/// out.write_prefixed_u8(0x80)?; +/// out.span_end_immediate(); +/// out.write_fixed_size("(")?; +/// out.span_start_register(); +/// out.write_fixed_size("rbp")?; +/// out.span_end_register(); +/// out.write_fixed_size(")")?; +/// Ok(()) +/// } +/// } +/// +/// // this might be how a user uses `AnsiDisplaySink`, which will write ANSI-ful text to `s` and +/// // print it. +/// +/// fake_yaxpeax_crate::format_memory_operand(&mut writer).expect("write succeeds"); +/// +/// println!("{}", s); +/// # } +/// ``` +pub mod ansi { + use crate::color_new::Color; + + // color sequences as described by ECMA-48 and, apparently, `man 4 console_codes` + /// translate [`yaxpeax_arch::color_new::Color`] to an ANSI control code that changes the + /// foreground color to match. + #[allow(dead_code)] // allowing this to be dead code because if colors are enabled and alloc is not, there will not be an AnsiDisplaySink, which is the sole user of this function. + fn color2ansi(color: Color) -> &'static str { + // for most of these, in 256 color space the darker color can be picked by the same color + // index as the brighter form (from the 8 color command set). dark grey is an outlier, + // where 38;5;0 and 30 both are black. there is no "grey" in the shorter command set to + // map to. but it turns out that 38;5;m is exactly the darker grey to use. + match color { + Color::Black => "\x1b[30m", + Color::DarkGrey => "\x1b[38;5;8m", + Color::Red => "\x1b[31m", + Color::DarkRed => "\x1b[38;5;1m", + Color::Green => "\x1b[32m", + Color::DarkGreen => "\x1b[38;5;2m", + Color::Yellow => "\x1b[33m", + Color::DarkYellow => "\x1b[38;5;3m", + Color::Blue => "\x1b[34m", + Color::DarkBlue => "\x1b[38;5;4m", + Color::Magenta => "\x1b[35m", + Color::DarkMagenta => "\x1b[38;5;5m", + Color::Cyan => "\x1b[36m", + Color::DarkCyan => "\x1b[38;5;6m", + Color::White => "\x1b[37m", + Color::Grey => "\x1b[38;5;7m", + } + } + + // could reasonably be always present, but only used if feature="alloc" + #[cfg(feature="alloc")] + const DEFAULT_FG: &'static str = "\x1b[39m"; + + #[cfg(feature="alloc")] + mod ansi_display_sink { + use crate::color_new::{Color, YaxColors}; + use crate::display::DisplaySink; + + /// adapter to insert ANSI color command sequences in formatted text to style printed + /// instructions. + /// + /// this enables similar behavior as the deprecated [`crate::Colorize`] trait, + /// for outputs that can process ANSI color commands. + /// + /// `AnsiDisplaySink` will silently ignore errors from writes to the underlying `T: + /// DisplaySink`. when writing to a string or other growable buffer, errors are likely + /// inseparable from `abort()`. when writing to stdout or stderr, write failures likely + /// mean output is piped to a process which has closed the pipe but are otherwise harmless. + /// `span_enter_*` and `span_exit_*` don't have error reporting mechanisms in their return + /// type, so the only available error mechanism would be to also `abort()`. + /// + /// if this turns out to be a bad decision, it'll have to be rethought! + pub struct AnsiDisplaySink<'sink, T: DisplaySink, Y: YaxColors> { + out: &'sink mut T, + span_stack: alloc::vec::Vec<Color>, + colors: Y + } + + impl<'sink, T: DisplaySink, Y: YaxColors> AnsiDisplaySink<'sink, T, Y> { + pub fn new(out: &'sink mut T, colors: Y) -> Self { + Self { + out, + span_stack: alloc::vec::Vec::new(), + colors, + } + } + + fn push_color(&mut self, color: Color) { + self.span_stack.push(color); + let _ = self.out.write_fixed_size(super::color2ansi(color)); + } + + fn restore_prev_color(&mut self) { + let _ = self.span_stack.pop(); + if let Some(prev_color) = self.span_stack.last() { + let _ = self.out.write_fixed_size(super::color2ansi(*prev_color)); + } else { + let _ = self.out.write_fixed_size(super::DEFAULT_FG); + }; + } + } + + impl<'sink, T: DisplaySink, Y: YaxColors> core::fmt::Write for AnsiDisplaySink<'sink, T, Y> { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + self.out.write_str(s) + } + fn write_char(&mut self, c: char) -> Result<(), core::fmt::Error> { + self.out.write_char(c) + } + } + + impl<'sink, T: DisplaySink, Y: YaxColors> DisplaySink for AnsiDisplaySink<'sink, T, Y> { + fn span_start_immediate(&mut self) { self.push_color(self.colors.number()); } + fn span_end_immediate(&mut self) { self.restore_prev_color() } + + fn span_start_register(&mut self) { self.push_color(self.colors.register()); } + fn span_end_register(&mut self) { self.restore_prev_color() } + + // ah.. the right way, currently, to colorize opcodes would be to collect text while in the + // opcode span, and request some kind of user-provided decoder ring to translate mnemonics + // into the right color. that's very unfortunate. maybe there should be another span for + // `opcode_kind(u8)` for impls to report what kind of opcode they'll be emitting.. + fn span_start_opcode(&mut self) { self.push_color(self.colors.misc_op()); } + fn span_end_opcode(&mut self) { self.restore_prev_color() } + + fn span_start_program_counter(&mut self) { self.push_color(self.colors.program_counter()); } + fn span_end_program_counter(&mut self) { self.restore_prev_color() } + + fn span_start_number(&mut self) { self.push_color(self.colors.number()); } + fn span_end_number(&mut self) { self.restore_prev_color() } + + fn span_start_address(&mut self) { self.push_color(self.colors.address()); } + fn span_end_address(&mut self) { self.restore_prev_color() } + + fn span_start_function_expr(&mut self) { self.push_color(self.colors.function()); } + fn span_end_function_expr(&mut self) { self.restore_prev_color() } + } + } + #[cfg(feature="alloc")] + pub use ansi_display_sink::AnsiDisplaySink; +} + +pub struct DefaultColors; + +impl YaxColors for DefaultColors { + fn arithmetic_op(&self) -> Color { + Color::Yellow + } + fn stack_op(&self) -> Color { + Color::DarkMagenta + } + fn nop_op(&self) -> Color { + Color::DarkBlue + } + fn stop_op(&self) -> Color { + Color::Red + } + fn control_flow_op(&self) -> Color { + Color::DarkGreen + } + fn data_op(&self) -> Color { + Color::Magenta + } + fn comparison_op(&self) -> Color { + Color::DarkYellow + } + fn invalid_op(&self) -> Color { + Color::DarkRed + } + fn misc_op(&self) -> Color { + Color::Cyan + } + fn platform_op(&self) -> Color { + Color::DarkCyan + } + + fn register(&self) -> Color { + Color::DarkCyan + } + fn program_counter(&self) -> Color { + Color::DarkRed + } + fn number(&self) -> Color { + Color::White + } + fn zero(&self) -> Color { + Color::White + } + fn one(&self) -> Color { + Color::White + } + fn minus_one(&self) -> Color { + Color::White + } + fn address(&self) -> Color { + Color::DarkGreen + } + fn symbol(&self) -> Color { + Color::Green + } + fn function(&self) -> Color { + Color::Green + } +} |