From 4aacd48dc4a38e2217e449e7f63e7db6ddc813ad Mon Sep 17 00:00:00 2001 From: oxalica Date: Tue, 19 Nov 2024 16:20:46 -0500 Subject: [PATCH] Impl experimental pipe operators `|>` and `<|` operators are introduced as experimental feature "pipe-operator" since Nix 2.24. Ref: Co-authored-by: Kira Malinova --- crates/ide/src/ide/syntax_highlighting.rs | 2 + crates/ide/src/ty/infer.rs | 22 ++++++----- crates/ide/src/ty/tests.rs | 6 +++ crates/syntax/src/ast.rs | 22 +++++++---- crates/syntax/src/kind.rs | 2 + crates/syntax/src/lexer.rs | 16 ++++++++ crates/syntax/src/parser.rs | 14 ++++--- .../syntax/test_data/parser/ok/0010-pipe.ast | 39 +++++++++++++++++++ .../syntax/test_data/parser/ok/0010-pipe.nix | 1 + 9 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 crates/syntax/test_data/parser/ok/0010-pipe.ast create mode 100644 crates/syntax/test_data/parser/ok/0010-pipe.nix diff --git a/crates/ide/src/ide/syntax_highlighting.rs b/crates/ide/src/ide/syntax_highlighting.rs index 6647f29..117fca0 100644 --- a/crates/ide/src/ide/syntax_highlighting.rs +++ b/crates/ide/src/ide/syntax_highlighting.rs @@ -51,6 +51,7 @@ pub enum HlOperator { Comparison, Arithmetic, Aggregation, + Pipe, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -161,6 +162,7 @@ pub(crate) fn highlight( } T![+] | T![-] | T![*] | T![/] => HlTag::Operator(HlOperator::Arithmetic), T![++] | T!["//"] => HlTag::Operator(HlOperator::Aggregation), + T![|>] => HlTag::Operator(HlOperator::Pipe), T!['{'] | T!['}'] | T!["${"] => HlTag::Punct(HlPunct::Brace), T!['['] | T![']'] => HlTag::Punct(HlPunct::Bracket), T!['('] | T![')'] => HlTag::Punct(HlPunct::Paren), diff --git a/crates/ide/src/ty/infer.rs b/crates/ide/src/ty/infer.rs index 09a4a51..a9918ab 100644 --- a/crates/ide/src/ty/infer.rs +++ b/crates/ide/src/ty/infer.rs @@ -213,6 +213,17 @@ impl<'db> InferCtx<'db> { self.unify_var(then_ty, else_ty); then_ty } + &Expr::Apply(lam, arg) + | &Expr::Binary(Some(BinaryOpKind::PipeRight), arg, lam) + | &Expr::Binary(Some(BinaryOpKind::PipeLeft), lam, arg) => { + let param_ty = self.new_ty_var(); + let ret_ty = self.new_ty_var(); + let lam_ty = self.infer_expr(lam); + self.unify_var_ty(lam_ty, Ty::Lambda(param_ty, ret_ty)); + let arg_ty = self.infer_expr(arg); + self.unify_var(arg_ty, param_ty); + ret_ty + } &Expr::Binary(op, lhs, rhs) => { let lhs_ty = self.infer_expr(lhs); let rhs_ty = self.infer_expr(rhs); @@ -256,6 +267,8 @@ impl<'db> InferCtx<'db> { self.unify_var(rhs_ty, ret_ty); ret_ty } + // Already handled by the outer match. + BinaryOpKind::PipeLeft | BinaryOpKind::PipeRight => unreachable!(), } } &Expr::Unary(op, arg) => { @@ -270,15 +283,6 @@ impl<'db> InferCtx<'db> { Some(UnaryOpKind::Negate) => arg_ty, } } - &Expr::Apply(lam, arg) => { - let param_ty = self.new_ty_var(); - let ret_ty = self.new_ty_var(); - let lam_ty = self.infer_expr(lam); - self.unify_var_ty(lam_ty, Ty::Lambda(param_ty, ret_ty)); - let arg_ty = self.infer_expr(arg); - self.unify_var(arg_ty, param_ty); - ret_ty - } Expr::HasAttr(set_expr, path) => { // TODO: Store the information of referenced paths somehow. self.infer_expr(*set_expr); diff --git a/crates/ide/src/ty/tests.rs b/crates/ide/src/ty/tests.rs index c404ec4..b2148e6 100644 --- a/crates/ide/src/ty/tests.rs +++ b/crates/ide/src/ty/tests.rs @@ -179,6 +179,12 @@ fn select() { ); } +#[test] +fn pipe() { + check("f: 1 |> f", expect!["(int → ?) → ?"]); + check("f: f <| 1", expect!["(int → ?) → ?"]); +} + #[test] fn external() { check_all_expect( diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs index 1e17027..335a26f 100644 --- a/crates/syntax/src/ast.rs +++ b/crates/syntax/src/ast.rs @@ -25,6 +25,9 @@ pub enum BinaryOpKind { Sub, Mul, Div, + + PipeLeft, + PipeRight, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -263,16 +266,20 @@ impl Expr { } pub fn contains_without_paren(&self, inner: &Self) -> bool { - fn bp(e: &Expr) -> Option { + const MIN_BP: i8 = i8::MIN; + const MAX_BP: i8 = i8::MAX; + + fn bp(e: &Expr) -> Option { Some(match e { Expr::With(_) | Expr::Lambda(_) | Expr::LetIn(_) | Expr::IfThenElse(_) - | Expr::Assert(_) => TOPLEVEL, + | Expr::Assert(_) => MIN_BP, // Binary and unary ops. They follow `infix_bp` in parser. Expr::BinaryOp(e) => match e.op_kind()? { + BinaryOpKind::PipeLeft | BinaryOpKind::PipeRight => 0, BinaryOpKind::Imply => 1, BinaryOpKind::Or => 3, BinaryOpKind::And => 5, @@ -307,18 +314,15 @@ impl Expr { | Expr::Ref(_) => 29, // Special. See below. - Expr::Paren(_) => PAREN, + Expr::Paren(_) => MAX_BP, }) } - const TOPLEVEL: u8 = 0; - const PAREN: u8 = 31; - match (bp(self), bp(inner)) { // Special case 1: `Paren`s can safely contain or be contained by anything. - (Some(PAREN), _) | (_, Some(PAREN)) => true, + (Some(MAX_BP), _) | (_, Some(MAX_BP)) => true, // Special case 2: top-levels can contain each other without ambiguity. - (Some(TOPLEVEL), Some(TOPLEVEL)) => true, + (Some(MIN_BP), Some(MIN_BP)) => true, // Otherwise, expressions with lower binding power contain higher ones. (Some(outer), Some(inner)) => outer < inner, // `false` by default. @@ -382,6 +386,8 @@ asts! { let op = match tok.kind() { T![->] => BinaryOpKind::Imply, T![&&] => BinaryOpKind::And, + T![|>] => BinaryOpKind::PipeRight, + T![<|] => BinaryOpKind::PipeLeft, T![||] => BinaryOpKind::Or, T![==] => BinaryOpKind::Equal, T![!=] => BinaryOpKind::NotEqual, diff --git a/crates/syntax/src/kind.rs b/crates/syntax/src/kind.rs index f8c31d9..2104d76 100644 --- a/crates/syntax/src/kind.rs +++ b/crates/syntax/src/kind.rs @@ -111,9 +111,11 @@ def! { EQ2 = [==], GT_EQ = [>=], LT_EQ = [<=], + LT_OR = [<|], MINUS_GT = [->], NOT_EQ = [!=], OR2 = [||], + OR_GT = [|>], PLUS2 = [++], QUOTE2 = ["''"], SLASH2 = ["//"], diff --git a/crates/syntax/src/lexer.rs b/crates/syntax/src/lexer.rs index 8fd0a48..e23cbed 100644 --- a/crates/syntax/src/lexer.rs +++ b/crates/syntax/src/lexer.rs @@ -109,6 +109,8 @@ regex_dfa! { QUOTE2 = r"''", DOT3 = r"\.\.\.", + OR_GT = r"\|>", + LT_OR = r"<\|", MINUS_GT = r"->", OR2 = r"\|\|", AND2 = r"&&", @@ -654,4 +656,18 @@ mod tests { "#]], ); } + + #[test] + fn pipe() { + check_lex( + "1<|2|>3", + expect![[r#" + INT "1" + LT_OR "<|" + INT "2" + OR_GT "|>" + INT "3" + "#]], + ); + } } diff --git a/crates/syntax/src/parser.rs b/crates/syntax/src/parser.rs index 324918f..41a1bd4 100644 --- a/crates/syntax/src/parser.rs +++ b/crates/syntax/src/parser.rs @@ -367,12 +367,12 @@ impl<'i> Parser<'i> { /// Operator level expression (low priority). /// Maybe consume nothing. fn expr_operator_opt(&mut self) { - self.expr_bp(0); + self.expr_bp(i8::MIN); } // Pratt parser. // Ref: https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html - fn expr_bp(&mut self, min_bp: u8) { + fn expr_bp(&mut self, min_bp: i8) { // Always consume whitespace first, even though not `allow_prefix`. let Some(tok) = self.peek_non_ws() else { self.error(ErrorKind::ExpectExpr); @@ -847,7 +847,7 @@ impl SyntaxKind { } #[rustfmt::skip] - fn prefix_bp(self) -> Option { + fn prefix_bp(self) -> Option { // See `infix_bp`. Some(match self { T![!] => 13, @@ -857,7 +857,7 @@ impl SyntaxKind { } #[rustfmt::skip] - fn postfix_bp(self) -> Option { + fn postfix_bp(self) -> Option { // See `infix_bp`. Some(match self { T![?] => 21, @@ -866,8 +866,10 @@ impl SyntaxKind { } #[rustfmt::skip] - fn infix_bp(self) -> Option<(u8, u8)> { + fn infix_bp(self) -> Option<(i8, i8)> { Some(match self { + T![|>] => (-1, 0), + T![<|] => (0, -1), T![->] => (2, 1), T![||] => (3, 4), T![&&] => (5, 6), @@ -892,4 +894,4 @@ impl SyntaxKind { } } -const APPLY_RBP: u8 = 26; +const APPLY_RBP: i8 = 26; diff --git a/crates/syntax/test_data/parser/ok/0010-pipe.ast b/crates/syntax/test_data/parser/ok/0010-pipe.ast new file mode 100644 index 0000000..2f86b05 --- /dev/null +++ b/crates/syntax/test_data/parser/ok/0010-pipe.ast @@ -0,0 +1,39 @@ +SOURCE_FILE@0..27 + LIST@0..26 + L_BRACK@0..1 "[" + PAREN@1..13 + L_PAREN@1..2 "(" + BINARY_OP@2..12 + BINARY_OP@2..9 + BINARY_OP@2..6 + LITERAL@2..3 + INT@2..3 "1" + MINUS_GT@3..5 "->" + LITERAL@5..6 + INT@5..6 "2" + OR_GT@6..8 "|>" + LITERAL@8..9 + INT@8..9 "3" + OR_GT@9..11 "|>" + LITERAL@11..12 + INT@11..12 "4" + R_PAREN@12..13 ")" + PAREN@13..25 + L_PAREN@13..14 "(" + BINARY_OP@14..24 + LITERAL@14..15 + INT@14..15 "5" + LT_OR@15..17 "<|" + BINARY_OP@17..24 + LITERAL@17..18 + INT@17..18 "6" + LT_OR@18..20 "<|" + BINARY_OP@20..24 + LITERAL@20..21 + INT@20..21 "7" + MINUS_GT@21..23 "->" + LITERAL@23..24 + INT@23..24 "8" + R_PAREN@24..25 ")" + R_BRACK@25..26 "]" + SPACE@26..27 "\n" diff --git a/crates/syntax/test_data/parser/ok/0010-pipe.nix b/crates/syntax/test_data/parser/ok/0010-pipe.nix new file mode 100644 index 0000000..b808492 --- /dev/null +++ b/crates/syntax/test_data/parser/ok/0010-pipe.nix @@ -0,0 +1 @@ +[(1->2|>3|>4)(5<|6<|7->8)]