snix_cli/
assignment.rs

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