pub const RULE_EQUAL: u8 = 0b0000_0001;
pub const RULE_UNDERSCORE: u8 = 0b0000_0010;
pub const RULE_HIERARCHY: u8 = 0b0000_0100;
pub const RULE_OPPOSITE: u8 = 0b0000_1000;
pub const RULE_BIGX: u8 = 0b0001_0000;
pub const RULE_HARDBLANK: u8 = 0b0010_0000;
pub const RULE_HORIZONTAL_SMUSHING: u8 = 0b0100_0000;
pub const RULE_HORIZONTAL_KERNING: u8 = 0b1000_0000;
pub fn smush_pair(left: char, right: char, rules: u8, hardblank: char) -> Option<char> {
if left == ' ' {
return Some(right);
}
if right == ' ' {
return Some(left);
}
if rules & RULE_EQUAL != 0 {
if let Some(c) = rule_equal(left, right, hardblank) {
return Some(c);
}
}
if rules & RULE_UNDERSCORE != 0 {
if let Some(c) = rule_underscore(left, right) {
return Some(c);
}
}
if rules & RULE_HIERARCHY != 0 {
if let Some(c) = rule_hierarchy(left, right) {
return Some(c);
}
}
if rules & RULE_OPPOSITE != 0 {
if let Some(c) = rule_opposite_pair(left, right) {
return Some(c);
}
}
if rules & RULE_BIGX != 0 {
if let Some(c) = rule_big_x(left, right) {
return Some(c);
}
}
if rules & RULE_HARDBLANK != 0 {
if let Some(c) = rule_hardblank(left, right, hardblank) {
return Some(c);
}
}
if rules & RULE_HORIZONTAL_SMUSHING != 0 {
Some(universal(left, right, hardblank))
} else {
None
}
}
fn rule_equal(left: char, right: char, hardblank: char) -> Option<char> {
if left == right && left != hardblank {
Some(left)
} else {
None
}
}
fn rule_underscore(left: char, right: char) -> Option<char> {
const REPLACERS: &str = "|/\\[]{}()<>";
if left == '_' && REPLACERS.contains(right) {
Some(right)
} else if right == '_' && REPLACERS.contains(left) {
Some(left)
} else {
None
}
}
fn rule_hierarchy(left: char, right: char) -> Option<char> {
const CLASSES: &[&str] = &["|", "/\\", "[]", "{}", "()", "<>"];
let l_rank = CLASSES.iter().position(|c| c.contains(left));
let r_rank = CLASSES.iter().position(|c| c.contains(right));
match (l_rank, r_rank) {
(Some(lr), Some(rr)) if lr != rr => Some(if lr > rr { left } else { right }),
_ => None,
}
}
fn rule_opposite_pair(left: char, right: char) -> Option<char> {
matches!(
(left, right),
('[', ']') | (']', '[') | ('{', '}') | ('}', '{') | ('(', ')') | (')', '(')
)
.then_some('|')
}
fn rule_big_x(left: char, right: char) -> Option<char> {
match (left, right) {
('/', '\\') => Some('|'),
('\\', '/') => Some('Y'),
('>', '<') => Some('X'),
_ => None,
}
}
fn rule_hardblank(left: char, right: char, hardblank: char) -> Option<char> {
if left == hardblank && right == hardblank {
Some(hardblank)
} else {
None
}
}
fn universal(left: char, right: char, hardblank: char) -> char {
if left == hardblank { left } else { right }
}
#[cfg(test)]
mod tests {
use super::*;
const ALL_RULES: u8 = RULE_EQUAL
| RULE_UNDERSCORE
| RULE_HIERARCHY
| RULE_OPPOSITE
| RULE_BIGX
| RULE_HARDBLANK
| RULE_HORIZONTAL_SMUSHING;
#[test]
fn space_yields_to_visible() {
assert_eq!(smush_pair(' ', 'a', 0, '$'), Some('a'));
assert_eq!(smush_pair('a', ' ', 0, '$'), Some('a'));
}
#[test]
fn rule_equal_merges_same_char() {
assert_eq!(smush_pair('|', '|', RULE_EQUAL, '$'), Some('|'));
assert_eq!(smush_pair('|', '|', RULE_EQUAL, '|'), None); }
#[test]
fn rule_underscore_replaces_with_visible() {
assert_eq!(smush_pair('_', '|', RULE_UNDERSCORE, '$'), Some('|'));
assert_eq!(smush_pair('[', '_', RULE_UNDERSCORE, '$'), Some('['));
assert_eq!(smush_pair('_', 'a', RULE_UNDERSCORE, '$'), None);
}
#[test]
fn rule_opposite_pair_yields_pipe() {
assert_eq!(smush_pair('[', ']', RULE_OPPOSITE, '$'), Some('|'));
assert_eq!(smush_pair(')', '(', RULE_OPPOSITE, '$'), Some('|'));
}
#[test]
fn rule_big_x_table() {
assert_eq!(smush_pair('/', '\\', RULE_BIGX, '$'), Some('|'));
assert_eq!(smush_pair('\\', '/', RULE_BIGX, '$'), Some('Y'));
assert_eq!(smush_pair('>', '<', RULE_BIGX, '$'), Some('X'));
}
#[test]
fn rule_hardblank_merges_hardblanks() {
assert_eq!(smush_pair('$', '$', RULE_HARDBLANK, '$'), Some('$'));
}
#[test]
fn no_smush_no_rules_returns_none() {
assert_eq!(smush_pair('@', '#', RULE_EQUAL, '$'), None);
}
#[test]
fn universal_fallback_when_smushing_enabled() {
assert_eq!(
smush_pair('@', '#', RULE_HORIZONTAL_SMUSHING, '$'),
Some('#')
);
assert_eq!(
smush_pair('$', '#', RULE_HORIZONTAL_SMUSHING, '$'),
Some('$')
);
}
#[test]
fn precedence_rule1_over_rule3() {
assert_eq!(smush_pair('|', '|', ALL_RULES, '$'), Some('|'));
}
#[test]
fn rule_equal_positive_letter() {
assert_eq!(smush_pair('x', 'x', RULE_EQUAL, '$'), Some('x'));
}
#[test]
fn rule_equal_negative_diff_chars() {
assert_eq!(smush_pair('a', 'b', RULE_EQUAL, '$'), None);
}
#[test]
fn rule_equal_hardblank_adjacent_abstains() {
assert_eq!(smush_pair('$', 'x', RULE_EQUAL, '$'), None);
}
#[test]
fn rule_underscore_positive_paren() {
assert_eq!(smush_pair('_', '(', RULE_UNDERSCORE, '$'), Some('('));
}
#[test]
fn rule_underscore_negative_letter() {
assert_eq!(smush_pair('_', 'a', RULE_UNDERSCORE, '$'), None);
}
#[test]
fn rule_underscore_hardblank_adjacent_abstains() {
assert_eq!(smush_pair('_', '$', RULE_UNDERSCORE, '$'), None);
}
#[test]
fn rule_hierarchy_positive_higher_class_wins() {
assert_eq!(smush_pair('(', '|', RULE_HIERARCHY, '$'), Some('('));
}
#[test]
fn rule_hierarchy_negative_same_class() {
assert_eq!(smush_pair('|', '|', RULE_HIERARCHY, '$'), None);
}
#[test]
fn rule_hierarchy_hardblank_adjacent_abstains() {
assert_eq!(smush_pair('$', '|', RULE_HIERARCHY, '$'), None);
}
#[test]
fn rule_opposite_positive_braces() {
assert_eq!(smush_pair('{', '}', RULE_OPPOSITE, '$'), Some('|'));
}
#[test]
fn rule_opposite_negative_same_dir() {
assert_eq!(smush_pair('[', '[', RULE_OPPOSITE, '$'), None);
}
#[test]
fn rule_opposite_hardblank_adjacent_abstains() {
assert_eq!(smush_pair('$', '[', RULE_OPPOSITE, '$'), None);
}
#[test]
fn rule_bigx_positive_diamond() {
assert_eq!(smush_pair('/', '\\', RULE_BIGX, '$'), Some('|'));
}
#[test]
fn rule_bigx_negative_non_pair() {
assert_eq!(smush_pair('/', '/', RULE_BIGX, '$'), None);
}
#[test]
fn rule_bigx_hardblank_adjacent_abstains() {
assert_eq!(smush_pair('$', '/', RULE_BIGX, '$'), None);
}
#[test]
fn rule_hardblank_positive_pair() {
assert_eq!(smush_pair('$', '$', RULE_HARDBLANK, '$'), Some('$'));
}
#[test]
fn rule_hardblank_negative_letter() {
assert_eq!(smush_pair('a', 'a', RULE_HARDBLANK, '$'), None);
}
#[test]
fn rule_hardblank_visible_paired_with_hardblank() {
assert_eq!(smush_pair('$', 'a', RULE_HARDBLANK, '$'), None);
}
#[test]
fn cross_rule_precedence_eq_over_underscore() {
assert_eq!(
smush_pair('_', '_', RULE_EQUAL | RULE_UNDERSCORE, '$'),
Some('_')
);
}
#[test]
fn cross_rule_precedence_underscore_over_hierarchy() {
assert_eq!(
smush_pair('_', '|', RULE_UNDERSCORE | RULE_HIERARCHY, '$'),
Some('|')
);
}
#[test]
fn cross_rule_precedence_hierarchy_over_opposite() {
assert_eq!(
smush_pair('(', '|', RULE_HIERARCHY | RULE_OPPOSITE, '$'),
Some('(')
);
}
#[test]
fn cross_rule_precedence_opposite_over_bigx() {
assert_eq!(
smush_pair(')', '(', RULE_OPPOSITE | RULE_BIGX, '$'),
Some('|')
);
}
#[test]
fn cross_rule_precedence_bigx_over_hardblank() {
assert_eq!(
smush_pair('/', '\\', RULE_BIGX | RULE_HARDBLANK, '$'),
Some('|')
);
}
#[test]
fn cross_rule_all_rules_letter_pair_yields_equal() {
assert_eq!(smush_pair('a', 'a', ALL_RULES, '$'), Some('a'));
}
}