From dc27e6d4d67a260476efa09301ab1b995b769479 Mon Sep 17 00:00:00 2001 From: Eyal Kalderon Date: Mon, 14 Oct 2019 01:11:03 +0800 Subject: [PATCH] Add support for 'or' expressions This particular kind of expression is tricky to implement because of a special case of backwards compatibility with a rarely used Nixpkgs function also named `or` (see [parser.y] for more details). Implementing support for this fallback case incurs a very slight performance penalty. [parser.y]: https://github.com/NixOS/nix/blob/f8b30338ac231262bdf19844f044f0572c460048/src/libexpr/parser.y#L376-L378 Also, since the `or` keyword is only ever used in projections and never on its own, we can safely remove the `ExprOr` type and rely entirely on the `fallback` argument for `ExprProj::new()` instead. --- nix-parser/src/ast.rs | 54 +---------------------------------- nix-parser/src/parser/expr.rs | 41 +++++++++++++++++++------- 2 files changed, 32 insertions(+), 63 deletions(-) diff --git a/nix-parser/src/ast.rs b/nix-parser/src/ast.rs index 38766b3..9bd76af 100644 --- a/nix-parser/src/ast.rs +++ b/nix-parser/src/ast.rs @@ -78,13 +78,11 @@ pub enum Expr { Let(ExprLet), /// `rec { foo = "bar"; }` Rec(ExprRec), - /// `x.y` + /// `x.y`, `x.y or "true"` Proj(Box), /// `if true then "success" else "failure"` If(Box), - /// `foo.bar or "failed"` - Or(Box), /// `assert true != false; true` Assert(Box), /// `with foo; foo.attr` @@ -122,7 +120,6 @@ impl Display for Expr { Expr::Proj(ref e) => write!(fmt, "{}", e), Expr::If(ref e) => write!(fmt, "{}", e), - Expr::Or(ref e) => write!(fmt, "{}", e), Expr::Assert(ref e) => write!(fmt, "{}", e), Expr::With(ref e) => write!(fmt, "{}", e), @@ -1090,55 +1087,6 @@ impl PartialEq for ExprIf { } } -#[derive(Clone, Debug)] -pub struct ExprOr { - expr: Expr, - fallback: Expr, - span: Span, -} - -impl ExprOr { - pub fn new(expr: Expr, fallback: Expr, span: Span) -> Self { - ExprOr { - expr, - fallback, - span, - } - } - - pub fn expr(&self) -> &Expr { - &self.expr - } - - pub fn fallback(&self) -> &Expr { - &self.fallback - } -} - -impl Display for ExprOr { - fn fmt(&self, fmt: &mut Formatter) -> FmtResult { - write!(fmt, "{} or {}", self.expr, self.fallback) - } -} - -impl HasSpan for ExprOr { - fn span(&self) -> Span { - self.span - } -} - -impl From for Expr { - fn from(e: ExprOr) -> Expr { - Expr::Or(Box::new(e)) - } -} - -impl PartialEq for ExprOr { - fn eq(&self, other: &Self) -> bool { - self.expr == other.expr && self.fallback == other.fallback - } -} - #[derive(Clone, Debug)] pub struct ExprAssert { cond: Expr, diff --git a/nix-parser/src/parser/expr.rs b/nix-parser/src/parser/expr.rs index 1d25c89..560812b 100644 --- a/nix-parser/src/parser/expr.rs +++ b/nix-parser/src/parser/expr.rs @@ -8,9 +8,10 @@ use nom::multi::many0; use nom::sequence::{pair, preceded}; use super::partial::{ - expect_terminated, map_partial, map_partial_spanned, pair_partial, verify_full, Partial, + expect_terminated, map_partial, map_partial_spanned, opt_partial, pair_partial, Partial, }; use super::{tokens, IResult}; +use crate::ast::tokens::Ident; use crate::ast::{BinaryOp, Expr, ExprBinary, ExprFnApp, ExprIf, ExprProj, ExprUnary, UnaryOp}; use crate::error::{Errors, UnexpectedError}; use crate::lexer::{Token, Tokens}; @@ -213,15 +214,35 @@ fn fn_app(input: Tokens) -> IResult> { } fn project(input: Tokens) -> IResult> { - let path = preceded(tokens::dot, verify_full(attr::attr_path)); - let expr = pair(atomic, opt(path)); - map(expr, |(base, path)| match path { - None => base, - Some(path) => base.map(|base| { - let span = Span::merge(base.span(), path.span()); - Expr::Proj(Box::new(ExprProj::new(base, path, None, span))) - }), - })(input) + let (input, atomic) = atomic(input)?; + + if let Ok((remaining, or_span)) = tokens::keyword_or(input) { + let expr = atomic.map(|atomic| { + let arg = Expr::Ident(Ident::from(("or", or_span))); + let span = Span::merge(atomic.span(), or_span); + Expr::from(ExprFnApp::new(atomic, arg, span)) + }); + + Ok((remaining, expr)) + } else if let Ok((remaining, _)) = tokens::dot(input) { + let default = alt((project, error, util::error_expr_if(tokens::eof, ""))); + let or_default = opt_partial(preceded(tokens::keyword_or, default)); + + let (remaining, path) = pair_partial(attr::attr_path, or_default)(remaining)?; + let proj = atomic.flat_map(|atomic| { + path.map(|(path, default)| { + let span = Span::merge(atomic.span(), path.span()); + match default { + Some(expr) => Expr::from(ExprProj::new(atomic, path, Some(expr), span)), + None => Expr::from(ExprProj::new(atomic, path, None, span)), + } + }) + }); + + Ok((remaining, proj)) + } else { + Ok((input, atomic)) + } } fn atomic(input: Tokens) -> IResult> {