The ideal state of syara-x — what we're building toward.
-
Parse once, execute many. All parsing, validation, and compilation happens upfront. The scan hot path touches only pre-computed structures (compiled ASTs, cached regexes, pre-embedded patterns).
-
Zero silent failures. Every misconfiguration, malformed rule, or unsupported modifier produces a clear error at compile time. Rules never silently skip matching.
-
Type-safe boundaries. Rust's type system should enforce invariants. No sentinel values, no stringly-typed state.
Option<T>for optional data, enums for variants, newtypes for constrained values. -
Defensive at system boundaries, trusting internally. Validate all external input (rule files, scanned content, WAV/image data, HTTP responses). Internal code paths can assume invariants established at the boundary.
-
Feature isolation. Each feature flag (
sbert,classifier,llm,phash,burn-llm) is a self-contained opt-in. No compile errors, no dead code, no behavioral changes when a flag is off. -
Embeddable and safe. The C API must be memory-safe by construction — no dangling pointers, no UB, no uninitialized out-params. The Rust API should be misuse-resistant.
.syara file
|
v
SyaraParser ──> Vec<Rule> # Regexes compiled once via LazyLock
| # Escaped quotes handled
| # Line numbers tracked for errors
v
Compiler ──> CompiledRules # Condition AST parsed and stored
| # All identifiers validated
| # Wildcards resolved
| # Modifiers validated (fullword, wide, etc.)
v
scan(text) / scan_file(path) # Pre-compiled AST evaluated (no re-parse)
| # Regex cache with entry API (no clones)
| # Pattern embeddings cached across scans
| # HTTP clients with timeouts
| # LLM prompts hardened against injection
v
Vec<Match> # Option<usize> positions
# Only matched rules carry tags/meta
- Layered tokenization: top-level brace-counter and comment-stripper in
parser/mod.rs; per-line tokenizer inparser/scanner.rs. The three state machines must agree on what counts as "inside a regex / string / triple-quoted literal" — pinned by the parity test inparser/scanner.rs::tests::parity_with_split_rules_regex_consumption. - No regex-over-line tokenization: section line parsing goes through
Scanner(escape-aware) rather thanLazyLock<Regex>body captures (escape-blind). See BUG-039 close-out for what the latter cost us. If you find yourself reaching for[^X]*over a body that may contain\X, stop and lex. - String handling: Byte-indexed cursor; UTF-8 char-boundary checks before slicing.
- Escapes:
\",\\,\n,\t,\rin quoted strings;\/(and any\X) preserved in regex bodies; triple-quoted ("""...""") bodies captured raw. - Error reporting: Per-line
linefield tracked through parsing; attached toSyaraError::ParseError. - Missing sections: Explicit error for missing
condition:, not silent empty string.
- Single-pass validation: Check identifiers, resolve wildcards, validate modifiers.
- Eager regex compile: Each
StringRuleis validate-compiled atcompile_strtime viaStringMatcher::validateso malformed patterns surface asSyaraError::InvalidPatternhere, not silently at scan time (BUG-040). - AST storage: Parse condition into
Exprand store it inCompiledRules— never re-parsed. - Modifier validation: Verify all modifiers are actually implemented before accepting them.
- Strict parsing: Error on unconsumed tokens, unknown characters, and malformed expressions.
- Hypothetical evaluation:
is_identifier_neededshould evaluate without cloning the match map — use a wrapper that overrides a single key.
- Pre-compiled AST: Evaluate stored
Expr, not re-parsed strings. scan_filecompleteness: Read file content for string matching alongside phash processing.- Cost-ordered execution: Preserved from current design — cheap matchers first, LLM last with short-circuit.
- All modifiers work:
fullwordadds\bboundaries,wideis cached,dotallis applied. - No regex clones: Use entry API or index-based access from cache.
- Accurate positions: Wide match positions map back to original text coordinates.
- Unicode parity:
AggressiveCleanerstrips Unicode digits to match Python behavior. - Input validation:
chunk_size == 0returns an error, not a panic. - Streaming: For large texts, cleaners and chunkers should avoid materializing full intermediate strings where possible.
- Shared embedding client: One
embed()implementation used by both semantic matcher and classifier. - Timeouts: All HTTP clients configured with connect + read timeouts (default 30s).
- Pattern caching: Pattern embeddings computed once and cached across scans.
- LLM hardening: Structured prompt with clear delimiters. Response parsing requires exact
YES/NOwith boundary check. Attack surface documented.
- Input validation:
hash_sizecapped at 8 in constructor. WAVchunk_lenbounded to file size or reasonable max. - WAV correctness: Account for channel count in frame calculation. Handle RIFF odd-chunk padding.
- Bounds safety: Video hash loop uses explicit bounds checks matching audio hash pattern.
- Cheap keys: Use
(cleaner_name, text)tuple or(&str, &str)as key — let HashMap's built-in hasher do the work. No SHA256 pre-hashing. - Zero-copy returns: Return
&strfrom cache, not clonedString.
- Idiomatic types:
Option<usize>for positions.FromStrtrait forModifier. - Lazy population:
Match::no_matchcarries only rule name andmatched: false— no cloned tags/meta.
- Safe out-params: Initialize
*out = nullat the start of every function. - Explicit capacity: Store
Veccapacity inSyaraMatchArray— no reliance onshrink_to_fit. - Full match data: Expose tags, meta, and matched_patterns to C consumers.
- cbindgen config: Proper
cbindgen.tomlwith opaque type declarations and include guards.
Two LLM backends coexist behind the LLMEvaluator trait:
LLMEvaluator (trait)
|
+-- HttpLLMEvaluator [feature = "llm"]
| OpenAI / Ollama via HTTP
|
+-- BurnLLMEvaluator [feature = "burn-llm"]
Local inference via Burn framework
Backend selected via Registry configuration. Both implement the same trait. HTTP backend for cloud/hosted models, Burn backend for air-gapped or latency-sensitive deployments.
- Unit tests per module: Each matcher, cleaner, chunker tested in isolation.
- Integration tests: Cover every modifier (
fullword,wide,dotall,nocase), regex-only matching, error paths, mixed rule types, andscan_filewith combined phash+string rules. - Negative tests: Malformed syntax, duplicate identifiers, unclosed braces, undefined references.
- Fuzz targets: Parser and WAV/image parsers should have fuzz harnesses for untrusted input.
- No mocks: Real execution paths, real regex compilation, real condition evaluation.
Target: every .rs file under 500 lines. Current violations:
parser/mod.rs— 716 lines (large in-module test suite; non-test impl is ~280 lines)parser/scanner.rs— 538 lines (large in-module test suite; non-test impl is ~310 lines)condition.rs— 456 lines (acceptable, but monitor)capi/src/lib.rs— 439 lines (acceptable)