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
use std::borrow::Cow;

/// A variable name is one or more ASCII alphanumerical characters, including underscore.
/// Note that numerical variable names like `$45` are allowed too.
///
/// Try to parse a variable name from the beginning of `s`.
/// Return the index of the character following the varname.
/// This returns 0 if `s` doesn't have a prefix that is a variable name.
pub fn varname_prefix(s: &str) -> usize {
    for (idx, ch) in s.char_indices() {
        match ch {
            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => {}
            _ => return idx,
        }
    }
    s.len()
}

/// A variable can contain either a regular expression or plain text.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Value<'a> {
    /// Verbatim text.
    Text(Cow<'a, str>),
    /// Regular expression.
    Regex(Cow<'a, str>),
}

/// Resolve variables by name.
pub trait VariableMap {
    /// Get the value of the variable `varname`, or return `None` for an unknown variable name.
    fn lookup(&self, varname: &str) -> Option<Value>;
}

impl VariableMap for () {
    fn lookup(&self, _: &str) -> Option<Value> {
        None
    }
}

/// An empty variable map.
pub const NO_VARIABLES: &'static dyn VariableMap = &();

#[cfg(test)]
mod tests {
    #[test]
    fn varname() {
        use super::varname_prefix;

        assert_eq!(varname_prefix(""), 0);
        assert_eq!(varname_prefix("\0"), 0);
        assert_eq!(varname_prefix("_"), 1);
        assert_eq!(varname_prefix("0"), 1);
        assert_eq!(varname_prefix("01"), 2);
        assert_eq!(varname_prefix("b"), 1);
        assert_eq!(varname_prefix("C"), 1);
        assert_eq!(varname_prefix("."), 0);
        assert_eq!(varname_prefix(".s"), 0);
        assert_eq!(varname_prefix("0."), 1);
        assert_eq!(varname_prefix("01="), 2);
        assert_eq!(varname_prefix("0a)"), 2);
    }
}