feat(tvix/repl): Allow binding variables at the top-level
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>
This commit is contained in:
parent
ac3d717944
commit
fc63594631
6 changed files with 157 additions and 16 deletions
74
tvix/cli/src/assignment.rs
Normal file
74
tvix/cli/src/assignment.rs
Normal file
|
|
@ -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:?}");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue