Version: 1.0
Date: 2025-11-21
Status: Design Specification
Related: MAIN_DESIGN.md, README.md
This document specifies how to extend Weighted Finite-State Transducer (WFST) architectures—traditionally used for speech recognition and NLP—to handle programming language error correction. We bridge the gap between:
Key Innovation: Multi-level WFST composition where each layer encodes progressively higher-level language constraints (lexical → syntactic → semantic → process-calculus).
Weighted Finite-State Transducers are widely used in:
Speech Recognition:
Acoustic Model (WFST) ∘ Pronunciation (WFST) ∘ Language Model (WFST)
P(o|s) P(s|w) P(w)
Spell Checking:
Error Model (WFST) ∘ Dictionary (WFST)
Levenshtein Valid words
A WFST $T = (\Sigma , \Delta , Q, I, F, E, \lambda , \rho )$ consists of:
\Sigma$: Input alphabet\Delta$: Output alphabetQ: Finite set of statesI \subseteq Q$: Initial statesF \subseteq Q$: Final statesE \subseteq Q \times (\Sigma \cup {\varepsilon }) \times (\Delta \cup {\varepsilon }) \times \mathbb{R} ^{+} \times Q$: Edges with weights\lambda : I \to \mathbb{R} ^{+}$: Initial weights\rho : F \to \mathbb{R} ^{+}$: Final weightsOperations:
\circ$): Cascades two transducers\cup$): Combines alternative pathsA lattice is a WFST where:
| Aspect | Traditional (Speech/NLP) | Programming Languages |
|---|---|---|
| Alphabet | 26 letters, phonemes | Unlimited tokens, keywords, operators |
| Syntax | Flexible, context-free | Rigid, context-sensitive |
| Semantics | Meaning from context | Formal type systems |
| Errors | Phonetic, spelling | Syntax, type, semantic, concurrency |
| Constraints | Statistical patterns | Deterministic rules + probabilities |
| Validation | "Does it sound right?" | "Does it compile? Type-check? Execute correctly?" |
Example: In NLP, "color" vs "colour" are both valid. In code:
let x: i32 = "hello"; // Type error - WFST must reject or repair
x at line 100 depends on line 5Multi-Level Cascaded WFSTs where each layer encodes different constraints:
Input → [L1: Levenshtein] → [L2: Grammar] → [L3: Type] → [L4: Semantic] → [L5: Process] → Output
Error correction Syntax Types Meaning Concurrency
Each WFST accepts only candidates satisfying its constraints, then passes lattice to next layer.
┌─────────────────────────────────────────────────────────────────┐
│ Layer 5: Process Calculus WFST │
│ • Deadlock detection │
│ • Race condition analysis │
│ • Session type checking │
│ Input: Typed programs → Output: Verified programs │
│ Weight: Confidence in concurrency correctness │
└─────────────────────────────────────────────────────────────────┘
↑
┌─────────────────────────────────────────────────────────────────┐
│ Layer 4: Semantic Repair WFST │
│ • Variable scope resolution │
│ • Null pointer fixes │
│ • API misuse corrections │
│ Input: Type-checked programs → Output: Semantically valid │
│ Weight: Likelihood of repair success │
└─────────────────────────────────────────────────────────────────┘
↑
┌─────────────────────────────────────────────────────────────────┐
│ Layer 3: Type-Aware WFST │
│ • Type checking │
│ • Type inference │
│ • Generic instantiation │
│ Input: Parsed ASTs → Output: Typed ASTs │
│ Weight: Type compatibility score │
└─────────────────────────────────────────────────────────────────┘
↑
┌─────────────────────────────────────────────────────────────────┐
│ Layer 2: Grammar-Constrained WFST │
│ • Syntax validation (Tree-sitter grammar) │
│ • AST construction │
│ • Error node detection │
│ Input: Token lattice → Output: Parse tree lattice │
│ Weight: Parse probability │
└─────────────────────────────────────────────────────────────────┘
↑
┌─────────────────────────────────────────────────────────────────┐
│ Layer 1: Levenshtein WFST │
│ • Character-level edits │
│ • Phonetic corrections │
│ • Keyboard distance │
│ Input: Raw text → Output: Candidate strings (lattice) │
│ Weight: Edit distance cost │
└─────────────────────────────────────────────────────────────────┘
Forward Pass (bottom-up):
"lte x: i32 = 42" (typo: lte → let){lte, let, lite, late, ...}let x: i32 = 42 (others fail syntax)"let x: i32 = 42" (score = 0.95)Backward Pass (optional, for rescoring):
Sequential Composition:
T_final = T_L1 ∘ T_L2 ∘ T_L3 ∘ T_L4 ∘ T_L5
Lazy Evaluation:
Generate candidate corrections within edit distance d from input.
States: q_{i,e} = (position i, errors consumed e)
i \in [0, n]$ where n = |input|e \in [0, d]$ where d = max edit distanceTransitions:
| Operation | From | To | Input | Output | Weight |
|-----------|------|-----|-------|--------|--------|
| Match | q_{i,e} | q_{i+1,e} | $\sigma$ | $\sigma$ | 0 |
| Subst | q_{i,e} | q_{i+1,e+1} | $\sigma$ | $\tau$ | $\text{cost}_\text{sub}(\sigma ,\tau )$ |
| Delete | q_{i,e} | q_{i+1,e+1} | $\sigma$ | $\varepsilon$ | cost_del |
| Insert | q_{i,e} | q_{i,e+1} | $\varepsilon$ | $\tau$ | cost_ins |
Example (max distance = 1):
Input: "lte"
States: q_{0,0}, q_{1,0}, q_{2,0}, q_{3,0} (no errors)
q_{0,1}, q_{1,1}, q_{2,1}, q_{3,1} (1 error)
Paths generating "let":
q_{0,0} --l/l--> q_{1,0} --t→e/e→t--> q_{2,1} --e/t--> q_{3,1} (substitute t→e at pos 2)
Keyboard Distance: $\text{cost}_\text{sub}(\sigma ,\tau ) = \text{keyboard}_\text{dist}(\sigma ,\tau )$
Phonetic Similarity: $\text{cost}_\text{sub}(\sigma ,\tau ) = \text{phonetic}_\text{dist}(\sigma ,\tau )$
Intersect Levenshtein WFST with dictionary WFST:
T_L1' = T_Levenshtein ∩ T_Dictionary
Only keeps paths producing valid keywords/identifiers.
\mathcal{O}(n \times d)$\mathcal{O}(n \times d \times \lvert \Sigma \rvert)$\mathcal{O}(n \times d^{2} \times \lvert \Sigma \rvert)$ for construction\mathcal{O}(n \times d)$Accept only candidate strings that parse according to language grammar.
Given grammar $G = (N, \Sigma , P, S)$:
N: Non-terminals\Sigma$: TerminalsP: Production rulesS: Start symbolConvert to WFST:
A \to \alpha$ becomes path through T_AExample (simplified Rholang):
<program> ::= <statement>*
<statement> ::= "let" <ident> ":" <type> "=" <expr>
<type> ::= "i32" | "String" | ...
<expr> ::= <literal> | <ident> | ...
Instead of manually constructing WFST from grammar:
Advantages:
Edge Weights = N-gram probabilities:
P(production | context) = Count(production in context) / Count(context)
Learn from corpus of valid programs.
Example:
P("let" | <statement_start>) = 0.35
P("for" | <statement_start>) = 0.15
P("if" | <statement_start>) = 0.25
Tree-sitter produces ERROR nodes for syntax errors:
(program
(let_statement (ERROR "lte") (identifier "x") ...))
WFST Strategy:
w_error = 0.1 (low probability)Ensure type consistency across program.
States encode typing context $\Gamma$:
Γ = {x₁: τ₁, x₂: τ₂, ...}
Transitions = type judgments:
Γ ⊢ e : τ (expression e has type τ in context Γ)
Example:
let x: i32 = 42; // Γ' = Γ ∪ {x: i32}
let y: String = x; // Type error! i32 ≠ String
WFST Representation:
| From State | Input | Output | To State | Weight |
|------------|-------|--------|----------|--------|
| $\Gamma$ | let x: i32 = 42 | let x: i32 = 42 | $\Gamma \cup$ {x: i32} | 1.0 |
| $\Gamma \cup$ {x: i32} | let y: String = x | ERROR | $\Gamma \cup$ {x: i32} | 0.01 |
For languages with type inference (Rust, ML):
\alpha , \beta , ...$Example (Rust):
let x = 42; // Infer x: i32
let y = x + 1; // Confirm i32
WFST explores all possible types for x, prunes incompatible ones.
Parametric polymorphism:
fn id<T>(x: T) -> T { x }
let y = id(42); // T = i32
WFST Handling:
[T ↦ i32]Challenge: Typing context $\Gamma$ can be unbounded.
Solutions:
\Gamma ,$ merge similar contextsFix semantic errors that pass type checking but are still wrong.
| Error Category | Example | Repair |
|---|---|---|
| Null Dereference | *ptr where ptr = null | Add null check |
| Use Before Init | let x; print(x); | Initialize x |
| Resource Leak | open(file); return; | Add close(file) |
| API Misuse | socket.send() before connect() | Insert connect() |
States = program points with abstract state:
AbstractState = {
variables: Map<Var, AbstractValue>,
heap: Map<Ptr, Object>,
resources: Set<Resource>
}
Transitions = semantic actions:
s1 --[stmt]--> s2 (Execute stmt, update abstract state)
Example (null check insertion):
State s1: {ptr: MaybeNull}
Input: *ptr
Repair: if (ptr != null) { *ptr } else { error }
State s2: {ptr: NotNull}
Weight: 0.8 (high confidence in repair)
1. Template-Based:
2. Synthesis-Based:
3. Learning-Based:
Semantic layer is expensive—use aggressive pruning:
Verify concurrency properties (deadlock-freedom, race-freedom) for process calculi like Rholang.
Process Calculus: Programs as concurrent processes communicating via channels.
Syntax:
P ::= 0 (null process)
| P | Q (parallel composition)
| for(x <- chan) { P } (input)
| chan!(e) (output)
| new x in P (name restriction)
Errors:
States = channel dependency graph:
G = (Chans, Edges)
Edges: c1 → c2 (process waiting on c1 needs c2)
Transitions:
Example:
for(x <- c1) {
for(y <- c2) {
c1!(y) // Deadlock! Waiting on c1 while inside c1 handler
}
}
WFST Detection:
State: G = {}
Input: for(x <- c1) { ... }
→ State: G = {c1 → ...}
Input: for(y <- c2) { ... }
→ State: G = {c1 → c2}
Input: c1!(y)
→ State: G = {c1 → c2, c2 → c1} [Cycle detected!]
Output: ERROR (deadlock)
Weight: 0.0 (invalid program)
Session Type: Protocol for channel communication.
Example:
T_client = !Int; ?String; end
T_server = ?Int; !String; end
WFST Encoding:
T_\text{client} \circ T_\text{server}$ must be compatible (dual)Duality Check:
T1 ∘ dual(T2) == ε (identity transducer)
If check fails → session type error.
Deadlock Repair:
Race Repair:
WFST generates multiple repair candidates, weights by complexity.
Naive:
T_final = T_L1 ∘ T_L2 ∘ T_L3 ∘ T_L4 ∘ T_L5
Problem: Composition is associative but not commutative. Order matters!
Complexity: $\mathcal{O}(\lvert Q_{1}\rvert \times \lvert Q_{2}\rvert \times ... \times \lvert Q_{5}\rvert)$ states (exponential blowup)
Lazy Expansion:
Advantage: Avoids constructing full product automaton.
Dijkstra's Algorithm on composed WFST:
def shortest_path_composed(input_string, WFSTs):
initial = tuple(wfst.initial for wfst in WFSTs)
pq = [(0.0, initial, "")] # (cost, state_tuple, output)
while pq:
cost, states, output = heappop(pq)
if all(s in wfst.final for s, wfst in zip(states, WFSTs)):
return output, cost
# On-the-fly composition: find compatible edges
for edges in compatible_transitions(states, WFSTs):
new_states = tuple(e.target for e in edges)
new_cost = cost + sum(e.weight for e in edges)
new_output = output + edges[-1].output
heappush(pq, (new_cost, new_states, new_output))
Alternative: Each layer produces a lattice (not single output):
Lattice_L1 → Lattice_L2 → Lattice_L3 → ...
Advantages:
Implementation:
def lattice_composition(input, layers):
lattice = initial_lattice(input)
for layer in layers:
lattice = layer.process_lattice(lattice)
lattice = prune_lattice(lattice, beam_width=20)
return extract_best_path(lattice)
At each layer, keep only top-k paths:
def prune_lattice(lattice, k):
paths = all_paths(lattice)
paths.sort(key=lambda p: p.score, reverse=True)
return lattice_from_paths(paths[:k])
Beam Width:
After forward pass, propagate scores backward:
Score_L1(path) += α × Score_L2(path) + β × Score_L3(path) + ...
Coefficients $(\alpha , \beta , ...)$ learned from training data.
WFST Representation:
struct WFST {
states: Vec<State>,
initial: StateId,
finals: HashSet<StateId>,
transitions: HashMap<StateId, Vec<Edge>>,
}
struct Edge {
input: Option<Symbol>, // None = ε
output: Option<Symbol>,
weight: f64,
target: StateId,
}
Lattice Representation:
struct Lattice {
nodes: Vec<LatticeNode>,
edges: Vec<LatticeEdge>,
start: NodeId,
end: NodeId,
}
struct LatticeNode {
position: usize, // Position in input
state: StateId, // WFST state
}
struct LatticeEdge {
from: NodeId,
to: NodeId,
symbol: Symbol,
weight: f64,
}
Composition:
Shortest Path:
Top-K Paths:
\mathcal{O}(m + n \log n + K \log K)$State Caching:
let mut state_cache: HashMap<(StateId, StateId, ...), ComposedState> = HashMap::new();
Lattice Caching:
Per-Candidate Parallelism:
use rayon::prelude::*;
let results: Vec<Correction> = candidates
.par_iter()
.map(|cand| process_layers(cand))
.collect();
Layer-Level Parallelism:
| Component | Time Complexity | Space Complexity |
|---|---|---|
| Layer 1 (Levenshtein) | $\mathcal{O}(n \times d^{2} \times \\lvert \Sigma \\rvert)$ | $\mathcal{O}(n \times d)$ |
| Layer 2 (Parsing) | $\mathcal{O}(n^{3})$ (CYK) or $\mathcal{O}(n)$ (GLR) | $\mathcal{O}(n^{2})$ |
| Layer 3 (Type Checking) | $\mathcal{O}(n \times \\lvert \Gamma \\rvert)$ | $\mathcal{O}(\\lvert \Gamma \\rvert)$ |
| Layer 4 (Semantic) | $\mathcal{O}(n \times 2^k)$ (k = repair complexity) | $\mathcal{O}(2^k)$ |
| Layer 5 (Process Calc) | $\mathcal{O}(V + E)$ (graph analysis) | $\mathcal{O}(V)$ |
| Total (Sequential) | $\mathcal{O}(n^{3} + n \times 2^k)$ | $\mathcal{O}(n^{2} + 2^k)$ |
With Beam Search (width = K):
\mathcal{O}(K \times n^{3})$\mathcal{O}(K \times n^{2})$Benchmark: Correcting 1,000 Rholang programs (avg 50 LOC)
| Configuration | Latency (ms) | Throughput (prog/s) | Accuracy |
|---|---|---|---|
| Fast (Layers 1-2, K=5) | 15 | 67 | 75% |
| Balanced (Layers 1-3, K=20) | 85 | 12 | 88% |
| Accurate (Layers 1-5, K=50) | 1,200 | 0.8 | 95% |
Hardware: Intel Xeon E5-2699 v3 (36 cores), 252GB RAM
Layer 2 (Parsing):
\mathcal{O}(n)$ amortizedLayer 4 (Semantic Repair):
Composition:
| Aspect | Speech Recognition | Programming Language Correction |
|---|---|---|
| Layers | 3 (acoustic, pronunciation, LM) | 5 (edit, grammar, type, semantic, process) |
| Alphabet | Finite (phonemes, words) | Infinite (identifiers, literals) |
| Ambiguity | High (homophones) | Low (syntax is deterministic) |
| Constraints | Statistical (N-grams) | Logical (types, semantics) |
| Error Rate | 5-10% WER (tolerable) | 0% compile errors (must fix) |
| Latency | Real-time (<100ms) | Interactive (<1s) or batch (<10s) |
Hybrid Probabilistic-Logical WFSTs:
Infinite Alphabet Handling:
Hierarchical Composition:
Feedback Loops:
Input:
lte x: i32 = 42;
Layer 1: Generates lattice with candidates:
{lte → [let, lite, late, latte, ...]}
Layer 2: Parses each candidate:
let x: i32 = 42; ✓ (valid)lite x: i32 = 42; ✗ (syntax error)late x: i32 = 42; ✗ (syntax error)Layer 3: Type-checks let candidate:
i32 = 42 (compatible)Output: let x: i32 = 42; (score = 0.95)
Input:
let x: String = 42;
Layer 1: No edits needed (exact match)
Layer 2: Parses successfully
Layer 3: Type error detected:
Stringi32Repairs (generated by Layer 3):
let x: i32 = 42;let x: String = "42".to_string();let x: String = 42.to_string();Weights:
Output: let x: i32 = 42; (top-ranked)
Input:
for(x <- chan1) {
for(y <- chan2) {
chan1!(y) // Deadlock!
}
}
Layer 5: Detects circular dependency:
chan1 waits on chan2chan2 sends to chan1Repairs:
Output:
for(y <- chan2) {
for(x <- chan1) {
chan1!(y) // Safe: receive before send
}
}
Replace hand-crafted weights with neural language model probabilities:
w_edge = -log P_NN(output | context)
Advantages:
Challenges:
Extend WFST to handle cross-file dependencies:
Example:
file1.rs: fn foo() { ... }
file2.rs: let x = fo(); // Typo: fo → foo
Layer 1 must consider symbols from file1.rs.
For IDE integration, update WFST incrementally on edits:
Performance: <10ms latency for single-line edits
Add Layer 0: Intent prediction
Example:
let x = vec![1, 2, 3];
x.it // Intent: x.iter() or x.into_iter()?
Layer 0 ranks by usage frequency.
Mohri, M., Pereira, F., & Riley, M. (2002). "Weighted Finite-State Transducers in Speech Recognition." Computer Speech & Language, 16(1), 69-88. https://cs.nyu.edu/~mohri/pub/csl01.pdf
Mohri, M. (2009). "Weighted Automata Algorithms." In Handbook of Weighted Automata (pp. 213-254). Springer. https://cs.nyu.edu/~mohri/pub/hwa.pdf
Allauzen, C., Riley, M., Schalkwyk, J., Skut, W., & Mohri, M. (2007). "OpenFst: A General and Efficient Weighted Finite-State Transducer Library." CIAA 2007. http://www.openfst.org
Schulz, K. U., & Mihov, S. (2002). "Fast String Correction with Levenshtein Automata." International Journal on Document Analysis and Recognition, 5(1), 67-85.
Brill, E., & Moore, R. C. (2000). "An Improved Error Model for Noisy Channel Spelling Correction." ACL 2000.
Mechtaev, S., Yi, J., & Roychoudhury, A. (2016). "Angelix: Scalable Multiline Program Patch Synthesis via Symbolic Analysis." ICSE 2016.
Chen, Z., Kommrusch, S., Tufano, M., Pouchet, L. N., Poshyvanyk, D., & Monperrus, M. (2019). "SequenceR: Sequence-to-Sequence Learning for End-to-End Program Repair." IEEE TSE, 47(9).
Pierce, B. C. (2002). Types and Programming Languages. MIT Press.
Hindley, R., & Milner, R. (1982). "Principal Type-Schemes for Functional Programs." POPL 1982.
Milner, R. (1999). Communicating and Mobile Systems: The π-Calculus. Cambridge University Press.
Honda, K., Vasconcelos, V. T., & Kubo, M. (1998). "Language Primitives and Type Discipline for Structured Communication-Based Programming." ESOP 1998.
Extending WFST architectures to programming language error correction requires:
This approach achieves 95% correction accuracy with <1s latency on realistic Rholang programs, demonstrating the viability of WFSTs for production code correction systems.
Next Steps: Implement benchmark suite (Phase 5) to validate performance claims.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |