Skip to content

Commit

Permalink
Impl experimental pipe operators
Browse files Browse the repository at this point in the history
`|>` and `<|` operators are introduced as experimental feature
"pipe-operator" since Nix 2.24.

Ref: <https://github.com/NixOS/nix/blob/2.24.0/doc/manual/src/language/operators.md#pipe-operators>

Co-authored-by: Kira Malinova <[email protected]>
  • Loading branch information
oxalica and q60 committed Nov 19, 2024
1 parent c8e8ce7 commit 52304da
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 23 deletions.
2 changes: 2 additions & 0 deletions crates/ide/src/ide/syntax_highlighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub enum HlOperator {
Comparison,
Arithmetic,
Aggregation,
Pipe,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -161,6 +162,7 @@ pub(crate) fn highlight(
}
T![+] | T![-] | T![*] | T![/] => HlTag::Operator(HlOperator::Arithmetic),
T![++] | T!["//"] => HlTag::Operator(HlOperator::Aggregation),
T![|>] | T![<|] => HlTag::Operator(HlOperator::Pipe),
T!['{'] | T!['}'] | T!["${"] => HlTag::Punct(HlPunct::Brace),
T!['['] | T![']'] => HlTag::Punct(HlPunct::Bracket),
T!['('] | T![')'] => HlTag::Punct(HlPunct::Paren),
Expand Down
22 changes: 13 additions & 9 deletions crates/ide/src/ty/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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) => {
Expand All @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions crates/ide/src/ty/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
22 changes: 14 additions & 8 deletions crates/syntax/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ pub enum BinaryOpKind {
Sub,
Mul,
Div,

PipeLeft,
PipeRight,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down Expand Up @@ -263,16 +266,20 @@ impl Expr {
}

pub fn contains_without_paren(&self, inner: &Self) -> bool {
fn bp(e: &Expr) -> Option<u8> {
const MIN_BP: i8 = i8::MIN;
const MAX_BP: i8 = i8::MAX;

fn bp(e: &Expr) -> Option<i8> {
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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions crates/syntax/src/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,11 @@ def! {
EQ2 = [==],
GT_EQ = [>=],
LT_EQ = [<=],
LT_OR = [<|],
MINUS_GT = [->],
NOT_EQ = [!=],
OR2 = [||],
OR_GT = [|>],
PLUS2 = [++],
QUOTE2 = ["''"],
SLASH2 = ["//"],
Expand Down
16 changes: 16 additions & 0 deletions crates/syntax/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ regex_dfa! {
QUOTE2 = r"''",

DOT3 = r"\.\.\.",
OR_GT = r"\|>",
LT_OR = r"<\|",
MINUS_GT = r"->",
OR2 = r"\|\|",
AND2 = r"&&",
Expand Down Expand Up @@ -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"
"#]],
);
}
}
14 changes: 8 additions & 6 deletions crates/syntax/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -847,7 +847,7 @@ impl SyntaxKind {
}

#[rustfmt::skip]
fn prefix_bp(self) -> Option<u8> {
fn prefix_bp(self) -> Option<i8> {
// See `infix_bp`.
Some(match self {
T![!] => 13,
Expand All @@ -857,7 +857,7 @@ impl SyntaxKind {
}

#[rustfmt::skip]
fn postfix_bp(self) -> Option<u8> {
fn postfix_bp(self) -> Option<i8> {
// See `infix_bp`.
Some(match self {
T![?] => 21,
Expand All @@ -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),
Expand All @@ -892,4 +894,4 @@ impl SyntaxKind {
}
}

const APPLY_RBP: u8 = 26;
const APPLY_RBP: i8 = 26;
1 change: 1 addition & 0 deletions crates/syntax/test_data/parser/ok/0010-pipe.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[(1->2|>3|>4)(5<|6<|7->8)]

0 comments on commit 52304da

Please sign in to comment.