glitchlings 1.0.0__cp313-cp313-win_amd64.whl

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 (86) hide show
  1. glitchlings/__init__.py +101 -0
  2. glitchlings/__main__.py +8 -0
  3. glitchlings/_corruption_engine/__init__.py +12 -0
  4. glitchlings/_corruption_engine.cp313-win_amd64.pyd +0 -0
  5. glitchlings/assets/__init__.py +180 -0
  6. glitchlings/assets/apostrofae_pairs.json +32 -0
  7. glitchlings/assets/ekkokin_homophones.json +2014 -0
  8. glitchlings/assets/hokey_assets.json +193 -0
  9. glitchlings/assets/lexemes/academic.json +1049 -0
  10. glitchlings/assets/lexemes/colors.json +1333 -0
  11. glitchlings/assets/lexemes/corporate.json +716 -0
  12. glitchlings/assets/lexemes/cyberpunk.json +22 -0
  13. glitchlings/assets/lexemes/lovecraftian.json +23 -0
  14. glitchlings/assets/lexemes/synonyms.json +3354 -0
  15. glitchlings/assets/mim1c_homoglyphs.json.gz.b64 +1064 -0
  16. glitchlings/assets/ocr_confusions.tsv +30 -0
  17. glitchlings/assets/pipeline_assets.json +29 -0
  18. glitchlings/attack/__init__.py +184 -0
  19. glitchlings/attack/analysis.py +1321 -0
  20. glitchlings/attack/core.py +819 -0
  21. glitchlings/attack/core_execution.py +378 -0
  22. glitchlings/attack/core_planning.py +612 -0
  23. glitchlings/attack/encode.py +114 -0
  24. glitchlings/attack/metrics.py +211 -0
  25. glitchlings/attack/metrics_dispatch.py +70 -0
  26. glitchlings/attack/tokenization.py +338 -0
  27. glitchlings/attack/tokenizer_metrics.py +373 -0
  28. glitchlings/auggie.py +285 -0
  29. glitchlings/compat/__init__.py +9 -0
  30. glitchlings/compat/loaders.py +355 -0
  31. glitchlings/compat/types.py +41 -0
  32. glitchlings/conf/__init__.py +39 -0
  33. glitchlings/conf/loaders.py +331 -0
  34. glitchlings/conf/schema.py +156 -0
  35. glitchlings/conf/types.py +72 -0
  36. glitchlings/config.toml +2 -0
  37. glitchlings/constants.py +139 -0
  38. glitchlings/dev/__init__.py +3 -0
  39. glitchlings/dev/docs.py +45 -0
  40. glitchlings/dlc/__init__.py +21 -0
  41. glitchlings/dlc/_shared.py +300 -0
  42. glitchlings/dlc/gutenberg.py +400 -0
  43. glitchlings/dlc/huggingface.py +68 -0
  44. glitchlings/dlc/langchain.py +147 -0
  45. glitchlings/dlc/nemo.py +283 -0
  46. glitchlings/dlc/prime.py +215 -0
  47. glitchlings/dlc/pytorch.py +98 -0
  48. glitchlings/dlc/pytorch_lightning.py +173 -0
  49. glitchlings/internal/__init__.py +16 -0
  50. glitchlings/internal/rust.py +159 -0
  51. glitchlings/internal/rust_ffi.py +599 -0
  52. glitchlings/main.py +426 -0
  53. glitchlings/protocols.py +91 -0
  54. glitchlings/runtime_config.py +24 -0
  55. glitchlings/util/__init__.py +41 -0
  56. glitchlings/util/adapters.py +65 -0
  57. glitchlings/util/keyboards.py +508 -0
  58. glitchlings/util/transcripts.py +108 -0
  59. glitchlings/zoo/__init__.py +161 -0
  60. glitchlings/zoo/assets/__init__.py +29 -0
  61. glitchlings/zoo/core.py +852 -0
  62. glitchlings/zoo/core_execution.py +154 -0
  63. glitchlings/zoo/core_planning.py +451 -0
  64. glitchlings/zoo/corrupt_dispatch.py +291 -0
  65. glitchlings/zoo/hokey.py +139 -0
  66. glitchlings/zoo/jargoyle.py +301 -0
  67. glitchlings/zoo/mim1c.py +269 -0
  68. glitchlings/zoo/pedant/__init__.py +109 -0
  69. glitchlings/zoo/pedant/core.py +99 -0
  70. glitchlings/zoo/pedant/forms.py +50 -0
  71. glitchlings/zoo/pedant/stones.py +83 -0
  72. glitchlings/zoo/redactyl.py +94 -0
  73. glitchlings/zoo/rng.py +280 -0
  74. glitchlings/zoo/rushmore.py +416 -0
  75. glitchlings/zoo/scannequin.py +370 -0
  76. glitchlings/zoo/transforms.py +331 -0
  77. glitchlings/zoo/typogre.py +194 -0
  78. glitchlings/zoo/validation.py +643 -0
  79. glitchlings/zoo/wherewolf.py +120 -0
  80. glitchlings/zoo/zeedub.py +165 -0
  81. glitchlings-1.0.0.dist-info/METADATA +404 -0
  82. glitchlings-1.0.0.dist-info/RECORD +86 -0
  83. glitchlings-1.0.0.dist-info/WHEEL +5 -0
  84. glitchlings-1.0.0.dist-info/entry_points.txt +3 -0
  85. glitchlings-1.0.0.dist-info/licenses/LICENSE +201 -0
  86. glitchlings-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,599 @@
1
+ """Centralized Rust FFI operations module.
2
+
3
+ This module is the **single entry point** for all Rust FFI calls in the codebase.
4
+ All glitchling transformations that delegate to Rust must go through this module.
5
+
6
+ **Design Philosophy:**
7
+
8
+ This module is explicitly *impure* - it loads and invokes compiled Rust functions
9
+ which are stateful operations. By centralizing all FFI here:
10
+
11
+ 1. Pure modules (validation.py, transforms.py, rng.py) never import Rust
12
+ 2. The Rust dependency is explicit and traceable
13
+ 3. Testing can mock this module to verify Python-only paths
14
+ 4. Side effects from FFI are isolated to one location
15
+
16
+ **Usage Pattern:**
17
+
18
+ # In a glitchling module (e.g., typogre.py)
19
+ from glitchlings.internal.rust_ffi import keyboard_typo_rust
20
+
21
+ def fatfinger(text: str, rate: float, ...) -> str:
22
+ # ... validation and setup ...
23
+ return keyboard_typo_rust(text, rate, layout, seed)
24
+
25
+ See AGENTS.md "Functional Purity Architecture" for full details.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from typing import Any, Literal, Mapping, Sequence, cast
31
+
32
+ from .rust import get_rust_operation, load_rust_module, resolve_seed
33
+
34
+ __all__ = [
35
+ # Seed resolution
36
+ "resolve_seed",
37
+ # Orchestration operations
38
+ "plan_operations_rust",
39
+ "compose_operations_rust",
40
+ "build_pipeline_rust",
41
+ "RustPipeline",
42
+ # Character-level operations
43
+ "keyboard_typo_rust",
44
+ "slip_modifier_rust",
45
+ "swap_homoglyphs_rust",
46
+ "ocr_artifacts_rust",
47
+ "inject_zero_widths_rust",
48
+ "stretch_word_rust",
49
+ # Word-level operations
50
+ "delete_random_words_rust",
51
+ "reduplicate_words_rust",
52
+ "swap_adjacent_words_rust",
53
+ "redact_words_rust",
54
+ "substitute_lexeme_rust",
55
+ "list_lexeme_dictionaries_rust",
56
+ "list_bundled_lexeme_dictionaries_rust",
57
+ "is_bundled_lexeme_rust",
58
+ "substitute_homophones_rust",
59
+ # Grammar operations
60
+ "apply_grammar_rule_rust",
61
+ ]
62
+
63
+
64
+ # ---------------------------------------------------------------------------
65
+ # Type Aliases
66
+ # ---------------------------------------------------------------------------
67
+
68
+ # Orchestration types
69
+ PlanResult = list[tuple[int, int]]
70
+ PipelineDescriptor = Mapping[str, Any]
71
+
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Pipeline wrapper
75
+ # ---------------------------------------------------------------------------
76
+
77
+
78
+ class RustPipeline:
79
+ """Thin wrapper around the compiled Rust Pipeline class."""
80
+
81
+ def __init__(
82
+ self,
83
+ descriptors: Sequence[PipelineDescriptor],
84
+ master_seed: int,
85
+ *,
86
+ include_only_patterns: Sequence[str] | None = None,
87
+ exclude_patterns: Sequence[str] | None = None,
88
+ ) -> None:
89
+ module = load_rust_module()
90
+ pipeline_cls = getattr(module, "Pipeline")
91
+ include_patterns_list = (
92
+ list(include_only_patterns) if include_only_patterns is not None else None
93
+ )
94
+ exclude_patterns_list = list(exclude_patterns) if exclude_patterns is not None else None
95
+ self._pipeline = pipeline_cls(
96
+ list(descriptors), int(master_seed), include_patterns_list, exclude_patterns_list
97
+ )
98
+
99
+ def run(self, text: str) -> str:
100
+ return cast(str, self._pipeline.run(text))
101
+
102
+ def run_batch(self, texts: Sequence[str]) -> list[str]:
103
+ """Process multiple texts in parallel.
104
+
105
+ Releases the GIL and processes all texts concurrently using rayon.
106
+ Results are returned in the same order as inputs.
107
+
108
+ Args:
109
+ texts: Sequence of text strings to process.
110
+
111
+ Returns:
112
+ List of corrupted texts in the same order as inputs.
113
+ """
114
+ return cast(list[str], self._pipeline.run_batch(list(texts)))
115
+
116
+
117
+ # ---------------------------------------------------------------------------
118
+ # Orchestration Operations
119
+ # ---------------------------------------------------------------------------
120
+
121
+
122
+ def plan_operations_rust(
123
+ specs: Sequence[Mapping[str, Any]],
124
+ master_seed: int,
125
+ ) -> PlanResult:
126
+ """Invoke Rust orchestration planner.
127
+
128
+ Args:
129
+ specs: Sequence of operation specifications with name/scope/order.
130
+ master_seed: Master seed for deterministic ordering.
131
+
132
+ Returns:
133
+ List of (index, derived_seed) tuples defining execution order.
134
+ """
135
+ plan_fn = get_rust_operation("plan_operations")
136
+ plan = plan_fn(specs, int(master_seed))
137
+ return [(int(index), int(seed)) for index, seed in plan]
138
+
139
+
140
+ def compose_operations_rust(
141
+ text: str,
142
+ descriptors: Sequence[PipelineDescriptor],
143
+ master_seed: int,
144
+ *,
145
+ include_only_patterns: Sequence[str] | None = None,
146
+ exclude_patterns: Sequence[str] | None = None,
147
+ ) -> str:
148
+ """Execute a sequence of operations through the Rust pipeline.
149
+
150
+ Args:
151
+ text: Input text to transform.
152
+ descriptors: Pipeline descriptors for each operation.
153
+ master_seed: Master seed for determinism.
154
+ include_only_patterns: Regex patterns limiting mutations to matching spans.
155
+ exclude_patterns: Regex patterns that should not be modified.
156
+
157
+ Returns:
158
+ Transformed text.
159
+ """
160
+ pipeline = RustPipeline(
161
+ descriptors,
162
+ int(master_seed),
163
+ include_only_patterns=include_only_patterns,
164
+ exclude_patterns=exclude_patterns,
165
+ )
166
+ return pipeline.run(text)
167
+
168
+
169
+ def build_pipeline_rust(
170
+ descriptors: Sequence[PipelineDescriptor],
171
+ master_seed: int,
172
+ *,
173
+ include_only_patterns: Sequence[str] | None = None,
174
+ exclude_patterns: Sequence[str] | None = None,
175
+ ) -> RustPipeline:
176
+ """Instantiate a Rust pipeline for reuse across calls."""
177
+ return RustPipeline(
178
+ descriptors,
179
+ master_seed,
180
+ include_only_patterns=include_only_patterns,
181
+ exclude_patterns=exclude_patterns,
182
+ )
183
+
184
+
185
+ # ---------------------------------------------------------------------------
186
+ # Character-Level Operations
187
+ # ---------------------------------------------------------------------------
188
+
189
+
190
+ def keyboard_typo_rust(
191
+ text: str,
192
+ rate: float,
193
+ layout: Mapping[str, Sequence[str]],
194
+ seed: int,
195
+ *,
196
+ shift_slip_rate: float | None = None,
197
+ shift_slip_exit_rate: float | None = None,
198
+ shift_map: Mapping[str, str] | None = None,
199
+ motor_weighting: str | None = None,
200
+ ) -> str:
201
+ """Introduce keyboard typos via Rust.
202
+
203
+ Args:
204
+ text: Input text.
205
+ rate: Probability of corrupting each character.
206
+ layout: Keyboard neighbor mapping.
207
+ seed: Deterministic seed.
208
+ shift_slip_rate: Probability of entering a shifted burst before fat-fingering.
209
+ shift_slip_exit_rate: Probability of releasing shift during a burst.
210
+ shift_map: Mapping of unshifted -> shifted keys for the active layout.
211
+ motor_weighting: Weighting mode for error sampling ('uniform', 'wet_ink',
212
+ 'hastily_edited').
213
+
214
+ Returns:
215
+ Text with simulated typing errors.
216
+ """
217
+ fn = get_rust_operation("keyboard_typo")
218
+ return cast(
219
+ str,
220
+ fn(
221
+ text,
222
+ rate,
223
+ layout,
224
+ seed,
225
+ shift_slip_rate,
226
+ shift_slip_exit_rate,
227
+ shift_map,
228
+ motor_weighting,
229
+ ),
230
+ )
231
+
232
+
233
+ def slip_modifier_rust(
234
+ text: str,
235
+ enter_rate: float,
236
+ exit_rate: float,
237
+ shift_map: Mapping[str, str],
238
+ seed: int | None,
239
+ ) -> str:
240
+ """Apply a modifier slippage burst using Rust.
241
+
242
+ Args:
243
+ text: Input text.
244
+ enter_rate: Probability of starting a shift burst.
245
+ exit_rate: Probability of ending a burst once started.
246
+ shift_map: Mapping of unshifted -> shifted characters.
247
+ seed: Deterministic seed.
248
+
249
+ Returns:
250
+ Text with modifier slippage applied.
251
+ """
252
+ fn = get_rust_operation("slip_modifier")
253
+ return cast(str, fn(text, enter_rate, exit_rate, shift_map, seed))
254
+
255
+
256
+ def swap_homoglyphs_rust(
257
+ text: str,
258
+ rate: float,
259
+ classes: list[str] | Literal["all"] | None,
260
+ banned: list[str] | None,
261
+ seed: int,
262
+ mode: str | None = None,
263
+ max_consecutive: int | None = None,
264
+ ) -> str:
265
+ """Replace characters with homoglyphs via Rust.
266
+
267
+ Args:
268
+ text: Input text.
269
+ rate: Probability of swapping each character.
270
+ classes: Homoglyph classes to use, or "all".
271
+ banned: Characters to never replace with.
272
+ seed: Deterministic seed.
273
+ mode: Substitution mode - "single_script", "mixed_script", "compatibility", or "aggressive".
274
+ max_consecutive: Maximum consecutive substitutions (locality control).
275
+
276
+ Returns:
277
+ Text with homoglyph substitutions.
278
+ """
279
+ fn = get_rust_operation("swap_homoglyphs")
280
+ return cast(str, fn(text, rate, classes, banned, seed, mode, max_consecutive))
281
+
282
+
283
+ def ocr_artifacts_rust(
284
+ text: str,
285
+ rate: float,
286
+ seed: int,
287
+ *,
288
+ burst_enter: float | None = None,
289
+ burst_exit: float | None = None,
290
+ burst_multiplier: float | None = None,
291
+ bias_k: int | None = None,
292
+ bias_beta: float | None = None,
293
+ space_drop_rate: float | None = None,
294
+ space_insert_rate: float | None = None,
295
+ ) -> str:
296
+ """Introduce OCR-like artifacts via Rust with research-backed enhancements.
297
+
298
+ This operation simulates OCR errors using three research-backed features:
299
+
300
+ **Burst Model (Kanungo et al., 1994)**
301
+ Real document defects are spatially correlated. Uses an HMM to create
302
+ error clusters simulating smudges, folds, or degraded scan regions.
303
+
304
+ **Document-Level Bias (UNLV-ISRI, 1995)**
305
+ Documents scanned under the same conditions show consistent error patterns.
306
+ Randomly selects K confusion patterns and amplifies their selection probability.
307
+
308
+ **Whitespace Errors (Smith, 2007; ICDAR)**
309
+ Models OCR segmentation failures that cause word merges/splits.
310
+
311
+ Args:
312
+ text: Input text.
313
+ rate: Base probability of introducing artifacts.
314
+ seed: Deterministic seed.
315
+ burst_enter: Probability of entering harsh (high-error) state (default 0.0).
316
+ burst_exit: Probability of exiting harsh state (default 0.3).
317
+ burst_multiplier: Rate multiplier in harsh state (default 3.0).
318
+ bias_k: Number of confusion patterns to amplify (default 0 = disabled).
319
+ bias_beta: Amplification factor for biased patterns (default 2.0).
320
+ space_drop_rate: Probability of dropping a space (default 0.0).
321
+ space_insert_rate: Probability of inserting a spurious space (default 0.0).
322
+
323
+ Returns:
324
+ Text with simulated OCR errors.
325
+
326
+ References:
327
+ - Kanungo et al. (1994) - Nonlinear Global and Local Document Degradation Models
328
+ - Rice et al. / UNLV-ISRI Annual Tests (1995)
329
+ - Smith (2007) - Tesseract OCR architecture
330
+ - ICDAR Robust Reading Competitions
331
+ """
332
+ fn = get_rust_operation("ocr_artifacts")
333
+ return cast(
334
+ str,
335
+ fn(
336
+ text,
337
+ rate,
338
+ seed,
339
+ burst_enter,
340
+ burst_exit,
341
+ burst_multiplier,
342
+ bias_k,
343
+ bias_beta,
344
+ space_drop_rate,
345
+ space_insert_rate,
346
+ ),
347
+ )
348
+
349
+
350
+ def inject_zero_widths_rust(
351
+ text: str,
352
+ rate: float,
353
+ characters: list[str],
354
+ seed: int | None,
355
+ *,
356
+ visibility: str | None = None,
357
+ placement: str | None = None,
358
+ max_consecutive: int | None = None,
359
+ ) -> str:
360
+ """Inject zero-width characters via Rust.
361
+
362
+ Args:
363
+ text: Input text.
364
+ rate: Probability of injection between characters.
365
+ characters: Palette of zero-width characters to use.
366
+ seed: Deterministic seed.
367
+ visibility: Visibility mode ('glyphless', 'with_joiners', 'semi_visible').
368
+ placement: Placement mode ('random', 'grapheme_boundary', 'script_aware').
369
+ max_consecutive: Maximum consecutive insertions (0 for unlimited).
370
+
371
+ Returns:
372
+ Text with injected zero-width characters.
373
+ """
374
+ fn = get_rust_operation("inject_zero_widths")
375
+ return cast(str, fn(text, rate, characters, seed, visibility, placement, max_consecutive))
376
+
377
+
378
+ def stretch_word_rust(
379
+ text: str,
380
+ rate: float,
381
+ extension_min: int,
382
+ extension_max: int,
383
+ word_length_threshold: int,
384
+ base_p: float,
385
+ seed: int | None,
386
+ ) -> str:
387
+ """Extend expressive segments via Rust.
388
+
389
+ Args:
390
+ text: Input text.
391
+ rate: Selection rate for candidate words.
392
+ extension_min: Minimum extra repetitions.
393
+ extension_max: Maximum extra repetitions.
394
+ word_length_threshold: Preferred max word length.
395
+ base_p: Base probability for sampler.
396
+ seed: Deterministic seed.
397
+
398
+ Returns:
399
+ Text with extended expressive segments.
400
+ """
401
+ fn = get_rust_operation("stretch_word")
402
+ return cast(
403
+ str,
404
+ fn(text, rate, extension_min, extension_max, word_length_threshold, base_p, seed),
405
+ )
406
+
407
+
408
+ # ---------------------------------------------------------------------------
409
+ # Word-Level Operations
410
+ # ---------------------------------------------------------------------------
411
+
412
+
413
+ def delete_random_words_rust(
414
+ text: str,
415
+ rate: float,
416
+ unweighted: bool,
417
+ seed: int,
418
+ ) -> str:
419
+ """Delete random words via Rust.
420
+
421
+ Args:
422
+ text: Input text.
423
+ rate: Probability of deleting each word.
424
+ unweighted: If True, use uniform selection; else weight by length.
425
+ seed: Deterministic seed.
426
+
427
+ Returns:
428
+ Text with words deleted.
429
+ """
430
+ fn = get_rust_operation("delete_random_words")
431
+ return cast(str, fn(text, rate, unweighted, seed))
432
+
433
+
434
+ def reduplicate_words_rust(
435
+ text: str,
436
+ rate: float,
437
+ unweighted: bool,
438
+ seed: int,
439
+ ) -> str:
440
+ """Reduplicate random words via Rust.
441
+
442
+ Args:
443
+ text: Input text.
444
+ rate: Probability of duplicating each word.
445
+ unweighted: If True, use uniform selection; else weight by length.
446
+ seed: Deterministic seed.
447
+
448
+ Returns:
449
+ Text with words duplicated.
450
+ """
451
+ fn = get_rust_operation("reduplicate_words")
452
+ return cast(str, fn(text, rate, unweighted, seed))
453
+
454
+
455
+ def swap_adjacent_words_rust(
456
+ text: str,
457
+ rate: float,
458
+ seed: int,
459
+ ) -> str:
460
+ """Swap adjacent words via Rust.
461
+
462
+ Args:
463
+ text: Input text.
464
+ rate: Probability of swapping adjacent word pairs.
465
+ seed: Deterministic seed.
466
+
467
+ Returns:
468
+ Text with adjacent words swapped.
469
+ """
470
+ fn = get_rust_operation("swap_adjacent_words")
471
+ return cast(str, fn(text, rate, seed))
472
+
473
+
474
+ def redact_words_rust(
475
+ text: str,
476
+ replacement: str,
477
+ rate: float,
478
+ merge: bool,
479
+ unweighted: bool,
480
+ seed: int,
481
+ ) -> str:
482
+ """Redact random words via Rust.
483
+
484
+ Args:
485
+ text: Input text.
486
+ replacement: Character to replace word characters with.
487
+ rate: Probability of redacting each word.
488
+ merge: If True, merge adjacent redactions.
489
+ unweighted: If True, use uniform selection; else weight by length.
490
+ seed: Deterministic seed.
491
+
492
+ Returns:
493
+ Text with words redacted.
494
+ """
495
+ fn = get_rust_operation("redact_words")
496
+ return cast(str, fn(text, replacement, rate, merge, unweighted, seed))
497
+
498
+
499
+ def substitute_lexeme_rust(
500
+ text: str,
501
+ lexemes: str,
502
+ mode: str,
503
+ rate: float,
504
+ seed: int | None,
505
+ ) -> str:
506
+ """Apply dictionary-based word substitution via Rust.
507
+
508
+ Args:
509
+ text: Input text.
510
+ lexemes: Name of the dictionary to use (colors, synonyms, corporate, academic, cyberpunk,
511
+ lovecraftian, or any custom dictionary discovered in the lexemes directory).
512
+ mode: Substitution mode ("literal" or "drift").
513
+ rate: Probability of transforming each matching word.
514
+ seed: Deterministic seed (only used for "drift" mode).
515
+
516
+ Returns:
517
+ Text with word substitutions applied.
518
+ """
519
+ fn = get_rust_operation("substitute_lexeme")
520
+ return cast(str, fn(text, lexemes, mode, rate, seed))
521
+
522
+
523
+ def list_lexeme_dictionaries_rust() -> list[str]:
524
+ """List available lexeme dictionaries.
525
+
526
+ Returns:
527
+ List of dictionary names available for Jargoyle.
528
+ """
529
+ fn = get_rust_operation("list_lexeme_dictionaries")
530
+ return cast(list[str], fn())
531
+
532
+
533
+ def list_bundled_lexeme_dictionaries_rust() -> list[str]:
534
+ """List bundled (built-in) lexeme dictionaries embedded at compile time.
535
+
536
+ Returns:
537
+ List of dictionary names that are embedded in the Rust binary.
538
+ """
539
+ fn = get_rust_operation("list_bundled_lexeme_dictionaries")
540
+ return cast(list[str], fn())
541
+
542
+
543
+ def is_bundled_lexeme_rust(name: str) -> bool:
544
+ """Check if a lexeme dictionary name refers to a bundled dictionary.
545
+
546
+ Args:
547
+ name: Name of the lexeme dictionary to check.
548
+
549
+ Returns:
550
+ True if the dictionary is bundled (embedded), False otherwise.
551
+ """
552
+ fn = get_rust_operation("is_bundled_lexeme")
553
+ return cast(bool, fn(name))
554
+
555
+
556
+ def substitute_homophones_rust(
557
+ text: str,
558
+ rate: float,
559
+ weighting: str,
560
+ seed: int | None,
561
+ ) -> str:
562
+ """Substitute words with homophones via Rust.
563
+
564
+ Args:
565
+ text: Input text.
566
+ rate: Probability of substituting each word.
567
+ weighting: Weighting mode for selection.
568
+ seed: Deterministic seed.
569
+
570
+ Returns:
571
+ Text with homophone substitutions.
572
+ """
573
+ fn = get_rust_operation("substitute_homophones")
574
+ return cast(str, fn(text, rate, weighting, seed))
575
+
576
+
577
+ # ---------------------------------------------------------------------------
578
+ # Grammar Operations
579
+ # ---------------------------------------------------------------------------
580
+
581
+
582
+ def apply_grammar_rule_rust(
583
+ text: str,
584
+ *,
585
+ stone: str,
586
+ seed: int,
587
+ ) -> str:
588
+ """Apply grammar rule transformation via Rust.
589
+
590
+ Args:
591
+ text: Input text.
592
+ stone: Grammar rule label defining transformation type.
593
+ seed: Deterministic seed.
594
+
595
+ Returns:
596
+ Text with grammar transformation applied.
597
+ """
598
+ fn = get_rust_operation("apply_grammar_rule")
599
+ return cast(str, fn(text, stone=stone, seed=seed))