liblevenshtein 0.9.1

Levenshtein/Universal Automata for approximate string matching using various dictionary backends
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
//! Error types for `.llre` file parsing and compilation.
//!
//! This module provides comprehensive error types for parsing `.llre` files,
//! including file-level errors (I/O, circular imports) and parse-level errors
//! (syntax, undefined symbols).

use std::fmt;
use std::path::PathBuf;

// Re-export Position from common module
pub use crate::phonetic::common::Position;

/// Error type for `.llre` file parsing and loading.
#[derive(Debug, Clone)]
pub struct LLreError {
    /// The kind of error
    pub kind: LLreErrorKind,
    /// Position where the error occurred (if applicable)
    pub position: Option<Position>,
    /// File where the error occurred (if applicable)
    pub file: Option<PathBuf>,
    /// Additional context about the error
    pub context: Option<String>,
}

/// The kind of `.llre` error.
#[derive(Debug, Clone, PartialEq)]
pub enum LLreErrorKind {
    // ==================== I/O Errors ====================
    /// File not found
    FileNotFound(String),

    /// Permission denied
    PermissionDenied(String),

    /// General I/O error
    IoError(String),

    // ==================== Import Errors ====================
    /// Circular import detected
    CircularImport(PathBuf),

    /// Import depth exceeded
    ImportDepthExceeded {
        /// Maximum import depth allowed before this error is raised.
        max: usize,
        /// Path of the import that exceeded the depth limit.
        path: PathBuf,
    },

    /// Import file not found
    ImportNotFound {
        /// The requested import path that could not be located.
        path: String,
        /// Directories that were searched when looking for the import.
        search_paths: Vec<PathBuf>,
    },

    /// Import resolution failed
    ImportResolutionFailed {
        /// The import path that failed to resolve.
        path: String,
        /// Why the resolution failed.
        reason: String,
    },

    // ==================== Lexer/Parse Errors ====================
    /// Unexpected end of input
    UnexpectedEof,

    /// Unexpected character
    UnexpectedChar(char),

    /// Invalid escape sequence
    InvalidEscape(char),

    /// Unterminated string literal
    UnterminatedString,

    /// Unterminated block comment
    UnterminatedComment,

    /// Invalid Unicode code point
    InvalidCodePoint(u32),

    /// Expected a specific token
    ExpectedToken {
        /// Description of the token kind the parser was expecting.
        expected: String,
        /// Description of the token that was encountered instead.
        found: String,
    },

    // ==================== Directive Errors ====================
    /// Invalid directive
    InvalidDirective(String),

    /// Unknown directive name
    UnknownDirective(String),

    /// Duplicate directive (e.g., multiple @name)
    DuplicateDirective(String),

    /// Invalid directive value
    InvalidDirectiveValue {
        /// Name of the directive that received the bad value.
        directive: String,
        /// The offending value.
        value: String,
        /// Why the value is invalid.
        reason: String,
    },

    // ==================== Pattern Errors ====================
    /// Missing pattern (empty file)
    MissingPattern,

    /// Multiple patterns (only one pattern per file)
    MultiplePatterns,

    /// Invalid regex pattern
    InvalidPattern(String),

    /// Pattern parsing error (delegates to regex parser)
    PatternParseError(String),

    // ==================== Flag Errors ====================
    /// Invalid flag name
    InvalidFlag(String),

    /// Duplicate flag
    DuplicateFlag(String),

    /// Conflicting flags
    ConflictingFlags {
        /// First flag in the conflict.
        flag1: String,
        /// Second flag in the conflict.
        flag2: String,
    },

    // ==================== Symbol/Import Errors ====================
    /// Undefined symbol reference
    UndefinedSymbol {
        /// Name of the symbol that could not be resolved.
        name: String,
        /// Names of symbols that are in scope, for diagnostics.
        available: Vec<String>,
    },

    /// Symbol type mismatch
    SymbolTypeMismatch {
        /// Name of the symbol involved in the mismatch.
        name: String,
        /// Type the consumer expected.
        expected: String,
        /// Type the symbol actually has.
        found: String,
    },

    /// Alias conflict (two imports with same alias)
    AliasConflict {
        /// The alias name that is reused.
        alias: String,
        /// Path of the first import that introduced the alias.
        path1: String,
        /// Path of the second, conflicting import.
        path2: String,
    },

    /// Cyclic pattern reference detected during symbol expansion
    CyclicPatternReference {
        /// The pattern name that completes the cycle
        name: String,
        /// The chain of pattern references that form the cycle
        chain: Vec<String>,
    },

    // ==================== Compilation Errors ====================
    /// NFA compilation failed
    NfaCompilationFailed(String),

    /// Pattern too complex
    PatternTooComplex {
        /// Computed pattern size that triggered the error.
        size: usize,
        /// Maximum allowed pattern size.
        max: usize,
    },

    /// Recursion depth exceeded during compilation
    RecursionDepthExceeded {
        /// Recursion depth reached when the limit was hit.
        depth: usize,
        /// Maximum allowed recursion depth.
        max: usize,
    },

    // ==================== Serialization Errors ====================
    /// Invalid binary format
    InvalidBinaryFormat(String),

    /// Version mismatch
    VersionMismatch {
        /// Binary format version the loader expected.
        expected: u8,
        /// Binary format version actually present in the input.
        found: u8,
    },

    /// Serialization failed
    SerializationFailed(String),

    /// Deserialization failed
    DeserializationFailed(String),

    // ==================== Wrapped Errors ====================
    /// LLev error (from imported .llev files)
    LLevError(String),

    /// Regex parse error
    RegexParseError(String),
}

impl LLreError {
    /// Create a new error with the given kind.
    pub fn new(kind: LLreErrorKind) -> Self {
        Self {
            kind,
            position: None,
            file: None,
            context: None,
        }
    }

    /// Create an error with position.
    pub fn with_position(kind: LLreErrorKind, position: Position) -> Self {
        Self {
            kind,
            position: Some(position),
            file: None,
            context: None,
        }
    }

    /// Create an error with file path.
    pub fn with_file(kind: LLreErrorKind, file: impl Into<PathBuf>) -> Self {
        Self {
            kind,
            position: None,
            file: Some(file.into()),
            context: None,
        }
    }

    /// Create an error with both position and file.
    pub fn with_position_and_file(
        kind: LLreErrorKind,
        position: Position,
        file: impl Into<PathBuf>,
    ) -> Self {
        Self {
            kind,
            position: Some(position),
            file: Some(file.into()),
            context: None,
        }
    }

    /// Add context to an error.
    pub fn with_context(mut self, context: impl Into<String>) -> Self {
        self.context = Some(context.into());
        self
    }

    /// Create a "file not found" error.
    pub fn file_not_found(path: impl Into<String>) -> Self {
        Self::new(LLreErrorKind::FileNotFound(path.into()))
    }

    /// Create an "unexpected EOF" error.
    pub fn unexpected_eof(position: Position) -> Self {
        Self::with_position(LLreErrorKind::UnexpectedEof, position)
    }

    /// Create an "unexpected character" error.
    pub fn unexpected_char(c: char, position: Position) -> Self {
        Self::with_position(LLreErrorKind::UnexpectedChar(c), position)
    }

    /// Create a "missing pattern" error.
    pub fn missing_pattern() -> Self {
        Self::new(LLreErrorKind::MissingPattern)
    }

    /// Create an "invalid pattern" error.
    pub fn invalid_pattern(reason: impl Into<String>, position: Position) -> Self {
        Self::with_position(LLreErrorKind::InvalidPattern(reason.into()), position)
    }

    /// Create from an LLev error.
    pub fn from_llev(err: &crate::phonetic::llev::LLevError) -> Self {
        Self {
            kind: LLreErrorKind::LLevError(err.to_string()),
            position: err.position,
            file: err.file.clone(),
            context: err.context.clone(),
        }
    }

    /// Create from a regex parse error.
    pub fn from_regex_parse(err: &crate::phonetic::regex::ParseError) -> Self {
        Self {
            kind: LLreErrorKind::RegexParseError(err.to_string()),
            position: Some(err.position),
            file: None,
            context: err.context.clone(),
        }
    }
}

impl fmt::Display for LLreError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Format location
        match (&self.file, &self.position) {
            (Some(file), Some(pos)) => {
                write!(f, "{}:{}: ", file.display(), pos)?;
            }
            (Some(file), None) => {
                write!(f, "{}: ", file.display())?;
            }
            (None, Some(pos)) => {
                write!(f, "{}: ", pos)?;
            }
            (None, None) => {}
        }

        // Format error kind
        write!(f, "{}", self.kind)?;

        // Add context if available
        if let Some(ref ctx) = self.context {
            write!(f, " (near '{}')", ctx)?;
        }

        Ok(())
    }
}

impl fmt::Display for LLreErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            // I/O Errors
            LLreErrorKind::FileNotFound(path) => {
                write!(f, "file not found: {}", path)
            }
            LLreErrorKind::PermissionDenied(path) => {
                write!(f, "permission denied: {}", path)
            }
            LLreErrorKind::IoError(msg) => {
                write!(f, "I/O error: {}", msg)
            }

            // Import Errors
            LLreErrorKind::CircularImport(path) => {
                write!(f, "circular import detected: {}", path.display())
            }
            LLreErrorKind::ImportDepthExceeded { max, path } => {
                write!(
                    f,
                    "import depth exceeded (max {}) at: {}",
                    max,
                    path.display()
                )
            }
            LLreErrorKind::ImportNotFound { path, search_paths } => {
                write!(f, "import not found: '{}' (searched: ", path)?;
                for (i, sp) in search_paths.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{}", sp.display())?;
                }
                write!(f, ")")
            }
            LLreErrorKind::ImportResolutionFailed { path, reason } => {
                write!(f, "failed to resolve import '{}': {}", path, reason)
            }

            // Lexer/Parse Errors
            LLreErrorKind::UnexpectedEof => {
                write!(f, "unexpected end of input")
            }
            LLreErrorKind::UnexpectedChar(c) => {
                write!(f, "unexpected character '{}'", c)
            }
            LLreErrorKind::InvalidEscape(c) => {
                write!(f, "invalid escape sequence '\\{}'", c)
            }
            LLreErrorKind::UnterminatedString => {
                write!(f, "unterminated string literal")
            }
            LLreErrorKind::UnterminatedComment => {
                write!(f, "unterminated block comment")
            }
            LLreErrorKind::InvalidCodePoint(cp) => {
                write!(f, "invalid Unicode code point: U+{:04X}", cp)
            }
            LLreErrorKind::ExpectedToken { expected, found } => {
                write!(f, "expected {}, found {}", expected, found)
            }

            // Directive Errors
            LLreErrorKind::InvalidDirective(msg) => {
                write!(f, "invalid directive: {}", msg)
            }
            LLreErrorKind::UnknownDirective(name) => {
                write!(f, "unknown directive '@{}'", name)
            }
            LLreErrorKind::DuplicateDirective(name) => {
                write!(f, "duplicate '@{}' directive", name)
            }
            LLreErrorKind::InvalidDirectiveValue {
                directive,
                value,
                reason,
            } => {
                write!(
                    f,
                    "invalid value '{}' for @{}: {}",
                    value, directive, reason
                )
            }

            // Pattern Errors
            LLreErrorKind::MissingPattern => {
                write!(
                    f,
                    "missing regex pattern (each .llre file must contain exactly one pattern)"
                )
            }
            LLreErrorKind::MultiplePatterns => {
                write!(
                    f,
                    "multiple patterns found (only one pattern allowed per .llre file)"
                )
            }
            LLreErrorKind::InvalidPattern(msg) => {
                write!(f, "invalid pattern: {}", msg)
            }
            LLreErrorKind::PatternParseError(msg) => {
                write!(f, "pattern parse error: {}", msg)
            }

            // Flag Errors
            LLreErrorKind::InvalidFlag(flag) => {
                write!(
                    f,
                    "invalid flag '{}' (valid: multiline, dotall, case_insensitive)",
                    flag
                )
            }
            LLreErrorKind::DuplicateFlag(flag) => {
                write!(f, "duplicate flag '{}'", flag)
            }
            LLreErrorKind::ConflictingFlags { flag1, flag2 } => {
                write!(f, "conflicting flags '{}' and '{}'", flag1, flag2)
            }

            // Symbol/Import Errors
            LLreErrorKind::UndefinedSymbol { name, available } => {
                if available.is_empty() {
                    write!(f, "undefined symbol '${}'", name)
                } else {
                    write!(
                        f,
                        "undefined symbol '${}'; available: {}",
                        name,
                        available
                            .iter()
                            .map(|s| format!("${}", s))
                            .collect::<Vec<_>>()
                            .join(", ")
                    )
                }
            }
            LLreErrorKind::SymbolTypeMismatch {
                name,
                expected,
                found,
            } => {
                write!(
                    f,
                    "symbol '{}' has wrong type: expected {}, found {}",
                    name, expected, found
                )
            }
            LLreErrorKind::AliasConflict {
                alias,
                path1,
                path2,
            } => {
                write!(
                    f,
                    "alias '{}' already used by '{}', cannot assign to '{}'",
                    alias, path1, path2
                )
            }
            LLreErrorKind::CyclicPatternReference { name, chain } => {
                write!(
                    f,
                    "cyclic pattern reference: '{}' forms a cycle (chain: {})",
                    name,
                    chain.join(" -> ")
                )
            }

            // Compilation Errors
            LLreErrorKind::NfaCompilationFailed(msg) => {
                write!(f, "NFA compilation failed: {}", msg)
            }
            LLreErrorKind::PatternTooComplex { size, max } => {
                write!(
                    f,
                    "pattern too complex: size {} exceeds maximum {}",
                    size, max
                )
            }
            LLreErrorKind::RecursionDepthExceeded { depth, max } => {
                write!(
                    f,
                    "recursion depth {} exceeded maximum {} during compilation",
                    depth, max
                )
            }

            // Serialization Errors
            LLreErrorKind::InvalidBinaryFormat(msg) => {
                write!(f, "invalid binary format: {}", msg)
            }
            LLreErrorKind::VersionMismatch { expected, found } => {
                write!(
                    f,
                    "version mismatch: expected {}, found {}",
                    expected, found
                )
            }
            LLreErrorKind::SerializationFailed(msg) => {
                write!(f, "serialization failed: {}", msg)
            }
            LLreErrorKind::DeserializationFailed(msg) => {
                write!(f, "deserialization failed: {}", msg)
            }

            // Wrapped Errors
            LLreErrorKind::LLevError(msg) => {
                write!(f, "llev error: {}", msg)
            }
            LLreErrorKind::RegexParseError(msg) => {
                write!(f, "{}", msg)
            }
        }
    }
}

impl std::error::Error for LLreError {}

// Conversion from LLev errors
impl From<crate::phonetic::llev::LLevError> for LLreError {
    fn from(err: crate::phonetic::llev::LLevError) -> Self {
        Self::from_llev(&err)
    }
}

// Conversion from regex parse errors
impl From<crate::phonetic::regex::ParseError> for LLreError {
    fn from(err: crate::phonetic::regex::ParseError) -> Self {
        Self::from_regex_parse(&err)
    }
}

// Conversion from std::io::Error
impl From<std::io::Error> for LLreError {
    fn from(err: std::io::Error) -> Self {
        use std::io::ErrorKind;
        let kind = match err.kind() {
            ErrorKind::NotFound => LLreErrorKind::FileNotFound(err.to_string()),
            ErrorKind::PermissionDenied => LLreErrorKind::PermissionDenied(err.to_string()),
            _ => LLreErrorKind::IoError(err.to_string()),
        };
        Self::new(kind)
    }
}

/// Result type for `.llre` operations.
pub type LLreResult<T> = Result<T, LLreError>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_display() {
        let err = LLreError::missing_pattern();
        assert!(err.to_string().contains("missing regex pattern"));
    }

    #[test]
    fn test_error_with_position() {
        let err = LLreError::unexpected_char('x', Position::new(2, 5, 15));
        assert!(err.to_string().contains("line 2"));
        assert!(err.to_string().contains("unexpected character 'x'"));
    }

    #[test]
    fn test_error_with_file() {
        let err = LLreError::with_file(
            LLreErrorKind::FileNotFound("test.llre".into()),
            "path/to/test.llre",
        );
        assert!(err.to_string().contains("path/to/test.llre"));
    }

    #[test]
    fn test_undefined_symbol_display() {
        let err = LLreError::new(LLreErrorKind::UndefinedSymbol {
            name: "VOWEL".into(),
            available: vec!["CONSONANT".into(), "DIGIT".into()],
        });
        let msg = err.to_string();
        assert!(msg.contains("$VOWEL"));
        assert!(msg.contains("$CONSONANT"));
        assert!(msg.contains("$DIGIT"));
    }
}