From 7984f7d1e8ebad92ebb6311e6369bf5c62aca864 Mon Sep 17 00:00:00 2001 From: Kovacsics Robert Date: Thu, 13 Feb 2025 23:26:33 +0000 Subject: [PATCH] WIP: Add scoped expressions inspired by OCaml The parsing works but it has the semantics of normal `with` currently. I am currently calling it `scoped dot`, because calling it `innermost with` would maybe make people confuse them with the current `outermost with` expressions --- src/libexpr/eval.cc | 4 +++- src/libexpr/nixexpr.hh | 11 +++++++++- src/libexpr/parser.y | 5 ++++- tests/functional/lang/eval-okay-scope-dot.exp | 1 + tests/functional/lang/eval-okay-scope-dot.nix | 20 +++++++++++++++++++ 5 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 tests/functional/lang/eval-okay-scope-dot.exp create mode 100644 tests/functional/lang/eval-okay-scope-dot.nix diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index dee764429e9..8f96a5661ba 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -697,7 +697,9 @@ void mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const En if (env.up && se.up) { mapStaticEnvBindings(st, *se.up, *env.up, vm); - if (se.isWith && !env.values[0]->isThunk()) { + if (se.isWith && + se.isWith->binding == ExprWith::Binding::Outermost && + !env.values[0]->isThunk()) { // add 'with' bindings. for (auto & j : *env.values[0]->attrs()) vm.insert_or_assign(std::string(st[j.name]), j.value); diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index a7ad580d2df..fc98185cddd 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -381,12 +381,21 @@ struct ExprLet : Expr struct ExprWith : Expr { + enum class Binding { + /** Classic `with` behaviour */ + Outermost, + /** `with` behaviour mimicking OCaml's `foo.( )` */ + Innermost, + }; + PosIdx pos; Expr * attrs, * body; size_t prevWith; ExprWith * parentWith; - ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { }; + ExprWith(const PosIdx & pos, Expr * attrs, Expr * body, Binding binding) + : pos(pos), attrs(attrs), body(body), binding(binding) { }; PosIdx getPos() const override { return pos; } + const Binding binding; COMMON_METHODS }; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index bde72140114..493bda2af42 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -211,7 +211,7 @@ expr_function | ASSERT expr ';' expr_function { $$ = new ExprAssert(CUR_POS, $2, $4); } | WITH expr ';' expr_function - { $$ = new ExprWith(CUR_POS, $2, $4); } + { $$ = new ExprWith(CUR_POS, $2, $4, ExprWith::Binding::Outermost); } | LET binds IN_KW expr_function { if (!$2->dynamicAttrs.empty()) throw ParseError({ @@ -286,6 +286,9 @@ expr_select be used to emit a warning when an affected expression is parsed. */ expr_simple OR_KW { $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, state->s.or_)}, state->positions.add(state->origin, @$.endOffset)); } + | expr_simple '.' '(' expr ')' { $$ = new ExprWith(CUR_POS, $1, $4, ExprWith::Binding::Innermost); } + | expr_simple '.' '[' expr_list ']' { $$ = new ExprWith(CUR_POS, $1, $4, ExprWith::Binding::Innermost); } + | expr_simple '.' '{' binds '}' { $$ = new ExprWith(CUR_POS, $1, $4, ExprWith::Binding::Innermost); } | expr_simple ; diff --git a/tests/functional/lang/eval-okay-scope-dot.exp b/tests/functional/lang/eval-okay-scope-dot.exp new file mode 100644 index 00000000000..6d89b96c4e2 --- /dev/null +++ b/tests/functional/lang/eval-okay-scope-dot.exp @@ -0,0 +1 @@ +"x: (as-a as-b) y: (bs-a bs-b)" diff --git a/tests/functional/lang/eval-okay-scope-dot.nix b/tests/functional/lang/eval-okay-scope-dot.nix new file mode 100644 index 00000000000..69be9186f7b --- /dev/null +++ b/tests/functional/lang/eval-okay-scope-dot.nix @@ -0,0 +1,20 @@ +let { + + a = "let-a"; + + as = { + a = "as-a"; + b = "as-b"; + }; + + bs = { + a = "bs-a"; + b = "bs-b"; + }; + + x = as.(a + " " + b); + + y = as.(bs.(a + " " + b)); + + body = "x: (" + x + ") y: (" + y + ")"; +}