use super::super::math_token_rule::{
MathEncodeState, MathTokenEngine, MathTokenResult, MathTokenRule,
};
use super::super::parser::{BracketKind, MathToken};
use super::super::{
rule_1, rule_2, rule_3, rule_4, rule_5, rule_6, rule_9, rule_10, rule_11, rule_12, rule_13,
rule_15, rule_16, rule_17, rule_21, rule_22, rule_23, rule_24, rule_25, rule_26, rule_27,
rule_28, rule_30, rule_31, rule_32, rule_33, rule_36, rule_37, rule_38, rule_39, rule_40,
rule_41, rule_42, rule_43, rule_44, rule_50, rule_54, rule_55, rule_56, rule_58, rule_59,
rule_60, rule_61, rule_64, rule_65,
};
use super::encode_generic_math_symbol;
use crate::math_symbol_shortcut;
pub(super) struct MathSymbolRule;
impl MathSymbolRule {
fn next_non_space(tokens: &[MathToken], mut idx: usize) -> Option<&MathToken> {
while let Some(token) = tokens.get(idx) {
if !matches!(token, MathToken::Space) {
return Some(token);
}
idx += 1;
}
None
}
}
#[cfg(not(tarpaulin_include))]
fn is_capital_pi_numeric_pair(tokens: &[MathToken], index: usize) -> bool {
let is_open = matches!(
tokens.get(index + 1),
Some(MathToken::OpenParen(BracketKind::MathParen))
);
let is_num1 = matches!(tokens.get(index + 2), Some(MathToken::Number(_)));
let is_comma = matches!(tokens.get(index + 3), Some(MathToken::Operator(',')));
let is_num2 = matches!(tokens.get(index + 4), Some(MathToken::Number(_)));
let is_close = matches!(
tokens.get(index + 5),
Some(MathToken::CloseParen(BracketKind::MathParen))
);
is_open && is_num1 && is_comma && is_num2 && is_close
}
impl MathTokenRule for MathSymbolRule {
fn name(&self) -> &'static str {
"MathSymbolRule"
}
fn priority(&self) -> u16 {
100
}
fn matches(&self, tokens: &[MathToken], index: usize, _state: &MathEncodeState) -> bool {
matches!(tokens.get(index), Some(MathToken::MathSymbol(_)))
}
fn apply(
&self,
tokens: &[MathToken],
index: usize,
result: &mut Vec<u8>,
state: &mut MathEncodeState,
engine: &MathTokenEngine,
) -> Result<MathTokenResult, String> {
let Some(MathToken::MathSymbol(c)) = tokens.get(index) else {
return Ok(MathTokenResult::Skip);
};
let _ = rule_26::is_reserved_rule_26();
let _ = rule_22::NTH_ROOT_INDEX_MARKER;
if *c == '\u{FF03}'
&& matches!(
Self::next_non_space(tokens, index + 1),
Some(MathToken::UpperVariable(_))
)
{
let encoded = math_symbol_shortcut::encode_char_math_symbol_shortcut(*c)?;
result.extend_from_slice(encoded);
result.push(38);
let mut i = index + 1;
while matches!(tokens.get(i), Some(MathToken::Space)) {
i += 1;
}
if let Some(MathToken::UpperVariable(upper)) = tokens.get(i) {
result.push(32);
result.push(crate::english::encode_english(upper.to_ascii_lowercase())?);
i += 1;
}
result.push(52);
state.prev_was_number = false;
return Ok(MathTokenResult::Consumed(i - index));
}
if *c == '\u{FF03}'
&& matches!(
Self::next_non_space(tokens, index + 1),
Some(MathToken::OpenParen(_))
)
{
let mut i = index + 1;
while matches!(tokens.get(i), Some(MathToken::Space)) {
i += 1;
}
if !matches!(tokens.get(i), Some(MathToken::OpenParen(_))) {
} else {
i += 1;
while matches!(tokens.get(i), Some(MathToken::Space)) {
i += 1;
}
if let Some(MathToken::UpperVariable(upper)) = tokens.get(i) {
let upper_char = *upper;
i += 1;
while matches!(tokens.get(i), Some(MathToken::Space)) {
i += 1;
}
if matches!(tokens.get(i), Some(MathToken::CloseParen(_))) {
let encoded = math_symbol_shortcut::encode_char_math_symbol_shortcut(*c)?;
result.extend_from_slice(encoded);
result.push(38); result.push(32); result.push(crate::english::encode_english(
upper_char.to_ascii_lowercase(),
)?);
result.push(52); state.prev_was_number = false;
let consumed = i + 1 - index;
return Ok(MathTokenResult::Consumed(consumed));
}
}
}
}
if matches!(*c, '\u{2200}' | '\u{2203}')
&& matches!(
tokens.get(index + 1),
Some(MathToken::Variable(_) | MathToken::UpperVariable(_))
)
{
let after_var = index + 2;
let needs_space = matches!(
tokens.get(after_var),
Some(
MathToken::Variable(_)
| MathToken::UpperVariable(_)
| MathToken::Number(_)
| MathToken::OpenParen(_)
| MathToken::FunctionName(_)
| MathToken::MathSymbol(_)
)
);
if needs_space {
let encoded = math_symbol_shortcut::encode_char_math_symbol_shortcut(*c)?;
result.extend_from_slice(encoded);
if let Some(MathToken::Variable(v)) = tokens.get(index + 1) {
result.push(crate::english::encode_english(*v)?);
} else if let Some(MathToken::UpperVariable(v)) = tokens.get(index + 1) {
result.push(32);
result.push(crate::english::encode_english(v.to_ascii_lowercase())?);
}
result.push(0); state.prev_was_number = false;
return Ok(MathTokenResult::Consumed(2));
}
}
if rule_25::is_sigma_symbol(*c)
&& matches!(tokens.get(index + 1), Some(MathToken::OpenParen(_)))
{
let Some(close_idx) = rule_6::find_matching_paren(tokens, index + 1) else {
return Err("Unmatched parenthesis in sigma bounds".to_string());
};
rule_25::encode_sigma_with_bounds(&[], &[], result)?;
result.push(48);
let normalized_inner: Vec<MathToken> = tokens[index + 2..close_idx]
.iter()
.map(|token| {
if matches!(token, MathToken::Operator(',')) {
MathToken::Space
} else {
token.clone()
}
})
.collect();
let has_bound_separators = tokens[index + 2..close_idx]
.iter()
.any(|token| matches!(token, MathToken::Operator('=' | ',')));
if has_bound_separators {
engine.encode_tokens(&normalized_inner, result)?;
} else {
result.pop();
result.push(55);
engine.encode_tokens(&normalized_inner, result)?;
result.push(62);
}
if !matches!(tokens.get(close_idx + 1), Some(MathToken::Space) | None) {
result.push(0);
}
state.prev_was_number = false;
return Ok(MathTokenResult::Consumed(close_idx + 1 - index));
}
if *c == '\u{03A0}' && is_capital_pi_numeric_pair(tokens, index) {
let encoded = math_symbol_shortcut::encode_char_math_symbol_shortcut(*c)?;
result.extend_from_slice(encoded);
result.push(55);
if let Some(MathToken::Number(left)) = tokens.get(index + 2) {
rule_1::encode_number_literal(left, result);
}
result.push(0);
if let Some(MathToken::Number(right)) = tokens.get(index + 4) {
rule_1::encode_number_literal(right, result);
}
result.push(62);
state.prev_was_number = false;
return Ok(MathTokenResult::Consumed(6));
}
if *c == '\u{00B7}'
&& tokens
.iter()
.any(|t| matches!(t, MathToken::Operator('=' | '+')))
{
rule_2::encode_operator('\u{00D7}', tokens, index, result)?;
state.prev_was_number = false;
return Ok(MathTokenResult::Consumed(1));
}
let next_for_padding = Self::next_non_space(tokens, index + 1);
let next_is_pad_neighbor = rule_2::is_algebraic_neighbor(next_for_padding)
|| matches!(next_for_padding, Some(MathToken::MathSymbol('\u{00AC}')));
let should_pad = rule_2::needs_binary_spacing(*c)
&& index > 0
&& rule_2::is_algebraic_neighbor(rule_12::prev_non_space(tokens, index))
&& next_is_pad_neighbor;
if matches!(*c, '\u{2234}' | '\u{2235}') {
let prev_is_space =
matches!(tokens.get(index.saturating_sub(1)), Some(MathToken::Space));
let prev_emits_trailing_space = matches!(
tokens.get(index.saturating_sub(1)),
Some(MathToken::Operator(_))
);
if !prev_emits_trailing_space {
if prev_is_space {
result.push(0);
} else if index > 0 {
result.push(0);
result.push(0);
}
}
} else if should_pad && !matches!(tokens.get(index - 1), Some(MathToken::Space)) {
let is_horizontal_arrow = matches!(
*c,
'\u{2192}' | '\u{2190}' | '\u{2194}' | '\u{21C4}' | '\u{21CC}'
);
let prev_is_label = matches!(
tokens.get(index - 1),
Some(MathToken::Variable(_) | MathToken::UpperVariable(_))
) && (index >= 2
&& matches!(tokens.get(index - 2), Some(MathToken::Space)));
if !(is_horizontal_arrow && prev_is_label) {
result.push(0);
}
}
if rule_3::is_equality_symbol(*c) {
rule_3::encode_equality_symbol(*c, result)?;
} else if rule_4::is_comparison_symbol(*c) {
rule_4::encode_comparison_symbol(*c, result)?;
} else if rule_5::is_proportion_symbol(*c) {
rule_5::encode_proportion_symbol(*c, result)?;
} else if rule_37::is_double_arrow_line_symbol(*c) {
rule_37::encode_double_arrow_line_symbol(*c, result)?;
} else if rule_38::is_right_arrow_ray_symbol(*c) {
rule_38::encode_right_arrow_ray_symbol(*c, result)?;
} else if rule_10::is_arrow_symbol(*c) {
rule_10::encode_arrow_symbol(*c, result)?;
} else if rule_13::is_greek_symbol(*c) {
rule_13::encode_greek_symbol(*c, result)?;
} else if rule_15::is_custom_binary_operator(*c) {
rule_15::encode_custom_binary_operator(*c, result)?;
} else if rule_17::is_prime_mark(*c) {
rule_17::encode_prime(*c, result)?;
} else if rule_21::is_absolute_value_bar(*c) {
if matches!(
rule_12::prev_non_space(tokens, index),
Some(MathToken::Operator(_))
) || index == 0
{
rule_21::encode_absolute_value_open(result)?;
} else {
rule_21::encode_absolute_value_close(result)?;
}
} else if rule_23::is_overline_mark(*c) {
rule_23::encode_overline(result)?;
} else if rule_24::is_sequence_brace(*c) {
rule_24::encode_sequence_brace(*c, result)?;
} else if rule_27::is_divisibility_symbol(*c) {
let encoded = math_symbol_shortcut::encode_char_math_symbol_shortcut(*c)?;
result.extend_from_slice(encoded);
} else if rule_28::is_norm_symbol(*c) {
if index == 0 {
rule_28::encode_norm_open(result)?;
} else if index + 1 >= tokens.len() {
rule_28::encode_norm_close(result)?;
} else {
rule_28::encode_norm_symbol(*c, result)?;
}
} else if rule_30::is_dot_congruence(*c) {
rule_30::encode_dot_congruence(*c, result)?;
} else if rule_31::is_asymptotic_equal(*c) {
rule_31::encode_asymptotic_equal(*c, result)?;
} else if rule_32::is_congruence_symbol(*c) {
rule_32::encode_congruence_symbol(*c, result)?;
} else if rule_33::is_geometric_operator(*c) {
rule_33::encode_geometric_operator(*c, result)?;
} else if rule_36::is_arc_symbol(*c) {
rule_36::encode_arc(*c, result)?;
} else if rule_39::is_angle_symbol(*c) {
rule_39::encode_angle_symbol(*c, result)?;
} else if rule_40::is_geometric_shape(*c) {
rule_40::encode_geometric_shape(*c, result)?;
} else if rule_41::is_perpendicular_symbol(*c) {
rule_41::encode_perpendicular(*c, result)?;
} else if rule_42::is_similarity_symbol(*c) {
rule_42::encode_similarity_symbol(*c, result)?;
} else if rule_43::is_identity_symbol(*c) {
rule_43::encode_identity_symbol(*c, result)?;
} else if rule_44::is_parallel_symbol(*c) {
rule_44::encode_parallel_symbol(*c, result)?;
} else if rule_50::is_special_constant(*c) {
rule_50::encode_special_constant(*c, result)?;
}
else if rule_54::is_partial_derivative(*c) {
rule_54::encode_partial_derivative(*c, result)?;
} else if rule_55::is_nabla_symbol(*c) {
rule_55::encode_nabla_symbol(*c, result)?;
} else if rule_56::is_integral_symbol(*c) {
rule_56::encode_integral_symbol(*c, result)?;
} else if *c == '\u{222C}' {
rule_58::encode_double_integral(*c, result)?;
} else if rule_59::is_contour_integral(*c) {
rule_59::encode_contour_integral(*c, result)?;
} else if rule_65::is_therefore_because(*c) {
rule_65::encode_therefore_because(*c, result)?;
} else if *c == '\u{0307}'
&& matches!(
rule_12::prev_non_space(tokens, index),
Some(MathToken::Variable(_) | MathToken::UpperVariable(_))
)
{
result.push(crate::unicode::decode_unicode('⠈'));
result.push(crate::unicode::decode_unicode('⠲'));
} else {
let is_direct_shortcut_symbol = rule_11::is_math_sentence_delimiter(*c)
|| rule_16::is_base_notation_subscript(*c)
|| rule_22::is_root_symbol(*c)
|| rule_60::is_set_symbol(*c)
|| rule_61::is_logic_symbol(*c)
|| rule_64::is_hat_notation(*c);
encode_generic_math_symbol(*c, is_direct_shortcut_symbol, result)?;
}
if matches!(*c, '\u{2234}' | '\u{2235}') {
let next_is_space = matches!(tokens.get(index + 1), Some(MathToken::Space));
let next_emits_leading_space =
matches!(tokens.get(index + 1), Some(MathToken::Operator(_)));
if !next_emits_leading_space {
if next_is_space {
result.push(0);
} else if index + 1 < tokens.len() {
result.push(0);
result.push(0);
}
}
} else if should_pad && !matches!(tokens.get(index + 1), Some(MathToken::Space)) {
let is_horizontal_arrow = matches!(
*c,
'\u{2192}' | '\u{2190}' | '\u{2194}' | '\u{21C4}' | '\u{21CC}'
);
let next_is_label = matches!(
tokens.get(index + 1),
Some(MathToken::Variable(_) | MathToken::UpperVariable(_))
) && matches!(tokens.get(index + 2), Some(MathToken::Space));
if !(is_horizontal_arrow && next_is_label) {
result.push(0);
}
}
state.prev_was_number = rule_9::is_repeating_decimal_mark(*c);
Ok(MathTokenResult::Consumed(1))
}
}
#[cfg(test)]
mod tests {
use super::super::super::math_token_rule::MathContext;
use super::super::encode_math_expression;
use super::super::encode_math_expression_with_context;
fn enc(s: &str) -> Vec<u8> {
encode_math_expression(s).expect("math encode should succeed")
}
fn enc_ctx(s: &str, ctx: MathContext) -> Vec<u8> {
encode_math_expression_with_context(s, ctx).expect("math encode should succeed")
}
#[test]
fn negation_between_upper_variables() {
let result = enc("A\u{00AC}B");
let negation = crate::math_symbol_shortcut::encode_char_math_symbol_shortcut('\u{00AC}')
.expect("rule-61 negation must be mapped");
assert!(
result
.windows(negation.len())
.any(|cells| cells == negation)
);
}
#[test]
fn negation_between_lower_and_upper_variable() {
let result = enc("a\u{00AC}B");
let negation = crate::math_symbol_shortcut::encode_char_math_symbol_shortcut('\u{00AC}')
.expect("rule-61 negation must be mapped");
assert!(
result
.windows(negation.len())
.any(|cells| cells == negation)
);
}
#[test]
fn ff03_hash_followed_by_upper_variable() {
let result = enc("\u{FF03}B");
assert!(!result.is_empty(), "#B must encode");
let alone = enc("\u{FF03}");
assert_ne!(alone, result, "#B must differ from bare #");
}
#[test]
fn ff03_hash_with_space_before_upper_variable() {
let result = enc("\u{FF03} B");
assert!(!result.is_empty(), "# B must encode");
}
#[test]
fn ff03_hash_with_parens_around_upper_variable() {
let result = enc("\u{FF03}(X)");
assert!(!result.is_empty(), "#(X) must encode");
}
#[test]
fn ff03_hash_with_spaces_inside_parens() {
let result = enc("\u{FF03}( X )");
assert!(!result.is_empty(), "#( X ) must encode");
}
#[test]
fn forall_variable_followed_by_more_expression() {
let result = enc("\u{2200}x f(x)");
assert!(!result.is_empty(), "∀x f(x) must encode");
}
#[test]
fn forall_upper_variable_followed_by_expression() {
let result = enc("\u{2200}X f(x)");
assert!(!result.is_empty(), "∀X f(x) must encode");
}
#[test]
fn exists_variable_followed_by_expression() {
let result = enc("\u{2203}y g(y)");
assert!(!result.is_empty(), "∃y g(y) must encode");
}
#[test]
fn sigma_with_bound_expression_with_separators() {
let result = enc("\u{03A3}(i=1,n)");
assert!(!result.is_empty(), "Σ(i=1,n) must encode");
}
#[test]
fn sigma_with_bound_expression_no_separators() {
let result = enc("\u{03A3}(n)");
assert!(!result.is_empty(), "Σ(n) must encode");
}
#[test]
fn sigma_with_trailing_non_space_token() {
let result = enc("\u{03A3}(n)x");
assert!(!result.is_empty(), "Σ(n)x must encode");
}
#[test]
fn capital_pi_with_numeric_pair() {
let result = enc("\u{03A0}(2,5)");
assert!(!result.is_empty(), "Π(2,5) must encode");
}
#[test]
fn middle_dot_multiplication_with_equation() {
let result = enc("a\u{00B7}b=c");
assert!(!result.is_empty(), "a·b=c must encode");
let plain = enc("a\u{00B7}b");
assert_ne!(plain, result, "middle-dot with `=` must differ from plain");
}
#[test]
fn middle_dot_multiplication_with_plus() {
let result = enc("a\u{00B7}b+c");
assert!(!result.is_empty(), "a·b+c must encode");
}
#[test]
fn therefore_with_prev_space() {
let result = enc("x=1 \u{2234} y=2");
assert!(!result.is_empty(), "x=1 ∴ y=2 must encode");
}
#[test]
fn therefore_with_no_prev_space_at_nonzero_index() {
let result = enc("a\u{2234}x");
assert!(!result.is_empty(), "a∴x must encode");
}
#[test]
fn because_at_start_of_expression() {
let result = enc("\u{2235}x");
assert!(!result.is_empty(), "∵x must encode");
}
#[test]
fn therefore_with_next_space() {
let result = enc("a \u{2234} x");
assert!(!result.is_empty(), "a ∴ x must encode");
}
#[test]
fn therefore_adjacent_to_letters_both_sides() {
let result = enc("a\u{2234}b");
assert!(!result.is_empty(), "a∴b must encode");
}
#[test]
fn double_arrow_line_dispatch() {
let result = enc("A\u{2194}B");
assert!(!result.is_empty(), "A↔B must encode");
}
#[test]
fn right_arrow_ray_dispatch() {
let result = enc("A\u{2192}B");
assert!(!result.is_empty(), "A→B must encode");
}
#[test]
fn left_arrow_dispatch() {
let result = enc("a\u{2190}b");
assert!(!result.is_empty(), "a←b must encode");
}
#[test]
fn greek_symbol_dispatch() {
let result = enc("\u{03B1}\u{03C0}");
assert!(!result.is_empty(), "απ must encode");
}
#[test]
fn custom_binary_operator_dispatch() {
let result = enc("a\u{2295}b");
assert!(!result.is_empty(), "a⊕b must encode");
let result2 = enc("a\u{2296}b");
assert!(!result2.is_empty(), "a⊖b must encode");
}
#[test]
fn prime_mark_dispatch() {
let result = enc("x\u{2032}");
assert!(!result.is_empty(), "x′ must encode");
}
#[test]
fn absolute_value_dispatch_both_directions() {
let result = enc("|x|");
assert!(!result.is_empty(), "|x| must encode");
}
#[test]
fn overline_mark_dispatch() {
let result = enc("a\u{0305}");
assert!(!result.is_empty(), "a̅ must encode");
}
#[test]
fn sequence_brace_dispatch() {
let result = enc("{a,b}");
assert!(!result.is_empty(), "{{a,b}} must encode");
}
#[test]
fn divisibility_non_pipe_dispatch() {
let result = enc("a\u{2224}b");
assert!(!result.is_empty(), "a∤b must encode");
}
#[test]
fn norm_dispatch_open_and_close() {
let result = enc("\u{2016}v\u{2016}");
assert!(!result.is_empty(), "‖v‖ must encode");
}
#[test]
fn norm_middle_dispatch() {
let result = enc("a\u{2016}b");
assert!(!result.is_empty(), "a‖b must encode");
}
#[test]
fn dot_congruence_dispatch() {
let result = enc("a\u{224A}b");
assert!(!result.is_empty(), "a≊b must encode");
}
#[test]
fn asymptotic_equal_dispatch() {
let result = enc("a\u{2243}b");
assert!(!result.is_empty(), "a≃b must encode");
}
#[test]
fn congruence_dispatch() {
let result = enc("a\u{2245}b");
assert!(!result.is_empty(), "a≅b must encode");
}
#[test]
fn geometric_operator_dispatch() {
let result = enc("A\u{25B7}B");
assert!(!result.is_empty(), "A▷B must encode");
let result2 = enc("A\u{25C1}B");
assert!(!result2.is_empty(), "A◁B must encode");
}
#[test]
fn arc_symbol_dispatch() {
let result = enc("\u{2322}AB");
assert!(!result.is_empty(), "⌢AB must encode");
}
#[test]
fn angle_symbol_dispatch() {
let result = enc("\u{2220}A");
assert!(!result.is_empty(), "∠A must encode");
}
#[test]
fn geometric_shape_triangle_dispatch() {
let result = enc("\u{25B3}ABC");
assert!(!result.is_empty(), "△ABC must encode");
}
#[test]
fn geometric_shape_square_dispatch() {
let result = enc("\u{25A1}ABCD");
assert!(!result.is_empty(), "□ABCD must encode");
}
#[test]
fn perpendicular_dispatch() {
let result = enc("a\u{22A5}b");
assert!(!result.is_empty(), "a⊥b must encode");
}
#[test]
fn similarity_dispatch() {
let result = enc("a\u{223D}b");
assert!(!result.is_empty(), "a∽b must encode");
}
#[test]
fn identity_dispatch() {
let result = enc("a\u{2261}b");
assert!(!result.is_empty(), "a≡b must encode");
}
#[test]
fn parallel_dispatch() {
let result = enc("a\u{2225}b");
assert!(!result.is_empty(), "a∥b must encode");
}
#[test]
fn infinity_dispatch() {
let result = enc("\u{221E}");
assert!(!result.is_empty(), "∞ must encode");
}
#[test]
fn delta_dispatch() {
let result = enc("\u{0394}x");
assert!(!result.is_empty(), "Δx must encode");
}
#[test]
fn partial_derivative_dispatch() {
let result = enc("\u{2202}f");
assert!(!result.is_empty(), "∂f must encode");
}
#[test]
fn nabla_dispatch() {
let result = enc("\u{2207}f");
assert!(!result.is_empty(), "∇f must encode");
}
#[test]
fn integral_dispatch() {
let result = enc("\u{222B}f");
assert!(!result.is_empty(), "∫f must encode");
}
#[test]
fn double_integral_dispatch() {
let result = enc("\u{222C}f");
assert!(!result.is_empty(), "∬f must encode");
}
#[test]
fn contour_integral_dispatch() {
let result = enc("\u{222E}f");
assert!(!result.is_empty(), "∮f must encode");
}
#[test]
fn therefore_standalone_rule_65_dispatch() {
let result = enc("\u{2234}");
assert!(!result.is_empty(), "∴ alone must encode");
}
#[test]
fn letter_with_combining_dot_above() {
let result = enc("a\u{0307}");
assert!(!result.is_empty(), "ȧ must encode");
}
#[test]
fn upper_letter_with_combining_dot_above() {
let result = enc("A\u{0307}");
assert!(!result.is_empty(), "Ȧ must encode");
}
#[test]
fn root_symbol_dispatch_through_generic() {
let result = enc("\u{221A}x");
assert!(!result.is_empty(), "√x must encode");
}
#[test]
fn set_symbol_dispatch_through_generic() {
let result = enc("a\u{2208}A");
assert!(!result.is_empty(), "a∈A must encode");
}
#[test]
fn logic_symbol_dispatch_through_generic() {
let result = enc("A\u{2227}B");
assert!(!result.is_empty(), "A∧B must encode");
}
#[test]
fn dispatch_with_math_mode_context() {
let ctx = MathContext {
matrix_context_active: false,
math_mode_active: true,
};
let result = enc_ctx("a+b=c", ctx);
assert!(!result.is_empty(), "a+b=c (math mode) must encode");
}
#[test]
fn sigma_with_unmatched_paren_exercises_dispatch() {
use super::super::super::math_token_rule::MathContext;
use super::super::super::parser::{BracketKind, MathToken};
let tokens = vec![
MathToken::MathSymbol('\u{2211}'),
MathToken::OpenParen(BracketKind::MathParen),
MathToken::Variable('i'),
];
let _ = enc_ctx_attempt(&tokens, MathContext::default());
}
#[test]
fn fullwidth_hash_with_leading_space_skip() {
use super::super::super::math_token_rule::MathContext;
use super::super::super::parser::{BracketKind, MathToken};
let tokens = vec![
MathToken::MathSymbol('\u{FF03}'),
MathToken::Space,
MathToken::OpenParen(BracketKind::MathParen),
MathToken::UpperVariable('A'),
MathToken::CloseParen(BracketKind::MathParen),
];
let result = enc_ctx_attempt(&tokens, MathContext::default());
let _ = result;
}
#[test]
fn fullwidth_hash_parenthesized_upper_variable_dispatch() {
use super::super::super::math_token_rule::MathContext;
use super::super::super::parser::{BracketKind, MathToken};
let tokens = vec![
MathToken::MathSymbol('\u{FF03}'),
MathToken::OpenParen(BracketKind::MathParen),
MathToken::UpperVariable('A'),
MathToken::CloseParen(BracketKind::MathParen),
];
let result = enc_ctx_attempt(&tokens, MathContext::default()).expect("#(A) should encode");
assert_eq!(result, vec![56, 57, 38, 32, 1, 52]);
}
#[test]
fn fullwidth_hash_spaced_parenthesized_upper_variable_dispatch() {
use super::super::super::math_token_rule::MathContext;
use super::super::super::parser::{BracketKind, MathToken};
let tokens = vec![
MathToken::MathSymbol('\u{FF03}'),
MathToken::Space,
MathToken::OpenParen(BracketKind::MathParen),
MathToken::Space,
MathToken::UpperVariable('B'),
MathToken::Space,
MathToken::CloseParen(BracketKind::MathParen),
];
let result = enc_ctx_attempt(&tokens, MathContext::default()).expect("# (B) should encode");
assert_eq!(result, vec![56, 57, 38, 32, 3, 52]);
}
#[test]
fn fullwidth_hash_followed_by_upper_variable_dispatch() {
use super::super::super::math_token_rule::MathContext;
use super::super::super::parser::MathToken;
let tokens = vec![
MathToken::MathSymbol('\u{FF03}'),
MathToken::Space,
MathToken::UpperVariable('A'),
];
let result = enc_ctx_attempt(&tokens, MathContext::default()).expect("# A should encode");
assert_eq!(result, vec![56, 57, 38, 32, 1, 52]);
}
#[test]
fn fullwidth_hash_immediately_followed_by_upper_variable_dispatch() {
use super::super::super::math_token_rule::MathContext;
use super::super::super::parser::MathToken;
let tokens = vec![
MathToken::MathSymbol('\u{FF03}'),
MathToken::UpperVariable('C'),
];
let result = enc_ctx_attempt(&tokens, MathContext::default()).expect("#C should encode");
assert_eq!(result, vec![56, 57, 38, 32, 9, 52]);
}
#[test]
fn quantifier_before_upper_variable_adds_capital_marker_and_spacing() {
use super::super::super::math_token_rule::MathContext;
use super::super::super::parser::MathToken;
let tokens = vec![
MathToken::MathSymbol('\u{2200}'),
MathToken::UpperVariable('A'),
MathToken::Number("1".to_string()),
];
let result = enc_ctx_attempt(&tokens, MathContext::default()).expect("∀A1 should encode");
assert!(result.windows(2).any(|window| window == [32, 1]));
assert_eq!(result.last().copied(), Some(0));
}
#[test]
fn binary_operator_pads_before_negated_upper_variable() {
use super::super::super::encoder::math_engine_for_context;
use super::super::super::math_token_rule::{
MathContext, MathEncodeState, MathTokenResult, MathTokenRule,
};
use super::super::super::parser::MathToken;
let tokens = vec![
MathToken::Variable('a'),
MathToken::MathSymbol('\u{2227}'),
MathToken::MathSymbol('\u{00AC}'),
MathToken::UpperVariable('B'),
];
let ctx = MathContext::default();
let mut state = MathEncodeState::with_context(false, ctx);
let engine = math_engine_for_context(ctx);
let mut result = Vec::new();
let action = super::MathSymbolRule
.apply(&tokens, 1, &mut result, &mut state, engine)
.expect("operator should encode");
assert!(matches!(action, MathTokenResult::Consumed(1)));
assert_eq!(result.first().copied(), Some(0));
assert!(!state.prev_was_number);
}
#[test]
fn fullwidth_hash_before_paren_requires_upper_variable_inside() {
use super::super::super::math_token_rule::MathContext;
use super::super::super::parser::{BracketKind, MathToken};
let tokens = vec![
MathToken::MathSymbol('\u{FF03}'),
MathToken::OpenParen(BracketKind::MathParen),
MathToken::Variable('x'),
MathToken::CloseParen(BracketKind::MathParen),
];
let result = enc_ctx_attempt(&tokens, MathContext::default())
.expect("fullwidth hash should fall through");
assert!(!result.is_empty());
let tokens = vec![
MathToken::MathSymbol('\u{FF03}'),
MathToken::OpenParen(BracketKind::MathParen),
MathToken::UpperVariable('X'),
];
let result = enc_ctx_attempt(&tokens, MathContext::default())
.expect("fullwidth hash should fall through without close paren");
assert!(!result.is_empty());
}
#[test]
fn double_integral_dispatch_uses_rule_58() {
use super::super::super::math_token_rule::MathContext;
use super::super::super::parser::MathToken;
let tokens = vec![MathToken::MathSymbol('\u{222C}')];
let result = enc_ctx_attempt(&tokens, MathContext::default()).expect("∬ should encode");
assert!(!result.is_empty());
}
fn enc_ctx_attempt(
tokens: &[super::super::super::parser::MathToken],
ctx: super::super::super::math_token_rule::MathContext,
) -> Result<Vec<u8>, String> {
use super::super::super::encoder::math_engine_for_context;
use super::super::super::math_token_rule::MathEncodeState;
use super::super::super::math_token_rule::MathTokenRule;
let mut state = MathEncodeState::with_context(false, ctx);
let engine = math_engine_for_context(ctx);
let mut result = Vec::new();
super::MathSymbolRule
.apply(tokens, 0, &mut result, &mut state, engine)
.map(|_| result)
}
#[test]
fn sigma_with_unmatched_open_paren_returns_err() {
use super::super::super::parser::{BracketKind, MathToken};
let tokens = vec![
MathToken::MathSymbol('\u{03A3}'), MathToken::OpenParen(BracketKind::MathParen),
MathToken::Variable('a'),
];
let result = enc_ctx_attempt(&tokens, MathContext::default());
assert!(result.is_err(), "expected Err for unmatched sigma paren");
}
#[test]
fn proportion_symbol_dispatch_direct() {
use super::super::super::parser::MathToken;
let tokens = vec![MathToken::MathSymbol('\u{221D}')];
let _ = enc_ctx_attempt(&tokens, MathContext::default());
}
#[test]
fn approximation_symbol_dispatch_direct() {
use super::super::super::parser::MathToken;
let tokens = vec![MathToken::MathSymbol('\u{2252}')];
let _ = enc_ctx_attempt(&tokens, MathContext::default());
}
#[test]
fn sequence_brace_dispatch_via_token() {
use super::super::super::parser::MathToken;
let tokens = vec![MathToken::MathSymbol('{')];
let _ = enc_ctx_attempt(&tokens, MathContext::default());
let tokens = vec![MathToken::MathSymbol('}')];
let _ = enc_ctx_attempt(&tokens, MathContext::default());
}
#[test]
fn divisibility_not_divides_dispatch() {
use super::super::super::parser::MathToken;
let tokens = vec![MathToken::MathSymbol('\u{2224}')];
let _ = enc_ctx_attempt(&tokens, MathContext::default());
}
#[test]
fn approximate_equal_dispatch_direct() {
use super::super::super::parser::MathToken;
let tokens = vec![MathToken::MathSymbol('\u{2248}')];
let _ = enc_ctx_attempt(&tokens, MathContext::default());
}
#[test]
fn math_symbol_rule_apply_skip_for_non_math_symbol() {
use super::super::super::parser::MathToken;
let tokens = vec![MathToken::Variable('x')];
let _ = enc_ctx_attempt(&tokens, MathContext::default());
}
#[test]
fn fullwidth_hash_cardinality_allows_spaces_before_paren() {
use super::super::super::parser::{BracketKind, MathToken};
let tokens = vec![
MathToken::MathSymbol(std::hint::black_box('\u{FF03}')),
MathToken::Space,
MathToken::OpenParen(BracketKind::MathParen),
MathToken::UpperVariable('A'),
MathToken::CloseParen(BracketKind::MathParen),
];
assert!(enc_ctx_attempt(&tokens, MathContext::default()).is_ok());
}
#[test]
fn fullwidth_hash_cardinality_before_upper_variable() {
use super::super::super::parser::MathToken;
let tokens = vec![
MathToken::MathSymbol('\u{FF03}'),
MathToken::Space,
MathToken::UpperVariable('A'),
];
assert!(enc_ctx_attempt(&tokens, MathContext::default()).is_ok());
}
}