Skip to main content

sim_lib_pattern/
glob_dialect.rs

1//! Shell-glob text-pattern compiler for the shared VM.
2
3use sim_kernel::{Error, Result};
4
5use crate::lua_dialect::parse_set_body;
6use crate::{PatternDialect, TextOp};
7
8/// Compiler for small shell-style glob patterns.
9#[derive(Clone, Copy, Debug, Default)]
10pub struct GlobPatternDialect;
11
12impl PatternDialect for GlobPatternDialect {
13    fn compile(&self, pattern: &str) -> Result<Vec<TextOp>> {
14        let chars = pattern.chars().collect::<Vec<_>>();
15        let mut index = 0;
16        let mut ops = vec![TextOp::AnchorStart];
17        while let Some(ch) = chars.get(index).copied() {
18            index += 1;
19            match ch {
20                '*' => {
21                    ops.push(TextOp::Any);
22                    ops.push(TextOp::Repeat {
23                        min: 0,
24                        max: None,
25                        greedy: true,
26                    });
27                }
28                '?' => ops.push(TextOp::Any),
29                '[' => {
30                    let negated = matches!(chars.get(index), Some('!') | Some('^'));
31                    if negated {
32                        index += 1;
33                    }
34                    ops.push(TextOp::Class(parse_set_body(
35                        &chars,
36                        &mut index,
37                        negated,
38                        "unterminated glob character set",
39                    )?));
40                }
41                '\\' => {
42                    let literal = chars
43                        .get(index)
44                        .copied()
45                        .ok_or_else(|| malformed("dangling escape"))?;
46                    index += 1;
47                    ops.push(TextOp::Literal(literal));
48                }
49                literal => ops.push(TextOp::Literal(literal)),
50            }
51        }
52        ops.push(TextOp::AnchorEnd);
53        Ok(ops)
54    }
55}
56
57/// Compiles a shell-style glob into shared VM operations.
58///
59/// # Errors
60///
61/// Returns an error when the glob pattern is malformed.
62pub fn compile_glob_pattern(pattern: &str) -> Result<Vec<TextOp>> {
63    GlobPatternDialect.compile(pattern)
64}
65
66fn malformed(message: &str) -> Error {
67    Error::Eval(format!("malformed glob pattern: {message}"))
68}