code wiki / _hdl_build / nx_stem.nx

nx_stem.nx source

↩ module page · 129 lines · 5579 B

1// nx_stem.nx -- SOVEREIGN morphological stemming (the stemming/lemmatization SOTA-parity rung; sota_stemming 2// canon). Inflected forms map to a common conflation root so a query recalls all forms: run/running/runs, 3// rank/ranking/ranked, index/indexes/indexing. Upgraded 2026-07-03 from Step-1a-only to a PORTER-LITE stemmer 4// (the high-recall steps: plurals + -ed/-ing + -ly + common derivational suffixes) with Porter's measure(m) 5// guard that stops over-stemming short words. NO index-format change: it drives QUERY-TIME dictionary 6// expansion (dss_stem_expand), so indexing is untouched. HONEST SCOPE: no irregulars (children->child needs a 7// lexicon). exports: nx_stem (buffered) + vr_stem (in-place, backward-compat). license_tier: ORIGINAL 8import "nx_syscalls.nx" 9 10func stm_isvowel(c: u8, prev: u8) -> i64 { 11 if c == (97 as u8) { return 1 } 12 if c == (101 as u8) { return 1 } 13 if c == (105 as u8) { return 1 } 14 if c == (111 as u8) { return 1 } 15 if c == (117 as u8) { return 1 } 16 if c == (121 as u8) { // y is a vowel iff preceded by a consonant 17 if prev == (0 as u8) { return 0 } 18 if stm_isvowel(prev, 0 as u8) == 1 { return 0 } 19 return 1 20 } 21 return 0 22} 23// Porter measure m = count of vowel->consonant transitions 24func stm_measure(s: *u8, n: i64) -> i64 { 25 var m: i64 = 0 26 var prevv: i64 = 0 - 1 27 var i: i64 = 0 28 var pc: u8 = 0 as u8 29 while i < n { 30 let v: i64 = stm_isvowel(s[i], pc) 31 if prevv == 1 { if v == 0 { m = m + 1 } } 32 prevv = v 33 pc = s[i] 34 i = i + 1 35 } 36 return m 37} 38func stm_hasvowel(s: *u8, n: i64) -> i64 { 39 var i: i64 = 0 40 var pc: u8 = 0 as u8 41 while i < n { if stm_isvowel(s[i], pc) == 1 { return 1 } pc = s[i]; i = i + 1 } 42 return 0 43} 44func stm_ends(s: *u8, n: i64, suf: *u8, sl: i64) -> i64 { 45 if n < sl { return 0 } 46 var i: i64 = 0 47 while i < sl { if s[n - sl + i] != suf[i] { return 0 } i = i + 1 } 48 return 1 49} 50func stm_dbl_consonant_end(s: *u8, n: i64) -> i64 { 51 if n < 2 { return 0 } 52 if s[n - 1] != s[n - 2] { return 0 } 53 if stm_isvowel(s[n - 1], s[n - 2]) == 1 { return 0 } 54 return 1 55} 56func stm_cvc(s: *u8, n: i64) -> i64 { 57 if n < 3 { return 0 } 58 let c1: u8 = s[n - 3]; let c2: u8 = s[n - 2]; let c3: u8 = s[n - 1] 59 if stm_isvowel(c1, 0 as u8) == 1 { return 0 } 60 if stm_isvowel(c2, c1) == 0 { return 0 } 61 if stm_isvowel(c3, c2) == 1 { return 0 } 62 if c3 == (119 as u8) { return 0 } 63 if c3 == (120 as u8) { return 0 } 64 if c3 == (121 as u8) { return 0 } 65 return 1 66} 67 68// nx_stem: buffered (out may alias inp -- the initial copy is a safe self-copy then). Returns out length. 69func nx_stem(inp: *u8, n0: i64, out: *u8) -> i64 { 70 var n: i64 = n0 71 var i: i64 = 0 72 while i < n { out[i] = inp[i]; i = i + 1 } 73 if n <= 2 { out[n] = 0 as u8; return n } 74 75 // Step 1a: plurals 76 if stm_ends(out, n, "sses" as *u8, 4) == 1 { n = n - 2 } 77 else { if stm_ends(out, n, "ies" as *u8, 3) == 1 { n = n - 2 } 78 else { if stm_ends(out, n, "ss" as *u8, 2) == 1 { n = n } 79 else { if stm_ends(out, n, "s" as *u8, 1) == 1 { n = n - 1 } } } } 80 81 // Step 1b: -eed / -ed / -ing 82 var did1b: i64 = 0 83 if stm_ends(out, n, "eed" as *u8, 3) == 1 { 84 if stm_measure(out, n - 3) > 0 { n = n - 1 } 85 } else { 86 if stm_ends(out, n, "ed" as *u8, 2) == 1 { 87 if stm_hasvowel(out, n - 2) == 1 { n = n - 2; did1b = 1 } 88 } else { 89 if stm_ends(out, n, "ing" as *u8, 3) == 1 { 90 if stm_hasvowel(out, n - 3) == 1 { n = n - 3; did1b = 1 } 91 } 92 } 93 } 94 if did1b == 1 { 95 if stm_ends(out, n, "at" as *u8, 2) == 1 { out[n] = 101 as u8; n = n + 1 } 96 else { if stm_ends(out, n, "bl" as *u8, 2) == 1 { out[n] = 101 as u8; n = n + 1 } 97 else { if stm_ends(out, n, "iz" as *u8, 2) == 1 { out[n] = 101 as u8; n = n + 1 } 98 else { 99 if stm_dbl_consonant_end(out, n) == 1 { 100 let last: u8 = out[n - 1] 101 if last != (108 as u8) { if last != (115 as u8) { if last != (122 as u8) { n = n - 1 } } } 102 } else { 103 if stm_measure(out, n) == 1 { if stm_cvc(out, n) == 1 { out[n] = 101 as u8; n = n + 1 } } 104 } 105 } } } 106 } 107 108 // Step 1c: y -> i when the stem has a vowel 109 if stm_ends(out, n, "y" as *u8, 1) == 1 { if stm_hasvowel(out, n - 1) == 1 { out[n - 1] = 105 as u8 } } 110 111 // Step 4 (subset): common derivational suffixes when (m>1) 112 if stm_ends(out, n, "ement" as *u8, 5) == 1 { if stm_measure(out, n - 5) > 1 { n = n - 5 } } 113 else { if stm_ends(out, n, "ment" as *u8, 4) == 1 { if stm_measure(out, n - 4) > 1 { n = n - 4 } } 114 else { if stm_ends(out, n, "ness" as *u8, 4) == 1 { if stm_measure(out, n - 4) > 1 { n = n - 4 } } 115 else { if stm_ends(out, n, "able" as *u8, 4) == 1 { if stm_measure(out, n - 4) > 1 { n = n - 4 } } 116 else { if stm_ends(out, n, "ible" as *u8, 4) == 1 { if stm_measure(out, n - 4) > 1 { n = n - 4 } } 117 else { if stm_ends(out, n, "al" as *u8, 2) == 1 { if stm_measure(out, n - 2) > 1 { n = n - 2 } } 118 else { if stm_ends(out, n, "er" as *u8, 2) == 1 { if stm_measure(out, n - 2) > 1 { n = n - 2 } } } } } } } } 119 120 // Step 1d: -ly (adverbs) when (m>1) 121 if stm_ends(out, n, "ly" as *u8, 2) == 1 { if stm_measure(out, n - 2) > 1 { n = n - 2 } } 122 123 out[n] = 0 as u8 124 return n 125} 126// backward-compat in-place wrapper (the R-UX-2 gate + any prior caller): stem w[0..n) in place, return new len 127func vr_stem(w: *u8, n: i64) -> i64 { 128 return nx_stem(w, n, w) 129}