### LaTeX Math with Integrals and Functions Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token_rules/latex_math/strip.rs.html Examples of LaTeX math expressions involving integrals with limits, and function notations with subscripts and superscripts. ```Rust "\\int_0^1 f(x) dx", ``` ```Rust "\\xrightarrow[g]{f}", ``` ```Rust "\\xrightleftharpoons[g]{f}", ``` ```Rust "\\xrightleftharpoons[a[b]c]{label}" ``` -------------------------------- ### Example BrailleRule Implementation Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/traits.rs.html Illustrates how to implement the BrailleRule trait for a specific rule, including metadata, phase, matching, and application logic. ```rust struct Rule11VowelYe; implement BrailleRule for Rule11VowelYe { fn meta(&self) -> &'static RuleMeta { &META } fn phase(&self) -> Phase { Phase::InterCharacter } fn matches(&self, ctx: &RuleContext) -> bool { /* check conditions */ } fn apply(&self, ctx: &mut RuleContext) -> Result { ctx.emit(36); // ⠤ separator Ok(RuleResult::Continue) } } ``` -------------------------------- ### RuleEngine Usage Example Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/engine.rs.html Demonstrates how to create a new RuleEngine, register rules, disable a specific rule by its section ID, and apply the engine to a character context. ```rust let mut engine = RuleEngine::new(); engine.register(Box::new(Rule11VowelYe)); engine.register(Box::new(Rule12VowelAe)); // Disable a specific rule: engine.disable("12"); // Apply to a character context: engine.apply(&mut ctx)?; ``` -------------------------------- ### RuleEngine Usage Example Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/mod.rs.html Demonstrates how to initialize a RuleEngine, register braille rules, disable specific rules, and apply enabled rules to a context. This is useful for setting up the braille conversion process. ```rust let mut engine = RuleEngine::new(); engine.register(Box::new(korean::rule_11::Rule11)); engine.register(Box::new(korean::rule_12::Rule12)); engine.disable("12"); // disable a specific rule engine.apply(&mut ctx)?; ``` -------------------------------- ### Encode Digital Notations Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token_rules/digital_notation.rs.html Examples of encoding different digital notations including sharps, flats, slashes, underscores, and colons. ```rust let _ = crate::encode("c#d"); let _ = crate::encode("e//f"); let _ = crate::encode("g_h.i"); let _ = crate::encode("x_y:z"); ``` -------------------------------- ### Initialize Encoder with Rules Source: https://docs.rs/braillify/2.0.1/src/braillify/encoder.rs.html Constructs a new `Encoder` instance and registers a comprehensive set of Korean-specific encoding rules with its `RuleEngine`. This setup is crucial for correct Braille conversion. ```rust impl Encoder { pub fn new(english_indicator: bool) -> Self { let mut rule_engine = rules::engine::RuleEngine::new(); // ── Preprocessing ──────────────────────────────── rule_engine.register(Box::new(rules::korean::rule_53::Rule53)); // ── WordShortcut ───────────────────────────────── rule_engine.register(Box::new(rules::korean::rule_18::Rule18)); // ── ModeManagement ─────────────────────────────── rule_engine.register(Box::new(rules::korean::rule_29::Rule29)); // ── CoreEncoding ───────────────────────────────── rule_engine.register(Box::new(rules::korean::rule_44::Rule44)); rule_engine.register(Box::new(rules::korean::rule_66::Rule66)); rule_engine.register(Box::new(rules::korean::rule_67::Rule67)); rule_engine.register(Box::new(rules::korean::rule_27::Rule27)); rule_engine.register(Box::new(rules::korean::rule_19::Rule19)); rule_engine.register(Box::new(rules::korean::rule_20::Rule20)); rule_engine.register(Box::new(rules::korean::rule_21::Rule21)); rule_engine.register(Box::new(rules::korean::rule_22::Rule22)); rule_engine.register(Box::new(rules::korean::rule_23::Rule23)); rule_engine.register(Box::new(rules::korean::rule_24::Rule24)); rule_engine.register(Box::new(rules::korean::rule_25::Rule25)); rule_engine.register(Box::new(rules::korean::rule_26::Rule26)); rule_engine.register(Box::new(rules::korean::rule_16::Rule16)); rule_engine.register(Box::new(rules::korean::rule_14::Rule14)); rule_engine.register(Box::new(rules::korean::rule_13::Rule13)); rule_engine.register(Box::new(rules::korean::rule_korean::RuleKorean)); rule_engine.register(Box::new(rules::korean::rule_28::Rule28)); rule_engine.register(Box::new(rules::korean::rule_40::Rule40)); rule_engine.register(Box::new(rules::korean::rule_31::Rule31)); rule_engine.register(Box::new(rules::korean::rule_8::Rule8)); rule_engine.register(Box::new(rules::korean::rule_2::Rule2)); rule_engine.register(Box::new(rules::korean::rule_1::Rule1)); rule_engine.register(Box::new(rules::korean::rule_3::Rule3)); rule_engine.register(Box::new( rules::korean::rule_english_symbol::RuleEnglishSymbol, )); rule_engine.register(Box::new(rules::korean::rule_68::Rule68)); rule_engine.register(Box::new(rules::korean::rule_69::Rule69)); rule_engine.register(Box::new(rules::korean::rule_70::Rule70)); rule_engine.register(Box::new(rules::korean::rule_71::Rule71)); rule_engine.register(Box::new(rules::korean::rule_72::Rule72)); rule_engine.register(Box::new(rules::korean::rule_73::Rule73)); rule_engine.register(Box::new(rules::korean::rule_74::Rule74)); rule_engine.register(Box::new(rules::korean::rule_61::Rule61)); rule_engine.register(Box::new(rules::korean::rule_41::Rule41)); rule_engine.register(Box::new(rules::korean::rule_56::Rule56)); rule_engine.register(Box::new(rules::korean::rule_57::Rule57)); rule_engine.register(Box::new(rules::korean::rule_58::Rule58)); rule_engine.register(Box::new(rules::korean::rule_60::Rule60)); rule_engine.register(Box::new(rules::korean::rule_64::Rule64)); rule_engine.register(Box::new(rules::korean::rule_64::Rule64Square)); rule_engine.register(Box::new(rules::korean::rule_65::Rule65)); rule_engine.register(Box::new(rules::korean::rule_49::Rule49)); rule_engine.register(Box::new(rules::korean::rule_space::RuleSpace)); // ... other fields initialization ... Encoder { is_english: false, // Default or determined elsewhere triple_big_english: false, english_indicator, has_processed_word: false, needs_english_continuation: false, parenthesis_stack: Vec::new(), default_mode: None, matrix_context_active: false, math_mode_active: false, rule_engine, token_engine: rules::token_engine::TokenRuleEngine::new(), } } } ``` -------------------------------- ### Encoder Initialization with Korean Rules Source: https://docs.rs/braillify/latest/src/braillify/encoder.rs.html Initializes a new `Encoder` instance and registers a comprehensive set of Korean-specific rules with the `RuleEngine`. This setup is crucial for accurate Korean Braille conversion. ```rust impl Encoder { pub fn new(english_indicator: bool) -> Self { let mut rule_engine = rules::engine::RuleEngine::new(); // ── Preprocessing ──────────────────────────────── rule_engine.register(Box::new(rules::korean::rule_53::Rule53)); // ── WordShortcut ───────────────────────────────── rule_engine.register(Box::new(rules::korean::rule_18::Rule18)); // ── ModeManagement ─────────────────────────────── rule_engine.register(Box::new(rules::korean::rule_29::Rule29)); // ── CoreEncoding ───────────────────────────────── rule_engine.register(Box::new(rules::korean::rule_44::Rule44)); rule_engine.register(Box::new(rules::korean::rule_66::Rule66)); rule_engine.register(Box::new(rules::korean::rule_67::Rule67)); rule_engine.register(Box::new(rules::korean::rule_27::Rule27)); rule_engine.register(Box::new(rules::korean::rule_19::Rule19)); rule_engine.register(Box::new(rules::korean::rule_20::Rule20)); rule_engine.register(Box::new(rules::korean::rule_21::Rule21)); rule_engine.register(Box::new(rules::korean::rule_22::Rule22)); rule_engine.register(Box::new(rules::korean::rule_23::Rule23)); rule_engine.register(Box::new(rules::korean::rule_24::Rule24)); rule_engine.register(Box::new(rules::korean::rule_25::Rule25)); rule_engine.register(Box::new(rules::korean::rule_26::Rule26)); rule_engine.register(Box::new(rules::korean::rule_16::Rule16)); rule_engine.register(Box::new(rules::korean::rule_14::Rule14)); rule_engine.register(Box::new(rules::korean::rule_13::Rule13)); rule_engine.register(Box::new(rules::korean::rule_korean::RuleKorean)); rule_engine.register(Box::new(rules::korean::rule_28::Rule28)); rule_engine.register(Box::new(rules::korean::rule_40::Rule40)); rule_engine.register(Box::new(rules::korean::rule_31::Rule31)); rule_engine.register(Box::new(rules::korean::rule_8::Rule8)); rule_engine.register(Box::new(rules::korean::rule_2::Rule2)); rule_engine.register(Box::new(rules::korean::rule_1::Rule1)); rule_engine.register(Box::new(rules::korean::rule_3::Rule3)); rule_engine.register(Box::new( rules::korean::rule_english_symbol::RuleEnglishSymbol, )); rule_engine.register(Box::new(rules::korean::rule_68::Rule68)); rule_engine.register(Box::new(rules::korean::rule_69::Rule69)); rule_engine.register(Box::new(rules::korean::rule_70::Rule70)); rule_engine.register(Box::new(rules::korean::rule_71::Rule71)); rule_engine.register(Box::new(rules::korean::rule_72::Rule72)); rule_engine.register(Box::new(rules::korean::rule_73::Rule73)); rule_engine.register(Box::new(rules::korean::rule_74::Rule74)); rule_engine.register(Box::new(rules::korean::rule_61::Rule61)); rule_engine.register(Box::new(rules::korean::rule_41::Rule41)); rule_engine.register(Box::new(rules::korean::rule_56::Rule56)); rule_engine.register(Box::new(rules::korean::rule_57::Rule57)); rule_engine.register(Box::new(rules::korean::rule_58::Rule58)); rule_engine.register(Box::new(rules::korean::rule_60::Rule60)); rule_engine.register(Box::new(rules::korean::rule_64::Rule64)); rule_engine.register(Box::new(rules::korean::rule_64::Rule64Square)); rule_engine.register(Box::new(rules::korean::rule_65::Rule65)); rule_engine.register(Box::new(rules::korean::rule_49::Rule49)); rule_engine.register(Box::new(rules::korean::rule_space::RuleSpace)); // ... other fields initialization ... Encoder { is_english: false, // Default or based on logic triple_big_english: false, english_indicator, has_processed_word: false, needs_english_continuation: false, parenthesis_stack: Vec::new(), default_mode: None, matrix_context_active: false, math_mode_active: false, rule_engine, token_engine: rules::token_engine::TokenRuleEngine::new(), } } } ``` -------------------------------- ### LaTeX Geometric and Perpendicularity Symbols Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token_rules/latex_math/strip.rs.html Examples of LaTeX commands for geometric shapes like angles and triangles, and symbols indicating perpendicularity and parallelism. ```Rust "\\angle ABC", ``` ```Rust "\\triangle ABC", ``` ```Rust "\\perp", ``` ```Rust "\\parallel" ``` -------------------------------- ### DefiniteIntegralRule Application Example Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/math/rule_57.rs.html Demonstrates the application of the DefiniteIntegralRule with a specific set of math tokens, including an opening parenthesis but no closing parenthesis. It asserts that the rule should result in a Skip outcome. ```rust use crate::rules::math::parser::{BracketKind, MathToken}; let r = super::DefiniteIntegralRule; let toks = vec![ MathToken::MathSymbol('\u{222B}'), MathToken::OpenParen(BracketKind::MathParen), MathToken::Variable('a'), // No CloseParen ]; let mut state = MathEncodeState::with_context(false, MathContext::default()); let mut result = Vec::new(); let engine = crate::rules::math::math_token_rule::MathTokenEngine::with_context( MathContext::default(), ); let res = r.apply(&toks, 0, &mut result, &mut state, &engine); assert!(matches!( res, Ok(crate::rules::math::math_token_rule::MathTokenResult::Skip) )); ``` -------------------------------- ### Registering and Applying a Rule Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/engine.rs.html Demonstrates how to create a `RuleEngine`, register a custom rule (`TestRule`), and then apply it to a `RuleContext`. Asserts that the rule is applied and produces the expected output. ```rust let mut engine = RuleEngine::new(); engine.register(Box::new(TestRule)); assert_eq!(engine.rule_count(), 1); let word_chars = vec!['가']; let char_type = crate::char_struct::CharType::new('가').unwrap(); let mut state = EncoderState::new(false); let mut result = Vec::new(); let mut skip = 0usize; let empty: Vec<&str> = vec![]; let mut ctx = RuleContext { word_chars: &word_chars, index: 0, char_type: &char_type, prev_word: "", remaining_words: &empty, has_korean_char: true, is_all_uppercase: false, ascii_starts_at_beginning: false, skip_count: &mut skip, state: &mut state, result: &mut result, }; let outcome = engine.apply(&mut ctx).unwrap(); assert_eq!(outcome, RuleResult::Consumed); assert_eq!(result, vec![99]); ``` -------------------------------- ### Test: Decimal Starting with Dot is Math Expression Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token_rules/math_expression.rs.html Verifies that decimal numbers starting with a dot are identified as math expressions. ```rust #[test] fn test_decimal_starting_with_dot_is_math() { // ".47"처럼 점으로 시작하는 형태는 math expression. let chars: Vec = ".47".chars().collect(); assert!(is_math_expression(&chars, ".47")); } ``` -------------------------------- ### Detect Starts with Math Symbol and Digits as Math Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token_rules/math_expression/detect.rs.html Identifies tokens starting with a math symbol followed by digits as mathematical expressions. ```Rust if starts_with_math_symbol && has_digits { return true; } ``` -------------------------------- ### Check for Hangul Wrap Start Bytes Source: https://docs.rs/braillify/latest/src/braillify/rules/emit.rs.html Checks if a token's byte slice matches the Hangul wrap start braille pattern (⠸⠷). ```rust fn is_hangul_wrap_start(token: &Token<'_>) -> bool { matches!(token, Token::PreEncoded(bytes) if bytes.as_slice() == HANGUL_WRAP_START_BYTES) } ``` -------------------------------- ### RuleContext Initialization and Phase Application Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/engine.rs.html Demonstrates the initialization of a `RuleContext` and the application of a specific phase using the `engine.apply_phase` method. This snippet is useful for understanding the state management and rule execution flow within the Braillify engine. ```rust let mut state = EncoderState::new(false); let mut result = Vec::new(); let mut ctx = RuleContext { word_chars: &word_chars, index: 0, char_type: &char_type, prev_word: "", remaining_words: &empty, has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, skip_count: &mut skip, state: &mut state, result: &mut result, }; let outcome = engine.apply_phase(Phase::CoreEncoding, &mut ctx).unwrap(); assert_eq!(outcome, RuleResult::Skip); ``` -------------------------------- ### Test: Invalid Start >= End Span Source: https://docs.rs/braillify/2.0.1/src/braillify/encoder.rs.html Tests the error condition where a formatting span's start index is greater than or equal to its end index. ```rust /// `inject_formatting_tokens` Err arm for span start >= end (line 297). #[test] fn inject_formatting_invalid_start_ge_end() { let text = "abc"; let spans = vec![FormattingSpan { kind: FormattingKind::Emphasis, range: 2..2, }]; let mut tokens: Vec> = vec![]; let result = inject_formatting_tokens(text, &spans, &mut tokens); assert!(result.is_err()); } ``` -------------------------------- ### LaTeX Matrix Environments Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token_rules/latex_math.rs.html Tests various LaTeX matrix environments including \begin{matrix}, \begin{pmatrix}, \begin{bmatrix}, \begin{Bmatrix}, \begin{vmatrix}, \begin{Vmatrix}, and \begin{array}. ```rust fn latex_matrix_environments() { let inputs: &[&str] = &[ // matrix family "$\begin{matrix} 1 & 2 \\ 3 & 4 \end{matrix}$", "$\begin{pmatrix} a & b \\ c & d \end{pmatrix}$", "$\begin{bmatrix} 1 \\ 2 \end{bmatrix}$", "$\begin{Bmatrix} x & y \end{Bmatrix}$", "$\begin{vmatrix} a & b \\ c & d \end{vmatrix}$", "$\begin{Vmatrix} 1 & 0 \\ 0 & 1 \end{Vmatrix}$", // arrays "$\begin{array}{cc} x & y \\ z & w \end{array}$", "$\begin{array}{ll} a & b \\ c & d \end{array}$", // determinant "$\begin{vmatrix} a & b \\ c & d \end{vmatrix}$", ]; for input in inputs { let _ = enc(input); } } ``` -------------------------------- ### Count English and Korean Words in Tokens Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token_rules/english_dominant_korean_wrap.rs.html Counts the number of words that start with an ASCII alphabetic character (English) and those that start with a Korean character from a given slice of tokens. ```rust fn count_script_words(tokens: &[Token<'_>]) -> (usize, usize) { let mut english_words = 0usize; let mut korean_words = 0usize; for token in tokens.iter() { let Token::Word(word) = token else { continue }; let Some(c) = first_script_char(word) else { continue; }; if c.is_ascii_alphabetic() { english_words += 1; } else if is_korean_char(c) { korean_words += 1; } } (english_words, korean_words) } ``` -------------------------------- ### Test Rule 18 Matches at Word Start Only Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/korean/rule_18.rs.html Ensures that Rule 18's matching logic only triggers when the shortcut is at the beginning of a word. It tests both a matching case at the start and a non-matching case in the middle of the word. ```rust #[test] fn rule18_matches_at_word_start_only() { use crate::char_struct::CharType; use crate::rules::context::EncoderState; let word_chars: Vec = "그래서".chars().collect(); let ct0 = CharType::new(word_chars[0]).unwrap(); let mut skip = 0usize; let mut state = EncoderState::new(false); let mut result = Vec::new(); let ctx_start = make_ctx(&word_chars, 0, &ct0, &mut skip, &mut state, &mut result); assert!(Rule18.matches(&ctx_start)); let ct1 = CharType::new(word_chars[1]).unwrap(); let mut skip2 = 0usize; let mut state2 = EncoderState::new(false); let mut result2 = Vec::new(); let ctx_mid = make_ctx(&word_chars, 1, &ct1, &mut skip2, &mut state2, &mut result2); assert!(!Rule18.matches(&ctx_mid)); } ``` -------------------------------- ### Check if Next Non-Space Token is Hangul Wrap Start Source: https://docs.rs/braillify/latest/src/braillify/rules/emit.rs.html Determines if the first non-space token after a given index is a Hangul wrap start. This is used to skip emitting the end-of-word wrap symbol when a Hangul wrap is intended to maintain English mode. ```rust fn next_non_space_is_hangul_wrap_start<'a>(tokens: &'a [Token<'a>], after_index: usize) -> bool { for token in tokens.iter().skip(after_index + 1) { match token { Token::Space(_) => continue, t => return is_hangul_wrap_start(t), } } false } ``` -------------------------------- ### MathTokenEngine::with_context Constructor Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/math/math_token_rule.rs.html Creates a new MathTokenEngine with a specified MathContext. ```rust impl MathTokenEngine { pub fn with_context(context: MathContext) -> Self { Self { rules: Vec::new(), context, } } ``` -------------------------------- ### Get Word Length in RuleContext Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/context.rs.html Returns the total number of characters in the current word. ```rust pub fn word_len(&self) -> usize { self.word_chars.len() } ``` -------------------------------- ### Apply Rule 69: Percent-per Shortcut Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/korean/rule_69.rs.html Handles the specific sequence '%p' followed by a non-alphabetic character or end of word. It encodes it as '⠴⠏⠏' and conditionally emits a zero-width space if followed by a Korean character. ```Rust if ctx.current_char() == '%' && ctx.word_chars.get(ctx.index + 1) == Some(&'p') && ctx .word_chars .get(ctx.index + 2) .is_none_or(|ch| !ch.is_ascii_alphabetic()) { ctx.emit_slice(&encode_unicode_cells("⠴⠏⠏")); *ctx.skip_count = 1; if ctx .word_chars .get(ctx.index + 2) .is_some_and(|ch| crate::utils::is_korean_char(*ch)) { ctx.emit(0); } return Ok(RuleResult::Consumed); } ``` -------------------------------- ### Get Current Character in RuleContext Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/context.rs.html Accesses the character at the current index within the word being processed. ```rust pub fn current_char(&self) -> char { self.word_chars[self.index] } ``` -------------------------------- ### Test encode_compact_ascii_notation for Out of Bounds Index Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/korean/rule_68.rs.html Checks that `encode_compact_ascii_notation` returns `None` for both invalid suffixes and out-of-bounds starting indices. ```rust #[test] fn encode_compact_ascii_notation_out_of_bounds() { let word: Vec = "A".chars().collect(); // Returns None because no suffix after A let result = encode_compact_ascii_notation(&word, 0, false).unwrap(); assert!(result.is_none()); // Out of range index let result = encode_compact_ascii_notation(&word, 99, false).unwrap(); assert!(result.is_none()); } ``` -------------------------------- ### build_math_engine Source: https://docs.rs/braillify/latest/src/braillify/rules/math/encoder.rs.html Constructs and configures a `MathTokenEngine` with a specific set of rules based on the provided `MathContext`. ```APIDOC ## build_math_engine ### Description This function initializes a new `MathTokenEngine` and registers a series of rules with it. The rules are registered in a specific order based on their priority, ensuring correct parsing and encoding of mathematical expressions. The engine is then finalized before being returned. ### Parameters - `context` (MathContext): The context for which the engine is being built. This influences which rules might be relevant or how they behave, although the current implementation registers a fixed set of rules regardless of context. ### Returns - `MathTokenEngine`: A fully configured `MathTokenEngine` instance. ### Registered Rules (by priority): - **Priority 10 (Lookahead Rules):** - `ConditionalProbFractionRule` - `GroupedFractionReversalRule` - `FractionReversalRule` - `VariableFractionInListRule` - `CombinatoricsRule` - `PartialDerivativeFractionRule` - `DefiniteIntegralRule` - **Priority 50 (Core Token Rules):** - `NumberRule` - `VariableRule` - `UpperVariableRule` - `KoreanWordRule` - `OperatorRule` - `FunctionNameRule` - `BracketRule` - `SuperscriptRule` - `SubscriptRule` - `DecimalPointRule` - `DigitSeparatorRule` - `SpaceRule` - `PrimeRule` - **Priority 100 (Math Symbol Dispatch):** - `MathSymbolRule` - `RawTokenRule` ``` -------------------------------- ### RuleEngine Initialization Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/engine.rs.html Creates a new, empty RuleEngine instance with no rules registered and the sorted flag set to false. ```rust pub fn new() -> Self { Self { rules: Vec::new(), disabled: HashSet::new(), sorted: false, } } ``` -------------------------------- ### Get KoreanChar Type Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/context.rs.html Checks if the current character is Korean and returns it as a `KoreanChar` if true. Otherwise, returns None. ```rust pub fn as_korean(&self) -> Option<&KoreanChar> { if let CharType::Korean(k) = self.char_type { Some(k) } else { None } } ``` -------------------------------- ### Building a Math Token Engine Source: https://docs.rs/braillify/latest/src/braillify/rules/math/encoder.rs.html Constructs and configures a `MathTokenEngine` by registering various rules for different priorities, including lookahead rules, core token rules, and math symbol dispatch rules. ```rust fn build_math_engine(context: MathContext) -> MathTokenEngine { let mut engine = MathTokenEngine::with_context(context); // Priority 10 — lookahead rules engine.register(Box::new(rule_7::ConditionalProbFractionRule)); engine.register(Box::new(rule_7::GroupedFractionReversalRule)); engine.register(Box::new(rule_7::FractionReversalRule)); engine.register(Box::new(rule_7::VariableFractionInListRule)); engine.register(Box::new(rule_12::CombinatoricsRule)); engine.register(Box::new(rule_54::PartialDerivativeFractionRule)); engine.register(Box::new(rule_57::DefiniteIntegralRule)); // Priority 50 — core token rules engine.register(Box::new(rule_1::NumberRule)); engine.register(Box::new(rule_12::VariableRule)); engine.register(Box::new(rule_12::UpperVariableRule)); engine.register(Box::new(KoreanWordRule)); engine.register(Box::new(rule_2::OperatorRule)); engine.register(Box::new(rule_47::FunctionNameRule)); engine.register(Box::new(rule_6::BracketRule)); engine.register(Box::new(rule_18::SuperscriptRule)); engine.register(Box::new(rule_19::SubscriptRule)); engine.register(Box::new(rule_8::DecimalPointRule)); engine.register(Box::new(DigitSeparatorRule)); engine.register(Box::new(SpaceRule)); engine.register(Box::new(rule_53::PrimeRule)); // Priority 100 — math symbol dispatch engine.register(Box::new(MathSymbolRule)); engine.register(Box::new(RawTokenRule)); engine.finalize(); engine } ``` -------------------------------- ### Get Previous Character in RuleContext Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/context.rs.html Safely retrieves the previous character in the word, returning None if at the beginning of the word. ```rust pub fn prev_char(&self) -> Option { if self.index > 0 { Some(self.word_chars[self.index - 1]) } else { None } } ``` -------------------------------- ### Get Next Character in RuleContext Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/context.rs.html Safely retrieves the next character in the word, returning None if at the end of the word. ```rust pub fn next_char(&self) -> Option { self.word_chars.get(self.index + 1).copied() } ``` -------------------------------- ### Rule Sorting by Phase and Priority Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/engine.rs.html Implements two rules, `PostRule` and `CoreRule`, with different phases. Demonstrates registering them and ensuring they are sorted correctly by the engine, verifying that `CoreEncoding` rules are processed before `PostProcessing` rules. ```rust static META_A: RuleMeta = RuleMeta { section: "a", subsection: None, name: "post", standard_ref: "", description: "", }; static META_B: RuleMeta = RuleMeta { section: "b", subsection: None, name: "core", standard_ref: "", description: "", }; struct PostRule; impl BrailleRule for PostRule { fn meta(&self) -> &'static RuleMeta { &META_A } fn phase(&self) -> Phase { Phase::PostProcessing } fn matches(&self, _: &RuleContext) -> bool { false } fn apply(&self, _: &mut RuleContext) -> Result { Ok(RuleResult::Skip) } } struct CoreRule; impl BrailleRule for CoreRule { fn meta(&self) -> &'static RuleMeta { &META_B } fn phase(&self) -> Phase { Phase::CoreEncoding } fn matches(&self, _: &RuleContext) -> bool { false } fn apply(&self, _: &mut RuleContext) -> Result { Ok(RuleResult::Skip) } } let mut engine = RuleEngine::new(); engine.register(Box::new(PostRule)); engine.register(Box::new(CoreRule)); engine.ensure_sorted(); let metas = engine.list_rules(); assert_eq!(metas[0].name, "core"); // CoreEncoding before PostProcessing assert_eq!(metas[1].name, "post"); ``` -------------------------------- ### LaTeX Delimiters and Floor/Ceiling Functions Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token_rules/latex_math/strip.rs.html Shows the use of LaTeX commands for adjustable-size delimiters like parentheses, brackets, and braces, as well as floor and ceiling functions. ```Rust "\\left(x\right)", ``` ```Rust "\\left[x\right]", ``` ```Rust "\\left\{x\right\}", ``` ```Rust "\\lfloor x \rfloor", ``` ```Rust "\\lceil x \rceil" ``` -------------------------------- ### Getting Rule Count Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/engine.rs.html Returns the total number of rules currently registered in the engine. This function is only available in test configurations. ```rust pub fn rule_count(&self) -> usize { self.rules.len() } ``` -------------------------------- ### Generic CloneToUninit Implementation Source: https://docs.rs/braillify/2.0.1/braillify/struct.EncodeOptions.html An experimental nightly-only API for copying data to uninitialized memory. ```rust unsafe fn clone_to_uninit(&self, dest: *mut u8) ``` -------------------------------- ### Encode Because Symbol at Start of Expression Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/math/encoder/symbol_rule.rs.html Tests the encoding of the because symbol '∵' when it appears at the very beginning of an expression, ensuring no leading spaces are added. ```rust fn because_at_start_of_expression() { let result = enc("\u{2235}x"); assert!(!result.is_empty(), "∵x must encode"); } ``` -------------------------------- ### RuleEngine::new Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/engine.rs.html Creates a new, empty RuleEngine instance with no rules registered and no rules disabled. ```APIDOC ## RuleEngine::new ### Description Creates a new, empty `RuleEngine` instance. ### Returns A new `RuleEngine` with an empty list of rules and an empty set of disabled rules. ``` -------------------------------- ### Apply Rule 69: Percent-ile Shortcut Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/korean/rule_69.rs.html Handles the specific sequence '%ile' by encoding it as '⠴⠏⠞' and consuming three characters. ```Rust if ctx.current_char() == '%' && ctx.word_chars.get(ctx.index + 1) == Some(&'i') && ctx.word_chars.get(ctx.index + 2) == Some(&'l') && ctx.word_chars.get(ctx.index + 3) == Some(&'e') { let encoded = encode_unicode_cells("⠴⠏⠞"); ctx.emit_slice(&encoded); *ctx.skip_count = 3; return Ok(RuleResult::Consumed); } ``` -------------------------------- ### Check for ASCII Alphabet Start Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token_rules/roman_numeral.rs.html Determines if a string begins with an ASCII alphabetic character. Useful for distinguishing between different token types. ```Rust #[rstest::rstest] #[case::lowercase_alpha("abc", true)] #[case::uppercase_alpha("Z", true)] #[case::digit_prefix("123", false)] #[case::empty("", false)] #[case::symbol_prefix("-", false)] fn starts_with_ascii_alpha_branches(#[case] input: &str, #[case] expected: bool) { assert_eq!(starts_with_ascii_alpha(input), expected); } ``` -------------------------------- ### Initialize Matrix Math Mode Engine Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/math/encoder.rs.html Tests the initialization of the `MATRIX_MATH_MODE_ENGINE` lazy block by calling `math_engine_for_context` with active matrix and math modes. ```rust let _ = math_engine_for_context(MathContext { matrix_context_active: true, math_mode_active: true, }); ``` -------------------------------- ### Get Current Encoding Mode Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/context.rs.html Retrieves the current encoding mode from the top of the mode stack. Defaults to Korean if the stack is empty. ```rust pub fn current_mode(&self) -> EncodingMode { self.mode_stack .last() .copied() .unwrap_or(EncodingMode::Korean) } ``` -------------------------------- ### Parse Underline-Notation Fraction Paths Source: https://docs.rs/braillify/latest/src/braillify/rules/math/parser.rs.html Tests various underline-notation fraction parsing scenarios, including suffixes on digit prefixes and generic underline-fractions. ```rust /// Underline-notation fraction normalization paths (lines around 300-330). #[test] fn underline_notation_fraction_paths() { // U+0332 suffix on digit prefix → digits/1 let _ = parse_math_expression("123\u{0332}"); // "1?/(...)" pattern let _ = parse_math_expression("1\u{0332}/(x+y)"); // "X?/Y" generic underline-fraction let _ = parse_math_expression("A\u{0332}/B"); } ``` -------------------------------- ### Pass-through Logic for English Mode Source: https://docs.rs/braillify/latest/src/braillify/english_logic.rs.html Demonstrates that `should_keep_english_mode_for_symbol` passes through the result of `should_render_symbol_as_english` when both pre-conditions are met, using an email-like string as an example. ```rust let chars: Vec = "user@host.com".chars().collect(); // '@' at index 4 let _ = super::should_keep_english_mode_for_symbol('@', &chars, 4, &[]); ``` -------------------------------- ### Create Token Rule Engine Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/emit.rs.html Initializes and populates a `TokenRuleEngine` with various token-level rules. This engine handles transformations on tokens before final Braille output. ```rust fn make_token_engine() -> crate::rules::token_engine::TokenRuleEngine { let mut engine = crate::rules::token_engine::TokenRuleEngine::new(); engine.register(Box::new( crate::rules::token_rules::normalize::NormalizeEllipsis, )); engine.register(Box::new( crate::rules::token_rules::emphasis_ring::EmphasisRingRule, )); engine.register(Box::new( crate::rules::token_rules::latex_fraction::LatexFractionRule, )); engine.register(Box::new( crate::rules::token_rules::inline_fraction::InlineFractionRule, )); engine.register(Box::new( crate::rules::token_rules::word_shortcut::WordShortcutRule, )); engine.register(Box::new( crate::rules::token_rules::uppercase_passage::UppercasePassageRule, )); engine.register(Box::new( crate::rules::token_rules::middle_dot_spacing::MiddleDotSpacingRule, )); engine.register(Box::new( crate::rules::token_rules::quote_attachment::QuoteAttachmentRule, )); engine.register(Box::new( crate::rules::token_rules::spacing::AsteriskSpacingRule, )); engine } ``` -------------------------------- ### Braillify CLI Main Function Source: https://docs.rs/braillify/2.0.1/src/braillify/cli.rs.html The main entry point for the Braillify CLI. It handles argument parsing, including reading from stdin if not a terminal, and dispatches to either one-shot conversion or REPL mode. ```rust use std::io::{self, IsTerminal, Read, Write}; use anyhow::{Result, bail}; use clap::Parser; use rustyline::{DefaultEditor, error::ReadlineError}; use crate::encode_to_unicode; #[derive(Parser, Debug)] #[command(name = "braillify", about = "한국어 점자 변환 CLI", version)] struct Cli { /// 입력 문자열. 없으면 REPL 모드로 진입합니다 input: Option, } pub fn run_cli(mut args: Vec) -> Result<()> { if args.len() == 1 && !std::io::stdin().is_terminal() { let mut buffer = vec![]; io::stdin().read_to_end(&mut buffer)?; if !buffer.is_empty() { args.push(String::from_utf8(buffer)?); } } match Cli::parse_from(args).input { Some(text) => run_one_shot(&text), None => run_repl(), } } fn run_one_shot(text: &str) -> Result<()> { let out = encode_to_unicode(text).map_err(|e| anyhow::anyhow!("점자 변환 실패: {}", e))?; let mut stdout = io::stdout(); stdout.write_all(out.as_bytes())?; stdout.flush()?; Ok(()) } /// Format a single REPL input line for printing. /// /// Pure function so unit tests can exercise the encode-success vs encode-error /// branches without spinning up rustyline. The outer `run_repl` loop is just /// `read line → format → write` glue; all logic lives here. pub(crate) fn process_repl_line(line: &str) -> String { match encode_to_unicode(line) { Ok(out) => out, Err(e) => format!("오류: {}", e), } } // Interactive rustyline loop is unreachable in cargo-tarpaulin runs (stdin is // never a terminal in CI/test harness). The pure encoding logic was extracted // to `process_repl_line` which is unit-tested directly above. #[cfg(not(tarpaulin_include))] fn run_repl() -> Result<()> { let mut rl = DefaultEditor::new()?; let mut stdout = io::stdout(); writeln!( stdout, "braillify REPL - 입력을 점자로 변환합니다. 종료: Ctrl+C or Ctrl+D" )?; stdout.flush()?; loop { match rl.readline("> ") { Ok(line) => { rl.add_history_entry(&line).ok(); writeln!(stdout, "{}", process_repl_line(&line))?; stdout.flush()?; } Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => { writeln!(stdout, "종료합니다.")?; stdout.flush()?; break; } Err(err) => bail!("입력 오류: {}", err), } } Ok(()) } ``` -------------------------------- ### Test FormattingKind Markers Source: https://docs.rs/braillify/2.0.1/src/braillify/lib.rs.html Ensures that each variant of `FormattingKind` (Emphasis, Bold, Custom1, Custom2) correctly returns its declared start and end markers. ```rust /// All four FormattingKind variants must produce their declared markers. /// Covers `FormattingKind::markers` arms for Emphasis/Bold/Custom1/Custom2. /// `FormattingKind::markers()` — 각 강조 종류별 시작·종료 점형 페어. #[rstest::rstest] #[case::emphasis(FormattingKind::Emphasis, [32, 36], [36, 4])] #[case::bold(FormattingKind::Bold, [48, 36], [36, 6])] #[case::custom1(FormattingKind::Custom1, [16, 36], [36, 2])] #[case::custom2(FormattingKind::Custom2, [8, 36], [36, 1])] fn formatting_kind_markers_all_variants( #[case] kind: FormattingKind, #[case] start: [u8; 2], #[case] end: [u8; 2], ) { assert_eq!(kind.markers(), (start, end)); } ``` -------------------------------- ### MathEncodeState::with_context Constructor Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/math/math_token_rule.rs.html Initializes MathEncodeState with logic context and provided MathContext. ```rust impl MathEncodeState { pub fn with_context(logic_context: bool, context: MathContext) -> Self { Self { prev_was_number: false, logic_context, matrix_context_active: context.matrix_context_active, } } } ``` -------------------------------- ### Test: Non-Math Letter then Digit Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token_rules/math_expression.rs.html Ensures that strings starting with letters followed by a digit (e.g., product codes) are not identified as math expressions. ```rust #[test] fn test_is_not_math_letter_then_digit() { // "MP3" starts with letters then digit → NOT math (avoids false positive) let chars: Vec = "MP3".chars().collect(); assert!(!is_math_expression(&chars, "MP3")); } ``` -------------------------------- ### Parse Basic Text into Tokens Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token.rs.html Demonstrates parsing a simple string into its constituent tokens, verifying word and space tokenization. ```Rust let ir = DocumentIR::parse("hello world", false); assert_eq!(ir.tokens.len(), 3); match &ir.tokens[0] { Token::Word(w) => assert_eq!(w.text, "hello"), _ => panic!("expected first token to be word"), } assert!(matches!(ir.tokens[1], Token::Space(SpaceKind::Regular))); match &ir.tokens[2] { Token::Word(w) => assert_eq!(w.text, "world"), _ => panic!("expected third token to be word"), } ``` -------------------------------- ### Test: Math Expression with Digit then Letters Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/token_rules/math_expression.rs.html Verifies that strings starting with a digit followed by letters (implying multiplication) are identified as math expressions. ```rust #[test] fn test_is_math_digit_then_letter() { // "3ab" starts with digit then letters → math multiplication let chars: Vec = "3ab".chars().collect(); assert!(is_math_expression(&chars, "3ab")); } ``` -------------------------------- ### Engine Apply Phase Skips Disabled Rules Test Setup Source: https://docs.rs/braillify/latest/src/braillify/rules/engine.rs.html Sets up a test for the `apply_phase` method in `engine.rs`, specifically focusing on the skip arm for disabled rules. It initializes a `RuleEngine` and registers a `TestRule` which is then disabled. ```rust use crate::char_struct::CharType; let mut engine = RuleEngine::new(); engine.register(Box::new(TestRule)); engine.disable("test"); let word_chars = vec!['x']; let char_type = CharType::English('x'); let empty: [&str; 0] = []; let mut skip = 0usize; ``` -------------------------------- ### Test encode_compact_ascii_notation for Lowercase Input Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/korean/rule_68.rs.html Verifies that `encode_compact_ascii_notation` returns `None` when the input starts with a lowercase letter, even if followed by a valid suffix. ```rust #[test] fn encode_compact_ascii_notation_lowercase_returns_none() { let word: Vec = "a⁺".chars().collect(); let result = encode_compact_ascii_notation(&word, 0, false).unwrap(); assert!(result.is_none()); } ``` -------------------------------- ### Test Rule 53 Match False When Not at Word Start Source: https://docs.rs/braillify/2.0.1/src/braillify/rules/korean/rule_53.rs.html Checks that Rule 53 returns false when the ellipsis pattern is not at the beginning of the word. ```rust use crate::char_struct::CharType; let word_chars: Vec = "......".chars().collect(); let ct = CharType::new(word_chars[0]).unwrap(); let mut skip = 0usize; let mut state = crate::rules::context::EncoderState::new(false); let mut out = Vec::new(); let ctx = make_ctx(&word_chars, 1, &ct, &mut skip, &mut state, &mut out); assert!(!Rule53.matches(&ctx)); ```