From f90ebc5f3b7fe9a89a4755d2bcce8d9304cc2d1f Mon Sep 17 00:00:00 2001 From: Martin Ball Date: Sun, 1 Nov 2026 18:40:08 +0000 Subject: [PATCH] CP6.6: ONLYINCOMPOUND and COMPOUNDRULE read in the dictionary's FLAG mode From the rebase onto craft-libs#483: the ONLYINCOMPOUND flag is a flag id read as FLAG long/num/UTF-8 says, so a stem carrying it is a word only inside a compound in every flag mode (English included). COMPOUNDRULE patterns with long or numeric flags write each flag in parentheses, `(aa)*(bb)` / `(1)*(2)`, as Hunspell reads them (checked black-box with the hunspell CLI); a bare flag in those modes or an unclosed group is refused. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 --- crates/proof/src/affix.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/proof/src/affix.rs b/crates/proof/src/affix.rs index ef119e58..09a444e5 100644 --- a/crates/proof/src/affix.rs +++ b/crates/proof/src/affix.rs @@ -1560,9 +1560,13 @@ impl AffixDict { self.flags.iter().any(|&id| self.set(id).contains(&flag)) || self.prefixes.iter().chain(&self.suffixes).any(|r| r.flag == flag || self.set(r.cont).contains(&flag)) }; - for (directive, flag) in - [("FORBIDDENWORD", self.forbidden), ("NOSUGGEST", self.no_suggest), ("SUBSTANDARD", self.substandard), ("KEEPCASE", self.keepcase), ("ONLYINCOMPOUND", self.only_in_compound)] - { + for (directive, flag) in [ + ("FORBIDDENWORD", self.forbidden), + ("NOSUGGEST", self.no_suggest), + ("SUBSTANDARD", self.substandard), + ("KEEPCASE", self.keepcase), + ("ONLYINCOMPOUND", self.only_in_compound), + ] { if let Some(flag) = flag.filter(|&f| self.flag_mode != FlagMode::Char || used(f)) { out.push_str(directive); out.push(' '); @@ -2373,6 +2377,19 @@ pub(crate) mod tests { } } + /// The English path reads `ONLYINCOMPOUND` in the dictionary's FLAG mode too. + #[test] + fn english_onlyincompound_reads_long_and_numeric_flags() { + for (aff, dic) in [ + ("FLAG long\nONLYINCOMPOUND cc\nCOMPOUNDMIN 1\nSFX Ss Y 1\nSFX Ss 0 s .\n", "3\nhouse/Ss\n1st/cc\nnth/Sscc\n"), + ("FLAG num\nONLYINCOMPOUND 7\nCOMPOUNDMIN 1\nSFX 1 Y 1\nSFX 1 0 s .\n", "3\nhouse/1\n1st/7\nnth/1,7\n"), + ] { + let d = AffixDict::from_hunspell_english(aff, dic, &[]).unwrap(); + assert!(d.check("house") && d.check("houses"), "{aff}"); + assert!(!d.check("1st") && !d.check("nth") && !d.check("nths"), "{aff}"); + } + } + #[test] fn unsupported_and_broken_input_is_an_error() { // `FLAG long` and `FLAG num` load now (see the tests below); an unknown mode doesn't.