mittens 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/Gemfile +1 -0
  4. data/README.md +2 -2
  5. data/Rakefile +10 -3
  6. data/lib/mittens/version.rb +1 -1
  7. data/vendor/snowball/.github/workflows/ci.yml +144 -17
  8. data/vendor/snowball/.github/workflows/coverage.yml +117 -0
  9. data/vendor/snowball/.github/workflows/runtime-tests.yml +26 -0
  10. data/vendor/snowball/.gitignore +33 -7
  11. data/vendor/snowball/CONTRIBUTING.rst +7 -0
  12. data/vendor/snowball/COPYING +1 -1
  13. data/vendor/snowball/GNUmakefile +736 -298
  14. data/vendor/snowball/NEWS +1394 -7
  15. data/vendor/snowball/README.rst +8 -8
  16. data/vendor/snowball/ada/generate/generate.adb +5 -5
  17. data/vendor/snowball/ada/src/stemmer.adb +177 -188
  18. data/vendor/snowball/ada/src/stemmer.ads +86 -89
  19. data/vendor/snowball/ada/src/stemwords.adb +21 -5
  20. data/vendor/snowball/algorithms/czech.sbl +255 -0
  21. data/vendor/snowball/algorithms/danish.sbl +22 -10
  22. data/vendor/snowball/algorithms/english.sbl +7 -4
  23. data/vendor/snowball/algorithms/esperanto.sbl +6 -7
  24. data/vendor/snowball/algorithms/estonian.sbl +9 -4
  25. data/vendor/snowball/algorithms/finnish.sbl +33 -21
  26. data/vendor/snowball/algorithms/german.sbl +5 -0
  27. data/vendor/snowball/algorithms/indonesian.sbl +115 -96
  28. data/vendor/snowball/algorithms/italian.sbl +26 -1
  29. data/vendor/snowball/algorithms/lithuanian.sbl +13 -13
  30. data/vendor/snowball/algorithms/norwegian.sbl +17 -6
  31. data/vendor/snowball/algorithms/persian.sbl +387 -0
  32. data/vendor/snowball/algorithms/polish.sbl +177 -0
  33. data/vendor/snowball/algorithms/sesotho.sbl +115 -0
  34. data/vendor/snowball/algorithms/turkish.sbl +4 -4
  35. data/vendor/snowball/compiler/analyser.c +2002 -711
  36. data/vendor/snowball/compiler/driver.c +460 -298
  37. data/vendor/snowball/compiler/generator.c +439 -1965
  38. data/vendor/snowball/compiler/generator_ada.c +605 -430
  39. data/vendor/snowball/compiler/generator_c.c +2227 -0
  40. data/vendor/snowball/compiler/generator_csharp.c +365 -274
  41. data/vendor/snowball/compiler/generator_dart.c +1476 -0
  42. data/vendor/snowball/compiler/generator_go.c +348 -270
  43. data/vendor/snowball/compiler/generator_java.c +330 -233
  44. data/vendor/snowball/compiler/generator_js.c +534 -385
  45. data/vendor/snowball/compiler/generator_pascal.c +361 -320
  46. data/vendor/snowball/compiler/generator_php.c +1445 -0
  47. data/vendor/snowball/compiler/generator_python.c +399 -299
  48. data/vendor/snowball/compiler/generator_rust.c +370 -261
  49. data/vendor/snowball/compiler/generator_zig.c +1474 -0
  50. data/vendor/snowball/compiler/header.h +153 -86
  51. data/vendor/snowball/compiler/space.c +121 -63
  52. data/vendor/snowball/compiler/tokeniser.c +286 -182
  53. data/vendor/snowball/compiler/tokens.h +71 -0
  54. data/vendor/snowball/csharp/Snowball/Among.cs +14 -26
  55. data/vendor/snowball/csharp/Snowball/AssemblyInfo.cs +3 -3
  56. data/vendor/snowball/csharp/Snowball/Stemmer.cs +41 -45
  57. data/vendor/snowball/csharp/Stemwords/Program.cs +20 -14
  58. data/vendor/snowball/cxx/.gitignore +4 -0
  59. data/vendor/snowball/cxx/generate_algorithms.pl +87 -0
  60. data/vendor/snowball/cxx/stemmer.h +12 -0
  61. data/vendor/snowball/cxx/stemwords.cxx +176 -0
  62. data/vendor/snowball/cxx/utilities.cxx +2 -0
  63. data/vendor/snowball/dart/.gitignore +4 -0
  64. data/vendor/snowball/dart/analysis_options.yaml +1 -0
  65. data/vendor/snowball/dart/example/test_app.dart +65 -0
  66. data/vendor/snowball/dart/generate_algorithms.pl +21 -0
  67. data/vendor/snowball/dart/lib/snowball.dart +18 -0
  68. data/vendor/snowball/dart/lib/src/snowball.dart +339 -0
  69. data/vendor/snowball/dart/pubspec.yaml +17 -0
  70. data/vendor/snowball/doc/libstemmer_dart_README +37 -0
  71. data/vendor/snowball/doc/libstemmer_js_README +2 -2
  72. data/vendor/snowball/doc/libstemmer_php_README +38 -0
  73. data/vendor/snowball/doc/libstemmer_python_README +3 -3
  74. data/vendor/snowball/examples/stemwords.c +18 -3
  75. data/vendor/snowball/go/README.md +12 -4
  76. data/vendor/snowball/go/env.go +12 -14
  77. data/vendor/snowball/go/stemwords/main.go +2 -1
  78. data/vendor/snowball/java/org/tartarus/snowball/CharArraySequence.java +36 -0
  79. data/vendor/snowball/java/org/tartarus/snowball/SnowballProgram.java +17 -34
  80. data/vendor/snowball/java/org/tartarus/snowball/SnowballStemmer.java +0 -2
  81. data/vendor/snowball/javascript/base-stemmer.js +219 -252
  82. data/vendor/snowball/javascript/stemwords.js +86 -58
  83. data/vendor/snowball/libstemmer/mkmodules.pl +1 -1
  84. data/vendor/snowball/libstemmer/modules.txt +4 -0
  85. data/vendor/snowball/pascal/SnowballProgram.pas +18 -31
  86. data/vendor/snowball/php/base-stemmer.php +453 -0
  87. data/vendor/snowball/php/stemwords.php +25 -0
  88. data/vendor/snowball/python/create_init.py +16 -21
  89. data/vendor/snowball/python/pyproject.toml +3 -0
  90. data/vendor/snowball/python/setup.py +5 -7
  91. data/vendor/snowball/python/snowballstemmer/among.py +1 -2
  92. data/vendor/snowball/python/snowballstemmer/basestemmer.py +24 -33
  93. data/vendor/snowball/python/stemwords.py +72 -63
  94. data/vendor/snowball/runtime/api.c +11 -42
  95. data/vendor/snowball/runtime/api.h +11 -9
  96. data/vendor/snowball/runtime/snowball_runtime.h +110 -0
  97. data/vendor/snowball/runtime/utilities.c +282 -106
  98. data/vendor/snowball/rust/src/main.rs +2 -2
  99. data/vendor/snowball/rust/src/snowball/snowball_env.rs +7 -9
  100. data/vendor/snowball/tests/compilertest +62 -0
  101. data/vendor/snowball/tests/errors/ae-errors.sbl +53 -0
  102. data/vendor/snowball/tests/errors/ae-errors.stderr +8 -0
  103. data/vendor/snowball/tests/errors/bad-dollar.sbl +14 -0
  104. data/vendor/snowball/tests/errors/bad-dollar.stderr +4 -0
  105. data/vendor/snowball/tests/errors/bad-grouping-definition.sbl +9 -0
  106. data/vendor/snowball/tests/errors/bad-grouping-definition.stderr +6 -0
  107. data/vendor/snowball/tests/errors/missing-bra.sbl +6 -0
  108. data/vendor/snowball/tests/errors/missing-bra.stderr +3 -0
  109. data/vendor/snowball/tests/errors/missing-command.sbl +9 -0
  110. data/vendor/snowball/tests/errors/missing-command.stderr +1 -0
  111. data/vendor/snowball/tests/errors/missing-ket-backwardmode.sbl +3 -0
  112. data/vendor/snowball/tests/errors/missing-ket-backwardmode.stderr +1 -0
  113. data/vendor/snowball/tests/errors/missing-ket.sbl +7 -0
  114. data/vendor/snowball/tests/errors/missing-ket.stderr +6 -0
  115. data/vendor/snowball/tests/errors/notdefined.sbl +4 -0
  116. data/vendor/snowball/tests/errors/notdefined.stderr +2 -0
  117. data/vendor/snowball/tests/errors/string-omitted.sbl +5 -0
  118. data/vendor/snowball/tests/errors/string-omitted.stderr +1 -0
  119. data/vendor/snowball/tests/errors/undeclared.sbl +20 -0
  120. data/vendor/snowball/tests/errors/undeclared.stderr +9 -0
  121. data/vendor/snowball/tests/errors/unexpected-token.sbl +24 -0
  122. data/vendor/snowball/tests/errors/unexpected-token.stderr +17 -0
  123. data/vendor/snowball/tests/errors/wrongdirection.sbl +14 -0
  124. data/vendor/snowball/tests/errors/wrongdirection.stderr +1 -0
  125. data/vendor/snowball/tests/runtime/among.sbl +19 -0
  126. data/vendor/snowball/tests/runtime/arithmeticexpr.sbl +86 -0
  127. data/vendor/snowball/tests/runtime/attachinsert.sbl +47 -0
  128. data/vendor/snowball/tests/runtime/booleans.sbl +19 -0
  129. data/vendor/snowball/tests/runtime/externals.sbl +22 -0
  130. data/vendor/snowball/tests/runtime/hop.sbl +12 -0
  131. data/vendor/snowball/tests/runtime/integertests.sbl +60 -0
  132. data/vendor/snowball/tests/runtime/intlimits.sbl +25 -0
  133. data/vendor/snowball/tests/runtime/naming.sbl +20 -0
  134. data/vendor/snowball/tests/runtime/not.sbl +30 -0
  135. data/vendor/snowball/tests/runtime/or.sbl +17 -0
  136. data/vendor/snowball/tests/runtime/repeat.sbl +16 -0
  137. data/vendor/snowball/tests/runtime/setlimit.sbl +24 -0
  138. data/vendor/snowball/tests/runtime/sizelen.sbl +56 -0
  139. data/vendor/snowball/tests/runtime/slice.sbl +27 -0
  140. data/vendor/snowball/tests/runtime/stringdollar.sbl +39 -0
  141. data/vendor/snowball/tests/runtime/strings.sbl +66 -0
  142. data/vendor/snowball/tests/runtime/test.sbl +19 -0
  143. data/vendor/snowball/tests/stemtest.c +41 -1
  144. data/vendor/snowball/tests/syntax/canon.sbl +32 -0
  145. data/vendor/snowball/tests/syntax/canon.stderr +0 -0
  146. data/vendor/snowball/tests/syntax/canon.syntax +57 -0
  147. data/vendor/snowball/tests/syntax/emptyprogram.sbl +2 -0
  148. data/vendor/snowball/tests/syntax/emptyprogram.syntax +0 -0
  149. data/vendor/snowball/tests/syntax/groupings.sbl +11 -0
  150. data/vendor/snowball/tests/syntax/inline.sbl +144 -0
  151. data/vendor/snowball/tests/syntax/inline.stderr +0 -0
  152. data/vendor/snowball/tests/syntax/inline.syntax +121 -0
  153. data/vendor/snowball/tests/syntax/legacy.sbl +17 -0
  154. data/vendor/snowball/tests/syntax/legacy.stderr +5 -0
  155. data/vendor/snowball/tests/syntax/legacy.syntax +19 -0
  156. data/vendor/snowball/tests/syntax/loops.sbl +14 -0
  157. data/vendor/snowball/tests/syntax/loops.stderr +9 -0
  158. data/vendor/snowball/tests/syntax/loops.syntax +29 -0
  159. data/vendor/snowball/tests/syntax/noops.sbl +76 -0
  160. data/vendor/snowball/tests/syntax/noops.stderr +27 -0
  161. data/vendor/snowball/tests/syntax/noops.syntax +135 -0
  162. data/vendor/snowball/tests/syntax/simplifyae.sbl +21 -0
  163. data/vendor/snowball/tests/syntax/simplifyae.stderr +0 -0
  164. data/vendor/snowball/tests/syntax/simplifyae.syntax +39 -0
  165. data/vendor/snowball/tests/syntax/unused.sbl +18 -0
  166. data/vendor/snowball/tests/syntax/unused.stderr +7 -0
  167. data/vendor/snowball/tests/syntax/unused.syntax +3 -0
  168. data/vendor/snowball/zig/env.zig +599 -0
  169. data/vendor/snowball/zig/generate_algorithms.pl +19 -0
  170. data/vendor/snowball/zig/stemwords.zig +123 -0
  171. metadata +102 -4
  172. data/vendor/snowball/compiler/syswords.h +0 -86
  173. data/vendor/snowball/runtime/header.h +0 -62
@@ -0,0 +1,387 @@
1
+ /*
2
+ * -----------------------------------------------------------------------------
3
+ * Persian Stemmer (HPS-like)
4
+ * -----------------------------------------------------------------------------
5
+ * Based on the paper:
6
+ * "HPS: A Hierarchical Persian Stemming Method"
7
+ * Ayshe Rashidi, Mina Zolfy Lighvan (2014)
8
+ * -----------------------------------------------------------------------------
9
+ * Differences from original HPS:
10
+ * 1) No POS-tagger stage:
11
+ * - HPS uses a POS tagger to route tokens to Noun, Adjective, or Verb
12
+ * suffix rules directly.
13
+ * - Here we fall back to the fixed order: Noun or Adjective, then Verb
14
+ * when POS is unknown.
15
+ * Reason: Implementation is tagger-agnostic and lightweight.
16
+ * 2) Lexical "-AN" guard:
17
+ * - Added `Protect_Lexical_AN` to avoid stripping true lexical endings
18
+ * like "...stan", "...ran", and known stems (Iran, Tehran, Ensan).
19
+ * Reason: reduces over-stemming where HPS relied on explicit hash lists.
20
+ * -----------------------------------------------------------------------------
21
+ * Implemented by: https://saeiddrv.com
22
+ * -----------------------------------------------------------------------------
23
+ */
24
+
25
+ stringescapes { }
26
+
27
+ // ============================================================================
28
+ // Alphabet & special symbols
29
+ // ============================================================================
30
+ stringdef alef '{U+0627}'
31
+ stringdef aa '{U+0622}'
32
+ stringdef be '{U+0628}'
33
+ stringdef pe '{U+067E}'
34
+ stringdef te '{U+062A}'
35
+ stringdef se '{U+062B}'
36
+ stringdef jim '{U+062C}'
37
+ stringdef che '{U+0686}'
38
+ stringdef heh_jimi '{U+062D}'
39
+ stringdef khe '{U+062E}'
40
+ stringdef dal '{U+062F}'
41
+ stringdef zal '{U+0630}'
42
+ stringdef re '{U+0631}'
43
+ stringdef ze '{U+0632}'
44
+ stringdef zhe '{U+0698}'
45
+ stringdef sin '{U+0633}'
46
+ stringdef shin '{U+0634}'
47
+ stringdef sad '{U+0635}'
48
+ stringdef zad '{U+0636}'
49
+ stringdef ta '{U+0637}'
50
+ stringdef za '{U+0638}'
51
+ stringdef ain '{U+0639}'
52
+ stringdef ghain '{U+063A}'
53
+ stringdef fe '{U+0641}'
54
+ stringdef ghaf '{U+0642}'
55
+ stringdef kaf '{U+06A9}'
56
+ stringdef gaf '{U+06AF}'
57
+ stringdef lam '{U+0644}'
58
+ stringdef mim '{U+0645}'
59
+ stringdef nun '{U+0646}'
60
+ stringdef vav '{U+0648}'
61
+ stringdef heh '{U+0647}'
62
+ stringdef ye '{U+06CC}'
63
+
64
+ stringdef space '{U+0020}'
65
+ stringdef zero_width_joiner '{U+200D}'
66
+ stringdef half_space '{U+200C}' // zero-width non-joiner (ZWNJ)
67
+ // Note: U+200C (ZERO WIDTH NON-JOINER) is commonly referred to as "nim-faseleh" ("half-space") in Persian.
68
+ // Although this name is not standard in English Unicode terminology, it reflects its widespread use
69
+ // in Persian as a morpheme separator within words. This character is here to normalize
70
+
71
+ // Arabic variants to normalize to Persian forms
72
+ stringdef ar_kaf '{U+0643}'
73
+ stringdef ar_ye '{U+064A}'
74
+ stringdef ar_ye_with_hamza_above '{U+0626}'
75
+ stringdef ar_heh '{U+06C1}'
76
+ stringdef ar_teh_marbuta '{U+0629}'
77
+ stringdef ar_alef_with_hamza_above '{U+0623}'
78
+ stringdef ar_alef_with_hamza_below '{U+0625}'
79
+ stringdef ar_vav_with_hamza_above '{U+0624}'
80
+
81
+ // ============================================================================
82
+ // Declarations (routines, flags, and marks)
83
+ // ============================================================================
84
+ routines (
85
+ Normalize_Characters // Forward: Unicode/script normalization
86
+ Prefixes // Forward: handles prefixes
87
+ Delete_ZWNJ // Forward: delete remaining ZWNJs after prefix handling
88
+ Protect_Lexical_AN // Probe: flag lexical -AN endings (per pass)
89
+
90
+ R1 // Test if cursor is in R1
91
+
92
+ // Noun tier (HPS categories)
93
+ AN_Exception
94
+ Irregular_Noun
95
+ Stem_Noun_or_Adjective
96
+ Stem_Verb
97
+ )
98
+
99
+ externals ( stem )
100
+
101
+ // Guard flags:
102
+ // - 'saw_present_prefix':
103
+ // true when a present-tense prefix (mi-/nemi-) is stripped; used as the
104
+ // default for `remove_verb_person_endings` for all suffix removal passes.
105
+ // - 'remove_verb_person_endings':
106
+ // true if a verb cue has been seen; used to enable verb person-ending rules
107
+ // in this pass.
108
+ booleans ( saw_present_prefix remove_verb_person_endings )
109
+
110
+ integers ( p1 )
111
+
112
+ // ============================================================================
113
+ // PHASE 1 — NORMALIZATION (forward)
114
+ // - Unify Arabic forms to Persian letters for stable downstream matching.
115
+ // - Delete ZWJ (U+200D) and ASCII spaces so the token is solid.
116
+ // - ZWNJ (U+200C) is preserved here; used as prefix-stem separator in Phase 2,
117
+ // then deleted by Delete_ZWNJ after prefix stripping is complete.
118
+ // ============================================================================
119
+ define Normalize_Characters as (
120
+ repeat (
121
+ [substring] among (
122
+ '{ar_kaf}' (<- '{kaf}')
123
+ '{ar_ye}' '{ar_ye_with_hamza_above}' (<- '{ye}')
124
+ '{ar_teh_marbuta}' '{ar_heh}' (<- '{heh}')
125
+ '{ar_alef_with_hamza_above}' '{ar_alef_with_hamza_below}' (<- '{alef}')
126
+ '{ar_vav_with_hamza_above}' (<- '{vav}')
127
+ '{zero_width_joiner}' ( delete )
128
+ '{space}' ( delete )
129
+ '' ( next )
130
+ )
131
+ )
132
+ )
133
+
134
+ // ============================================================================
135
+ // PHASE 2 — PREFIX HANDLING (forward, anchored)
136
+ // - HPS: handle known prefixes before suffix processing.
137
+ // - Requires ZWNJ (U+200C) between prefix and stem to avoid mishandling
138
+ // words that begin with the same syllables but are not prefixed forms.
139
+ // - At least two characters are required after the prefix.
140
+ // - Sets `saw_present_prefix` when matching {mi}/{nemi} to enable person endings later.
141
+ // - Note: negating derivational prefixes na- and bi- are intentionally not
142
+ // stripped as they create words with opposite meanings (e.g. na+dorost =
143
+ // incorrect, bi+khatr = safe) and conflating them with their base forms
144
+ // would harm search precision.
145
+ // - Similarly, nemi- negates the meaning so we don't remove it but do still
146
+ // set `saw_present_prefix` when it is present.
147
+ // ============================================================================
148
+ define Prefixes as (
149
+ [substring] among (
150
+ '{nun}{mim}{ye}{half_space}' // NEMI‌ + ZWNJ: detect but keep
151
+ (hop 2 set saw_present_prefix)
152
+ '{mim}{ye}{half_space}' // MI‌ + ZWNJ
153
+ (hop 2 delete set saw_present_prefix)
154
+ )
155
+ )
156
+
157
+ // ============================================================================
158
+ // PHASE 2b — DELETE REMAINING ZWNJs (forward)
159
+ // - After prefix detection, any remaining ZWNJs (e.g. in compound words)
160
+ // are no longer needed and are removed here.
161
+ // ============================================================================
162
+ define Delete_ZWNJ as (
163
+ repeat (
164
+ goto (['{half_space}'] delete)
165
+ )
166
+ )
167
+
168
+ backwardmode (
169
+
170
+ define R1 as $p1 <= cursor
171
+
172
+ // ============================================================================
173
+ // PROBE — PROTECT LEXICAL "-AN" (not plural)
174
+ // - HPS strips -AN as a plural; however, many stems end in orthographic "AN":
175
+ // ...stan / ...san / ...ran (place names/lexical stems), and frequent
176
+ // stems such as Iran/Tehran/Ensan/Ostan.
177
+ // - We signal f if a trailing pattern indicates lexical AN.
178
+ // - This is a probe only: it NEVER edits the buffer; it only signals.
179
+ // ============================================================================
180
+ define Protect_Lexical_AN as (
181
+ not AN_Exception
182
+ not among (
183
+ '{sin}{te}{alef}{nun}' // ...stan
184
+ '{sin}{alef}{nun}' // ...san
185
+ '{re}{alef}{nun}' // ...ran
186
+ '{vav}{alef}{nun}' // ...van: onvaan, divaan, javaan, ravaan
187
+ )
188
+ )
189
+
190
+ // ============================================================================
191
+ // PHASE 3 — SUFFIX STRIPPING (backward)
192
+ // - All routines in backward mode start matching from the end of the token.
193
+ // - IMPORTANT: no successful "no-op" arms; every success must modify the text.
194
+ // This ensures 'repeat' loops terminate.
195
+ // - Most suffixes are only removed/replaced if they are entirely in R1 to
196
+ // avoid over-stemming short words.
197
+ // ============================================================================
198
+
199
+ // --- Lexical "-AN" suffix list.
200
+ // Small curated set of stems ending in orthographic -AN that are not plural.
201
+ // Used by `Protect_Lexical_AN` to prevent stripping on place names / lexical nouns.
202
+ define AN_Exception as (
203
+ among (
204
+ '{aa}{lam}{mim}{alef}{nun}' // alman
205
+ '{aa}{sin}{mim}{alef}{nun}' // asman
206
+ '{alef}{ye}{mim}{alef}{nun}' // eiman
207
+ '{alef}{ye}{shin}{alef}{nun}' // ishan
208
+ '{alef}{mim}{kaf}{alef}{nun}' // emkan
209
+ '{alef}{sad}{fe}{heh}{alef}{nun}' // esfahan
210
+ '{aa}{zal}{re}{be}{alef}{ye}{jim}{alef}{nun}' // azerbaijan
211
+ '{be}{ye}{alef}{nun}' // bayan
212
+ '{pe}{alef}{ye}{alef}{nun}' // payan
213
+ '{pe}{ye}{mim}{alef}{nun}' // payman
214
+ '{jim}{re}{ye}{alef}{nun}' // jaryan
215
+ '{dal}{re}{mim}{alef}{nun}' // darman
216
+ '{re}{mim}{alef}{nun}' // roman
217
+ '{ze}{nun}{dal}{alef}{nun}' // zendan
218
+ '{sin}{alef}{ze}{mim}{alef}{nun}' // sazman
219
+ '{sin}{lam}{ta}{alef}{nun}' // soltan
220
+ '{gaf}{ye}{lam}{alef}{nun}' // Gilan
221
+ '{ghaf}{heh}{re}{mim}{alef}{nun}' // ghahramaan
222
+ '{kaf}{re}{mim}{alef}{nun}' // Kerman
223
+ '{khe}{alef}{nun}{dal}{alef}{nun}' // khandan
224
+ '{lam}{be}{nun}{alef}{nun}' // lobnan
225
+ '{mim}{ye}{ze}{alef}{nun}' // mizan
226
+ '{mim}{sin}{lam}{mim}{alef}{nun}' // mosalman
227
+ '{nun}{shin}{alef}{nun}' // neshan
228
+ '{heh}{mim}{dal}{alef}{nun}' // hamedan
229
+ '{ye}{vav}{nun}{alef}{nun}' // yunan
230
+ '{kaf}{heh}{kaf}{shin}{alef}{nun}' // kahkeshan (galaxy)
231
+ '{aa}{te}{shin}{fe}{shin}{alef}{nun}' // atashfeshan (volcano)
232
+ '{pe}{re}{ye}{shin}{alef}{nun}' // perishan (confused)
233
+ '{dal}{re}{khe}{shin}{alef}{nun}' // darakhshan (shining)
234
+ '{heh}{mim}{ze}{mim}{alef}{nun}' // hamzaman (simultaneous)
235
+ '{sin}{alef}{khe}{te}{mim}{alef}{nun}' // sakhteman (building)
236
+ '{sin}{lam}{ye}{mim}{alef}{nun}' // soleyman (Solomon)
237
+ ( atlimit )
238
+ )
239
+ )
240
+
241
+ // --- Noun irregular rewrites.
242
+ // Only include entries that actually change the buffer.
243
+ define Irregular_Noun as (
244
+ [substring] among (
245
+ '{alef}{khe}{be}{alef}{re}' (<- '{khe}{be}{re}')
246
+ '{alef}{sin}{alef}{te}{ye}{dal}' (<- '{alef}{sin}{te}{alef}{dal}')
247
+ )
248
+ )
249
+
250
+ // --- Noun/Adjective step.
251
+ define Stem_Noun_or_Adjective as (
252
+ Irregular_Noun or
253
+ setlimit tomark p1 for (
254
+ [substring] among (
255
+ // Noun possessive suffixes
256
+ '{alef}{mim}' (delete) // -AM
257
+ '{alef}{shin}' (delete) // -ASH
258
+
259
+ // -MAN/-TAN/-SHAN are intentionally disabled: these 3-char suffixes
260
+ // are ambiguous with plurals of nouns whose root ends in the same letter:
261
+ // ketab+maan (our book) -> strip -maan -> ketab (correct)
262
+ // mardom+an (people pl.) -> strip -an -> mardom (correct, NOT mardom->mard)
263
+ // naqqash+an (painters) -> strip -an -> naqqash (correct, NOT ->naqqaa)
264
+ // derakht+an (trees) -> strip -an -> derakht (correct, NOT ->derakh)
265
+ // Without a lexicon we cannot distinguish the two cases. The -AN
266
+ // rule below handles both at the cost of missing possessive stripping.
267
+ // '{mim}{alef}{nun}' (delete) // -MAN
268
+ // '{te}{alef}{nun}' (delete) // -TAN
269
+ // '{shin}{alef}{nun}' (delete) // -SHAN
270
+
271
+ // Noun plurals.
272
+ // -YAN and -GAN are productive.
273
+ '{ye}{alef}{nun}' (delete) // -YAN
274
+ '{gaf}{alef}{nun}' (delete) // -GAN
275
+ '{heh}{alef}{ye}' (delete) // -HAY
276
+ '{alef}{nun}{ye}' (delete) // -ANI
277
+ '{heh}{alef}' (delete) // -HA
278
+ '{alef}{te}' (delete) // -AT (Arabic sound plural; can also be possessive "your")
279
+ '{alef}{nun}' (delete) // -AN (only if not lexical)
280
+ '{ye}{nun}' (delete) // -IN (also adjective derivation ending)
281
+
282
+ // Other derivational noun endings (conservative set).
283
+ '{gaf}{alef}{heh}' (delete) // -GAH
284
+ '{be}{alef}{nun}' (delete) // -BAN
285
+ '{gaf}{ye}' (delete) // -GI (abstract noun)
286
+ '{ye}{te}' (delete) // -YAT
287
+ '{ye}{ye}' (delete) // -YY (double Y; also adj. relative -Y)
288
+
289
+ // Adjective: comparative/superlative (HPS).
290
+ '{te}{re}{ye}{nun}' (delete) // -TARIN
291
+ '{te}{re}' (not atlimit delete) // -TAR
292
+
293
+ // Adjective derivational endings (HPS list + common variants).
294
+ '{alef}{nun}{heh}' (delete) // -ANE
295
+ '{mim}{nun}{dal}' (delete) // -MAND
296
+ '{vav}{alef}{re}' (delete) // -VAR
297
+ '{nun}{alef}{kaf}' (delete) // -NAK
298
+ '{gaf}{alef}{re}' (delete) // -GAR
299
+ )
300
+ )
301
+ )
302
+
303
+ // --- Verb step.
304
+ define Stem_Verb as (
305
+ (
306
+ // Participle + person/aux clitic tails
307
+ [substring] among (
308
+ '{alef}{ye}{dal}'
309
+ '{alef}{ye}{mim}'
310
+ '{alef}{nun}{dal}'
311
+ '{alef}{sin}{te}'
312
+ '{alef}{sin}'
313
+ '{alef}{ye}'
314
+ '{ye}{dal}'
315
+ '{ye}{mim}'
316
+ (R1 delete)
317
+ )
318
+ ) or (
319
+ [substring] among (
320
+ // Generic person and singular/plural endings — only when a verb cue is active
321
+ '{alef}{nun}{dal}' // -AND
322
+ '{ye}{dal}' // -ID
323
+ '{ye}{mim}' // -IM
324
+ '{alef}{mim}' // -AM
325
+ '{dal}' // -D
326
+ '{mim}' // -M
327
+ ( remove_verb_person_endings R1 delete )
328
+
329
+ // Specific past-3sg stem fixes
330
+ '{re}{fe}{te}{mim}'
331
+ '{re}{fe}{te}{ye}'
332
+ '{re}{fe}{te}{ye}{mim}'
333
+ '{re}{fe}{te}{ye}{dal}'
334
+ '{re}{fe}{te}{alef}{nun}{dal}'
335
+ (<- '{re}{fe}{te}')
336
+
337
+ // Mood/tense markers (HPS).
338
+ '{nun}{dal}{heh}' // agent noun -NDEH → present root
339
+ '{alef}{nun}' // infinitive -N / -AN
340
+ (R1 delete set remove_verb_person_endings)
341
+ '{dal}{heh}' // past part. -deh
342
+ (not atlimit <-'{dal}' set remove_verb_person_endings)
343
+ '{te}{heh}' // past part. -teh
344
+ (not atlimit <-'{te}' set remove_verb_person_endings)
345
+ )
346
+ )
347
+ )
348
+ )
349
+
350
+ // ============================================================================
351
+ // MAIN (HPS pipeline)
352
+ // ============================================================================
353
+ define stem as (
354
+ unset saw_present_prefix
355
+
356
+ // 1) Normalize script/spacing (ZWNJ preserved for prefix detection)
357
+ do Normalize_Characters
358
+
359
+ // 2) Handle leading prefixes (requires ZWNJ separator)
360
+ do Prefixes
361
+
362
+ // 2b) Delete remaining ZWNJs now that prefix detection is done
363
+ do Delete_ZWNJ
364
+
365
+ // Set p1 to be 3 characters into the string.
366
+ $p1 = limit
367
+ do ( hop 3 setmark p1 )
368
+
369
+ // 3) Remove suffixes. Each pass removes one suffix, trying noun and
370
+ // adjective endings first, then verb endings. So if we remove a noun
371
+ // or adjective ending, we next check for another noun or adjective
372
+ // ending.
373
+ //
374
+ // Note: The loop exits if no suffix is removed and the buffer gets
375
+ // shorter when a suffix is removed so we can't loop forever.
376
+ backwards repeat test (
377
+ // Set remove_verb_person_endings = saw_present_prefix.
378
+ unset remove_verb_person_endings
379
+ try ( saw_present_prefix set remove_verb_person_endings )
380
+
381
+ Protect_Lexical_AN
382
+ // HPS as described in the paper uses a POS tagger. We don't have that
383
+ // information so we use a heuristic and try noun/adjective endings first,
384
+ // then verb endings.
385
+ Stem_Noun_or_Adjective or Stem_Verb
386
+ )
387
+ )
@@ -0,0 +1,177 @@
1
+ /* Polish stemmer. Author: Dmitry Shachnev */
2
+
3
+ stringescapes {}
4
+
5
+ stringdef ak '{U+0105}' // ą a + ogonek
6
+ stringdef ek '{U+0119}' // ę e + ogonek
7
+ stringdef l/ '{U+0142}' // ł l + stroke
8
+ stringdef c' '{U+0107}' // ć c + acute (kreska)
9
+ stringdef n' '{U+0144}' // ń n + acute (kreska)
10
+ stringdef o' '{U+00f3}' // ó o + acute (kreska)
11
+ stringdef s' '{U+015b}' // ś s + acute (kreska)
12
+ stringdef z' '{U+017a}' // ź z + acute (kreska)
13
+
14
+
15
+ externals (stem)
16
+
17
+
18
+ routines (
19
+ mark_regions
20
+ remove_endings
21
+ normalize_consonant
22
+ R1
23
+ )
24
+
25
+ integers ( p1 )
26
+
27
+ groupings ( v )
28
+
29
+ define v 'a{ak}e{ek}io{o'}uy'
30
+
31
+
32
+ define mark_regions as (
33
+ $p1 = limit
34
+ gopast v
35
+ gopast non-v setmark p1
36
+ )
37
+
38
+ backwardmode (
39
+ define R1 as ($p1 <= cursor)
40
+
41
+ define remove_endings as (
42
+ // Verbs.
43
+ do (
44
+ setlimit tomark p1 for ([substring]) among (
45
+ // conditionals:
46
+ 'bym' // 1st person singular (czytał(a)bym)
47
+ 'by{s'}' // 2nd person singular (czytał(a)byś)
48
+ 'by{s'}my' // 1st person plural (czytalibyśmy)
49
+ 'by{s'}cie' // 2nd person plural (czytalibyście)
50
+ 'by' // 3rd person singular/plural (czytał(a)by, czytaliby)
51
+ (delete)
52
+ )
53
+ )
54
+ [substring] among (
55
+ 'asz' 'esz' 'isz' // present 2nd person singular (czytasz, piszesz, nosisz)
56
+ 'amy' 'emy' 'imy' // present 1st person plural (czytamy, piszemy, nosimy)
57
+ 'acie' 'ecie' 'icie' // present 2nd person plural (czytacie, piszecie, nosicie)
58
+ 'aj{ak}' // present 3rd person plural (czytają)
59
+ 'e{s'}{c'}' // infinitive (przynieść)
60
+ 'a{s'}{c'}' // infinitive (popaść)
61
+ 'a{c'}' // infinitive (czytać)
62
+ 'ie{c'}' // infinitive (lecieć)
63
+ 'i{c'}' // infinitive (wozić)
64
+ '{ak}{c'}' // infinitive (marznąć)
65
+ 'aj{ak}c' '{ak}c' // contemporary adverbial participle (transgressive) (czytając, lecąc)
66
+ 'a{l/}em' 'ia{l/}em' 'i{l/}em' // past 1st person singular masculine (czytałem, leciałem, chodziłem)
67
+ 'a{l/}am' 'ia{l/}am' 'i{l/}am' 'am' // past 1st person singular feminine (czytałam, leciałam, chodziłam, marzłam)
68
+ 'a{l/}e{s'}' 'ia{l/}e{s'}' 'i{l/}e{s'}' // past 2nd person singular masculine (czytałeś, leciałeś, chodziłeś)
69
+ 'a{l/}a{s'}' 'ia{l/}a{s'}' 'i{l/}a{s'}' // past 2nd person singular feminine (czytałaś, leciałaś, chodziłaś)
70
+ 'a{l/}' 'ia{l/}' 'i{l/}' // past 3rd person singular masculine (czytał, leciał, chodził)
71
+ 'a{l/}a' 'ia{l/}a' 'i{l/}a' // past 3rd person singular feminine (czytała, leciała, chodziła)
72
+ 'a{l/}o' 'ia{l/}o' 'i{l/}o' // past 3rd person singular neuter (czytało, leciało, chodziło)
73
+ 'ali{s'}my' 'ieli{s'}my' 'ili{s'}my' // past 1st person plural virile (czytaliśmy, lecieliśmy, chodziliśmy)
74
+ 'a{l/}y{s'}my' 'ia{l/}y{s'}my' 'i{l/}y{s'}my' // past 1st person plural nonvirile (czytałyśmy, leciałyśmy, chodziłyśmy)
75
+ 'ali{s'}cie' 'ieli{s'}cie' 'ili{s'}cie' // past 2nd person plural virile (czytaliście, lecieliście, chodziliście)
76
+ 'a{l/}y{s'}cie' 'ia{l/}y{s'}cie' 'i{l/}y{s'}cie' // past 2nd person plural nonvirile (czytałyście, leciałyście, chodziłyście)
77
+ 'ali' 'ieli' 'ili' // past 3rd person plural virile (czytali, lecieli, chodzili)
78
+ 'a{l/}y' 'ia{l/}y' 'i{l/}y' // past 3rd person plural nonvirile (czytały, leciały, chodziły)
79
+ 'aj' // imperative 2nd person singular (czytaj)
80
+ 'ajcie' // imperative 2nd person plural (czytajcie)
81
+ 'cie' // imperative 2nd person plural (chodźcie)
82
+ '{ek}' // present 1st person singular (lecę)
83
+ (delete)
84
+ 'sz{ek}' // present 1st person singular (noszę)
85
+ (<- 's')
86
+ 'sz{ak}' // present 3rd person plural (noszą)
87
+ // Also an adjectival form (singular feminine accusative), e.g. lepszą.
88
+ // This heuristic does the right thing in common cases.
89
+ (R1 and delete or <-'s')
90
+
91
+ // There are short verbs whose root consists of only one consonant, e.g. być, żyć.
92
+ // Stemming them to one letter would merge them with these letters used in other
93
+ // contexts, which is undesirable. But let's at least merge all past tense forms
94
+ // together, e.g. byłem, byłam, byłyśmy, etc. to był.
95
+ '{l/}e{s'}'
96
+ '{l/}a{s'}'
97
+ 'li{s'}my'
98
+ '{l/}y{s'}my'
99
+ 'li{s'}cie'
100
+ '{l/}y{s'}cie'
101
+ (<- '{l/}')
102
+
103
+ // Adjectives (including comparative/superlative forms)
104
+ // as well as participles.
105
+ 'y' // singular masculine nominative (nowy)
106
+ 'ego' 'iego' // singular masculine genitive (nowego, polskiego)
107
+ 'emu' 'iemu' // singular masculine dative (nowemu, polskiemu)
108
+ 'ym' 'im' // singular masculine instrumental (nowym, polskim)
109
+ 'ej' 'iej' // singular feminine genitive (nowej, polskiej)
110
+ 'ych' 'ich' // plural genitive (nowych, polskich)
111
+ 'ymi' 'imi' // plural instrumental (nowymi, polskimi)
112
+ (
113
+ delete
114
+ try (
115
+ [substring] among (
116
+ 'aj{ak}c' // participle suffix (czytający)
117
+ '{ak}c' // participle suffix (lecący)
118
+ 'iejsz' // comparative suffix (piękniejszy)
119
+ 'sz' // comparative suffix (lepszy)
120
+ (delete)
121
+ 'sz{ak}c' // participle suffix (noszący)
122
+ (<- 's')
123
+ )
124
+ )
125
+ )
126
+ // We cannot remove endings like -ą and -e unconditionally, because these
127
+ // letters appear in too many contexts. But we can safely remove them if we
128
+ // know that our word is a participle or a comparative/superlative form.
129
+ 'aj{ak}ca' '{ak}ca' 'iejsza' 'sza' // singular feminine nominative (czytająca, lecąca, piękniejsza, lepsza)
130
+ 'aj{ak}c{ak}' '{ak}c{ak}' 'iejsz{ak}' // singular feminine accusative (czytającą, lecącą, piękniejszą); -szą is handled separately
131
+ 'aj{ak}ce' '{ak}ce' 'iejsze' 'sze' // singular neuter nominative (czytające, lecące, piękniejsze, lepsze)
132
+ (delete)
133
+ // Handle participles like nosząca, prosząca.
134
+ 'sz{ak}ca'
135
+ 'sz{ak}c{ak}'
136
+ 'sz{ak}ce'
137
+ (<- 's')
138
+
139
+ // Noun forms (excluding endings that were already handled above).
140
+ 'a' R1 'o' R1 // singular nominative (książka, lato)
141
+ 'i' R1 'u' R1 'ia' R1 // singular genitive (książki, stołu, słonia)
142
+ 'owi' R1 'iowi' R1 // singular dative (stołowi, słoniowi)
143
+ '{ak}' R1 'i{ak}' R1 'em' R1 'iem' R1 // singular instrumental (książką, możliwością, stołem, słoniem)
144
+ 'e' R1 'iu' R1 // singular locative (stole, słoniu)
145
+ 'ie' R1 // plural nominative (słonie)
146
+ '{o'}w' R1 // plural genitive (stołów)
147
+ 'om' R1 'iom' R1 // plural dative (książkom, słoniom)
148
+ 'ami' R1 'iami' R1 // plural instrumental (książkami, słoniami)
149
+ 'ach' R1 'iach' R1 // plural locative (książkach, słoniach)
150
+ (delete)
151
+ )
152
+ try (['{'}'] delete)
153
+ )
154
+
155
+ define normalize_consonant as (
156
+ // Remove kreska mark, because most of oblique cases do not have it.
157
+ // Don't mutate single character inputs.
158
+ [substring] not atlimit among (
159
+ '{c'}' (<- 'c') // e.g. miłość → miłośc
160
+ '{n'}' (<- 'n') // e.g. słoń → słon
161
+ '{s'}' (<- 's') // e.g. gęś → gęs
162
+ '{z'}' (<- 'z') // e.g. miedź → miedz
163
+ )
164
+ )
165
+ )
166
+
167
+ define stem as (
168
+ do mark_regions
169
+ // Make sure we don't produce too short outputs. The "backwards" will
170
+ // set the backwards limit to the current cursor position.
171
+ (
172
+ hop 2
173
+ backwards remove_endings
174
+ ) or (
175
+ backwards normalize_consonant
176
+ )
177
+ )
@@ -0,0 +1,115 @@
1
+ /* stringescapes UTF-8 */
2
+
3
+ /*
4
+ Sesotho stemmer for the Snowball project
5
+ ----------------------------------------
6
+ Author: Kamohelo Lebjane
7
+ Purpose: To reduce Sesotho words to their morphological stems.
8
+
9
+ Language notes:
10
+ Sesotho (Southern Sotho) is an agglutinative Bantu language.
11
+ Words often contain prefixes for noun classes and suffixes
12
+ for tense, aspect, or derivation.
13
+
14
+ An agglutinative language is a type of language that primarily forms words
15
+ by stringing together morphemes (word parts)—each typically representing a single
16
+ grammatical meaning—without significant modification to their forms (agglutinations).
17
+ In such languages, affixes (prefixes, suffixes, infixes, or circumfixes)
18
+ are added to a root word in a linear and systematic way, creating complex
19
+ words that encode detailed grammatical information.
20
+
21
+ Examples:
22
+ baruti -> rut (root)
23
+ moruti -> rut (root)
24
+ rutile -> rut (root)
25
+
26
+ The rules below remove common noun class prefixes
27
+ and common verb suffixes, keeping the main root form.
28
+ */
29
+
30
+ /* --- Routine declarations --- */
31
+ routines (
32
+ mark_regions
33
+ remove_noun_prefixes
34
+ remove_verb_suffixes
35
+ remove_nominal_suffixes
36
+ )
37
+
38
+ /* --- External declarations --- */
39
+ externals ( stem )
40
+
41
+ /* --- Groupings --- */
42
+ groupings ( v )
43
+
44
+ /* --- Character sets --- */
45
+ define v 'aeiou'
46
+
47
+ /* --- Integer for tracking position --- */
48
+ integers ( pV )
49
+
50
+ /* --- Mark vowel region --- */
51
+ define mark_regions as (
52
+ // Set pV after the first vowel and at least 2 characters into the string.
53
+ // Signals f if the string doesn't contain a vowel or is shorter than 2
54
+ // characters.
55
+ test (gopast v setmark pV)
56
+ test (hop 2 do ($(cursor > pV) setmark pV))
57
+ )
58
+
59
+ /* --- Remove noun class prefixes --- */
60
+ define remove_noun_prefixes as (
61
+ [substring] among(
62
+ 'mo' 'ba' 'me' 'le'
63
+ 'ma' 'se' 'boi' 'li'
64
+ )
65
+ /* Require at least two characters remain */
66
+ test (next not atlimit)
67
+ /* Only delete if there's a vowel after the cursor position */
68
+ gopast v delete
69
+ )
70
+
71
+ backwardmode (
72
+
73
+ /* --- Remove verb suffixes (from end of word) --- */
74
+ define remove_verb_suffixes as (
75
+ setlimit tomark pV for (
76
+ [substring] among(
77
+ 'ile' /* perfect tense */
78
+ 'isa' /* causative */
79
+ 'etse' /* applicative */
80
+ 'ela' /* applicative */
81
+ 'ang' /* plural imperative */
82
+ 'ong' /* continuous/derived form */
83
+ 'eng'
84
+ 'wa' /* passive */
85
+ 'a' /* infinitive marker */
86
+ (delete)
87
+ )
88
+ )
89
+ )
90
+
91
+ /* --- Remove nominal suffixes --- */
92
+ define remove_nominal_suffixes as (
93
+ setlimit tomark pV for (
94
+ [substring] among(
95
+ 'nyana' /* diminutive form */
96
+ 'ana' /* diminutive form */
97
+ 'ano'
98
+ 'oa'
99
+ 'i'
100
+ (delete)
101
+ )
102
+ )
103
+ )
104
+
105
+ )
106
+
107
+ /* --- MAIN STEMMER --- */
108
+ define stem as (
109
+ mark_regions // Signals f if the string is too short to stem.
110
+ backwards (
111
+ do remove_nominal_suffixes
112
+ do remove_verb_suffixes
113
+ )
114
+ do remove_noun_prefixes
115
+ )