diff options
author | Aspen Smith <root@gws.fyi> | 2024-07-05T03·46-0400 |
---|---|---|
committer | clbot <clbot@tvl.fyi> | 2024-07-05T16·40+0000 |
commit | fc63594631590547c9a31001806095f2e079a20e (patch) | |
tree | bed08e994d56fb224a62be6ab371f9cd1fec17e6 /tvix/cli/src/assignment.rs | |
parent | ac3d717944412b17d7dcd18006c2f9f522b1b3f7 (diff) |
feat(tvix/repl): Allow binding variables at the top-level r/8346
Allow binding variables at the REPL's toplevel in the same way the Nix REPL does, using the syntax <ident> = <expr>. This fully, strictly evaluates the value and sets it in the repl's "env", which gets passed in at the toplevel when evaluating expressions. The laziness behavior differs from Nix's, but I think this is good: ❯ nix repl Welcome to Nix version 2.3.18. Type :? for help. nix-repl> x = builtins.trace "x" 1 nix-repl> x trace: x 1 nix-repl> x 1 vs tvix: tvix-repl> x = builtins.trace "x" 1 trace: "x" :: string tvix-repl> x => 1 :: int tvix-repl> x => 1 :: int Bug: https://b.tvl.fyi/issues/371 Change-Id: Ieb2d626b7195fa87be638c9a4dae2eee45eb9ab1 Reviewed-on: https://cl.tvl.fyi/c/depot/+/11954 Reviewed-by: flokli <flokli@flokli.de> Tested-by: BuildkiteCI Autosubmit: aspen <root@gws.fyi>
Diffstat (limited to 'tvix/cli/src/assignment.rs')
-rw-r--r-- | tvix/cli/src/assignment.rs | 74 |
1 files changed, 74 insertions, 0 deletions
diff --git a/tvix/cli/src/assignment.rs b/tvix/cli/src/assignment.rs new file mode 100644 index 000000000000..6fd9725d2956 --- /dev/null +++ b/tvix/cli/src/assignment.rs @@ -0,0 +1,74 @@ +use rnix::{Root, SyntaxKind, SyntaxNode}; +use rowan::ast::AstNode; + +/// An assignment of an identifier to a value in the context of a REPL. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct Assignment<'a> { + pub(crate) ident: &'a str, + pub(crate) value: rnix::ast::Expr, +} + +impl<'a> Assignment<'a> { + /// Try to parse an [`Assignment`] from the given input string. + /// + /// Returns [`None`] if the parsing fails for any reason, since the intent is for us to + /// fall-back to trying to parse the input as a regular expression or other REPL commands for + /// any reason, since the intent is for us to fall-back to trying to parse the input as a + /// regular expression or other REPL command. + pub fn parse(input: &'a str) -> Option<Self> { + let mut tt = rnix::tokenizer::Tokenizer::new(input); + macro_rules! next { + ($kind:ident) => {{ + loop { + let (kind, tok) = tt.next()?; + if kind == SyntaxKind::TOKEN_WHITESPACE { + continue; + } + if kind != SyntaxKind::$kind { + return None; + } + break tok; + } + }}; + } + + let ident = next!(TOKEN_IDENT); + let _equal = next!(TOKEN_ASSIGN); + let (green, errs) = rnix::parser::parse(tt); + let value = Root::cast(SyntaxNode::new_root(green))?.expr()?; + + if !errs.is_empty() { + return None; + } + + Some(Self { ident, value }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simple_assignments() { + for input in ["x = 4", "x = \t\t\n\t4", "x=4"] { + let res = Assignment::parse(input).unwrap(); + assert_eq!(res.ident, "x"); + assert_eq!(res.value.to_string(), "4"); + } + } + + #[test] + fn complex_exprs() { + let input = "x = { y = 4; z = let q = 7; in [ q (y // { z = 9; }) ]; }"; + let res = Assignment::parse(input).unwrap(); + assert_eq!(res.ident, "x"); + } + + #[test] + fn not_an_assignment() { + let input = "{ x = 4; }"; + let res = Assignment::parse(input); + assert!(res.is_none(), "{input:?}"); + } +} |