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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2
This commit is contained in:
Martin BallandClaude Opus 5.5 committed 2026-11-01 22:34:28 +00:00
1 parent 35285e27eb
commit f90ebc5f3b
1 file changed
+20 -3
+20 -3
View File
@@ -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.