#![forbid(unsafe_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EquivalenceClass {
CursorPath,
ResetVariant,
StyleOrder,
RedundantState,
BatchedWrites,
TrailingWhitespace,
}
impl EquivalenceClass {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::CursorPath => "cursor-path",
Self::ResetVariant => "reset-variant",
Self::StyleOrder => "style-order",
Self::RedundantState => "redundant-state",
Self::BatchedWrites => "batched-writes",
Self::TrailingWhitespace => "trailing-whitespace",
}
}
#[must_use]
pub const fn safe_to_optimize(&self) -> bool {
true
}
#[must_use]
pub const fn typical_savings(&self) -> TypicalSavings {
match self {
Self::CursorPath => TypicalSavings::Medium,
Self::ResetVariant => TypicalSavings::Small,
Self::StyleOrder => TypicalSavings::Negligible,
Self::RedundantState => TypicalSavings::Large,
Self::BatchedWrites => TypicalSavings::Medium, Self::TrailingWhitespace => TypicalSavings::Small,
}
}
pub const ALL: &'static [EquivalenceClass] = &[
Self::CursorPath,
Self::ResetVariant,
Self::StyleOrder,
Self::RedundantState,
Self::BatchedWrites,
Self::TrailingWhitespace,
];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TypicalSavings {
Negligible,
Small,
Medium,
Large,
}
impl TypicalSavings {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::Negligible => "negligible",
Self::Small => "small",
Self::Medium => "medium",
Self::Large => "large",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NonEquivalentVariation {
MissingRowReset,
WrongCursorPosition,
DroppedAttribute,
ColorDepthMismatch,
ContentMismatch,
WideCharMisalignment,
HyperlinkStateLeak,
}
impl NonEquivalentVariation {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::MissingRowReset => "missing-row-reset",
Self::WrongCursorPosition => "wrong-cursor-position",
Self::DroppedAttribute => "dropped-attribute",
Self::ColorDepthMismatch => "color-depth-mismatch",
Self::ContentMismatch => "content-mismatch",
Self::WideCharMisalignment => "wide-char-misalignment",
Self::HyperlinkStateLeak => "hyperlink-state-leak",
}
}
#[must_use]
pub const fn severity(&self) -> ViolationSeverity {
match self {
Self::MissingRowReset => ViolationSeverity::Major,
Self::WrongCursorPosition => ViolationSeverity::Critical,
Self::DroppedAttribute => ViolationSeverity::Major,
Self::ColorDepthMismatch => ViolationSeverity::Minor,
Self::ContentMismatch => ViolationSeverity::Critical,
Self::WideCharMisalignment => ViolationSeverity::Major,
Self::HyperlinkStateLeak => ViolationSeverity::Minor,
}
}
pub const ALL: &'static [NonEquivalentVariation] = &[
Self::MissingRowReset,
Self::WrongCursorPosition,
Self::DroppedAttribute,
Self::ColorDepthMismatch,
Self::ContentMismatch,
Self::WideCharMisalignment,
Self::HyperlinkStateLeak,
];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ViolationSeverity {
Minor,
Major,
Critical,
}
impl ViolationSeverity {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::Minor => "minor",
Self::Major => "major",
Self::Critical => "critical",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TrackedState {
Foreground,
Background,
Bold,
Italic,
Underline,
Strikethrough,
Dim,
Reverse,
Hidden,
Blink,
CursorPosition,
}
impl TrackedState {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::Foreground => "fg",
Self::Background => "bg",
Self::Bold => "bold",
Self::Italic => "italic",
Self::Underline => "underline",
Self::Strikethrough => "strikethrough",
Self::Dim => "dim",
Self::Reverse => "reverse",
Self::Hidden => "hidden",
Self::Blink => "blink",
Self::CursorPosition => "cursor",
}
}
#[must_use]
pub const fn suppressible(&self) -> bool {
true
}
#[must_use]
pub const fn transition_cost_bytes(&self) -> u32 {
match self {
Self::Foreground | Self::Background => 16, Self::CursorPosition => 8, _ => 4, }
}
}
#[derive(Debug, Clone)]
pub struct SuppressionDecision {
pub state: TrackedState,
pub suppressed: bool,
pub bytes_saved: u32,
pub reason: SuppressionReason,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SuppressionReason {
Redundant,
Changed,
Initial,
ConservativeMode,
}
impl SuppressionReason {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::Redundant => "redundant",
Self::Changed => "changed",
Self::Initial => "initial",
Self::ConservativeMode => "conservative-mode",
}
}
}
#[derive(Debug, Clone)]
pub struct TranscriptEntry {
pub offset: usize,
pub content: String,
pub effect: TranscriptEffect,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TranscriptEffect {
CursorMove,
StyleSet,
StyleReset,
Content,
Control,
}
impl TranscriptEffect {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::CursorMove => "cursor-move",
Self::StyleSet => "style-set",
Self::StyleReset => "style-reset",
Self::Content => "content",
Self::Control => "control",
}
}
}
#[derive(Debug, Clone)]
pub struct TranscriptComparison {
pub equivalent: bool,
pub baseline_bytes: usize,
pub optimized_bytes: usize,
pub bytes_saved: i64,
pub exploited_classes: Vec<EquivalenceClass>,
pub violations: Vec<NonEquivalentVariation>,
}
impl TranscriptComparison {
#[must_use]
pub fn reduction_pct(&self) -> f64 {
if self.baseline_bytes == 0 {
return 0.0;
}
(self.bytes_saved as f64 / self.baseline_bytes as f64) * 100.0
}
#[must_use]
pub fn to_json(&self) -> String {
let classes: Vec<String> = self
.exploited_classes
.iter()
.map(|c| format!("\"{}\"", c.label()))
.collect();
let violations: Vec<String> = self
.violations
.iter()
.map(|v| format!("\"{}\"", v.label()))
.collect();
format!(
r#"{{
"equivalent": {},
"baseline_bytes": {},
"optimized_bytes": {},
"bytes_saved": {},
"reduction_pct": {:.2},
"exploited_classes": [{}],
"violations": [{}]
}}"#,
self.equivalent,
self.baseline_bytes,
self.optimized_bytes,
self.bytes_saved,
self.reduction_pct(),
classes.join(", "),
violations.join(", "),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ConservativeMode {
DumbTerminal,
Multiplexer,
ImageMode,
BracketedPaste,
}
impl ConservativeMode {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::DumbTerminal => "dumb-terminal",
Self::Multiplexer => "multiplexer",
Self::ImageMode => "image-mode",
Self::BracketedPaste => "bracketed-paste",
}
}
#[must_use]
pub const fn restricted_classes(&self) -> &'static [EquivalenceClass] {
match self {
Self::DumbTerminal => &[
EquivalenceClass::CursorPath,
EquivalenceClass::RedundantState,
],
Self::Multiplexer => &[EquivalenceClass::RedundantState],
Self::ImageMode => &[EquivalenceClass::CursorPath],
Self::BracketedPaste => &[],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_equivalence_classes_safe() {
for class in EquivalenceClass::ALL {
assert!(
class.safe_to_optimize(),
"{} should be safe to optimize",
class.label()
);
}
}
#[test]
fn equivalence_class_labels() {
for class in EquivalenceClass::ALL {
assert!(!class.label().is_empty());
assert!(!class.typical_savings().label().is_empty());
}
assert_eq!(EquivalenceClass::ALL.len(), 6);
}
#[test]
fn redundant_state_has_largest_savings() {
assert_eq!(
EquivalenceClass::RedundantState.typical_savings(),
TypicalSavings::Large
);
}
#[test]
fn non_equivalent_variations_all_labeled() {
for var in NonEquivalentVariation::ALL {
assert!(!var.label().is_empty());
assert!(!var.severity().label().is_empty());
}
assert_eq!(NonEquivalentVariation::ALL.len(), 7);
}
#[test]
fn critical_violations_identified() {
assert_eq!(
NonEquivalentVariation::WrongCursorPosition.severity(),
ViolationSeverity::Critical
);
assert_eq!(
NonEquivalentVariation::ContentMismatch.severity(),
ViolationSeverity::Critical
);
}
#[test]
fn severity_ordering() {
assert!(ViolationSeverity::Minor < ViolationSeverity::Major);
assert!(ViolationSeverity::Major < ViolationSeverity::Critical);
}
#[test]
fn tracked_state_suppression() {
for state in [
TrackedState::Foreground,
TrackedState::Background,
TrackedState::Bold,
TrackedState::CursorPosition,
] {
assert!(state.suppressible());
assert!(state.transition_cost_bytes() > 0);
}
}
#[test]
fn color_transitions_most_expensive() {
assert!(
TrackedState::Foreground.transition_cost_bytes()
> TrackedState::Bold.transition_cost_bytes()
);
}
#[test]
fn transcript_comparison_reduction() {
let comp = TranscriptComparison {
equivalent: true,
baseline_bytes: 1000,
optimized_bytes: 700,
bytes_saved: 300,
exploited_classes: vec![EquivalenceClass::RedundantState],
violations: vec![],
};
assert!((comp.reduction_pct() - 30.0).abs() < 0.01);
}
#[test]
fn transcript_comparison_zero_baseline() {
let comp = TranscriptComparison {
equivalent: true,
baseline_bytes: 0,
optimized_bytes: 0,
bytes_saved: 0,
exploited_classes: vec![],
violations: vec![],
};
assert!((comp.reduction_pct()).abs() < 0.01);
}
#[test]
fn transcript_comparison_with_violations() {
let comp = TranscriptComparison {
equivalent: false,
baseline_bytes: 1000,
optimized_bytes: 800,
bytes_saved: 200,
exploited_classes: vec![],
violations: vec![NonEquivalentVariation::DroppedAttribute],
};
assert!(!comp.equivalent);
assert_eq!(comp.violations.len(), 1);
}
#[test]
fn transcript_comparison_to_json_valid() {
let comp = TranscriptComparison {
equivalent: true,
baseline_bytes: 500,
optimized_bytes: 350,
bytes_saved: 150,
exploited_classes: vec![
EquivalenceClass::RedundantState,
EquivalenceClass::CursorPath,
],
violations: vec![],
};
let json = comp.to_json();
assert!(json.contains("\"equivalent\": true"));
assert!(json.contains("\"bytes_saved\": 150"));
assert!(json.contains("\"redundant-state\""));
assert!(json.contains("\"reduction_pct\":"));
}
#[test]
fn conservative_modes_restrict_classes() {
let dumb = ConservativeMode::DumbTerminal;
assert!(!dumb.restricted_classes().is_empty());
assert!(
dumb.restricted_classes()
.contains(&EquivalenceClass::CursorPath)
);
let mux = ConservativeMode::Multiplexer;
assert!(
mux.restricted_classes()
.contains(&EquivalenceClass::RedundantState)
);
let paste = ConservativeMode::BracketedPaste;
assert!(paste.restricted_classes().is_empty());
}
#[test]
fn suppression_reasons_labeled() {
for reason in [
SuppressionReason::Redundant,
SuppressionReason::Changed,
SuppressionReason::Initial,
SuppressionReason::ConservativeMode,
] {
assert!(!reason.label().is_empty());
}
}
#[test]
fn transcript_effects_labeled() {
for effect in [
TranscriptEffect::CursorMove,
TranscriptEffect::StyleSet,
TranscriptEffect::StyleReset,
TranscriptEffect::Content,
TranscriptEffect::Control,
] {
assert!(!effect.label().is_empty());
}
}
#[test]
fn typical_savings_ordered() {
assert!(TypicalSavings::Negligible < TypicalSavings::Small);
assert!(TypicalSavings::Small < TypicalSavings::Medium);
assert!(TypicalSavings::Medium < TypicalSavings::Large);
}
}