1
1
mirror of https://github.com/oxalica/nil.git synced 2024-11-22 02:55:39 +03:00

Impl experimental pipe operators

`|>` 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 <llathasa@outlook.com>
This commit is contained in:
oxalica 2024-11-19 16:20:46 -05:00 committed by oxalica
parent c8e8ce7244
commit 52304da8e9
9 changed files with 101 additions and 23 deletions

View File

@ -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![|>] | T![<|] => HlTag::Operator(HlOperator::Pipe),
T!['{'] | T!['}'] | T!["${"] => HlTag::Punct(HlPunct::Brace),
T!['['] | T![']'] => HlTag::Punct(HlPunct::Bracket),
T!['('] | T![')'] => HlTag::Punct(HlPunct::Paren),

View File

@ -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);

View File

@ -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(

View File

@ -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<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,
@ -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,

View File

@ -111,9 +111,11 @@ def! {
EQ2 = [==],
GT_EQ = [>=],
LT_EQ = [<=],
LT_OR = [<|],
MINUS_GT = [->],
NOT_EQ = [!=],
OR2 = [||],
OR_GT = [|>],
PLUS2 = [++],
QUOTE2 = ["''"],
SLASH2 = ["//"],

View File

@ -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"
"#]],
);
}
}

View File

@ -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<u8> {
fn prefix_bp(self) -> Option<i8> {
// See `infix_bp`.
Some(match self {
T![!] => 13,
@ -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,
@ -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;

View File

@ -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"

View File

@ -0,0 +1 @@
[(1->2|>3|>4)(5<|6<|7->8)]