Skip to main content

snix_cli_eval/
assignment.rs

1use rnix::SyntaxKind;
2
3/// An assignment of an identifier to a value in the context of a REPL.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub(crate) struct Assignment<'a> {
6    pub(crate) ident: &'a str,
7    pub(crate) value: rnix::ast::Expr,
8}
9
10impl<'a> Assignment<'a> {
11    /// Try to parse an [`Assignment`] from the given input string.
12    ///
13    /// Returns [`None`] if the parsing fails for any reason, since the intent is for us to
14    /// fall-back to trying to parse the input as a regular expression or other REPL commands for
15    /// any reason, since the intent is for us to fall-back to trying to parse the input as a
16    /// regular expression or other REPL command.
17    pub fn parse(input: &'a str) -> Option<Self> {
18        let mut tt = rnix::tokenize(input);
19        macro_rules! next {
20            ($kind:ident) => {{
21                loop {
22                    let (kind, tok) = tt.next()?;
23                    if kind == SyntaxKind::TOKEN_WHITESPACE {
24                        continue;
25                    }
26                    if kind != SyntaxKind::$kind {
27                        return None;
28                    }
29                    break tok;
30                }
31            }};
32        }
33
34        let ident = next!(TOKEN_IDENT);
35        let equal = next!(TOKEN_ASSIGN);
36
37        let value_start = equal.as_ptr() as usize + equal.len() - input.as_ptr() as usize;
38        let value_expr = &input[value_start..];
39        let root = rnix::Root::parse(value_expr);
40        let value = root.tree().expr()?;
41
42        if !root.errors().is_empty() {
43            return None;
44        }
45
46        Some(Self { ident, value })
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn simple_assignments() {
56        for input in ["x = 4", "x     =       \t\t\n\t4", "x=4"] {
57            let res = Assignment::parse(input).unwrap();
58            assert_eq!(res.ident, "x");
59            assert_eq!(res.value.to_string(), "4");
60        }
61    }
62
63    #[test]
64    fn complex_exprs() {
65        let input = "x = { y = 4; z = let q = 7; in [ q (y // { z = 9; }) ]; }";
66        let res = Assignment::parse(input).unwrap();
67        assert_eq!(res.ident, "x");
68    }
69
70    #[test]
71    fn not_an_assignment() {
72        let input = "{ x = 4; }";
73        let res = Assignment::parse(input);
74        assert!(res.is_none(), "{input:?}");
75    }
76}