From 3288752dfbb07f40c6c39a8294ed2091f25ff9b9 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Fri, 5 Dec 2025 20:20:00 -0500 Subject: [PATCH] parser: simplify BoolSlice logic with descriptive helper methods --- parsing/src/splitting.rs | 83 ++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/parsing/src/splitting.rs b/parsing/src/splitting.rs index f26eb2c..c802d14 100644 --- a/parsing/src/splitting.rs +++ b/parsing/src/splitting.rs @@ -62,46 +62,73 @@ impl BoolSlice { self.number && !self.masked_num } + /// Returns true if this char is a function name letter (not a standalone variable) + const fn is_function_letter(&self) -> bool { + self.letter && !self.is_unmasked_variable() + } + + /// Returns true if this is a "term" - something that can be multiplied + const fn is_term(&self) -> bool { + self.is_unmasked_number() || self.is_unmasked_variable() || self.letter + } + const fn calculate_mask(&mut self, other: &BoolSlice) { if other.masked_num && self.number { - // If previous char was a masked number, and current char is a number, mask current char's variable status + // Propagate number masking through consecutive digits self.masked_num = true; } else if other.masked_var && self.variable { - // If previous char was a masked variable, and current char is a variable, mask current char's variable status + // Propagate variable masking through consecutive variables self.masked_var = true; - } else if other.letter && !other.is_unmasked_variable() { + } else if other.is_function_letter() { + // After a function letter, mask following numbers/variables as part of function name self.masked_num = self.number; self.masked_var = self.variable; } } - const fn splitable(&self, c: &char, other: &BoolSlice, split: &SplitType) -> bool { - if (*c == '*') | (matches!(split, &SplitType::Term) && other.open_parens) { - true - } else if other.closing_parens { - // Cases like `)x`, `)2`, and `)(` - return (*c == '(') - | (self.letter && !self.is_unmasked_variable()) - | self.is_unmasked_variable() - | self.is_unmasked_number(); - } else if *c == '(' { - // Cases like `x(` and `2(` - return (other.is_unmasked_variable() | other.is_unmasked_number()) && !other.letter; - } else if other.is_unmasked_number() { - // Cases like `2x` and `2sin(x)` - return self.is_unmasked_variable() | self.letter; - } else if self.is_unmasked_variable() | self.letter { - // Cases like `e2` and `xx` - return other.is_unmasked_number() - | (other.is_unmasked_variable() && self.is_unmasked_variable()) - | other.is_unmasked_variable(); - } else if (self.is_unmasked_number() | self.letter | self.is_unmasked_variable()) - && (other.is_unmasked_number() | other.letter) - { + /// Determines if we should split (insert implicit multiplication) before current char + const fn splitable(&self, c: &char, prev: &BoolSlice, split: &SplitType) -> bool { + // Always split on explicit multiplication + if *c == '*' { return true; - } else { - return self.is_unmasked_number() && other.is_unmasked_variable(); } + + // For Term split type, also split after open parens + if matches!(split, &SplitType::Term) && prev.open_parens { + return true; + } + + // After closing paren: split before `(`, letters, variables, or numbers + // e.g., `)x`, `)2`, `)(`, `)sin` + if prev.closing_parens { + return *c == '(' || self.is_term(); + } + + // Before open paren: split if previous was a standalone number or variable + // e.g., `x(`, `2(` but not `sin(` + if *c == '(' { + return (prev.is_unmasked_variable() || prev.is_unmasked_number()) && !prev.letter; + } + + // After a number: split before variables or function letters + // e.g., `2x`, `2sin` + if prev.is_unmasked_number() { + return self.is_unmasked_variable() || self.letter; + } + + // Current is a variable or letter: split if previous was a number or variable + // e.g., `e2`, `xx`, `xe` + if self.is_unmasked_variable() || self.letter { + return prev.is_unmasked_number() || prev.is_unmasked_variable(); + } + + // Current is a number after a variable + // e.g., `x2` + if self.is_unmasked_number() && prev.is_unmasked_variable() { + return true; + } + + false } }