packages feed

nova-nix 0.5.0.0 → 0.6.0.0

raw patch · 55 files changed

+801/−514 lines, 55 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,172 +1,189 @@ # Changelog -## 0.5.0.0 — 2026-06-11+## 0.6.0.0 - 2026-06-12 +### Milestone: A Stage-1 stdenv That Builds Real Software++nova-nix now builds real third-party software from source on Windows through a small stage-1 stdenv. A package is `{ name; src; buildInputs; }`, and a `setup.sh` genericBuild drives unpack, configure, build, install, and a fixup that makes outputs standalone - proven on GNU hello, GNU sed, and zlib plus a program that links it.++- **New: a store-pinned MSYS2 userland seed and `mkDerivation`** - the seed bash runs `setup.sh`, which unpacks the source, runs `./configure && make && make install`, and builds through the mingw toolchain. GNU hello and GNU sed build from source with a two-line derivation.+- **New: `buildInputs`** - a package depends on another package. Each input's `include/` goes on `CPPFLAGS`, `lib/` on `LDFLAGS`, and `bin/` on `PATH`, and its derivation builds first. Proven by libgreet (a library built from source) and greeter (a program that links it).+- **New: a `cc-wrapper`** - a `gcc`/`cc` shim installed on the build `PATH` ahead of the real toolchain. It strips ambient header/library search paths from the environment (hermeticity), defaults to `-std=gnu17` so classic gnulib-bearing sources compile under gcc 15+'s C23 default, and adds `-Wl,--no-insert-timestamp` for reproducible PE headers. A package can override the standard with its own `-std`.+- **New: a fixup phase** - Windows has no RPATH, so a distributable binary must carry its non-system DLLs beside it. fixup reads each output binary's PE import table and, for every imported DLL outside the guaranteed Windows ABI, finds it among the declared inputs and bundles it, recursing to a fixpoint. An undeclared non-system DLL fails the build rather than shipping silently broken.+- **New: build-phase overrides** - optional `dontConfigure` / `buildPhase` / `installPhase` attrs let a package build differently from the autotools default (e.g. via a hand-written makefile) while the common case stays a two-line derivation.+- **Real third-party library: zlib 1.3.2** - its `./configure` emits Unix `.so` naming under MSYS2, so zlib builds via its `win32/Makefile.gcc` for a proper `zlib1.dll` and `libz.dll.a` import lib. A demo links zlib (statically against `libz.a`, or dynamically against `zlib1.dll` with the DLL bundled) and round-trips data.+- **Relicensed from BSD-3-Clause to Apache-2.0.** Apache adds an explicit patent grant and trademark terms, and a `NOTICE` file now carries the copyright (Novavero AI Inc.). Earlier releases on Hackage remain under BSD-3-Clause.+- **Substituter: retry transient NAR download failures** before falling back to a local build, matching Nix's `download-attempts` (5 attempts, linear backoff), so a dropped connection or a stale CDN negative no longer forces a rebuild.+- **`builtin:fetchurl` sends a User-Agent**, so hosts that reject empty-UA requests (some GNU mirrors) serve the fetch.+- **Docs:** 100% Haddock coverage across all modules.++## 0.5.0.0 - 2026-06-11+ ### Milestone: Push and Pull Against a Binary Cache  A native Windows build can now publish its outputs to a binary cache and substitute them back on another machine. The toolchain seed and the `hello` build push to `cache.novavero.ai` with `nova-nix push`, and a fresh store realizes them by signed download instead of rebuilding. -- **New: `nova-nix push`** — uploads store paths and their full reference closure to a binary cache. Each path is NAR-serialized (via `nova-cache`), its NAR uploaded before its narinfo so a client never sees metadata pointing at absent data, and the server signs each narinfo on receipt. Already-cached paths are skipped via one `GET /narinfo-hashes` diff, the recorded NAR hash is cross-checked against the store database before upload, and a signed round-trip is verified at the end. Published narinfos use the canonical `/nix/store` path form. Usage: `nova-nix push --cache URL --key-file KEY (--all | <paths>)`.-- **New: `build --substituter URL --trusted-key K`** — try a binary cache before building. The trusted key (`name:base64`) is required alongside the substituter, since substituting without signature verification would be unsafe; the build falls back to local compilation for any path the cache lacks.-- **New: `--store DIR`** — run a command against an alternate store directory instead of the platform default, so a build or push can target a scratch store.-- **Reproducible `hello`** — `hello.nix` links with `-Wl,--no-insert-timestamp`, and the builder pins `SOURCE_DATE_EPOCH` (1980-01-01) for every build, so determinism-aware tools write fixed timestamps. Two cold bootstraps produce a byte-identical `hello.exe`.-- **Dependencies modernized** — builds against `nova-cache >= 0.4.2` (Ed25519 signing, `normalizeKeyText`), `crypton >= 1.1` with `ram` replacing the deprecated `memory`, and current upper bounds for `containers`, `http-client-tls`, and `time`. No source change beyond the dependency swap; derivation-hash parity is unaffected (CI-verified).+- **New: `nova-nix push`** - uploads store paths and their full reference closure to a binary cache. Each path is NAR-serialized (via `nova-cache`), its NAR uploaded before its narinfo so a client never sees metadata pointing at absent data, and the server signs each narinfo on receipt. Already-cached paths are skipped via one `GET /narinfo-hashes` diff, the recorded NAR hash is cross-checked against the store database before upload, and a signed round-trip is verified at the end. Published narinfos use the canonical `/nix/store` path form. Usage: `nova-nix push --cache URL --key-file KEY (--all | <paths>)`.+- **New: `build --substituter URL --trusted-key K`** - try a binary cache before building. The trusted key (`name:base64`) is required alongside the substituter, since substituting without signature verification would be unsafe; the build falls back to local compilation for any path the cache lacks.+- **New: `--store DIR`** - run a command against an alternate store directory instead of the platform default, so a build or push can target a scratch store.+- **Reproducible `hello`** - `hello.nix` links with `-Wl,--no-insert-timestamp`, and the builder pins `SOURCE_DATE_EPOCH` (1980-01-01) for every build, so determinism-aware tools write fixed timestamps. Two cold bootstraps produce a byte-identical `hello.exe`.+- **Dependencies modernized** - builds against `nova-cache >= 0.4.2` (Ed25519 signing, `normalizeKeyText`), `crypton >= 1.1` with `ram` replacing the deprecated `memory`, and current upper bounds for `containers`, `http-client-tls`, and `time`. No source change beyond the dependency swap; derivation-hash parity is unaffected (CI-verified). -## 0.4.0.0 — 2026-06-10+## 0.4.0.0 - 2026-06-10  ### Milestone: The First Native Windows Build -`nova-nix build pkgs/windows/hello.nix` compiles and runs a C program through a Nix derivation, natively on Windows: evaluation records the derivation closure, the builder realizes 17 fixed-output fetches and a toolchain unpack in dependency order, and a compiler that is itself a store path produces `C:\nix\store\…-hello\hello.exe`. The first package built natively on Windows by a Nix implementation.+`nova-nix build pkgs/windows/hello.nix` compiles and runs a C program through a Nix derivation, natively on Windows: evaluation records the derivation closure, the builder realizes 17 fixed-output fetches and a toolchain unpack in dependency order, and a compiler that is itself a store path produces `C:\nix\store\...-hello\hello.exe`. The first package built natively on Windows by a Nix implementation. -- **New: `builtin:unpack`** — an in-process archive extractor, the counterpart to `builtin:fetchurl`: the unpacker is the thing being bootstrapped, so it cannot be a store tool, and ambient `tar` is unpinned. Extracts `.tar.zst`/`.tar` archives listed in `srcs` into the single `out` output, in order. MSYS2 packages share the `mingw64/` prefix, so a package set merges into one working toolchain root. Symlink and hardlink entries are materialized as copies (store outputs must not require Developer Mode), pacman per-package metadata is skipped, and cross-archive file collisions and path traversal fail the build loudly. MSYS2's zstd frames carry no pledged content size, so decompression is streaming. New dependencies: `tar >= 0.7.1`, `zstd`.-- **New: `pkgs/windows/seed.nix`** — stage 0 of the native Windows stdenv: the 17-package runtime closure of `mingw-w64-x86_64-gcc`, sha256-pinned against `repo.msys2.org`, fetched and merged into a single toolchain root. `pkgs/windows/hello.nix` is the proof build; its recipe also documents the Windows DLL rule (a subprocess resolves DLLs via `PATH`, not RPATH, so dependencies' `bin` dirs go on the build `PATH`).-- **Fix: the build driver materializes eval-coerced source paths** — `src = ./file` produced only the source store path *text* during evaluation (eval performs no store writes — the parity runner's store is not writable), so input validation failed on the missing path. The driver now copies each coerced source into the store, marks it read-only, and registers it with its real NAR hash before building.-- **`data/nix/fetchurl.nix` documentation rewritten as original prose** — the functional content is fixed byte-for-byte by derivation-hash parity and is unchanged (CI-verified).+- **New: `builtin:unpack`** - an in-process archive extractor, the counterpart to `builtin:fetchurl`: the unpacker is the thing being bootstrapped, so it cannot be a store tool, and ambient `tar` is unpinned. Extracts `.tar.zst`/`.tar` archives listed in `srcs` into the single `out` output, in order. MSYS2 packages share the `mingw64/` prefix, so a package set merges into one working toolchain root. Symlink and hardlink entries are materialized as copies (store outputs must not require Developer Mode), pacman per-package metadata is skipped, and cross-archive file collisions and path traversal fail the build loudly. MSYS2's zstd frames carry no pledged content size, so decompression is streaming. New dependencies: `tar >= 0.7.1`, `zstd`.+- **New: `pkgs/windows/seed.nix`** - stage 0 of the native Windows stdenv: the 17-package runtime closure of `mingw-w64-x86_64-gcc`, sha256-pinned against `repo.msys2.org`, fetched and merged into a single toolchain root. `pkgs/windows/hello.nix` is the proof build; its recipe also documents the Windows DLL rule (a subprocess resolves DLLs via `PATH`, not RPATH, so dependencies' `bin` dirs go on the build `PATH`).+- **Fix: the build driver materializes eval-coerced source paths** - `src = ./file` produced only the source store path *text* during evaluation (eval performs no store writes - the parity runner's store is not writable), so input validation failed on the missing path. The driver now copies each coerced source into the store, marks it read-only, and registers it with its real NAR hash before building.+- **`data/nix/fetchurl.nix` documentation rewritten as original prose** - the functional content is fixed byte-for-byte by derivation-hash parity and is unchanged (CI-verified). -## 0.3.0.0 — 2026-06-06+## 0.3.0.0 - 2026-06-06  ### Milestone: Derivation-Hash Parity with Upstream Nix  `(import <nixpkgs> {}).hello.drvPath`, and all 253 derivations in its closure, now hash byte-for-byte identically to `nix-instantiate`, verified in CI on the same nixpkgs tree. A new `eval --aterm` mode dumps a derivation's ATerm for diffing against `nix derivation show`. -- **Fix: indented-string indentation is stripped per literal string-part, before interpolation** — the common indentation was removed from the fully-interpolated string, so a multi-line interpolated value (e.g. `${commonPreHook}` in the stdenv `preHook`) could lower the computed minimum indent to zero and leave the literal lines indented. Indentation is now computed from the literal parts only — interpolated values are opaque content — matching C++ Nix.-- **Fix: path literals interpolated into strings are copied to the store** — `"${./foo}"` left the raw source path in the result; it now copies the file to the store and yields its store path, with the path added to the string context so it lands in the derivation's `inputSrcs`. `builtins.toString` still does not copy, per Nix semantics.-- **Fix: `builtins.placeholder`** — now returns `/` followed by the full SHA-256 of `nix-output:<name>` in Nix base-32, matching Nix's `hashPlaceholder`. It previously truncated the hash and wrapped it as a `/nix/store` path, so any derivation using `${placeholder "out"}` (e.g. perl's `configureFlags`) diverged.-- **Fix: a derivation's default output is its first declared output** — a bare reference to a multi-output derivation now resolves to `outputs[0]` (both name and path), not a hardcoded `out`. This affected packages whose first output is not `out`, such as `xz` (first output `bin`).-- **Fix: `builtins.attrNames` is sorted lexicographically** — names were returned in interned-symbol order rather than sorted by key, and inconsistently with `builtins.attrValues`. Surfaced in `fetchurl`'s `impureEnvVars` (the generated `NIX_MIRRORS_*` list).+- **Fix: indented-string indentation is stripped per literal string-part, before interpolation** - the common indentation was removed from the fully-interpolated string, so a multi-line interpolated value (e.g. `${commonPreHook}` in the stdenv `preHook`) could lower the computed minimum indent to zero and leave the literal lines indented. Indentation is now computed from the literal parts only - interpolated values are opaque content - matching C++ Nix.+- **Fix: path literals interpolated into strings are copied to the store** - `"${./foo}"` left the raw source path in the result; it now copies the file to the store and yields its store path, with the path added to the string context so it lands in the derivation's `inputSrcs`. `builtins.toString` still does not copy, per Nix semantics.+- **Fix: `builtins.placeholder`** - now returns `/` followed by the full SHA-256 of `nix-output:<name>` in Nix base-32, matching Nix's `hashPlaceholder`. It previously truncated the hash and wrapped it as a `/nix/store` path, so any derivation using `${placeholder "out"}` (e.g. perl's `configureFlags`) diverged.+- **Fix: a derivation's default output is its first declared output** - a bare reference to a multi-output derivation now resolves to `outputs[0]` (both name and path), not a hardcoded `out`. This affected packages whose first output is not `out`, such as `xz` (first output `bin`).+- **Fix: `builtins.attrNames` is sorted lexicographically** - names were returned in interned-symbol order rather than sorted by key, and inconsistently with `builtins.attrValues`. Surfaced in `fetchurl`'s `impureEnvVars` (the generated `NIX_MIRRORS_*` list). -## 0.2.0.0 — 2026-06-05+## 0.2.0.0 - 2026-06-05  ### Milestone: `import <nixpkgs> {}` Evaluates -- **`import <nixpkgs> {}` now evaluates to WHNF** — the top-level package set resolves and enumerates ~23,000 attributes, and a package evaluates through the full stdenv bootstrap down to a derivation: `(import <nixpkgs> {}).hello.drvPath` yields a `/nix/store/…-hello-2.12.1.drv` path. (Derivation-hash parity with upstream Nix and on-Windows building are the next fronts — this milestone is evaluation, not yet building.)-- **Fix: `inherit <name>;` de Bruijn level in positional let/rec blocks** — `inherit x;` (without `from`) in a positional `let`/`rec` block desugars to `x = x;` with the right-hand side resolved against the *outer* scope, so it refers to the enclosing `x` rather than the binding being defined. But the desugared thunk is evaluated at runtime in the *inner* (let/rec) env, which adds one parent-chain level the outer resolution did not count — so every resolved level was short by one. This produced `nn_env_lookup_resolved: idx out of bounds` whenever an outer *lexical* variable was inherited inside a positional block — first triggered by perl's `inherit version;` (argument-set slot 7) nested in a 4-binding `let`. Fixed by shifting the desugared `inherit` RHS up one de Bruijn level in `Nix.Expr.Resolve`. (`inherit (from) …` was already correct: its RHS resolves against the inner scope.)-- **Fix: Hackage build — ship C headers in the sdist** — the `cbits/*.h` headers are `#include`d by the C sources (via `include-dirs: cbits`) but were never listed in the cabal file, so `cabal sdist` omitted them and the Hackage build failed with `nn_*.h: No such file or directory`. Added `extra-source-files: cbits/*.h`. A regression introduced in 0.1.9.0 (the first release to ship the C data layer); CI never caught it because CI builds the working tree, not the sdist. Verified by building from a freshly-extracted sdist tarball in isolation.+- **`import <nixpkgs> {}` now evaluates to WHNF** - the top-level package set resolves and enumerates ~23,000 attributes, and a package evaluates through the full stdenv bootstrap down to a derivation: `(import <nixpkgs> {}).hello.drvPath` yields a `/nix/store/...-hello-2.12.1.drv` path. (Derivation-hash parity with upstream Nix and on-Windows building are the next fronts - this milestone is evaluation, not yet building.)+- **Fix: `inherit <name>;` de Bruijn level in positional let/rec blocks** - `inherit x;` (without `from`) in a positional `let`/`rec` block desugars to `x = x;` with the right-hand side resolved against the *outer* scope, so it refers to the enclosing `x` rather than the binding being defined. But the desugared thunk is evaluated at runtime in the *inner* (let/rec) env, which adds one parent-chain level the outer resolution did not count - so every resolved level was short by one. This produced `nn_env_lookup_resolved: idx out of bounds` whenever an outer *lexical* variable was inherited inside a positional block - first triggered by perl's `inherit version;` (argument-set slot 7) nested in a 4-binding `let`. Fixed by shifting the desugared `inherit` RHS up one de Bruijn level in `Nix.Expr.Resolve`. (`inherit (from) ...` was already correct: its RHS resolves against the inner scope.)+- **Fix: Hackage build - ship C headers in the sdist** - the `cbits/*.h` headers are `#include`d by the C sources (via `include-dirs: cbits`) but were never listed in the cabal file, so `cabal sdist` omitted them and the Hackage build failed with `nn_*.h: No such file or directory`. Added `extra-source-files: cbits/*.h`. A regression introduced in 0.1.9.0 (the first release to ship the C data layer); CI never caught it because CI builds the working tree, not the sdist. Verified by building from a freshly-extracted sdist tarball in isolation. -## 0.1.9.0 — 2026-06-05+## 0.1.9.0 - 2026-06-05  ### nixpkgs Evaluation: Lazy Derivations -- **Fix: `import <nixpkgs> {}` infinite recursion — lazy `derivation` over `derivationStrict`** — `builtins.derivation` was eager: forcing a derivation to WHNF forced its entire env/input closure. This turned nixpkgs' lazy `perl` ↔ `libxcrypt` dependency cycle into a blackhole — `perl`'s `assert (libxcrypt != null)` forced `libxcrypt`'s build, which forced `perl` mid-construction. Restored Nix's two-tier model: the eager `builtins.derivationStrict` primop (identical content-hashing, renamed from the old `builtins.derivation` body) plus a lazy `derivation` wrapper over it (mirroring `corepkgs/derivation.nix`). Forcing a derivation to WHNF now yields its attribute spine without computing `drvPath`/`outPath` or forcing its inputs, so referencing a package no longer builds its closure. No C changes; `drvPath`/`outPath` hashes unchanged. The stdenv bootstrap now progresses through derivation construction instead of looping (next blocker: a variable-slot bug while evaluating perl's `@`-pattern).+- **Fix: `import <nixpkgs> {}` infinite recursion - lazy `derivation` over `derivationStrict`** - `builtins.derivation` was eager: forcing a derivation to WHNF forced its entire env/input closure. This turned nixpkgs' lazy `perl` and `libxcrypt` dependency cycle into a blackhole - `perl`'s `assert (libxcrypt != null)` forced `libxcrypt`'s build, which forced `perl` mid-construction. Restored Nix's two-tier model: the eager `builtins.derivationStrict` primop (identical content-hashing, renamed from the old `builtins.derivation` body) plus a lazy `derivation` wrapper over it (mirroring `corepkgs/derivation.nix`). Forcing a derivation to WHNF now yields its attribute spine without computing `drvPath`/`outPath` or forcing its inputs, so referencing a package no longer builds its closure. No C changes; `drvPath`/`outPath` hashes unchanged. The stdenv bootstrap now progresses through derivation construction instead of looping (next blocker: a variable-slot bug while evaluating perl's `@`-pattern).  ### Technical Audit + C Data Layer Polish -- **Fix: Nix integer division semantics** — `builtins.div` and `builtins.mod` now use floored division (`div`/`mod`) instead of truncated (`quot`/`rem`). `-7 / 3` correctly returns `-3` (was `-2`). Affects `builtinDiv`, `builtinMod`, and `evalDiv`.-- **Fix: `inherit (from)` in positional let/rec blocks** — The resolution pass treated `inherit (expr) x y;` as positional, but the bytecode evaluator did not handle `BcInheritFrom` in `allBcPositional`, `bcBindingSlotCount`, `buildBcSlotThunks`, or `buildBcAttrMapFromSlots`. This caused env slot count mismatches and `idx out of bounds` assertions on any nixpkgs expression using `inherit (from)` in a positional let block.-- **C memory safety audit** — `nn_bytecode.c`: `ensure_op_space`/`ensure_data_space` now return error codes instead of failing silently; overflow guard on capacity doubling. `nn_lambda.c`: `NN_ASSERT` bounds checks on entry accessors prevent null dereference when `formal_count == 0`. `nn_attrset.c`: documented partial-realloc-failure behavior.-- **Dead code removal** — Removed unused `cattrsetIntersect` Haskell wrapper, `nn_attrset_intersect`, and `nn_attrset_values_ptr` C functions.-- **New: `nn_assert.h`** — Debug-mode bounds checking macro. Compiles to nothing under `NDEBUG`.-- **Performance** — Stress test: 6.25 MB max residency, 56.3% GC productivity (down from 69.7 MB / 1.6% pre-C-data-layer).+- **Fix: Nix integer division semantics** - `builtins.div` and `builtins.mod` now use floored division (`div`/`mod`) instead of truncated (`quot`/`rem`). `-7 / 3` correctly returns `-3` (was `-2`). Affects `builtinDiv`, `builtinMod`, and `evalDiv`.+- **Fix: `inherit (from)` in positional let/rec blocks** - The resolution pass treated `inherit (expr) x y;` as positional, but the bytecode evaluator did not handle `BcInheritFrom` in `allBcPositional`, `bcBindingSlotCount`, `buildBcSlotThunks`, or `buildBcAttrMapFromSlots`. This caused env slot count mismatches and `idx out of bounds` assertions on any nixpkgs expression using `inherit (from)` in a positional let block.+- **C memory safety audit** - `nn_bytecode.c`: `ensure_op_space`/`ensure_data_space` now return error codes instead of failing silently; overflow guard on capacity doubling. `nn_lambda.c`: `NN_ASSERT` bounds checks on entry accessors prevent null dereference when `formal_count == 0`. `nn_attrset.c`: documented partial-realloc-failure behavior.+- **Dead code removal** - Removed unused `cattrsetIntersect` Haskell wrapper, `nn_attrset_intersect`, and `nn_attrset_values_ptr` C functions.+- **New: `nn_assert.h`** - Debug-mode bounds checking macro. Compiles to nothing under `NDEBUG`.+- **Performance** - Stress test: 6.25 MB max residency, 56.3% GC productivity (down from 69.7 MB / 1.6% pre-C-data-layer). -## 0.1.8.0 — 2026-03-08+## 0.1.8.0 - 2026-03-08  ### Builder Correctness, Windows Store Paths, Hackage Fix -- **Fix #1: builder no longer pre-creates output paths** — `buildDerivationInner` no longer calls `createDirectoryIfMissing` for `$out`, `$dev`, etc. before running the builder. The builder is now responsible for creating its own outputs, matching real Nix semantics. Builds fail with `"builder succeeded but outputs missing"` if the builder exits successfully but expected outputs don't exist.-- **File outputs supported** — `$out` can now be a regular file, not just a directory. `registerSingleOutput`, `addToStore`, `scanReferences`, and `pathExists` all use `doesPathExist` instead of `doesDirectoryExist`. `moveOutput` (renamed from `moveDirectory`) handles both files and directories in cross-device fallback. `collectRegularFiles` accepts file paths directly for reference scanning.-- **Fix #2: Windows store paths use native separators** — CLI now uses `platformStoreDir` (`C:\nix\store` on Windows, `/nix/store` on Unix) for filesystem operations and display. Evaluator internals keep canonical `/nix/store` for ATerm hash compatibility.-- **Fix: `crypton < 1.1` in .cabal file** — Previous `crypton` pin was only in `cabal.project` (which Hackage ignores). Moved to `.cabal` so the Hackage solver respects it. `crypton >= 1.1` switched from `memory` to `ram` for `ByteArrayAccess`, breaking `http-client-tls`.+- **Fix #1: builder no longer pre-creates output paths** - `buildDerivationInner` no longer calls `createDirectoryIfMissing` for `$out`, `$dev`, etc. before running the builder. The builder is now responsible for creating its own outputs, matching real Nix semantics. Builds fail with `"builder succeeded but outputs missing"` if the builder exits successfully but expected outputs don't exist.+- **File outputs supported** - `$out` can now be a regular file, not just a directory. `registerSingleOutput`, `addToStore`, `scanReferences`, and `pathExists` all use `doesPathExist` instead of `doesDirectoryExist`. `moveOutput` (renamed from `moveDirectory`) handles both files and directories in cross-device fallback. `collectRegularFiles` accepts file paths directly for reference scanning.+- **Fix #2: Windows store paths use native separators** - CLI now uses `platformStoreDir` (`C:\nix\store` on Windows, `/nix/store` on Unix) for filesystem operations and display. Evaluator internals keep canonical `/nix/store` for ATerm hash compatibility.+- **Fix: `crypton < 1.1` in .cabal file** - Previous `crypton` pin was only in `cabal.project` (which Hackage ignores). Moved to `.cabal` so the Hackage solver respects it. `crypton >= 1.1` switched from `memory` to `ram` for `ByteArrayAccess`, breaking `http-client-tls`. -## 0.1.7.1 — 2026-03-07+## 0.1.7.1 - 2026-03-07  ### Hackage Build Fix -- **Fix: `nova-cache < 0.3.1` bound** — Hackage ignores `cabal.project` constraints, so the solver picked `nova-cache-0.3.1.0` (which uses `ram`) alongside `http-client-tls` (which uses `memory`), causing `ByteArrayAccess` instance mismatches. Narrowed bound to `< 0.3.1` to force `nova-cache-0.3.0.0` (which uses `memory`) until `http-client-tls` migrates to `ram`.+- **Fix: `nova-cache < 0.3.1` bound** - Hackage ignores `cabal.project` constraints, so the solver picked `nova-cache-0.3.1.0` (which uses `ram`) alongside `http-client-tls` (which uses `memory`), causing `ByteArrayAccess` instance mismatches. Narrowed bound to `< 0.3.1` to force `nova-cache-0.3.0.0` (which uses `memory`) until `http-client-tls` migrates to `ram`. -## 0.1.7.0 — 2026-03-07+## 0.1.7.0 - 2026-03-07  ### Build Fix: drvPath Resolution + cmd.exe Quoting -- **Fix: `nova-nix build` drvPath resolution** — `builtinDerivation` now writes `drvPath` and output paths (`out`, `dev`, etc.) into `drvEnv` on the `Derivation` struct. Previously these were only present in the eval result attr set, so `buildAndRegister` could not find the `.drv` store path for dependency resolution (error: "no drvPath available for dependency resolution").-- **Fix: `extractDerivation` returns `StorePath`** — The CLI `build` command now extracts the `drvPath` `StorePath` directly from the eval result alongside the `Derivation`, passing it explicitly to `buildWithDeps`. Eliminates the fragile `Map.lookup "drvPath" drvEnv` fallback.-- **Fix: cmd.exe builder quoting on Windows** — New `mkBuilderProcess` detects when the builder is `cmd.exe` with `/c` and uses `Proc.shell` instead of `Proc.proc`. GHC's `proc` wraps each argument in double-quotes for `CommandLineToArgvW`, but cmd.exe doesn't use that convention — `args = [ "/c" "echo Hello" ]` would fail because cmd.exe tried to find an executable literally named `"echo Hello"`.-- **Fix: UTF-16 auto-detection** — New `readFileAutoEncoding` detects UTF-16 LE/BE and UTF-8 BOM at the byte level before decoding. PowerShell's `>` operator writes UTF-16 LE by default — a Windows-first Nix implementation must handle this at the input boundary. Wired into all 5 file-read sites (Parser, Eval/IO, Main).-- **nova-cache `>= 0.3.0`** — Bumped lower bound to track nova-cache 0.3.x series.-- **`crypton < 1.1` pin** — `http-client-tls` still uses `memory`'s `ByteArrayAccess`; `crypton >= 1.1` switched to `ram`, causing instance mismatches. Pinned in `cabal.project` until `http-client-tls` migrates.+- **Fix: `nova-nix build` drvPath resolution** - `builtinDerivation` now writes `drvPath` and output paths (`out`, `dev`, etc.) into `drvEnv` on the `Derivation` struct. Previously these were only present in the eval result attr set, so `buildAndRegister` could not find the `.drv` store path for dependency resolution (error: "no drvPath available for dependency resolution").+- **Fix: `extractDerivation` returns `StorePath`** - The CLI `build` command now extracts the `drvPath` `StorePath` directly from the eval result alongside the `Derivation`, passing it explicitly to `buildWithDeps`. Eliminates the fragile `Map.lookup "drvPath" drvEnv` fallback.+- **Fix: cmd.exe builder quoting on Windows** - New `mkBuilderProcess` detects when the builder is `cmd.exe` with `/c` and uses `Proc.shell` instead of `Proc.proc`. GHC's `proc` wraps each argument in double-quotes for `CommandLineToArgvW`, but cmd.exe doesn't use that convention - `args = [ "/c" "echo Hello" ]` would fail because cmd.exe tried to find an executable literally named `"echo Hello"`.+- **Fix: UTF-16 auto-detection** - New `readFileAutoEncoding` detects UTF-16 LE/BE and UTF-8 BOM at the byte level before decoding. PowerShell's `>` operator writes UTF-16 LE by default - a Windows-first Nix implementation must handle this at the input boundary. Wired into all 5 file-read sites (Parser, Eval/IO, Main).+- **nova-cache `>= 0.3.0`** - Bumped lower bound to track nova-cache 0.3.x series.+- **`crypton < 1.1` pin** - `http-client-tls` still uses `memory`'s `ByteArrayAccess`; `crypton >= 1.1` switched to `ram`, causing instance mismatches. Pinned in `cabal.project` until `http-client-tls` migrates. -## 0.1.6.0 — 2026-02-26+## 0.1.6.0 - 2026-02-26  ### De Bruijn-Style Positional Env + SmallArray Slots -- **De Bruijn-style variable resolution** — New `Nix.Expr.Resolve` pass replaces `EVar` with `EResolvedVar level index` for lambda-bound variables. Called at parse time; all subsequent evaluation uses positional indices instead of name-based `Map` lookups.-- **Array-based Env** — `envSlots :: SmallArray Thunk` replaces `envBindings :: Map Text Thunk`. Lambda formals get O(1) positional indexing via `indexSmallArray` instead of O(log n) `Map.lookup`. Let/rec bindings and builtins remain in name-based `envLazyScope` (already zero Map.Bin overhead).-- **Scope chain** — `Env` uses parent pointer chain instead of `Map.union`. Variable lookup walks the chain: positional slots, then `envLazyScope`, then parent, then with-scopes. Avoids O(n) `Map.union` when extending large envs.-- **Inherit desugaring** — `inherit x;` is desugared to `x = x;` in the resolution pass so inherited lambda formals resolve to `EResolvedVar` (lambda slots have no names at runtime).-- **Heap savings** — Eliminates 29.9M Map.Bin nodes (1.37 GB) from lambda formals. Replaced by SmallArray (one heap object per env, O(1) index) + scope chain parent pointers.+- **De Bruijn-style variable resolution** - New `Nix.Expr.Resolve` pass replaces `EVar` with `EResolvedVar level index` for lambda-bound variables. Called at parse time; all subsequent evaluation uses positional indices instead of name-based `Map` lookups.+- **Array-based Env** - `envSlots :: SmallArray Thunk` replaces `envBindings :: Map Text Thunk`. Lambda formals get O(1) positional indexing via `indexSmallArray` instead of O(log n) `Map.lookup`. Let/rec bindings and builtins remain in name-based `envLazyScope` (already zero Map.Bin overhead).+- **Scope chain** - `Env` uses parent pointer chain instead of `Map.union`. Variable lookup walks the chain: positional slots, then `envLazyScope`, then parent, then with-scopes. Avoids O(n) `Map.union` when extending large envs.+- **Inherit desugaring** - `inherit x;` is desugared to `x = x;` in the resolution pass so inherited lambda formals resolve to `EResolvedVar` (lambda slots have no names at runtime).+- **Heap savings** - Eliminates 29.9M Map.Bin nodes (1.37 GB) from lambda formals. Replaced by SmallArray (one heap object per env, O(1) index) + scope chain parent pointers. - `primitive` dependency added for `Data.Primitive.SmallArray` -## 0.1.5.0 — 2026-02-26+## 0.1.5.0 - 2026-02-26  ### New Builtins, Coercion Fixes, CI Cleanup -- **`builtins.warn`** — prints warning to stderr, returns second arg-- **`builtins.path`** — copies file/dir to store with `SCPlain` context-- **`builtins.seq` fix** — now forces first arg to WHNF (was silent no-op)-- **`builtins.trace`/`traceVerbose` fix** — print to stderr via `MonadEval.traceMessage`-- **`coerceToString` fix** — handle `VAttrs` with `__toString` and `outPath`, enabling `"${pkgs.hello}/bin/hello"` interpolation (was type error on all attrsets)-- **`fetchTarball` fix** — downloads and extracts via curl|tar pipeline (was returning raw .tar.gz)-- **`MonadEval`** — added `traceMessage`, `copyPathToStore` methods+- **`builtins.warn`** - prints warning to stderr, returns second arg+- **`builtins.path`** - copies file/dir to store with `SCPlain` context+- **`builtins.seq` fix** - now forces first arg to WHNF (was silent no-op)+- **`builtins.trace`/`traceVerbose` fix** - print to stderr via `MonadEval.traceMessage`+- **`coerceToString` fix** - handle `VAttrs` with `__toString` and `outPath`, enabling `"${pkgs.hello}/bin/hello"` interpolation (was type error on all attrsets)+- **`fetchTarball` fix** - downloads and extracts via curl|tar pipeline (was returning raw .tar.gz)+- **`MonadEval`** - added `traceMessage`, `copyPathToStore` methods - CI: switched to `haskell-actions/run-ormolu@v16` (matching all Novavero repos) -## 0.1.4.0 — 2026-02-26+## 0.1.4.0 - 2026-02-26  ### Memory Optimization + Lazy Non-Rec Attrsets -- **Lazy non-rec attrsets** — `evalNonRecAttrs` now uses `LazyAttrs` to defer thunk+IORef allocation until first access. For `all-packages.nix` (~15k entries per stdenv stage), only accessed packages allocate thunks.-- **Per-binding Env in `LazyBinding`** — Each `LazyExpr`/`LazyInherit`/`LazyInheritFrom` carries its own `Env` instead of sharing one per-attrset. Fixes a bug where `//` merges of `LazyAttrs` from different scopes lost variables — broke `lib.extends` overlay pattern used by `makeExtensibleWithCustomName`.-- **Batched formal set matching** — `matchFormalSet` creates ONE `Env` with all formals instead of N singleton `Env`s. Uses knot-tying so default expressions can reference other formals, including forward references (`{ a ? b, b ? 1 }: a` → `1`).-- **Env scope chain** — `Env` now uses a parent pointer chain instead of `Map.union`. Variable lookup walks the chain: local bindings, then parent, then with-scopes. Avoids O(n) `Map.union` when extending large envs (e.g. 30k-entry nixpkgs rec set). Peak Map.Bin heap: 956MB → ~40MB.-- **ThunkCell release** — On force, `Pending expr env` is overwritten to `Computed val`, dropping all references to `Expr` and `Env` (matching C++ Nix in-place mutation). Previously retained dead closures indefinitely.-- **Lazy `//` operator** — `mergeAttrSets` preserves `LazyAttrs` when possible, merging binding recipe maps instead of materializing all thunks.-- **Lazy with-scopes** — `pushWithScope` accepts `AttrSet` directly so `LazyAttrs` with-scopes stay lazy. `lookupWithScopes` uses `attrSetLookup` to materialize only the accessed key.-- **Key-driven `intersectAttrs`** — Only touches keys present in both sets instead of materializing entire attrsets.-- **New builtins** — `min`, `max`, `mod`, base64 `encode`/`decode` (via nova-cache 0.2.4)+- **Lazy non-rec attrsets** - `evalNonRecAttrs` now uses `LazyAttrs` to defer thunk+IORef allocation until first access. For `all-packages.nix` (~15k entries per stdenv stage), only accessed packages allocate thunks.+- **Per-binding Env in `LazyBinding`** - Each `LazyExpr`/`LazyInherit`/`LazyInheritFrom` carries its own `Env` instead of sharing one per-attrset. Fixes a bug where `//` merges of `LazyAttrs` from different scopes lost variables - broke `lib.extends` overlay pattern used by `makeExtensibleWithCustomName`.+- **Batched formal set matching** - `matchFormalSet` creates ONE `Env` with all formals instead of N singleton `Env`s. Uses knot-tying so default expressions can reference other formals, including forward references (`{ a ? b, b ? 1 }: a` evaluates to `1`).+- **Env scope chain** - `Env` now uses a parent pointer chain instead of `Map.union`. Variable lookup walks the chain: local bindings, then parent, then with-scopes. Avoids O(n) `Map.union` when extending large envs (e.g. 30k-entry nixpkgs rec set). Peak Map.Bin heap: 956MB down to ~40MB.+- **ThunkCell release** - On force, `Pending expr env` is overwritten to `Computed val`, dropping all references to `Expr` and `Env` (matching C++ Nix in-place mutation). Previously retained dead closures indefinitely.+- **Lazy `//` operator** - `mergeAttrSets` preserves `LazyAttrs` when possible, merging binding recipe maps instead of materializing all thunks.+- **Lazy with-scopes** - `pushWithScope` accepts `AttrSet` directly so `LazyAttrs` with-scopes stay lazy. `lookupWithScopes` uses `attrSetLookup` to materialize only the accessed key.+- **Key-driven `intersectAttrs`** - Only touches keys present in both sets instead of materializing entire attrsets.+- **New builtins** - `min`, `max`, `mod`, base64 `encode`/`decode` (via nova-cache 0.2.4) - `nova-cache >= 0.2.4` dependency bump - 511 tests, -Werror clean, ormolu 0.7.7.0 clean, hlint clean -## 0.1.3.0 — 2026-02-25+## 0.1.3.0 - 2026-02-25  ### nixpkgs Compatibility: Module System, Callable Sets, Parser Fixes -- **`__functor` support** — Attribute sets with `__functor` are now callable, matching real Nix. `(set.__functor set) arg` dispatch enables nixpkgs patterns like `lib.makeOverridable` and `lib.setFunctionArgs`.-- **`builtins.setFunctionArgs`** — Wraps a function in a callable attrset with `__functor` and `__functionArgs` metadata. Used by `lib.mirrorFunctionArgs`, `lib.makeOverridable`, and the entire nixpkgs override system.-- **Null dynamic keys** — `{ ${null} = val; }` now skips the binding instead of erroring (matching real Nix). Used extensively by the nixpkgs module system for conditional attributes like `${if cond then "key" else null}`.-- **Indented string interpolation fix** — `''${expr}''` now parses correctly. The lexer had an overzealous guard (`isIndStringEscape`) that blocked `''` from opening an indented string when followed by `$`. This broke `lib.generators` and many nixpkgs files.-- **`splitVersion` fix** — Dots are separators, not components. `splitVersion "1.2.3"` now returns `["1" "2" "3"]` (was `["1" "." "2" "." "3"]`). Fixes `lib.versions.majorMinor` and all version comparison logic.-- **`builtins.functionArgs` on callable sets** — Now inspects `__functionArgs` metadata on attrsets produced by `setFunctionArgs`, enabling `lib.functionArgs` to work on overridable functions.-- **CLI eval sub-parser** — `--strict` and `--nix-path` flags now work after `eval` command (e.g. `nova-nix eval --strict --expr '...'`). Previously only worked before the command.-- **Bundled `<nix/fetchurl.nix>`** — Ships as a Cabal data-file, added to search paths automatically. nixpkgs stdenv bootstrap imports this file; no system Nix install needed.-- **nixpkgs module system working** — `lib.evalModules`, `lib.mkOption`, `lib.mkIf`, `lib.types.*` all evaluate correctly.-- **nixpkgs functional patterns working** — `lib.makeOverridable`, `lib.makeExtensible`, `lib.fix`, `lib.generators.toKeyValue` all correct.-- **`lib.systems.elaborate` instant** — Was taking minutes before lazy builtins; now returns immediately for any system string.+- **`__functor` support** - Attribute sets with `__functor` are now callable, matching real Nix. `(set.__functor set) arg` dispatch enables nixpkgs patterns like `lib.makeOverridable` and `lib.setFunctionArgs`.+- **`builtins.setFunctionArgs`** - Wraps a function in a callable attrset with `__functor` and `__functionArgs` metadata. Used by `lib.mirrorFunctionArgs`, `lib.makeOverridable`, and the entire nixpkgs override system.+- **Null dynamic keys** - `{ ${null} = val; }` now skips the binding instead of erroring (matching real Nix). Used extensively by the nixpkgs module system for conditional attributes like `${if cond then "key" else null}`.+- **Indented string interpolation fix** - `''${expr}''` now parses correctly. The lexer had an overzealous guard (`isIndStringEscape`) that blocked `''` from opening an indented string when followed by `$`. This broke `lib.generators` and many nixpkgs files.+- **`splitVersion` fix** - Dots are separators, not components. `splitVersion "1.2.3"` now returns `["1" "2" "3"]` (was `["1" "." "2" "." "3"]`). Fixes `lib.versions.majorMinor` and all version comparison logic.+- **`builtins.functionArgs` on callable sets** - Now inspects `__functionArgs` metadata on attrsets produced by `setFunctionArgs`, enabling `lib.functionArgs` to work on overridable functions.+- **CLI eval sub-parser** - `--strict` and `--nix-path` flags now work after `eval` command (e.g. `nova-nix eval --strict --expr '...'`). Previously only worked before the command.+- **Bundled `<nix/fetchurl.nix>`** - Ships as a Cabal data-file, added to search paths automatically. nixpkgs stdenv bootstrap imports this file; no system Nix install needed.+- **nixpkgs module system working** - `lib.evalModules`, `lib.mkOption`, `lib.mkIf`, `lib.types.*` all evaluate correctly.+- **nixpkgs functional patterns working** - `lib.makeOverridable`, `lib.makeExtensible`, `lib.fix`, `lib.generators.toKeyValue` all correct.+- **`lib.systems.elaborate` instant** - Was taking minutes before lazy builtins; now returns immediately for any system string. - 101 builtins (up from 91), 511 tests -## 0.1.2.0 — 2026-02-24+## 0.1.2.0 - 2026-02-24  ### Lazy Builtins + Correctness Fixes -- **Lazy `map`** — returns deferred thunks instead of eagerly forcing all elements. `map f [80000 items]` is now O(1) until elements are demanded.-- **Lazy `genList`** — each element is a deferred `f(i)` thunk, forced only on demand-- **Lazy `mapAttrs`** — each attr value is a deferred `f key val` thunk. Critical for nixpkgs which `mapAttrs` over the entire package set.-- **Semi-lazy `concatMap`** — forces applications to discover list structure for concatenation, but element thunks within sub-lists stay lazy-- **`@`-pattern scoping fix** — `{ system ? args.system or ..., ... }@args:` now correctly binds the `@`-name before evaluating defaults, matching real Nix. Previously, default expressions couldn't reference the `@`-binding.-- **Synthetic thunk memo cell fix** — `mkSyntheticThunk` uses env bindings (not expr) for IORef uniqueness, preventing GHC's full-laziness transform from sharing one memo cell across all deferred thunks. Without this, `map f [a b c]` would return `[f(a) f(a) f(a)]`.+- **Lazy `map`** - returns deferred thunks instead of eagerly forcing all elements. `map f [80000 items]` is now O(1) until elements are demanded.+- **Lazy `genList`** - each element is a deferred `f(i)` thunk, forced only on demand+- **Lazy `mapAttrs`** - each attr value is a deferred `f key val` thunk. Critical for nixpkgs which `mapAttrs` over the entire package set.+- **Semi-lazy `concatMap`** - forces applications to discover list structure for concatenation, but element thunks within sub-lists stay lazy+- **`@`-pattern scoping fix** - `{ system ? args.system or ..., ... }@args:` now correctly binds the `@`-name before evaluating defaults, matching real Nix. Previously, default expressions couldn't reference the `@`-binding.+- **Synthetic thunk memo cell fix** - `mkSyntheticThunk` uses env bindings (not expr) for IORef uniqueness, preventing GHC's full-laziness transform from sharing one memo cell across all deferred thunks. Without this, `map f [a b c]` would return `[f(a) f(a) f(a)]`. - `deferApply` helper: builds thunks wrapping `EApp (EResolvedVar 0 0) (EResolvedVar 0 1)` in a self-contained env with positional slots, reusing existing eval + memoization machinery - 511 tests (3 new: map laziness, genList laziness, mapAttrs laziness) -## 0.1.1.0 — 2026-02-24+## 0.1.1.0 - 2026-02-24 -- **Thunk memoization** — Per-thunk `IORef` memo cells (matching real Nix in-place mutation). `forceThunk` is a `MonadEval` method: IO evaluators memoize per-allocation, pure evaluators re-evaluate. GC reclaims dead thunks naturally — no unbounded global cache. `unsafePerformIO` CAF float-out prevented via `NOINLINE` + `seq` pattern.-- **8.4x builtin dispatch speedup** — Case dispatch replacing polymorphic `Map` reconstruction on every call. Direct pattern match on builtin name for zero-allocation dispatch in the hot path.-- **Regex builtins** — `builtins.match` and `builtins.split` via `regex-tdfa` (pure Haskell POSIX ERE, cross-platform)-- `ESearchPath !Text` AST constructor — `<nixpkgs>` is now its own node, desugared at eval time to `builtins.findFile builtins.nixPath "nixpkgs"` (matching real Nix semantics)+- **Thunk memoization** - Per-thunk `IORef` memo cells (matching real Nix in-place mutation). `forceThunk` is a `MonadEval` method: IO evaluators memoize per-allocation, pure evaluators re-evaluate. GC reclaims dead thunks naturally - no unbounded global cache. `unsafePerformIO` CAF float-out prevented via `NOINLINE` + `seq` pattern.+- **8.4x builtin dispatch speedup** - Case dispatch replacing polymorphic `Map` reconstruction on every call. Direct pattern match on builtin name for zero-allocation dispatch in the hot path.+- **Regex builtins** - `builtins.match` and `builtins.split` via `regex-tdfa` (pure Haskell POSIX ERE, cross-platform)+- `ESearchPath !Text` AST constructor - `<nixpkgs>` is now its own node, desugared at eval time to `builtins.findFile builtins.nixPath "nixpkgs"` (matching real Nix semantics) - NIX_PATH parsing: `parseNixPath` converts colon-separated `name=path` entries into `{ prefix, path }` attrset thunks - `builtinEnv` now accepts search paths: `builtinEnv :: Integer -> [Thunk] -> Env` - `EvalState.esSearchPaths` populated from `NIX_PATH` environment variable at startup - Directory imports: `import ./dir` automatically resolves to `./dir/default.nix` - Dynamic attribute keys: `{ ${expr} = val; }` fully supported in all contexts (non-rec, rec, let, select, hasAttr) - Two-phase binding resolution for knot-tying: `resolveBindingKeys` (monadic, evaluates dynamic keys) then `buildResolvedBindingsMap` (pure, enables recursive self-reference)-- Monadic `resolveKey` replaces pure `resolveStaticKey` — handles both `StaticKey` and `DynamicKey` uniformly+- Monadic `resolveKey` replaces pure `resolveStaticKey` - handles both `StaticKey` and `DynamicKey` uniformly - CLI `--nix-path NAME=PATH` flag (repeatable, merged with NIX_PATH) - CLI `--expr EXPR` for inline expression evaluation - CLI deep-force and pretty-printing for eval output@@ -175,17 +192,17 @@ - 91 builtins (up from 88) - 508 tests (14 new: parseNixPath, search path parsing/eval, dynamic keys, directory import, populated search path resolution) -## 0.1.0.0 — 2026-02-24+## 0.1.0.0 - 2026-02-24  ### Cross-Platform Fixes  - Cross-platform ATerm serialization: `storePathToText` always uses `/` for store paths regardless of OS, `parseStorePath` accepts both `/` and `\`-- Builder inherits system environment, overlays build env via `Map.union` — fixes silent process failures on Windows (missing `SYSTEMROOT`)-- Builder PATH derived from builder location (`buildPath`) — includes builder dir and MSYS2 sibling `usr/bin` for coreutils discovery+- Builder inherits system environment, overlays build env via `Map.union` - fixes silent process failures on Windows (missing `SYSTEMROOT`)+- Builder PATH derived from builder location (`buildPath`) - includes builder dir and MSYS2 sibling `usr/bin` for coreutils discovery - `findTestShell` prefers known Git for Windows bash over WSL launcher (`System32\bash.exe`)-- Parser strips UTF-8 BOM — Windows editors (Notepad, PowerShell) commonly add byte order marks-- Demo `test.nix` included: derivations, lambdas, builtins.map, arithmetic — runs on all platforms-- 16 builtins exposed at top level without `builtins.` prefix (`toString`, `map`, `throw`, `import`, `derivation`, `abort`, `baseNameOf`, `dirOf`, `isNull`, `removeAttrs`, `placeholder`, `scopedImport`, `fetchTarball`, `fetchGit`, `fetchurl`, `toFile`) — matches real Nix language spec, required for nixpkgs compatibility+- Parser strips UTF-8 BOM - Windows editors (Notepad, PowerShell) commonly add byte order marks+- Demo `test.nix` included: derivations, lambdas, builtins.map, arithmetic - runs on all platforms+- 16 builtins exposed at top level without `builtins.` prefix (`toString`, `map`, `throw`, `import`, `derivation`, `abort`, `baseNameOf`, `dirOf`, `isNull`, `removeAttrs`, `placeholder`, `scopedImport`, `fetchTarball`, `fetchGit`, `fetchurl`, `toFile`) - matches real Nix language spec, required for nixpkgs compatibility  ### String Contexts, Dependency Resolution, Binary Substituter @@ -195,8 +212,8 @@ - `derivation` builtin now collects string contexts into `drvInputDrvs` and `drvInputSrcs` - New builtins: `hasContext`, `getContext`, `appendContext` - `Nix.DependencyGraph`: BFS graph construction with `Data.Sequence` (O(V+E)), topological sort via Kahn's algorithm, cycle detection-- `Nix.Substituter`: full HTTP binary cache protocol — narinfo fetch/parse, Ed25519 signature verification, NAR download/decompress/unpack, store DB registration, priority-ordered multi-cache-- `Nix.Builder.buildWithDeps`: recursive dependency resolution — topo sort, cache check, binary substitution, local build fallback+- `Nix.Substituter`: full HTTP binary cache protocol - narinfo fetch/parse, Ed25519 signature verification, NAR download/decompress/unpack, store DB registration, priority-ordered multi-cache+- `Nix.Builder.buildWithDeps`: recursive dependency resolution - topo sort, cache check, binary substitution, local build fallback - CLI `nova-nix build` now builds full dependency trees, not just single derivations - Cleanup pass: eliminated all partial functions (`T.head`/`T.tail` to `T.uncons`, `!!` to safe lookup, `last` to pattern match), flattened deeply nested code into composed functions, `Data.Sequence` BFS queues throughout, semantic section organization - 494 tests (68 new: string context, context propagation, dependency graph, substituter, build orchestrator)@@ -217,7 +234,7 @@ - Full Nix expression parser (hand-rolled recursive descent, 13 precedence levels) - Lazy evaluator with thunk-based evaluation, knot-tying for recursive bindings - 85 builtins: type checks, arithmetic, bitwise, strings, lists, attrsets, higher-order, JSON, hashing, version parsing, tryEval, deepSeq, genericClosure, all IO builtins, derivation-- MonadEval typeclass — evaluator is polymorphic in its effect monad (PureEval for tests, EvalIO for real evaluation)+- MonadEval typeclass - evaluator is polymorphic in its effect monad (PureEval for tests, EvalIO for real evaluation) - IO builtins: import, readFile, pathExists, readDir, getEnv, toPath, toFile, findFile, scopedImport, fetchurl, fetchTarball, fetchGit, currentTime - derivation builtin: attrset to .drv build recipe with computed drvPath and outPath - ATerm serialization with string escaping, sorted environments@@ -231,7 +248,7 @@  ### Security -- Total functions only — no `read`, `head`, `tail`, `!!`, `fromJust`, or `Map.!`+- Total functions only - no `read`, `head`, `tail`, `!!`, `fromJust`, or `Map.!` - Argument injection prevention in fetch builtins (`--` separator before user URLs) - Path traversal validation in writeToStore (rejects `/`, `..`, null bytes) - Content-hashed temp directories for fetchGit (no predictable paths)@@ -239,7 +256,7 @@  ### Architecture -- Store paths parameterized via `StoreDir` — no hardcoded `/nix/store/` strings+- Store paths parameterized via `StoreDir` - no hardcoded `/nix/store/` strings - Cross-device safe directory moves (rename with copy+remove fallback) - Hash utilities deduplicated into Nix.Hash (single source of truth) - currentSystem is a constant (not a function), matching real Nix semantics
LICENSE view
@@ -1,29 +1,202 @@-Copyright (c) 2026 Novavero AI -All rights reserved.+                                 Apache License+                           Version 2.0, January 2004+                        http://www.apache.org/licenses/ -Redistribution and use in source and binary forms, with or without-modification, are permitted provided that the following conditions are met:+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -1. Redistributions of source code must retain the above copyright notice,-   this list of conditions and the following disclaimer.+   1. Definitions. -2. Redistributions in binary form must reproduce the above copyright notice,-   this list of conditions and the following disclaimer in the documentation-   and/or other materials provided with the distribution.+      "License" shall mean the terms and conditions for use, reproduction,+      and distribution as defined by Sections 1 through 9 of this document. -3. Neither the name of the copyright holder nor the names of its-   contributors may be used to endorse or promote products derived from-   this software without specific prior written permission.+      "Licensor" shall mean the copyright owner or entity authorized by+      the copyright owner that is granting the License. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF-THE POSSIBILITY OF SUCH DAMAGE.+      "Legal Entity" shall mean the union of the acting entity and all+      other entities that control, are controlled by, or are under common+      control with that entity. For the purposes of this definition,+      "control" means (i) the power, direct or indirect, to cause the+      direction or management of such entity, whether by contract or+      otherwise, or (ii) ownership of fifty percent (50%) or more of the+      outstanding shares, or (iii) beneficial ownership of such entity.++      "You" (or "Your") shall mean an individual or Legal Entity+      exercising permissions granted by this License.++      "Source" form shall mean the preferred form for making modifications,+      including but not limited to software source code, documentation+      source, and configuration files.++      "Object" form shall mean any form resulting from mechanical+      transformation or translation of a Source form, including but+      not limited to compiled object code, generated documentation,+      and conversions to other media types.++      "Work" shall mean the work of authorship, whether in Source or+      Object form, made available under the License, as indicated by a+      copyright notice that is included in or attached to the work+      (an example is provided in the Appendix below).++      "Derivative Works" shall mean any work, whether in Source or Object+      form, that is based on (or derived from) the Work and for which the+      editorial revisions, annotations, elaborations, or other modifications+      represent, as a whole, an original work of authorship. For the purposes+      of this License, Derivative Works shall not include works that remain+      separable from, or merely link (or bind by name) to the interfaces of,+      the Work and Derivative Works thereof.++      "Contribution" shall mean any work of authorship, including+      the original version of the Work and any modifications or additions+      to that Work or Derivative Works thereof, that is intentionally+      submitted to Licensor for inclusion in the Work by the copyright owner+      or by an individual or Legal Entity authorized to submit on behalf of+      the copyright owner. For the purposes of this definition, "submitted"+      means any form of electronic, verbal, or written communication sent+      to the Licensor or its representatives, including but not limited to+      communication on electronic mailing lists, source code control systems,+      and issue tracking systems that are managed by, or on behalf of, the+      Licensor for the purpose of discussing and improving the Work, but+      excluding communication that is conspicuously marked or otherwise+      designated in writing by the copyright owner as "Not a Contribution."++      "Contributor" shall mean Licensor and any individual or Legal Entity+      on behalf of whom a Contribution has been received by Licensor and+      subsequently incorporated within the Work.++   2. Grant of Copyright License. Subject to the terms and conditions of+      this License, each Contributor hereby grants to You a perpetual,+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable+      copyright license to reproduce, prepare Derivative Works of,+      publicly display, publicly perform, sublicense, and distribute the+      Work and such Derivative Works in Source or Object form.++   3. Grant of Patent License. Subject to the terms and conditions of+      this License, each Contributor hereby grants to You a perpetual,+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable+      (except as stated in this section) patent license to make, have made,+      use, offer to sell, sell, import, and otherwise transfer the Work,+      where such license applies only to those patent claims licensable+      by such Contributor that are necessarily infringed by their+      Contribution(s) alone or by combination of their Contribution(s)+      with the Work to which such Contribution(s) was submitted. If You+      institute patent litigation against any entity (including a+      cross-claim or counterclaim in a lawsuit) alleging that the Work+      or a Contribution incorporated within the Work constitutes direct+      or contributory patent infringement, then any patent licenses+      granted to You under this License for that Work shall terminate+      as of the date such litigation is filed.++   4. Redistribution. You may reproduce and distribute copies of the+      Work or Derivative Works thereof in any medium, with or without+      modifications, and in Source or Object form, provided that You+      meet the following conditions:++      (a) You must give any other recipients of the Work or+          Derivative Works a copy of this License; and++      (b) You must cause any modified files to carry prominent notices+          stating that You changed the files; and++      (c) You must retain, in the Source form of any Derivative Works+          that You distribute, all copyright, patent, trademark, and+          attribution notices from the Source form of the Work,+          excluding those notices that do not pertain to any part of+          the Derivative Works; and++      (d) If the Work includes a "NOTICE" text file as part of its+          distribution, then any Derivative Works that You distribute must+          include a readable copy of the attribution notices contained+          within such NOTICE file, excluding those notices that do not+          pertain to any part of the Derivative Works, in at least one+          of the following places: within a NOTICE text file distributed+          as part of the Derivative Works; within the Source form or+          documentation, if provided along with the Derivative Works; or,+          within a display generated by the Derivative Works, if and+          wherever such third-party notices normally appear. The contents+          of the NOTICE file are for informational purposes only and+          do not modify the License. You may add Your own attribution+          notices within Derivative Works that You distribute, alongside+          or as an addendum to the NOTICE text from the Work, provided+          that such additional attribution notices cannot be construed+          as modifying the License.++      You may add Your own copyright statement to Your modifications and+      may provide additional or different license terms and conditions+      for use, reproduction, or distribution of Your modifications, or+      for any such Derivative Works as a whole, provided Your use,+      reproduction, and distribution of the Work otherwise complies with+      the conditions stated in this License.++   5. Submission of Contributions. Unless You explicitly state otherwise,+      any Contribution intentionally submitted for inclusion in the Work+      by You to the Licensor shall be under the terms and conditions of+      this License, without any additional terms or conditions.+      Notwithstanding the above, nothing herein shall supersede or modify+      the terms of any separate license agreement you may have executed+      with Licensor regarding such Contributions.++   6. Trademarks. This License does not grant permission to use the trade+      names, trademarks, service marks, or product names of the Licensor,+      except as required for reasonable and customary use in describing the+      origin of the Work and reproducing the content of the NOTICE file.++   7. Disclaimer of Warranty. Unless required by applicable law or+      agreed to in writing, Licensor provides the Work (and each+      Contributor provides its Contributions) on an "AS IS" BASIS,+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or+      implied, including, without limitation, any warranties or conditions+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A+      PARTICULAR PURPOSE. You are solely responsible for determining the+      appropriateness of using or redistributing the Work and assume any+      risks associated with Your exercise of permissions under this License.++   8. Limitation of Liability. In no event and under no legal theory,+      whether in tort (including negligence), contract, or otherwise,+      unless required by applicable law (such as deliberate and grossly+      negligent acts) or agreed to in writing, shall any Contributor be+      liable to You for damages, including any direct, indirect, special,+      incidental, or consequential damages of any character arising as a+      result of this License or out of the use or inability to use the+      Work (including but not limited to damages for loss of goodwill,+      work stoppage, computer failure or malfunction, or any and all+      other commercial damages or losses), even if such Contributor+      has been advised of the possibility of such damages.++   9. Accepting Warranty or Additional Liability. While redistributing+      the Work or Derivative Works thereof, You may choose to offer,+      and charge a fee for, acceptance of support, warranty, indemnity,+      or other liability obligations and/or rights consistent with this+      License. However, in accepting such obligations, You may act only+      on Your own behalf and on Your sole responsibility, not on behalf+      of any other Contributor, and only if You agree to indemnify,+      defend, and hold each Contributor harmless for any liability+      incurred by, or claims asserted against, such Contributor by reason+      of your accepting any such warranty or additional liability.++   END OF TERMS AND CONDITIONS++   APPENDIX: How to apply the Apache License to your work.++      To apply the Apache License to your work, attach the following+      boilerplate notice, with the fields enclosed by brackets "[]"+      replaced with your own identifying information. (Don't include+      the brackets!)  The text should be enclosed in the appropriate+      comment syntax for the file format. We also recommend that a+      file or class name and description of purpose be included on the+      same "printed page" as the copyright notice for easier+      identification within third-party archives.++   Copyright [yyyy] [name of copyright owner]++   Licensed under the Apache License, Version 2.0 (the "License");+   you may not use this file except in compliance with the License.+   You may obtain a copy of the License at++       http://www.apache.org/licenses/LICENSE-2.0++   Unless required by applicable law or agreed to in writing, software+   distributed under the License is distributed on an "AS IS" BASIS,+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+   See the License for the specific language governing permissions and+   limitations under the License.
README.md view
@@ -1,12 +1,12 @@ <div align="center"> <h1>nova-nix</h1> <p><strong>A Windows-native Nix, from scratch.</strong></p>-<p>Parser, lazy evaluator, content-addressed store, derivation builder, and binary-cache substituter — in Haskell and C99. Runs natively on Windows, macOS, and Linux. No WSL, no Cygwin.</p>+<p>Parser, lazy evaluator, content-addressed store, derivation builder, and binary-cache substituter - in Haskell and C99. Runs natively on Windows, macOS, and Linux. No WSL, no Cygwin.</p>  [![CI](https://github.com/Novavero-AI/nova-nix/actions/workflows/ci.yml/badge.svg)](https://github.com/Novavero-AI/nova-nix/actions/workflows/ci.yml) [![Hackage](https://img.shields.io/hackage/v/nova-nix.svg)](https://hackage.haskell.org/package/nova-nix) ![GHC](https://img.shields.io/badge/GHC-9.8-purple)-![License](https://img.shields.io/badge/license-BSD--3--Clause-blue)+![License](https://img.shields.io/badge/license-Apache--2.0-blue)  </div> @@ -38,7 +38,7 @@ "/nix/store/gciipqhqkdlqqn803zd4a389v86ran45-hello-2.12.1.drv" ``` -That `drvPath`, and the 253-derivation closure behind it, byte-matches upstream `nix-instantiate` — verified in CI on the same nixpkgs tree. It targets the Nix 2.24 language.+That `drvPath`, and the 253-derivation closure behind it, byte-matches upstream `nix-instantiate` - verified in CI on the same nixpkgs tree. It targets the Nix 2.24 language.  ## Quickstart @@ -79,22 +79,22 @@  ## How it works -Six layers — Haskell for logic, C99 for data:+Six layers - Haskell for logic, C99 for data: -1. **Parser** (`Nix.Parser`, `Nix.Expr`) — hand-rolled recursive descent; the full Nix grammar to a 19-constructor AST.-2. **Evaluator** (`Nix.Eval`) — the AST compiles to a flat 24-opcode bytecode that a lazy, thunk-memoizing evaluator runs. Recursive `let`/`rec` are knot-tied; reference cycles are caught by blackhole detection. The evaluator is polymorphic over its effect via `MonadEval`: `PureEval` for tests, `EvalIO` for the real thing.-3. **Data layer** (`cbits/nn_*.c`) — nine arena-allocated C99 modules (interned symbols, sorted attrsets, thunks, environments, lists, context strings, bytecode, lambdas) hold evaluation data off the GHC heap. Haskell calls C to create and query it; C never calls back.-4. **Store** (`Nix.Store`) — content-addressed `/nix/store` (`C:\nix\store` on Windows) with SQLite metadata and reference scanning.-5. **Builder** (`Nix.Builder`) — dependency graph, topological sort, and binary-cache substitution before local builds.-6. **Substituter** (`Nix.Substituter`) — the HTTP binary-cache protocol: narinfo parsing, Ed25519 verification, NAR download and unpack. Built on [nova-cache](https://github.com/Novavero-AI/nova-cache).+1. **Parser** (`Nix.Parser`, `Nix.Expr`) - hand-rolled recursive descent; the full Nix grammar to a 19-constructor AST.+2. **Evaluator** (`Nix.Eval`) - the AST compiles to a flat 24-opcode bytecode that a lazy, thunk-memoizing evaluator runs. Recursive `let`/`rec` are knot-tied; reference cycles are caught by blackhole detection. The evaluator is polymorphic over its effect via `MonadEval`: `PureEval` for tests, `EvalIO` for the real thing.+3. **Data layer** (`cbits/nn_*.c`) - nine arena-allocated C99 modules (interned symbols, sorted attrsets, thunks, environments, lists, context strings, bytecode, lambdas) hold evaluation data off the GHC heap. Haskell calls C to create and query it; C never calls back.+4. **Store** (`Nix.Store`) - content-addressed `/nix/store` (`C:\nix\store` on Windows) with SQLite metadata and reference scanning.+5. **Builder** (`Nix.Builder`) - dependency graph, topological sort, and binary-cache substitution before local builds.+6. **Substituter** (`Nix.Substituter`) - the HTTP binary-cache protocol: narinfo parsing, Ed25519 verification, NAR download and unpack. Built on [nova-cache](https://github.com/Novavero-AI/nova-cache). -Two decisions shape the rest. **Haskell owns evaluation, C owns data layout** — bulk eval state lives off the GHC heap, so large evaluations don't thrash the collector. And **`derivation` is a lazy wrapper over the eager `derivationStrict` primop** (as in Nix's `corepkgs/derivation.nix`), so referencing a package never forces its build closure.+Two decisions shape the rest. **Haskell owns evaluation, C owns data layout** - bulk eval state lives off the GHC heap, so large evaluations don't thrash the collector. And **`derivation` is a lazy wrapper over the eager `derivationStrict` primop** (as in Nix's `corepkgs/derivation.nix`), so referencing a package never forces its build closure.  ## Windows-native  | Unix assumption | How nova-nix handles it | |---|---|-| `/nix/store` | `C:\nix\store` — every path is parameterized, never hardcoded |+| `/nix/store` | `C:\nix\store` - every path is parameterized, never hardcoded | | `fork`/`exec` | `CreateProcess` via `System.Process` | | Symlinks | Developer-mode symlinks, with junction / copy fallback | | 260-char path limit | `\\?\` extended-length prefixes |@@ -102,13 +102,15 @@  ## Roadmap -**Done** — parser, lazy bytecode evaluator, the Nix `builtins` set, the C99 data layer, content-addressed store, derivation builder, binary-cache substituter and `push`, `import <nixpkgs> {}` evaluation, derivation-hash parity with upstream Nix (`hello`'s 253-derivation closure byte-matches `nix-instantiate`), and native Windows builds from a store-pinned MinGW-w64 toolchain (stage 0), published to and substituted from a binary cache.+**Done** - parser, lazy bytecode evaluator, the Nix `builtins` set, the C99 data layer, content-addressed store, derivation builder, binary-cache substituter and `push`, `import <nixpkgs> {}` evaluation, derivation-hash parity with upstream Nix (`hello`'s 253-derivation closure byte-matches `nix-instantiate`), and native Windows builds from a store-pinned MinGW-w64 toolchain (stage 0), published to and substituted from a binary cache. +**In progress** - an experimental stage-1 stdenv: a store-pinned MSYS2 userland seed and a lean `setup.sh` + `mkDerivation`, so a package builds from just `{ name; src; }`. It wires inter-package dependencies (`buildInputs`), routes compilation through a `gcc` wrapper (hermetic flags, deterministic links), and bundles non-system DLLs so outputs run standalone. Proven by building GNU hello, GNU sed, and zlib from source, and by linking a program against a library built the same way.+ **Next** -- Windows stdenv stage 1 — a setup script and compiler wrappers over the seed; rebuild coreutils and bash from source.-- Parity across more of nixpkgs — extend the byte-match check beyond `hello`'s closure.-- Cache — serve large NARs from object storage, and compress them.+- Grow the native package set - more real-world libraries and programs built from source through the stdenv.+- Parity across more of nixpkgs - extend the byte-match check beyond `hello`'s closure.+- Cache - serve large NARs from object storage, and compress them.  ## Library usage @@ -136,4 +138,4 @@  --- -<p align="center"><sub>BSD-3-Clause · <a href="https://github.com/Novavero-AI">Novavero AI</a></sub></p>+<p align="center"><sub>Apache-2.0 - <a href="https://github.com/Novavero-AI">Novavero AI Inc.</a></sub></p>
app/Main.hs view
@@ -285,7 +285,7 @@           store <- openStore (chosenStoreDir opts)           -- Materialize eval-coerced source paths (src = ./file, path           -- interpolation): evaluation computes their store paths as text-          -- only — the parity runner's store is not writable — so the build+          -- only - the parity runner's store is not writable - so the build           -- driver performs the copy and registration.           materializeEvalSources store sourceCache           buildResult <- buildAndRegister store caches drvClosure drv drvSP@@ -315,7 +315,7 @@     _ -> do       hPutStrLn stderr "error: derivation result missing _derivation field"       exitFailure-  -- Extract drvPath — this is the store path of the .drv file itself,+  -- Extract drvPath - this is the store path of the .drv file itself,   -- computed by hashing the ATerm serialization during evaluation.   drvSP <- case attrSetLookup "drvPath" attrs of     Just thunk | Just (VStr path _) <- readThunkValue thunk -> case parseStorePath defaultStoreDir path of@@ -364,7 +364,7 @@   -- recipe) to the store.  buildWithDeps reads these back to construct the   -- dependency graph; without them it cannot realize any non-leaf derivation.   writeDrvClosure store drvClosure-  -- Write the root .drv too (idempotent — it is also in the closure) so a build+  -- Write the root .drv too (idempotent - it is also in the closure) so a build   -- still works if the closure was not captured (e.g. a pre-built store drv).   writeDrv store drv drvSP   -- Build with dependency resolution@@ -453,7 +453,7 @@  -- | Copy eval-coerced source paths into the store and register them.  The -- evaluator's source-path cache maps each coerced filesystem path to its--- @source@ fixed-output store path (text only — eval performs no store+-- @source@ fixed-output store path (text only - eval performs no store -- writes).  Each entry not already valid is copied in, made read-only, and -- registered with its real NAR hash.  A copied source carries no references. materializeEvalSources :: Store -> Map.Map T.Text T.Text -> IO ()@@ -502,7 +502,7 @@  -- | Optionally deep-force a value before printing. -- With @--strict@, all thunks are recursively materialized.--- Without it, thunks display as @«thunk»@ — safe for large results.+-- Without it, thunks display as @"thunk"@ - safe for large results. finalize :: (MonadEval m) => Bool -> NixValue -> m NixValue finalize True = deepForceValue finalize False = pure@@ -540,18 +540,18 @@   let entries = attrSetToAscList attrs       rendered = map (\(k, t) -> k <> " = " <> prettyThunk t <> ";") entries    in "{ " <> T.intercalate " " rendered <> " }"-prettyValue (VLambda {}) = "«lambda»"-prettyValue (VBuiltin name _) = "«builtin " <> name <> "»"-prettyValue (VCompiledRegex _) = "«compiled-regex»"+prettyValue (VLambda {}) = "<lambda>"+prettyValue (VBuiltin name _) = "<builtin " <> name <> ">"+prettyValue (VCompiledRegex _) = "<compiled-regex>" prettyValue (VDerivation drv) =   case drvOutputs drv of-    (out : _) -> "«derivation " <> T.pack (storePathToFilePath platformStoreDir (doPath out)) <> "»"-    [] -> "«derivation»"+    (out : _) -> "<derivation " <> T.pack (storePathToFilePath platformStoreDir (doPath out)) <> ">"+    [] -> "<derivation>"  -- | Pretty-print a thunk.  After deep-forcing, all thunks should be -- computed thunks render their value; pending thunks render as a placeholder. prettyThunk :: Thunk -> T.Text-prettyThunk thunk = maybe "«thunk»" prettyValue (readThunkValue thunk)+prettyThunk thunk = maybe "<thunk>" prettyValue (readThunkValue thunk)  -- | Escape a string for Nix-style output (quotes, backslashes, newlines, tabs, carriage returns). escapeNixString :: T.Text -> T.Text
cbits/nn_arena.c view
@@ -1,10 +1,10 @@ /*- * nn_arena.c — Batch StablePtr collection from the thunk arena.+ * nn_arena.c - Batch StablePtr collection from the thunk arena.  *  * The Haskell teardown (Nix.Eval.Arena) needs every Haskell StablePtr the C  * arena holds so it can free them.  These two entry points delegate to the  * single-pass block-list walk in nn_thunk.c (nn_thunk_{count,collect}_- * stableptrs), which is O(total) — index-based iteration via nn_thunk_get+ * stableptrs), which is O(total) - index-based iteration via nn_thunk_get  * would be O(total * blocks), since each get re-walks the block list.  *  * StablePtr sources (see nn_thunk.c):
cbits/nn_arena.h view
@@ -1,9 +1,9 @@ /*- * nn_arena.h — Unified arena lifecycle and StablePtr cleanup.+ * nn_arena.h - Unified arena lifecycle and StablePtr cleanup.  *  * Provides batch collection of StablePtr payloads from the thunk arena  * for efficient cleanup.  After M6 bytecode integration, PENDING thunks- * hold (bc_idx, StablePtr Env) — the Expr is eliminated, replaced by+ * hold (bc_idx, StablePtr Env) - the Expr is eliminated, replaced by  * a bytecode index.  COMPUTED/NN_VALUE_PTR thunks hold StablePtr NixValue.  *  * Inline scalar/C-pointer payloads (INT, FLOAT, BOOL, NULL, STR,
cbits/nn_attrset.c view
@@ -1,5 +1,5 @@ /*- * nn_attrset.c — Sorted symbol-keyed attribute set.+ * nn_attrset.c - Sorted symbol-keyed attribute set.  *  * Two contiguous arrays: keys (nn_symbol_t) and values (void*),  * sorted by symbol ID after freeze.  Binary search for lookup.@@ -11,7 +11,7 @@  * This replaces Haskell's Map.Bin tree:  *   Map.Bin: ~48 bytes/node (constructor + key + value + size + left + right)  *   nn_attrset: ~12 bytes/entry (4-byte key + 8-byte pointer)- *   For 30k entries: 1.4 MB → 360 KB (4x reduction, plus cache-friendly)+ *   For 30k entries: 1.4 MB down to 360 KB (4x reduction, plus cache-friendly)  */  #include "nn_attrset.h"@@ -324,7 +324,7 @@             nn_attrset_insert(result, b->keys[ib], b->values[ib]);             ib++;         } else {-            /* Same key — b wins (right-biased //). */+            /* Same key - b wins (right-biased //). */             nn_attrset_insert(result, b->keys[ib], b->values[ib]);             ia++;             ib++;@@ -354,7 +354,7 @@     uint32_t i;      for (i = 0; i < set->count; i++) {-        /* Linear scan of removal keys — fine for small removal lists.+        /* Linear scan of removal keys - fine for small removal lists.          * For large removal lists, sort them and merge-scan instead. */         uint32_t j;         int skip = 0;
cbits/nn_attrset.h view
@@ -1,26 +1,26 @@ /*- * nn_attrset.h — Sorted symbol-keyed attribute set for nova-nix.+ * nn_attrset.h - Sorted symbol-keyed attribute set for nova-nix.  *  * Replaces Haskell's Data.Map.Strict (48 bytes per Bin node, pointer-  * chasing balanced tree) with a contiguous sorted array of (symbol, slot)  * pairs.  Binary search for O(log n) lookup; linear scan for iteration.  *  * Construction is two-phase:- *   1. nn_attrset_new(cap) + nn_attrset_insert() — append unsorted- *   2. nn_attrset_freeze() — sort by symbol ID, enable binary search+ *   1. nn_attrset_new(cap) + nn_attrset_insert() - append unsorted+ *   2. nn_attrset_freeze() - sort by symbol ID, enable binary search  *  * After freeze, the set is immutable (values can be updated in-place  * but keys cannot change).  This matches Nix semantics: attribute sets  * are constructed once and never gain/lose keys.  *- * Values are opaque void* — Haskell passes StablePtr Thunk.  The C+ * Values are opaque void* - Haskell passes StablePtr Thunk.  The C  * side never dereferences them.  *  * Lifecycle: arena-managed.  Every set is tracked at creation and bulk-freed  * by nn_attrset_free_all at evaluation teardown; nn_attrset_free is that  * teardown's per-set primitive and must not be called on a tracked set (it  * would then be double-freed by nn_attrset_free_all).- * Values (StablePtrs) are NOT freed by nn_attrset_free — Haskell owns them.+ * Values (StablePtrs) are NOT freed by nn_attrset_free - Haskell owns them.  */  #ifndef NN_ATTRSET_H@@ -48,7 +48,7 @@ /* --- Construction (before freeze) --- */  /* Append a key-value pair.  Keys need not be sorted or unique at this- * stage — duplicates are resolved at freeze time (last insert wins,+ * stage - duplicates are resolved at freeze time (last insert wins,  * matching Nix // merge semantics).  * Grows the internal arrays if capacity is exceeded. */ void nn_attrset_insert(nn_attrset_t *set, nn_symbol_t key, void *value);@@ -99,12 +99,12 @@ /* Create a new set that is the right-biased union of `a` and `b`  * (keys in `b` override `a`).  Both inputs must be frozen.  * The result is returned frozen and is arena-tracked (freed by- * nn_attrset_free_all) — do NOT pass it to nn_attrset_free. */+ * nn_attrset_free_all) - do NOT pass it to nn_attrset_free. */ nn_attrset_t *nn_attrset_union(const nn_attrset_t *a, const nn_attrset_t *b);  /* Create a new set with the given keys removed.  Input must be frozen.  * The result is returned frozen and is arena-tracked (freed by- * nn_attrset_free_all) — do NOT pass it to nn_attrset_free. */+ * nn_attrset_free_all) - do NOT pass it to nn_attrset_free. */ nn_attrset_t *nn_attrset_remove_keys(     const nn_attrset_t *set,     const nn_symbol_t *keys,
cbits/nn_ctxstr.c view
@@ -1,5 +1,5 @@ /*- * nn_ctxstr.c — Context-bearing string allocation and tracking.+ * nn_ctxstr.c - Context-bearing string allocation and tracking.  *  * Each nn_ctxstr_t is a single contiguous malloc (header + flexible  * array of nn_sce_t elements).  Pointers are tracked in a global
cbits/nn_ctxstr.h view
@@ -1,5 +1,5 @@ /*- * nn_ctxstr.h — Context-bearing strings for nova-nix.+ * nn_ctxstr.h - Context-bearing strings for nova-nix.  *  * Nix strings carry a StringContext: a set of store path references  * that track derivation dependencies.  Context-free strings use@@ -7,9 +7,9 @@  * tag 8 (nn_ctxstr_t pointer).  *  * Each context element is one of:- *   SCPlain      — plain store path reference (inputSrcs)- *   SCDrvOutput  — derivation output (.drv path + output name)- *   SCAllOutputs — all outputs of a derivation (drvPath itself)+ *   SCPlain      - plain store path reference (inputSrcs)+ *   SCDrvOutput  - derivation output (.drv path + output name)+ *   SCAllOutputs - all outputs of a derivation (drvPath itself)  *  * StorePaths are represented as two interned symbols (hash + name).  *@@ -58,7 +58,7 @@ /* --- Lifecycle --- */  /* Allocate a new nn_ctxstr_t with space for ctx_count elements.- * Elements are uninitialized — caller must fill via nn_ctxstr_set_*.+ * Elements are uninitialized - caller must fill via nn_ctxstr_set_*.  * Tracked globally for bulk cleanup. */ nn_ctxstr_t *nn_ctxstr_new(uint32_t text, uint16_t ctx_count); 
cbits/nn_env.c view
@@ -1,5 +1,5 @@ /*- * nn_env.c — C-native evaluation environments.+ * nn_env.c - C-native evaluation environments.  *  * Arena-allocated nn_env_t structs and backing arrays.  Pages are  * 256 KB each, allocated on demand as a linked list.  Each allocation@@ -36,7 +36,7 @@ static struct nn_env_page *g_current_page = NULL; static uint64_t g_total_bytes = 0; -/* Global empty env — initialized in nn_env_init, valid until destroy. */+/* Global empty env - initialized in nn_env_init, valid until destroy. */ static nn_env_t g_empty_env;  /* --- Internal helpers --- */
cbits/nn_env.h view
@@ -1,16 +1,16 @@ /*- * nn_env.h — C-native evaluation environments.+ * nn_env.h - C-native evaluation environments.  *  * Nix evaluation creates millions of environments, each with a  * variable-size array of slot pointers, optional lazy scope (attr set),  * optional parent pointer, and a with-scopes chain.  *  * All nn_env_t structs and their backing arrays are arena-allocated- * from a page-based bump allocator.  Nothing is individually freed —+ * from a page-based bump allocator.  Nothing is individually freed -  * nn_env_destroy() tears down everything at eval end.  *  * Lifecycle: nn_env_init() before evaluation, nn_env_destroy() after.- * Not thread-safe — single-threaded evaluation only.+ * Not thread-safe - single-threaded evaluation only.  */  #ifndef NN_ENV_H
cbits/nn_lambda.c view
@@ -1,5 +1,5 @@ /*- * nn_lambda.c — Lambda closure structs for nova-nix.+ * nn_lambda.c - Lambda closure structs for nova-nix.  *  * Each nn_lambda_t is a malloc'd struct holding the closure env,  * body bytecode index, and formals specification.  Tracked in a
cbits/nn_lambda.h view
@@ -1,5 +1,5 @@ /*- * nn_lambda.h — Lambda closure structs for nova-nix.+ * nn_lambda.h - Lambda closure structs for nova-nix.  *  * Stores lambda closures entirely in C: captured environment (nn_env_t*),  * body bytecode index, and formal parameter specification.  Replaces@@ -8,7 +8,7 @@  *  * Formals are stored as a type tag (Name/Set/NamedSet) plus an array  * of (symbol, has_default, default_bc_idx) entries for set patterns.- * Interned symbols avoid any string allocation — pointer equality+ * Interned symbols avoid any string allocation - pointer equality  * for name comparison.  *  * Lambda structs are malloc'd and tracked for bulk cleanup via
cbits/nn_list.c view
@@ -1,5 +1,5 @@ /*- * nn_list.c — Contiguous array of thunk pointers for lists.+ * nn_list.c - Contiguous array of thunk pointers for lists.  *  * Each nn_list_t is a small header (malloc'd, tracked) pointing to  * a contiguous items array (page-allocated via nn_env_alloc_slots).
cbits/nn_list.h view
@@ -1,5 +1,5 @@ /*- * nn_list.h — Contiguous array of thunk pointers for nova-nix lists.+ * nn_list.h - Contiguous array of thunk pointers for nova-nix lists.  *  * Replaces Haskell's [Thunk] (linked-list cons cells, ~48 bytes per  * element on the GHC heap) with a flat C array of nn_thunk_t* pointers@@ -13,7 +13,7 @@  * fill via nn_list_set, then read with nn_list_get/nn_list_count.  *  * Lifecycle: constructed during evaluation, freed via nn_list_free_all()- * at arena teardown.  Not thread-safe — single-threaded evaluation only.+ * at arena teardown.  Not thread-safe - single-threaded evaluation only.  */  #ifndef NN_LIST_H@@ -21,7 +21,7 @@  #include <stdint.h> -/* Forward declaration — nn_thunk_t is defined in nn_thunk.h */+/* Forward declaration - nn_thunk_t is defined in nn_thunk.h */ struct nn_thunk;  /* --- Types --- */
cbits/nn_symbol.c view
@@ -1,5 +1,5 @@ /*- * nn_symbol.c — Interned string symbol table.+ * nn_symbol.c - Interned string symbol table.  *  * Implementation: open-addressed hash table with linear probing.  * String data is stored in a contiguous arena (bulk allocation,@@ -7,7 +7,7 @@  * values; 0 is the invalid sentinel.  *  * The table doubles when load exceeds 75%.  Deletion is not- * supported — symbols live for the entire evaluation lifetime.+ * supported - symbols live for the entire evaluation lifetime.  */  #include "nn_symbol.h"@@ -32,7 +32,7 @@     uint32_t hash;      /* cached FNV-1a hash */ } nn_symbol_entry_t; -/* Hash table slot: maps hash → symbol ID.  Empty slots have id == 0. */+/* Hash table slot: maps hash -> symbol ID.  Empty slots have id == 0. */ typedef struct {     uint32_t hash;     uint32_t id;        /* 1-based symbol ID, 0 = empty */@@ -202,7 +202,7 @@     /* Probe for existing entry. */     for (;;) {         uint32_t id = g_sym.slots[idx].id;-        if (id == 0) break;  /* empty slot — not found */+        if (id == 0) break;  /* empty slot - not found */          if (g_sym.slots[idx].hash == hash) {             nn_symbol_entry_t *e = &g_sym.entries[id];@@ -214,7 +214,7 @@         idx = (idx + 1) & g_sym.slots_mask;     } -    /* Not found — insert new symbol. */+    /* Not found - insert new symbol. */     entries_ensure();     g_sym.count++;     uint32_t new_id = g_sym.count;  /* 1-based */
cbits/nn_symbol.h view
@@ -1,5 +1,5 @@ /*- * nn_symbol.h — Interned string symbols for nova-nix.+ * nn_symbol.h - Interned string symbols for nova-nix.  *  * Attribute names are the most repeated data in Nix evaluation.  * nixpkgs uses ~8k unique names across 30k+ packages, but each name@@ -11,7 +11,7 @@  * Lookup uses open-addressing with FNV-1a hashing.  *  * Lifecycle: nn_symbol_init() before evaluation, nn_symbol_destroy()- * after.  Not thread-safe — single-threaded evaluation only.+ * after.  Not thread-safe - single-threaded evaluation only.  */  #ifndef NN_SYMBOL_H@@ -44,7 +44,7 @@  /* Intern a string, returning its symbol.  If the string was already  * interned, returns the existing symbol (O(1) amortized).- * The input string is copied — the caller may free it after this call.+ * The input string is copied - the caller may free it after this call.  * str must not be NULL.  len is the byte length (not null-terminated). */ nn_symbol_t nn_symbol_intern(const char *str, size_t len); @@ -59,7 +59,7 @@ /* --- Comparison --- */  /* Symbols are equal iff their uint32_t values are equal.- * No function call needed — just use == directly.+ * No function call needed - just use == directly.  * This macro exists for documentation purposes. */ #define nn_symbol_eq(a, b) ((a) == (b)) 
cbits/nn_thunk.c view
@@ -1,5 +1,5 @@ /*- * nn_thunk.c — Arena-allocated thunk memoization cells.+ * nn_thunk.c - Arena-allocated thunk memoization cells.  *  * Chunked bump allocator: linked list of blocks, each containing a  * contiguous array of nn_thunk_t slots.  When the current block is@@ -59,7 +59,7 @@ static nn_thunk_t * arena_alloc(struct nn_thunk_arena *arena) {-    /* Current block full — allocate a new one */+    /* Current block full - allocate a new one */     if (arena->current->count >= arena->current->capacity) {         struct nn_thunk_block *block = alloc_block(arena->block_capacity);         if (!block) return NULL;
cbits/nn_thunk.h view
@@ -1,5 +1,5 @@ /*- * nn_thunk.h — Arena-allocated thunk memoization cells for nova-nix.+ * nn_thunk.h - Arena-allocated thunk memoization cells for nova-nix.  *  * Thunks are the core memoization mechanism in Nix evaluation.  Each  * thunk starts as PENDING (holding a bytecode index + C environment@@ -10,7 +10,7 @@  * traced) with a 16-byte C struct in an arena (invisible to GHC GC).  * For 8M thunks: ~450 MB less GHC heap pressure.  *- * Arena: chunked bump allocator.  Thunks are never individually freed —+ * Arena: chunked bump allocator.  Thunks are never individually freed -  * the entire arena is destroyed at evaluation end.  Pointers into the  * arena remain valid until nn_thunk_destroy().  *@@ -22,7 +22,7 @@  * via val_tag 4-9), or StablePtrs (for VBuiltin/VDrv via tag 255).  *  * Lifecycle: nn_thunk_init() before evaluation, nn_thunk_destroy()- * after.  Not thread-safe — single-threaded evaluation only.+ * after.  Not thread-safe - single-threaded evaluation only.  */  #ifndef NN_THUNK_H@@ -85,7 +85,7 @@ void nn_thunk_init(uint32_t initial_capacity);  /* Destroy the global thunk arena, freeing all block memory.- * Does NOT free payload StablePtrs — caller must iterate and free+ * Does NOT free payload StablePtrs - caller must iterate and free  * them first (via nn_thunk_count/nn_thunk_get).  * All nn_thunk_t pointers become invalid after this call. */ void nn_thunk_destroy(void);@@ -198,7 +198,7 @@ nn_thunk_t *nn_thunk_get(uint32_t index);  /* Single-pass (O(total)) collection of Haskell StablePtr payloads across the- * whole arena.  Walks the block list directly — nn_thunk_get is O(blocks) per+ * whole arena.  Walks the block list directly - nn_thunk_get is O(blocks) per  * call, so index-based iteration would be O(total * blocks).  StablePtr  * sources: PENDING/BLACKHOLE payload, and COMPUTED + NN_VALUE_PTR payload.  * nn_thunk_collect_stableptrs writes up to max_count pointers into `output`
data/nix/fetchurl.nix view
@@ -4,7 +4,7 @@ # <nix/fetchurl.nix>; nixpkgs' bootstrap imports it directly, so nova-nix # must provide one with the exact same interface and derivation output. # Every attribute below feeds the .drv ATerm, so the produced derivation-# (and therefore its store path) byte-matches upstream Nix — this is+# (and therefore its store path) byte-matches upstream Nix - this is # enforced by the parity CI job.  The argument names, default chains, and # derivation attributes are all fixed by that contract; only fetch one # thing at a time and let the fixed-output hash do the verifying.
nova-nix.cabal view
@@ -1,23 +1,25 @@ cabal-version:      3.0 name:               nova-nix-version:            0.5.0.0+version:            0.6.0.0 synopsis:           Windows-native Nix implementation in Haskell and C99 description:   A from-scratch implementation of the Nix package manager that runs-  natively on Windows, macOS, and Linux — no WSL or Cygwin.  A+  natively on Windows, macOS, and Linux - no WSL or Cygwin.  A   Haskell evaluator handles parsing and lazy evaluation, backed by a C99   data layer that keeps evaluation data off the GHC heap.  It evaluates   real Nix expressions (including nixpkgs), computes derivations and   content-addressed store paths that byte-match upstream Nix, and builds-  packages natively on Windows from a store-pinned MinGW-w64 toolchain.+  real packages from source natively on Windows through a stage-1 stdenv+  over a store-pinned MinGW-w64 toolchain.   Built outputs are content-addressed, substituted from a binary cache   before building, and pushed to one with @nova-nix push@.    Built on @nova-cache@ for NAR serialization, narinfo handling, and   Ed25519-signed binary substitution. -license:            BSD-3-Clause+license:            Apache-2.0 license-file:       LICENSE+copyright:          2026 Novavero AI Inc. author:             Devon Tomlin maintainer:         devon.tomlin@novavero.ai homepage:           https://github.com/Novavero-AI/nova-nix
src/Nix/Builder.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE ScopedTypeVariables #-} --- | Derivation builder — executes build recipes.+-- | Derivation builder - executes build recipes. -- -- == The build process --@@ -22,7 +22,7 @@ -- -- The key difference is process creation.  Linux uses @fork\/exec@ with -- namespace isolation.  We use 'System.Process.createProcess' which maps--- to @CreateProcess@ on Windows — native, no POSIX layer.+-- to @CreateProcess@ on Windows - native, no POSIX layer. -- -- For now, builds run without sandboxing (same as Nix on macOS did for -- years).  Future work: Windows Job Objects for resource limits,@@ -95,7 +95,7 @@ envPath = "PATH"  -- | The magic builder string for the built-in URL fetcher.  A derivation with--- this builder is not executed as a process — the Builder downloads its @url@+-- this builder is not executed as a process - the Builder downloads its @url@ -- and verifies it against @outputHash@ (see 'runBuiltinFetchurl'). builtinFetchurlBuilder :: Text builtinFetchurlBuilder = "builtin:fetchurl"@@ -109,6 +109,12 @@ httpStatusOk :: Int httpStatusOk = 200 +-- | User-Agent sent by @builtin:fetchurl@.  Some upstream servers (e.g.+-- ftp.gnu.org) reject requests with no User-Agent as bot traffic with a 403,+-- so the fetcher identifies itself like any other download client.+fetchUserAgent :: BS.ByteString+fetchUserAgent = "nova-nix (+https://github.com/Novavero-AI/nova-nix)"+ -- | Environment variable for the reproducible-builds.org build timestamp. envSourceDateEpoch :: Text envSourceDateEpoch = "SOURCE_DATE_EPOCH"@@ -117,7 +123,7 @@ -- Determinism-aware tools (binutils @ld@ writes it into the PE header -- instead of the wall clock; gcc uses it for @__DATE__@\/@__TIME__@) -- produce identical output across builds.  1980 rather than 0 because--- zip timestamps cannot represent dates before 1980 — the same value+-- zip timestamps cannot represent dates before 1980 - the same value -- nixpkgs' stdenv uses.  A derivation env may override it. sourceDateEpochValue :: Text sourceDateEpochValue = "315532800"@@ -198,7 +204,7 @@       let buildDir = computeBuildDir config drv       createDirectoryIfMissing True buildDir -      -- 3. Compute output paths (but do NOT pre-create them — the builder+      -- 3. Compute output paths (but do NOT pre-create them - the builder       --    is responsible for creating $out, $dev, etc.)       let outputDirs = [(doName out, buildDir </> T.unpack (doName out)) | out <- drvOutputs drv] @@ -223,7 +229,7 @@           pure (BuildFailure ("builder failed: " <> stderrText) exitCode)         Right () -> do           -- 7. Success: validate that the builder created all expected outputs.-          --    Outputs may be files or directories — both are valid (real Nix+          --    Outputs may be files or directories - both are valid (real Nix           --    allows $out to be a single file, a directory tree, or a symlink).           missing <- filterM (fmap not . doesPathExist . snd) outputDirs           case missing of@@ -312,7 +318,7 @@ -- ---------------------------------------------------------------------------  -- | Build the process environment from the derivation env + standard vars.--- The builder path is used to derive PATH entries — the builder's own+-- The builder path is used to derive PATH entries - the builder's own -- directory and its sibling @usr\/bin@ are included so that coreutils -- shipped alongside the builder (e.g. Git for Windows' MSYS2 tools) -- are available.  This mirrors real Nix where PATH contains only@@ -377,7 +383,7 @@ -- The build environment is overlaid on top of the inherited system -- environment.  Build variables take priority, but system-critical -- variables (e.g. SYSTEMROOT on Windows) pass through.  This matches--- unsandboxed build behavior — proper isolation comes with Phase 5.+-- unsandboxed build behavior - proper isolation comes with Phase 5. runBuilder ::   FilePath ->   [String] ->@@ -404,7 +410,7 @@  -- | Create the appropriate process spec for the builder. ----- On Windows, cmd.exe uses its own command line parser — @\/c@ takes a+-- On Windows, cmd.exe uses its own command line parser - @\/c@ takes a -- raw command string, not individually-quoted arguments.  GHC's 'Proc.proc' -- wraps each arg in double quotes for the @CommandLineToArgvW@ convention, -- but cmd.exe doesn't use that convention, so a derivation like:@@ -413,7 +419,7 @@ -- derivation { builder = "cmd.exe"; args = [ "\/c" "echo Hello" ]; ... } -- @ ----- would fail because GHC quotes @echo Hello@ → @\"echo Hello\"@, and+-- would fail because GHC quotes @echo Hello@ as @\"echo Hello\"@, and -- cmd.exe tries to find an executable literally named @\"echo Hello\"@. -- -- Fix: when the builder is cmd.exe with @\/c@, use 'Proc.shell' which@@ -472,7 +478,7 @@       _ -> Nothing  -- | Download a URL to a strict 'BS.ByteString' using nova-nix's own linked--- HTTP client (the same 'Network.HTTP.Client' the substituter uses) — no+-- HTTP client (the same 'Network.HTTP.Client' the substituter uses) - no -- external @curl@, which is what makes this a genuine builtin.  Any network -- exception is turned into a 'Left' so it becomes a clean build failure. downloadUrl :: Text -> IO (Either Text BS.ByteString)@@ -485,7 +491,8 @@     fetch :: IO (Either Text BS.ByteString)     fetch = do       manager <- HTTP.newManager HTTPS.tlsManagerSettings-      request <- HTTP.parseRequest (T.unpack url)+      request0 <- HTTP.parseRequest (T.unpack url)+      let request = request0 {HTTP.requestHeaders = ("User-Agent", fetchUserAgent) : HTTP.requestHeaders request0}       response <- HTTP.httpLbs request manager       pure (bodyOrError response)     bodyOrError response@@ -495,11 +502,11 @@         code = HTTP.statusCode (HTTP.responseStatus response)  -- | Verify the fetched bytes against the derivation's fixed-output hash, read--- from the canonical output spec.  @doHashAlgo@ is @sha256@/@sha512@/… (or+-- from the canonical output spec.  @doHashAlgo@ is @sha256@/@sha512@/... (or -- @r:sha256@ for recursive); @doHash@ is the base-16 digest eval normalized the -- user's hash into.  Flat mode is fully supported across algorithms; recursive--- (unpack/executable) fetches are a separate feature — they require unpacking--- the download — and fail with a clear message rather than a wrong result.+-- (unpack/executable) fetches are a separate feature - they require unpacking+-- the download - and fail with a clear message rather than a wrong result. verifyFetchHash :: Text -> DerivationOutput -> BS.ByteString -> Either (Int, Text) () verifyFetchHash url out body   | recursive =@@ -551,7 +558,7 @@       allCandidates = collectAllCandidates drv ++ inputOutputs       -- Deriver path is not available from the Derivation type alone;       -- the caller (buildWithDeps) would need to pass it through.-      -- Register with no deriver for now — queryDeriver will return Nothing.+      -- Register with no deriver for now - queryDeriver will return Nothing.       drvPathText = Nothing       -- (temp output dir, final store path) for every output, used to detect       -- self- and cross-output references that appear as build-temp paths.@@ -700,7 +707,7 @@ buildInOrder config store drvMap (sp : rest) =   case Map.lookup sp drvMap of     Nothing ->-      -- Not a derivation we know about — might be a source path.  Skip.+      -- Not a derivation we know about - might be a source path.  Skip.       buildInOrder config store drvMap rest     Just drv -> do       status <- resolveDep config store drv
src/Nix/Builder/Unpack.hs view
@@ -1,31 +1,31 @@ {-# LANGUAGE ScopedTypeVariables #-} --- | __builtin:unpack__ — in-process archive extraction for the bootstrap seed.+-- | __builtin:unpack__ - in-process archive extraction for the bootstrap seed. -- -- == Why this is a builtin -- -- The Windows stdenv bootstrap fetches pre-built toolchain tarballs (MSYS2 -- @.pkg.tar.zst@ packages) into the store before any toolchain exists there.--- Nothing in the store can unpack them — the unpacker is the thing being--- bootstrapped — and ambient @tar.exe@ is unpinned and may lack zstd support.+-- Nothing in the store can unpack them - the unpacker is the thing being+-- bootstrapped - and ambient @tar.exe@ is unpinned and may lack zstd support. -- So, like 'Nix.Builder.runBuiltinFetchurl', extraction runs in-process: -- a derivation whose @builder@ is @builtin:unpack@ is handled by this module -- instead of being spawned as a subprocess. -- -- == Derivation interface ----- * @srcs@ — whitespace-separated archive store paths, extracted in order+-- * @srcs@ - whitespace-separated archive store paths, extracted in order --   into the single @out@ output.  MSYS2 packages share a top-level --   @mingw64\/@ prefix, so extracting a package set into one output yields a --   working toolchain root directly (no separate union step).--- * @out@ — the merged tree.+-- * @out@ - the merged tree. -- -- == Determinism -- -- Extraction is a pure function of the archive bytes: entries are written in -- archive order, a file appearing in two archives is an error (pacman -- enforces the same no-conflict invariant), and pacman's per-package--- metadata entries (@.PKGINFO@, @.MTREE@, …) are skipped — they are not part+-- metadata entries (@.PKGINFO@, @.MTREE@, ...) are skipped - they are not part -- of the installed tree and would otherwise collide across packages. -- -- Symlink and hardlink entries are materialized by __copying__ their target:@@ -76,7 +76,7 @@ -- ---------------------------------------------------------------------------  -- | The magic builder string for the built-in archive extractor.  A--- derivation with this builder is not executed as a process — the Builder+-- derivation with this builder is not executed as a process - the Builder -- extracts its @srcs@ archives into @$out@ (see 'runBuiltinUnpack'). builtinUnpackBuilder :: Text builtinUnpackBuilder = "builtin:unpack"@@ -178,7 +178,7 @@  -- | Choose a decompressor from the archive file name.  @.tar.zst@ (MSYS2 -- packages) and plain @.tar@ are supported.  MSYS2's zstd frames do not--- pledge a content size, so the lazy (streaming) zstd decoder is required —+-- pledge a content size, so the lazy (streaming) zstd decoder is required - -- the strict single-shot API rejects them. decoderFor :: FilePath -> Maybe (BL.ByteString -> BL.ByteString) decoderFor path@@ -321,7 +321,7 @@ -- | pacman package metadata at the archive root (@.PKGINFO@, @.BUILDINFO@, -- @.MTREE@, @.INSTALL@): describes the package to pacman, is not part of the -- installed tree, and collides across packages when several archives merge--- into one output — so root-level dotfile entries are skipped.+-- into one output - so root-level dotfile entries are skipped. isPackageMetadata :: [FilePath] -> Bool isPackageMetadata comps = case comps of   [name] -> "." `isPrefixOf` name
src/Nix/Builtins.hs view
@@ -55,7 +55,7 @@    in newCEnv nullPtr 0 (Just scope) Nothing nullPtr 0  -- | Builtins exposed at the top level (without @builtins.@ prefix).--- This matches real Nix — nixpkgs uses these unqualified everywhere.+-- This matches real Nix - nixpkgs uses these unqualified everywhere. topLevelBuiltinNames :: [Text] topLevelBuiltinNames =   [ "abort",
src/Nix/Derivation.hs view
@@ -46,7 +46,7 @@ -- 7. __Environment__: environment variables for the build -- -- The HASH of this .drv file (after ATerm serialization) determines the--- output store path.  Change any input → different hash → different output+-- output store path.  Change any input yields a different hash and a different output -- path.  This is how Nix achieves reproducibility. module Nix.Derivation   ( -- * Derivation type@@ -145,7 +145,7 @@   }   deriving (Eq, Show) --- | A complete derivation — everything needed to build a package.+-- | A complete derivation - everything needed to build a package. data Derivation = Derivation   { -- | What this derivation produces.     drvOutputs :: ![DerivationOutput],@@ -178,7 +178,7 @@ --     when computing a derivation's own output paths (not yet known) via --     @hashDerivationModulo@. --   * @inputSubst@: when @Just subs@, render the input-derivations section---     from @subs@ — pairs of @(moduloHashHex, outputNames)@ — instead of+--     from @subs@ - pairs of @(moduloHashHex, outputNames)@ - instead of --     from 'drvInputDrvs'.  Each input derivation's store path is replaced --     by its own modulo hash, so two derivations differing only in an --     input's path spelling (not its content) hash identically.
src/Nix/Eval.hs view
@@ -5,7 +5,7 @@ -- -- Nix evaluation is LAZY.  Attribute set members and list elements -- are stored as thunks and only forced when their value is demanded.--- Function arguments are likewise thunked — @(x: 1) (throw "boom")@+-- Function arguments are likewise thunked - @(x: 1) (throw "boom")@ -- returns @1@ because @x@ is never referenced. -- -- The evaluator maintains an environment ('Env') that maps variable@@ -177,14 +177,14 @@  -- | Force a thunk to a value. ----- Delegates to 'forceThunk' which is a 'MonadEval' method — this+-- Delegates to 'forceThunk' which is a 'MonadEval' method - this -- allows IO evaluators to implement memoization (caching the result -- after the first force) while pure evaluators simply re-evaluate. force :: (MonadEval m) => Thunk -> m NixValue force = forceThunk evalBytecode  -- | Evaluate a bytecode instruction by index.--- This is the primary evaluator — reads opcodes from the C bytecode+-- This is the primary evaluator - reads opcodes from the C bytecode -- store and dispatches.  All recursive evaluation goes through this -- function, never through 'eval' directly. evalBytecode :: (MonadEval m) => Env -> Word32 -> m NixValue@@ -244,7 +244,7 @@               bodyIdx = unsafePerformIO (cbcArg2 bcIdx)               -- Lazy with: defer forcing the scope until a WITH_VAR lookup               -- actually needs it.  This is critical for nixpkgs where-              -- all-packages.nix uses `with pkgs;` inside a fixpoint —+              -- all-packages.nix uses `with pkgs;` inside a fixpoint -               -- eagerly forcing the scope would blackhole.               scopeThunk = cheapThunkBc env scopeIdx            in evalBytecode (pushLazyWithScope scopeThunk env) bodyIdx@@ -339,7 +339,7 @@   (VStr {}, VStr {}) -> evalBinary force OpAdd left right   (VStr {}, VPath _) -> evalBinary force OpAdd left right   -- Otherwise: string concatenation with STRICT coercion (Nix coerceMore=false)-  -- — only strings and sets with __toString/outPath coerce; numbers, bools,+  -- - only strings and sets with __toString/outPath coerce; numbers, bools,   -- null, lists, and functions are type errors, matching C++ Nix's `+`.   _ -> do     (leftStr, leftCtx) <- coerceAddOperand left@@ -404,7 +404,7 @@  -- | Evaluate an indented string literal from bytecode data buffer.  The common -- indentation is stripped from the LITERAL chunks before concatenation, so an--- interpolated multi-line value cannot drag the computed indent down — matching+-- interpolated multi-line value cannot drag the computed indent down - matching -- C++ Nix. evalBcIndStr :: (MonadEval m) => Env -> Word32 -> m NixValue evalBcIndStr env bcIdx0 = do@@ -415,7 +415,7 @@   pure (VStr text ctx)  -- | Evaluate string parts from the bytecode data buffer.  Each part is two--- words: (tag, value).  tag=0 -> literal (value = symbol), tag=1 ->+-- words: (tag, value).  tag=0 means literal (value = symbol), tag=1 means -- interpolation (value = bc_idx).  The 'Bool' marks literal (@True@) vs -- interpolated (@False@) so indented strings strip only the literals. evalBcStringParts :: (MonadEval m) => Env -> Int -> Word32 -> m [(Bool, Text, StringContext)]@@ -496,7 +496,7 @@               applyValue partiallyApplied argVal     _ -> throwEvalError ("attempt to call " <> typeName funcVal <> ", which is not a function") --- | Evaluate a lambda literal from bytecode — returns VLambda.+-- | Evaluate a lambda literal from bytecode - returns VLambda. evalBcLambda :: (MonadEval m) => Env -> Word32 -> m NixValue evalBcLambda env bcIdx0 =   let flags_ = unsafePerformIO (cbcFlags bcIdx0)@@ -614,7 +614,7 @@           attrMap = buildBcAttrMapFromSlots bindings thunkList        in filled `seq` pure (VAttrs (attrSetFromMap attrMap))   | otherwise = do-      -- Fallback: dynamic/nested keys — two-phase CAttrSet.+      -- Fallback: dynamic/nested keys - two-phase CAttrSet.       allKeys <- bcBindingAllKeys env bindings       let cset = buildCAttrSetKeys allKeys           attrSet = AttrSet cset@@ -651,7 +651,7 @@           filled = fillCSlots slotsPtr (buildBcSlotThunks letEnv env bindings)        in filled `seq` evalBytecode letEnv bodyIdx     else do-      -- Fallback: dynamic/nested keys — two-phase CAttrSet.+      -- Fallback: dynamic/nested keys - two-phase CAttrSet.       allKeys <- bcBindingAllKeys env bindings       let cset = buildCAttrSetKeys allKeys           parentEnv = buildCaptureEnv env captureInfo@@ -693,7 +693,7 @@     slotThunk (BcInherit syms) =       map (inheritLookup outerEnv . symbolText . Symbol) syms     slotThunk (BcInheritFrom fromBcIdx syms) =-      -- inherit (from) x y z; → one thunk per name that selects from the from-expr.+      -- inherit (from) x y z; becomes one thunk per name that selects from the from-expr.       -- Each thunk gets a minimal env with the from-value at slot 0.       let fromThunk = mkThunkBc recEnv fromBcIdx           mkInheritThunk sym =@@ -740,7 +740,7 @@     addBinding acc (BcInherit syms) =       pure (foldl' (\a sym -> let name = symbolText (Symbol sym) in insertWithMerge a name (inheritLookup thunkEnv name)) acc syms)     addBinding acc (BcInheritFrom fromBcIdx syms) =-      -- inherit (from) name -> select name from the from-expr.+      -- inherit (from) name selects name from the from-expr.       -- Create a small env with the from value at slot 0, then a       -- synthetic expression that selects name from slot 0.       let addInheritFrom a sym =@@ -794,7 +794,7 @@ -- ---------------------------------------------------------------------------  -- | Evaluate a search path expression.--- Desugars to @builtins.findFile builtins.nixPath "name"@ — exactly how+-- Desugars to @builtins.findFile builtins.nixPath "name"@ - exactly how -- real Nix handles @\<name\>@ expressions. evalSearchPath :: (MonadEval m) => Env -> Text -> m NixValue evalSearchPath env name = do@@ -1025,7 +1025,7 @@   )  -- | Central registry of all builtins.  Adding a new builtin is a single--- entry here plus its implementation function — no other files need changes.+-- entry here plus its implementation function - no other files need changes. builtinRegistry :: (MonadEval m) => Map Text (BuiltinDef m) builtinRegistry =   Map.fromList@@ -1146,9 +1146,9 @@       -- 'derivationStrict' primop (matches C++ Nix corepkgs/derivation.nix).       builtin1 "derivation" builtinDerivationLazy,       builtin1 "derivationStrict" builtinDerivationStrict,-      -- Error context (pass-through — context only matters on error)+      -- Error context (pass-through - context only matters on error)       builtin2 "addErrorContext" (\_ val -> pure val),-      -- Attr position (return null — nixpkgs handles this gracefully)+      -- Attr position (return null - nixpkgs handles this gracefully)       builtin2 "unsafeGetAttrPos" (\_ _ -> pure VNull),       -- Debugging (traceVerbose: same as trace for now, --trace-verbose not yet gated)       builtin2 "traceVerbose" builtinTrace,@@ -1198,7 +1198,7 @@ -- (the pattern string), compile it immediately and store the compiled -- RE.Regex in a VCompiledRegex, replacing the raw VStr.  The compiled -- form is carried in VBuiltin's accumulated args and reused on every--- subsequent application — zero recompilation.+-- subsequent application - zero recompilation. precompileArgs :: Text -> [NixValue] -> [NixValue] precompileArgs "match" [VStr pat _] =   let anchored = "^" <> pat <> "$"@@ -1226,7 +1226,7 @@ -- -- Direct case dispatch avoids rebuilding the polymorphic 'builtinRegistry' -- Map on every call.  'builtinRegistry' is polymorphic in @m@ so GHC--- cannot cache it as a CAF — it gets reconstructed on every use.+-- cannot cache it as a CAF - it gets reconstructed on every use. -- Pattern matching on the name is zero-allocation. executeBuiltin :: (MonadEval m) => Text -> [NixValue] -> m NixValue executeBuiltin name args = case name of@@ -1346,9 +1346,9 @@   -- Derivation construction: lazy 'derivation' over eager 'derivationStrict'   "derivation" -> apply1 builtinDerivationLazy   "derivationStrict" -> apply1 builtinDerivationStrict-  -- Error context (pass-through — context only matters on error)+  -- Error context (pass-through - context only matters on error)   "addErrorContext" -> apply2 (\_ val -> pure val)-  -- Attr position (return null — nixpkgs handles this gracefully)+  -- Attr position (return null - nixpkgs handles this gracefully)   "unsafeGetAttrPos" -> apply2 (\_ _ -> pure VNull)   -- Debugging (traceVerbose: same as trace for now)   "traceVerbose" -> apply2 builtinTrace@@ -1382,7 +1382,7 @@       _ -> throwEvalError ("builtins." <> name <> ": internal arity error")  -- ------------------------------------------------------------------------------ Builtin implementations — type checking+-- Builtin implementations - type checking -- ---------------------------------------------------------------------------  typeOfValue :: NixValue -> Text@@ -1434,7 +1434,7 @@ isFunctionVal _ = False  -- ------------------------------------------------------------------------------ Builtin implementations — list (arity 1)+-- Builtin implementations - list (arity 1) -- ---------------------------------------------------------------------------  builtinLength :: (MonadEval m) => NixValue -> m NixValue@@ -1456,7 +1456,7 @@ builtinTail other = throwEvalError ("builtins.tail: expected a list, got " <> typeName other)  -- ------------------------------------------------------------------------------ Builtin implementations — string (arity 1)+-- Builtin implementations - string (arity 1) -- ---------------------------------------------------------------------------  builtinStringLength :: (MonadEval m) => NixValue -> m NixValue@@ -1465,7 +1465,7 @@   throwEvalError ("builtins.stringLength: expected a string, got " <> typeName other)  -- ------------------------------------------------------------------------------ Builtin implementations — control+-- Builtin implementations - control -- ---------------------------------------------------------------------------  builtinThrow :: (MonadEval m) => NixValue -> m NixValue@@ -1477,7 +1477,7 @@ builtinAbort other = abortEvaluation ("builtins.abort: expected a string, got " <> typeName other)  -- ------------------------------------------------------------------------------ Builtin implementations — attr set (arity 1)+-- Builtin implementations - attr set (arity 1) -- ---------------------------------------------------------------------------  builtinAttrNames :: (MonadEval m) => NixValue -> m NixValue@@ -1525,7 +1525,7 @@     _ -> throwEvalError "builtins.listToAttrs: element must be a set"  -- ------------------------------------------------------------------------------ Builtin implementations — attr set (arity 2)+-- Builtin implementations - attr set (arity 2) -- ---------------------------------------------------------------------------  builtinHasAttr :: (MonadEval m) => NixValue -> NixValue -> m NixValue@@ -1601,7 +1601,7 @@         _ -> throwEvalError "builtins.catAttrs: list element must be a set"  -- ------------------------------------------------------------------------------ Builtin implementations — list higher-order (arity 2)+-- Builtin implementations - list higher-order (arity 2) -- ---------------------------------------------------------------------------  builtinMap :: (MonadEval m) => NixValue -> NixValue -> m NixValue@@ -1817,7 +1817,7 @@     _ -> throwEvalError "builtins.groupBy: function must return a string"  -- ------------------------------------------------------------------------------ Builtin implementations — string (arity 2)+-- Builtin implementations - string (arity 2) -- ---------------------------------------------------------------------------  builtinConcatStringsSep :: (MonadEval m) => NixValue -> NixValue -> m NixValue@@ -1839,7 +1839,7 @@   throwEvalError ("builtins.concatStringsSep: expected a string, got " <> typeName other)  -- ------------------------------------------------------------------------------ Builtin implementations — arity 3+-- Builtin implementations - arity 3 -- ---------------------------------------------------------------------------  builtinFoldl :: (MonadEval m) => NixValue -> NixValue -> NixValue -> m NixValue@@ -1880,7 +1880,7 @@ -- Builtin helpers -- --------------------------------------------------------------------------- --- | Build a thunk that defers @f arg@ — the application only happens when+-- | Build a thunk that defers @f arg@ - the application only happens when -- the thunk is forced.  Reuses the existing eval machinery via a synthetic -- @EApp (EResolvedVar 0 0) (EResolvedVar 0 1)@ in a self-contained env. -- Slot 0 = function, slot 1 = argument.@@ -1916,7 +1916,7 @@ -- | Coerce a value to a string for a DERIVATION field (an env value or an -- arg).  Like 'coerceToStringPermissive', but a path literal is copied into -- the store: it becomes its source store path, with that path added to the--- string context so it lands in the derivation's @inputSrcs@ — matching C+++-- string context so it lands in the derivation's @inputSrcs@ - matching C++ -- Nix's copy-to-store coercion of paths in derivation arguments/environment. coerceToStoreString :: (MonadEval m) => NixValue -> m (Text, StringContext) coerceToStoreString (VPath p) = do@@ -1932,7 +1932,7 @@  -- | Coerce a value for string interpolation (@"${...}"@).  Like -- 'coerceToString', but a path literal is copied into the store and replaced by--- its source store path (with context) — matching C++ Nix, where interpolation+-- its source store path (with context) - matching C++ Nix, where interpolation -- uses @copyToStore = true@, unlike 'builtins.toString', which does not copy. coerceToStringInterp :: (MonadEval m) => NixValue -> m (Text, StringContext) coerceToStringInterp (VPath p) = do@@ -1957,7 +1957,7 @@ storeDirPrefix = defaultStoreDirText <> "/"  -- ------------------------------------------------------------------------------ Builtin implementations — numeric + context+-- Builtin implementations - numeric + context -- ---------------------------------------------------------------------------  isPathVal :: NixValue -> Bool@@ -2148,7 +2148,7 @@ builtinLessThan a b = VBool <$> nixCompare force a b  -- ------------------------------------------------------------------------------ Builtin implementations — arithmetic + bitwise+-- Builtin implementations - arithmetic + bitwise -- ---------------------------------------------------------------------------  builtinAdd :: (MonadEval m) => NixValue -> NixValue -> m NixValue@@ -2213,13 +2213,13 @@ builtinBitXor _ _ = throwEvalError "builtins.bitXor: expected two integers"  -- ------------------------------------------------------------------------------ Builtin implementations — attr set higher-order+-- Builtin implementations - attr set higher-order -- ---------------------------------------------------------------------------  builtinMapAttrs :: (MonadEval m) => NixValue -> NixValue -> m NixValue builtinMapAttrs func (VAttrs attrs) =   -- Each attr value is a deferred @f key val@, forced only on demand.-  -- Eagerly builds all thunks via attrSetMapWithKey — with C arena thunks+  -- Eagerly builds all thunks via attrSetMapWithKey - with C arena thunks   -- (~16 bytes each), this is cheaper than the former MappedAttrs overhead   -- and keeps all data off the GHC heap.   -- Slot 0 = function, slot 1 = key, slot 2 = value.@@ -2258,11 +2258,11 @@       Map.fromList         [(efName f, evaluated (VBool (isJust (efDefault f)))) | f <- formals] --- | @builtins.setFunctionArgs f args@ — wraps @f@ in a callable attrset+-- | @builtins.setFunctionArgs f args@ - wraps @f@ in a callable attrset -- with @__functor@ (so it remains callable) and @__functionArgs@ metadata. -- Used by nixpkgs @lib.mirrorFunctionArgs@ / @lib.makeOverridable@. ----- @__functor@ is @self: f@ — a lambda that ignores @self@ and returns+-- @__functor@ is @self: f@ - a lambda that ignores @self@ and returns -- the original function.  The function is captured in the closure env. builtinSetFunctionArgs :: (MonadEval m) => NixValue -> NixValue -> m NixValue builtinSetFunctionArgs func (VAttrs argSpec) =@@ -2289,7 +2289,7 @@   attrSets <- mapM forceToAttrSet thunks   let merged = mergeAllAttrs attrSets       -- Lazy: each result is a deferred f(name)(values) thunk, not eagerly-      -- evaluated.  Critical for nixpkgs evalModules fixpoint — config is a+      -- evaluated.  Critical for nixpkgs evalModules fixpoint - config is a       -- self-referencing lazy attrset that must be COMPUTED (holding the lazy       -- result) before any individual attribute thunks are forced.       resultPairs = map (deferZip func) (Map.toList merged)@@ -2311,7 +2311,7 @@   throwEvalError ("builtins.zipAttrsWith: expected a list, got " <> typeName other)  -- ------------------------------------------------------------------------------ Builtin implementations — string manipulation+-- Builtin implementations - string manipulation -- ---------------------------------------------------------------------------  builtinReplaceStrings ::@@ -2370,7 +2370,7 @@       | otherwise = findMatch rest txt  -- ------------------------------------------------------------------------------ Builtin implementations — regex (POSIX ERE via regex-tdfa)+-- Builtin implementations - regex (POSIX ERE via regex-tdfa) -- ---------------------------------------------------------------------------  -- ---------------------------------------------------------------------------@@ -2379,7 +2379,7 @@  -- | Global regex compilation cache.  Keyed by the raw pattern string -- (including anchoring for match).  Idempotent memoization via--- unsafePerformIO — same rationale as thunk memoization.+-- unsafePerformIO - same rationale as thunk memoization. {-# NOINLINE regexCacheRef #-} regexCacheRef :: IORef (Map Text RE.Regex) regexCacheRef = unsafePerformIO (newIORef Map.empty)@@ -2435,7 +2435,7 @@           -- match is an Array of (String, (offset, len)) pairs.           -- Index 0 is the full match; indices 1.. are capture groups.           let groups = Array.elems match-              -- Skip index 0 (full match) — return only capture groups.+              -- Skip index 0 (full match) - return only capture groups.               captureGroups = drop 1 groups               -- A non-participating capture group has offset (-1); C++ Nix               -- yields null for it, not the empty string.@@ -2445,7 +2445,7 @@  -- | @builtins.split regex str@: split a string by a POSIX ERE. -- Returns an alternating list of non-matched strings and match-group lists.--- Example: @split "(x)" "axbxc"@ → @["a" ["x"] "b" ["x"] "c"]@+-- Example: @split "(x)" "axbxc"@ yields @["a" ["x"] "b" ["x"] "c"]@ builtinSplit :: (MonadEval m) => NixValue -> NixValue -> m NixValue -- Pre-compiled path: regex was compiled at partial-application time. builtinSplit (VCompiledRegex (CompiledRegex _ compiled)) (VStr str _) =@@ -2473,7 +2473,7 @@ -- | Build the alternating list for builtins.split. buildSplitResult :: String -> Int -> [Array.Array Int (String, (Int, Int))] -> [Thunk] buildSplitResult remaining pos [] =-  -- No more matches — emit the rest of the string.+  -- No more matches - emit the rest of the string.   [evaluated (mkStr (T.pack (drop pos remaining)))] buildSplitResult remaining pos (match : rest) =   let elems = Array.elems match@@ -2484,7 +2484,7 @@       before = T.pack (take (matchStart - pos) (drop pos remaining))       -- Capture groups (indices 1..)       groups = drop 1 (Array.elems match)-      -- A non-participating capture group has offset (-1) → null (as 'match').+      -- A non-participating capture group has offset (-1) becomes null (as 'match').       groupThunks =         map (\(s, (off, _)) -> if off < 0 then evaluated VNull else evaluated (mkStr (T.pack s))) groups       -- Continue after this match@@ -2507,7 +2507,7 @@ compareVersionParts :: [Text] -> [Text] -> Int64 compareVersionParts [] [] = 0 -- When one version runs out, Nix pads the shorter side with an empty--- component and keeps comparing — so "1.0" > "1.0pre" (empty sorts AFTER+-- component and keeps comparing - so "1.0" > "1.0pre" (empty sorts AFTER -- "pre"), not "<" as a naive length comparison would give. compareVersionParts [] (b : bs) =   case compareComponent "" b of@@ -2617,7 +2617,7 @@   Just _ -> findVersionDash t (idx + 1)  -- ------------------------------------------------------------------------------ Builtin implementations — serialization + hashing+-- Builtin implementations - serialization + hashing -- ---------------------------------------------------------------------------  builtinToJSON :: (MonadEval m) => NixValue -> m NixValue@@ -2686,7 +2686,7 @@           let (q, r) = quotRem v 16            in go q (hexDigit r : acc) --- | Safe hex digit lookup (total for 0–15).+-- | Safe hex digit lookup (total for 0-15). hexDigit :: Int -> Char hexDigit n   | n >= 0 && n <= 9 = chr (ord '0' + n)@@ -2830,7 +2830,7 @@ digestToHex = bytesToHexText . BA.convert  -- ------------------------------------------------------------------------------ Builtin implementations — deep evaluation+-- Builtin implementations - deep evaluation -- ---------------------------------------------------------------------------  builtinDeepSeq :: (MonadEval m) => NixValue -> NixValue -> m NixValue@@ -2843,11 +2843,11 @@ deepForce (VAttrs attrs) = mapM_ (force >=> deepForce) (attrSetElems attrs) deepForce _ = pure () --- | @builtins.seq a b@ — evaluate @a@ to WHNF, then return @b@.+-- | @builtins.seq a b@ - evaluate @a@ to WHNF, then return @b@. builtinSeq :: (MonadEval m) => NixValue -> NixValue -> m NixValue builtinSeq !_first = pure --- | @builtins.trace msg val@ — print @msg@ to stderr, return @val@.+-- | @builtins.trace msg val@ - print @msg@ to stderr, return @val@. builtinTrace :: (MonadEval m) => NixValue -> NixValue -> m NixValue builtinTrace msgVal result = do   msg <- case msgVal of@@ -2856,7 +2856,7 @@   traceMessage ("trace: " <> msg)   pure result --- | @builtins.warn msg val@ — print warning to stderr, return @val@.+-- | @builtins.warn msg val@ - print warning to stderr, return @val@. builtinWarn :: (MonadEval m) => NixValue -> NixValue -> m NixValue builtinWarn msgVal result = do   msg <- case msgVal of@@ -2876,7 +2876,7 @@ showValueForTrace other = "<<" <> typeName other <> ">>"  -- ------------------------------------------------------------------------------ Builtin implementations — graph traversal+-- Builtin implementations - graph traversal -- ---------------------------------------------------------------------------  builtinGenericClosure :: (MonadEval m) => NixValue -> m NixValue@@ -2900,7 +2900,7 @@  -- | BFS loop for genericClosure.  Uses Data.Sequence for O(1) queue -- append (the old list-based version was O(n) per operator call).--- seenKeys is still a linear scan — Nix value equality is monadic so+-- seenKeys is still a linear scan - Nix value equality is monadic so -- Set/HashMap is not directly applicable without specialising on key type. closureLoop ::   (MonadEval m) =>@@ -2971,7 +2971,7 @@   pure (VAttrs (attrSetFromMap (Map.fromList [(name, evaluated (mkStr fileType)) | (name, fileType) <- entries])))  -- ------------------------------------------------------------------------------ Builtin implementations — environment + paths+-- Builtin implementations - environment + paths -- ---------------------------------------------------------------------------  builtinGetEnv :: (MonadEval m) => NixValue -> m NixValue@@ -2989,7 +2989,7 @@   throwEvalError ("builtins.toPath: expected a string or path, got " <> typeName other)  -- ------------------------------------------------------------------------------ Builtin implementations — store path operations+-- Builtin implementations - store path operations -- ---------------------------------------------------------------------------  builtinPlaceholder :: (MonadEval m) => NixValue -> m NixValue@@ -3015,7 +3015,7 @@       throwEvalError ("builtins.storePath: not a valid store path: " <> p)  -- ------------------------------------------------------------------------------ Builtin implementations — Nix search path+-- Builtin implementations - Nix search path -- ---------------------------------------------------------------------------  builtinFindFile :: (MonadEval m) => NixValue -> NixValue -> m NixValue@@ -3075,7 +3075,7 @@   | otherwise = findFirst rest name  -- ------------------------------------------------------------------------------ Builtin implementations — store file creation+-- Builtin implementations - store file creation -- ---------------------------------------------------------------------------  builtinToFile :: (MonadEval m) => NixValue -> NixValue -> m NixValue@@ -3088,7 +3088,7 @@   throwEvalError ("builtins.toFile: expected a string, got " <> typeName other)  -- ------------------------------------------------------------------------------ Builtin implementations — scoped import+-- Builtin implementations - scoped import -- ---------------------------------------------------------------------------  builtinScopedImport :: (MonadEval m) => NixValue -> NixValue -> m NixValue@@ -3100,7 +3100,7 @@   throwEvalError ("builtins.scopedImport: expected a set, got " <> typeName other)  -- ------------------------------------------------------------------------------ Builtin implementations — network fetchers+-- Builtin implementations - network fetchers -- ---------------------------------------------------------------------------  builtinFetchurl :: (MonadEval m) => NixValue -> m NixValue@@ -3202,14 +3202,14 @@         Left _ -> pure Nothing  -- ------------------------------------------------------------------------------ Builtin implementations — derivation construction+-- Builtin implementations - derivation construction -- --------------------------------------------------------------------------- --- | Eager derivation computation — @builtins.derivationStrict@.  Forces all+-- | Eager derivation computation - @builtins.derivationStrict@.  Forces all -- input attrs into env vars, content-hashes, and returns the full derivation -- attrset (drvPath, outPath, per-output, _derivation).  Called LAZILY by the -- @derivation@ wrapper ('builtinDerivationLazy'), so forcing a derivation to--- WHNF never forces this — matching C++ Nix's derivationStrict/derivation split.+-- WHNF never forces this - matching C++ Nix's derivationStrict/derivation split. builtinDerivationStrict :: (MonadEval m) => NixValue -> m NixValue builtinDerivationStrict (VAttrs attrs) = do   -- Extract required attributes@@ -3244,7 +3244,7 @@    -- Collect string-coercible attrs into the build env, EXCLUDING "args"   -- (C++ Nix puts args in the Derive() args field, never the env).  The-  -- per-output env vars ($out, …) are added below.  Carries merged context.+  -- per-output env vars ($out, ...) are added below.  Carries merged context.   (drvEnvPairs, envContext) <- collectDrvEnvWithContext (Map.delete "__ignoreNulls" (Map.delete "args" materialized))    let fullContext = envContext <> argsContext@@ -3310,7 +3310,7 @@       inputSubst <- mapM resolveInputModulo (Map.toList inputDrvs)       let maskedEnv = foldr (`Map.insert` "") baseEnv outputNames           maskedDrv = mkDrv (map maskedOutput outputNames) maskedEnv-          -- Masked modulo hash → this derivation's own output paths.+          -- Masked modulo hash yields this derivation's own output paths.           moduloMasked = sha256Digest (TE.encodeUtf8 (toATermForHash True (Just inputSubst) maskedDrv))           outStorePaths = [(n, makeOutputPath n moduloMasked drvName) | n <- outputNames]           outPathTexts = [(n, storePathToText defaultStoreDir sp) | (n, sp) <- outStorePaths]@@ -3334,7 +3334,7 @@         ((_, p) : _) -> p         [] -> ""       -- The default output is the FIRST in @outputs@ (matching C++ Nix, which-      -- returns @(head outputsList).value@) — not necessarily @out@.+      -- returns @(head outputsList).value@) - not necessarily @out@.       mainOutName = case outPaths of         ((n, _) : _) -> n         [] -> "out"@@ -3375,7 +3375,7 @@   throwEvalError ("derivation: expected a set, got " <> typeName other)  -- | Detect a fixed-output derivation.  Returns @Just (algo, mode, rawDigest)@--- when @outputHash@ is present and non-empty (fetchurl, fetchgit, …), else+-- when @outputHash@ is present and non-empty (fetchurl, fetchgit, ...), else -- 'Nothing' for an ordinary input-addressed derivation.  @mode@ is -- @\"flat\"@ or @\"recursive\"@; @algo@ is e.g. @\"sha256\"@. detectFixedOutput :: (MonadEval m) => AttrSet -> m (Maybe (Text, Text, BS.ByteString))@@ -3418,10 +3418,10 @@   | otherwise =       throwEvalError ("derivation: cannot determine outputHash algorithm for " <> ohash) --- | Lazy @derivation@ wrapper — mirrors C++ Nix's @corepkgs/derivation.nix@.+-- | Lazy @derivation@ wrapper - mirrors C++ Nix's @corepkgs/derivation.nix@. -- Returns a WHNF attrset whose @drvPath@/@outPath@/output-path/@_derivation@ -- attrs are LAZY thunks that defer to 'builtinDerivationStrict'.  Forcing a--- derivation to WHNF therefore does NOT force its input/env closure — which is+-- derivation to WHNF therefore does NOT force its input/env closure - which is -- essential for nixpkgs, where merely referencing a derivation (e.g. -- @drv != null@, @assert (libxcrypt != null)@) must not build its whole closure. --@@ -3494,10 +3494,10 @@             Left _ -> pure Nothing  -- ------------------------------------------------------------------------------ Builtin implementations — hashFile, readFileType+-- Builtin implementations - hashFile, readFileType -- --------------------------------------------------------------------------- --- | @builtins.hashFile algo path@ — hash raw bytes of a file on disk.+-- | @builtins.hashFile algo path@ - hash raw bytes of a file on disk. -- Returns base-16 hex string, matching @builtins.hashString@ output format. builtinHashFile :: (MonadEval m) => NixValue -> NixValue -> m NixValue builtinHashFile (VStr algo _) (VPath path) = do@@ -3520,7 +3520,7 @@   "md5" -> pure (mkStr (digestToHex (CH.hash bytes :: CH.Digest CH.MD5)))   _ -> throwEvalError ("builtins." <> ctx <> ": unknown hash algorithm '" <> algo <> "'") --- | @builtins.readFileType path@ — classify a filesystem entry.+-- | @builtins.readFileType path@ - classify a filesystem entry. -- Returns @"regular"@, @"directory"@, @"symlink"@, or @"unknown"@. builtinReadFileType :: (MonadEval m) => NixValue -> m NixValue builtinReadFileType (VPath path) = mkStr <$> getFileType path@@ -3529,10 +3529,10 @@   throwEvalError ("builtins.readFileType: expected a path, got " <> typeName other)  -- ------------------------------------------------------------------------------ Builtin implementations — convertHash+-- Builtin implementations - convertHash -- --------------------------------------------------------------------------- --- | @builtins.convertHash { hash, hashAlgo?, toHashFormat }@ — convert+-- | @builtins.convertHash { hash, hashAlgo?, toHashFormat }@ - convert -- between hash representations.  Supports base16, nix32, base64, and sri. builtinConvertHash :: (MonadEval m) => NixValue -> m NixValue builtinConvertHash (VAttrs attrs) = do@@ -3561,7 +3561,7 @@   -- Prefixed format: algo:hex or algo:nix32   | Just (algo, rest) <- parseAlgoPrefix hashStr =       decodeWithAlgo algo rest-  -- Plain hash — need hashAlgo attribute+  -- Plain hash - need hashAlgo attribute   | otherwise = do       algo <- requireStrAttr "convertHash" "hashAlgo" attrs       decodeWithAlgo algo hashStr@@ -3601,7 +3601,7 @@   Nothing -> throwEvalError ("builtins." <> ctx <> ": missing required attribute '" <> key <> "'")  -- ------------------------------------------------------------------------------ Base64 encode/decode — delegates to nova-cache (base64-bytestring under the hood)+-- Base64 encode/decode - delegates to nova-cache (base64-bytestring under the hood) -- ---------------------------------------------------------------------------  -- | Encode bytes to base64.@@ -3613,7 +3613,7 @@ decodeBase64Pure t =   -- Strip whitespace and any existing padding, then re-pad to a multiple of   -- 4.  base64-bytestring's 'decode' requires correct padding, so SRI hashes-  -- (correctly-padded standard base64, e.g. @sha256-…NQ=@) would otherwise be+  -- (correctly-padded standard base64, e.g. @sha256-...NQ=@) would otherwise be   -- rejected once their trailing @=@ was removed.   let stripped = T.filter (\c -> c /= '\n' && c /= '\r' && c /= '=') t       padLen = (4 - (T.length stripped `mod` 4)) `mod` 4@@ -3629,10 +3629,10 @@   Left _ -> throwEvalError ("builtins." <> ctx <> ": invalid base64 encoding")  -- ------------------------------------------------------------------------------ Builtin implementations — fromTOML+-- Builtin implementations - fromTOML -- --------------------------------------------------------------------------- --- | @builtins.fromTOML str@ — parse a TOML document to a Nix value.+-- | @builtins.fromTOML str@ - parse a TOML document to a Nix value. -- Hand-rolled parser covering the TOML v1.0 subset used by nixpkgs: -- bare/quoted keys, dotted keys, basic/literal strings (multiline), -- integers (dec/hex/oct/bin), floats (inc. inf/nan), booleans,@@ -3863,7 +3863,7 @@   | T.isPrefixOf "0x" s || T.isPrefixOf "0X" s = parseHexInt (T.drop 2 s)   | T.isPrefixOf "0o" s || T.isPrefixOf "0O" s = parseOctInt (T.drop 2 s)   | T.isPrefixOf "0b" s || T.isPrefixOf "0B" s = parseBinInt (T.drop 2 s)-  -- Contains date separators → treat as datetime string+  -- Contains date separators, so treat as a datetime string   | T.any (== 'T') s && T.any (== '-') s = Right (TOMLStr s)   | T.count "-" s >= 2 && T.any isDigit s = Right (TOMLStr s)   | T.any (== ':') s && T.any isDigit s = Right (TOMLStr s)@@ -4054,10 +4054,10 @@         _ -> Map.insert k (TOMLTable updated) m  -- ------------------------------------------------------------------------------ Builtin implementations — toXML+-- Builtin implementations - toXML -- --------------------------------------------------------------------------- --- | @builtins.toXML val@ — convert a Nix value to its XML representation.+-- | @builtins.toXML val@ - convert a Nix value to its XML representation. -- Matches the format defined by the Nix manual: strings, ints, floats, -- bools, nulls, lists, and attrsets map to their XML counterparts. builtinToXML :: (MonadEval m) => NixValue -> m NixValue@@ -4116,7 +4116,7 @@     escapeChar c = T.singleton c  -- ------------------------------------------------------------------------------ Builtin implementations — builtins.path+-- Builtin implementations - builtins.path -- ---------------------------------------------------------------------------  -- | @builtins.path { path; name?; filter?; sha256?; recursive?; }@@@ -4148,10 +4148,10 @@         (_, name) -> name  -- ------------------------------------------------------------------------------ Builtin implementations — filterSource+-- Builtin implementations - filterSource -- --------------------------------------------------------------------------- --- | @builtins.filterSource filter path@ — copy a path to the store,+-- | @builtins.filterSource filter path@ - copy a path to the store, -- filtering entries via a predicate.  The filter function receives -- @(path, type)@ where type is @"regular"@, @"directory"@, @"symlink"@, -- or @"unknown"@.@@ -4164,7 +4164,7 @@   throwEvalError ("builtins.filterSource: expected a path, got " <> typeName other)  -- ------------------------------------------------------------------------------ Builtin stubs — experimental features+-- Builtin stubs - experimental features -- ---------------------------------------------------------------------------  builtinOutputOf :: (MonadEval m) => NixValue -> NixValue -> m NixValue
src/Nix/Eval/CAttrSet.hs view
@@ -1,6 +1,6 @@ -- | C-backed sorted attribute set via FFI. ----- Wraps @cbits/nn_attrset.c@ — a contiguous sorted array of+-- Wraps @cbits/nn_attrset.c@ - a contiguous sorted array of -- @(nn_symbol_t, void*)@ pairs.  Replaces Haskell's @Data.Map.Strict@ -- for attribute sets, cutting per-entry overhead from ~48 bytes -- (Map.Bin node) to ~12 bytes (4-byte symbol + 8-byte pointer).@@ -54,7 +54,7 @@ type CAttrSet = Ptr NnAttrSet  -- ------------------------------------------------------------------------------ FFI imports (all unsafe — no callbacks, fast data access)+-- FFI imports (all unsafe - no callbacks, fast data access) -- ---------------------------------------------------------------------------  foreign import ccall unsafe "nn_attrset_new"@@ -106,7 +106,7 @@ -- ---------------------------------------------------------------------------  -- | Insert a key-value pair.  Call before freeze.--- The value is a CThunkPtr cast to Ptr () — C never dereferences it.+-- The value is a CThunkPtr cast to Ptr () - C never dereferences it. cattrsetInsert :: CAttrSet -> Symbol -> CThunkPtr -> IO () cattrsetInsert set (Symbol sym) ptr =   c_nn_attrset_insert set sym (castPtr ptr)@@ -120,7 +120,7 @@ -- ---------------------------------------------------------------------------  -- | Look up a key.  Returns 'Nothing' if not found.--- The returned 'CThunkPtr' is borrowed — the thunk arena owns it.+-- The returned 'CThunkPtr' is borrowed - the thunk arena owns it. cattrsetLookup :: CAttrSet -> Symbol -> IO (Maybe CThunkPtr) cattrsetLookup set (Symbol sym) = do   ptr <- c_nn_attrset_lookup set sym
src/Nix/Eval/CBytecode.hs view
@@ -2,7 +2,7 @@  -- | C-backed bytecode storage via FFI. ----- Wraps @cbits/nn_bytecode.c@ — two growable arrays storing flat Nix+-- Wraps @cbits/nn_bytecode.c@ - two growable arrays storing flat Nix -- expression bytecode: an @nn_op_t[]@ instruction array (16 bytes each) -- and a @uint32_t[]@ data buffer for variable-length operands. --@@ -111,7 +111,7 @@ import Data.Word (Word16, Word32, Word8)  -- ------------------------------------------------------------------------------ FFI imports (all unsafe — no callbacks, fast data access)+-- FFI imports (all unsafe - no callbacks, fast data access) -- ---------------------------------------------------------------------------  foreign import ccall unsafe "nn_bytecode_init"@@ -182,21 +182,28 @@ -- Read instructions -- --------------------------------------------------------------------------- +-- | The opcode byte of instruction @i@ (one of the @Op*@ constants). cbcOpcode :: Word32 -> IO Word8 cbcOpcode = c_nn_bc_opcode +-- | The flags byte of instruction @i@ - the sub-type selector (e.g. which+-- unary/binary operator), interpreted via the sub-type-flag constants below. cbcFlags :: Word32 -> IO Word8 cbcFlags = c_nn_bc_flags +-- | The inline 16-bit argument of instruction @i@ (e.g. a de Bruijn slot). cbcShortArg :: Word32 -> IO Word16 cbcShortArg = c_nn_bc_short_arg +-- | The first 32-bit argument of instruction @i@. cbcArg1 :: Word32 -> IO Word32 cbcArg1 = c_nn_bc_arg1 +-- | The second 32-bit argument of instruction @i@. cbcArg2 :: Word32 -> IO Word32 cbcArg2 = c_nn_bc_arg2 +-- | The third 32-bit argument of instruction @i@. cbcArg3 :: Word32 -> IO Word32 cbcArg3 = c_nn_bc_arg3 @@ -204,6 +211,7 @@ -- Read data -- --------------------------------------------------------------------------- +-- | Read entry @i@ of the bytecode data pool (interned literals, symbols). cbcData :: Word32 -> IO Word32 cbcData = c_nn_bc_data @@ -211,9 +219,11 @@ -- Diagnostics -- --------------------------------------------------------------------------- +-- | Total number of compiled instructions. cbcOpCount :: IO Word32 cbcOpCount = c_nn_bc_op_count +-- | Total number of data-pool entries. cbcDataCount :: IO Word32 cbcDataCount = c_nn_bc_data_count @@ -221,47 +231,60 @@ -- Opcode constants -- --------------------------------------------------------------------------- +-- | Literal-constant opcodes: integer, float, boolean, and null. pattern OpLitInt, OpLitFloat, OpLitBool, OpLitNull :: Word8 pattern OpLitInt = 0 pattern OpLitFloat = 1 pattern OpLitBool = 2 pattern OpLitNull = 3 +-- | Literal URI and path opcodes. pattern OpLitUri, OpLitPath :: Word8 pattern OpLitUri = 4 pattern OpLitPath = 5 +-- | String opcodes: an ordinary @"..."@ string and an indented @''...''@ string. pattern OpStr, OpIndStr :: Word8 pattern OpStr = 6 pattern OpIndStr = 7 +-- | Variable-reference opcodes: a lexical variable, a @with@-scope lookup, and a+-- resolved (de Bruijn) variable. pattern OpVar, OpWithVar, OpResolvedVar :: Word8 pattern OpVar = 8 pattern OpWithVar = 9 pattern OpResolvedVar = 10 +-- | Collection-construction opcodes: attribute set and list. pattern OpAttrs, OpList :: Word8 pattern OpAttrs = 11 pattern OpList = 12 +-- | Attribute-select (@a.b@), has-attribute (@a ? b@), and function-application+-- opcodes. pattern OpSelect, OpHasAttr, OpApp :: Word8 pattern OpSelect = 13 pattern OpHasAttr = 14 pattern OpApp = 15 +-- | Binding-form opcodes: lambda and @let@. pattern OpLambda, OpLet :: Word8 pattern OpLambda = 16 pattern OpLet = 17 +-- | Control-form opcodes: @if@, @with@, and @assert@. pattern OpIf, OpWith, OpAssert :: Word8 pattern OpIf = 18 pattern OpWith = 19 pattern OpAssert = 20 +-- | Operator opcodes: unary and binary (the specific operator is in the flags+-- byte, decoded via the sub-type-flag constants below). pattern OpUnary, OpBinary :: Word8 pattern OpUnary = 21 pattern OpBinary = 22 +-- | Search-path opcode: an angle-bracket path lookup like @\<nixpkgs\>@. pattern OpSearchPath :: Word8 pattern OpSearchPath = 23 @@ -269,53 +292,66 @@ -- Sub-type flags -- --------------------------------------------------------------------------- +-- | @OpUnary@ flag values: which unary operator to apply. unaryNot, unaryNegate :: Word8 unaryNot = 0 unaryNegate = 1 +-- | @OpBinary@ flag values, arithmetic: @+@ @-@ @*@ @/@. binaryAdd, binarySub, binaryMul, binaryDiv :: Word8 binaryAdd = 0 binarySub = 1 binaryMul = 2 binaryDiv = 3 +-- | @OpBinary@ flag values, logical: @&&@ @||@ @->@. binaryAnd, binaryOr, binaryImpl :: Word8 binaryAnd = 4 binaryOr = 5 binaryImpl = 6 +-- | @OpBinary@ flag values, equality: @==@ @!=@. binaryEq, binaryNeq :: Word8 binaryEq = 7 binaryNeq = 8 +-- | @OpBinary@ flag values, ordering: @<@ @<=@ @>@ @>=@. binaryLt, binaryLte, binaryGt, binaryGte :: Word8 binaryLt = 9 binaryLte = 10 binaryGt = 11 binaryGte = 12 +-- | @OpBinary@ flag values: list concatenation (@++@) and attrset update (@//@). binaryConcat, binaryUpdate :: Word8 binaryConcat = 13 binaryUpdate = 14 +-- | Lambda formal kinds: a plain name, a @{ ... }@ pattern, or a named pattern+-- (@args@@{ ... }@). formalName, formalSet, formalNamedSet :: Word8 formalName = 0 formalSet = 1 formalNamedSet = 2 +-- | String-part kinds: a literal chunk or an interpolated @${...}@ expression. strpartLit, strpartInterp :: Word32 strpartLit = 0 strpartInterp = 1 +-- | Attribute-binding kinds: a @name = value@ binding or an @inherit@. bindNamed, bindInherit :: Word32 bindNamed = 0 bindInherit = 1 +-- | Closure capture kinds: capture nothing, capture specific slots, or capture+-- slots plus the enclosing @with@ scopes. captureNone, captureSlots, captureWithScopes :: Word32 captureNone = 0 captureSlots = 1 captureWithScopes = 2 +-- | Attribute-key kinds: a static (compile-time) key or a dynamic @${...}@ key. attrkeyStatic, attrkeyDynamic :: Word32 attrkeyStatic = 0 attrkeyDynamic = 1
src/Nix/Eval/CCtxStr.hs view
@@ -1,6 +1,6 @@ -- | C-backed context-bearing strings via FFI. ----- Wraps @cbits/nn_ctxstr.c@ — a string with its StringContext stored+-- Wraps @cbits/nn_ctxstr.c@ - a string with its StringContext stored -- as a contiguous C struct.  Context-free strings continue to use -- thunk tag 4 (bare nn_symbol_t); this module handles tag 8 for -- strings with non-empty context.@@ -42,7 +42,7 @@ type CCtxStrPtr = Ptr NnCtxStr  -- ------------------------------------------------------------------------------ FFI imports (all unsafe — no callbacks, fast data access)+-- FFI imports (all unsafe - no callbacks, fast data access) -- ---------------------------------------------------------------------------  foreign import ccall unsafe "nn_ctxstr_new"@@ -83,7 +83,7 @@ -- ---------------------------------------------------------------------------  -- | Allocate a new context string with space for @ctxCount@ elements.--- Elements are uninitialized — caller must fill via set functions.+-- Elements are uninitialized - caller must fill via set functions. cctxstrNew :: Word32 -> Word16 -> IO CCtxStrPtr cctxstrNew = c_nn_ctxstr_new @@ -95,12 +95,18 @@ -- Element setters -- --------------------------------------------------------------------------- +-- | Set context element @i@ to a plain store-path reference (@SCPlain@),+-- identified by its name and hash symbols. cctxstrSetPlain :: CCtxStrPtr -> Word16 -> Word32 -> Word32 -> IO () cctxstrSetPlain = c_nn_ctxstr_set_plain +-- | Set context element @i@ to a derivation-output reference (@SCDrvOutput@):+-- name and hash symbols plus the output-name symbol. cctxstrSetDrvOutput :: CCtxStrPtr -> Word16 -> Word32 -> Word32 -> Word32 -> IO () cctxstrSetDrvOutput = c_nn_ctxstr_set_drv_output +-- | Set context element @i@ to an all-outputs reference (@SCAllOutputs@),+-- identified by its name and hash symbols. cctxstrSetAllOutputs :: CCtxStrPtr -> Word16 -> Word32 -> Word32 -> IO () cctxstrSetAllOutputs = c_nn_ctxstr_set_all_outputs @@ -108,20 +114,26 @@ -- Accessors -- --------------------------------------------------------------------------- +-- | The interned-symbol id of the context string's text. cctxstrText :: CCtxStrPtr -> IO Word32 cctxstrText = c_nn_ctxstr_text +-- | The number of context elements attached to the string. cctxstrCtxCount :: CCtxStrPtr -> IO Word16 cctxstrCtxCount = c_nn_ctxstr_ctx_count +-- | The tag of context element @i@ (plain / drv-output / all-outputs). cctxstrElemTag :: CCtxStrPtr -> Word16 -> IO Word8 cctxstrElemTag = c_nn_ctxstr_elem_tag +-- | The store-path-hash symbol of context element @i@. cctxstrElemHash :: CCtxStrPtr -> Word16 -> IO Word32 cctxstrElemHash = c_nn_ctxstr_elem_hash +-- | The store-path-name symbol of context element @i@. cctxstrElemName :: CCtxStrPtr -> Word16 -> IO Word32 cctxstrElemName = c_nn_ctxstr_elem_name +-- | The output-name symbol of context element @i@ (drv-output elements only). cctxstrElemOutput :: CCtxStrPtr -> Word16 -> IO Word32 cctxstrElemOutput = c_nn_ctxstr_elem_output
src/Nix/Eval/CEnv.hs view
@@ -1,6 +1,6 @@ -- | C-backed evaluation environments. ----- Wraps @cbits/nn_env.c@ — arena-allocated @nn_env_t@ structs that+-- Wraps @cbits/nn_env.c@ - arena-allocated @nn_env_t@ structs that -- hold slot arrays, lazy scopes, parent pointers, and with-scopes. -- All memory is freed in bulk via 'cenvDestroy' at evaluation end. --@@ -49,14 +49,14 @@ import Nix.Eval.CThunk (CThunkPtr)  -- ------------------------------------------------------------------------------ Opaque C type (phantom — never constructed on the Haskell side)+-- Opaque C type (phantom - never constructed on the Haskell side) -- ---------------------------------------------------------------------------  -- | Phantom type representing the C @nn_env_t@ struct. data NnEnv  -- ------------------------------------------------------------------------------ FFI imports (all unsafe — no callbacks, fast operations)+-- FFI imports (all unsafe - no callbacks, fast operations) -- ---------------------------------------------------------------------------  foreign import ccall unsafe "nn_env_init"@@ -204,7 +204,7 @@ -- ---------------------------------------------------------------------------  -- | Resolved variable lookup: walk @level@ parent hops, then read--- @slots[idx]@.  Single FFI call — O(level) in C.+-- @slots[idx]@.  Single FFI call - O(level) in C. cenvLookupResolved :: Ptr NnEnv -> Int -> Int -> IO CThunkPtr cenvLookupResolved env level idx =   -- C @int@ params: convert at the boundary (CInt), not HsInt (64-bit).
src/Nix/Eval/CLambda.hs view
@@ -1,6 +1,6 @@ -- | C-backed lambda closure structs via FFI. ----- Wraps @cbits/nn_lambda.c@ — a malloc'd struct holding the closure+-- Wraps @cbits/nn_lambda.c@ - a malloc'd struct holding the closure -- environment (nn_env_t*), body bytecode index, and formal parameter -- specification.  Replaces StablePtr NixValue for VLambda (~25% of -- remaining GHC heap after M6) with C-native tag 9 in the thunk system.@@ -41,7 +41,7 @@ type CLambdaPtr = Ptr NnLambda  -- ------------------------------------------------------------------------------ FFI imports (all unsafe — no callbacks, fast data access)+-- FFI imports (all unsafe - no callbacks, fast data access) -- ---------------------------------------------------------------------------  foreign import ccall unsafe "nn_lambda_new"
src/Nix/Eval/CList.hs view
@@ -1,6 +1,6 @@ -- | C-backed contiguous list of thunk pointers via FFI. ----- Wraps @cbits/nn_list.c@ — a flat array of @nn_thunk_t*@ pointers.+-- Wraps @cbits/nn_list.c@ - a flat array of @nn_thunk_t*@ pointers. -- Replaces Haskell's @[Thunk]@ (linked-list cons cells, ~48 bytes per -- element) with a contiguous C array (8 bytes per element). --@@ -46,7 +46,7 @@ type CListPtr = Ptr NnList  -- ------------------------------------------------------------------------------ FFI imports (all unsafe — no callbacks, fast data access)+-- FFI imports (all unsafe - no callbacks, fast data access) -- ---------------------------------------------------------------------------  foreign import ccall unsafe "nn_list_new"@@ -135,7 +135,7 @@ -- arena-allocated and valid until evaluation end. newtype CList = CList {unCList :: CListPtr} --- | Pointer equality — two CLists are the same if they point to the+-- | Pointer equality - two CLists are the same if they point to the -- same C struct.  Deep equality is handled by the evaluator. instance Eq CList where   CList a == CList b = a == b@@ -152,7 +152,7 @@ emptyCList = CList nullPtr  -- | Convert a Haskell list of 'CThunkPtr' to a 'CList'.--- Uses 'unsafePerformIO' — safe because allocation is idempotent+-- Uses 'unsafePerformIO' - safe because allocation is idempotent -- per call (no shared mutable state beyond the tracking array). {-# NOINLINE clistFromThunks #-} clistFromThunks :: [CThunkPtr] -> CList
src/Nix/Eval/CThunk.hs view
@@ -1,6 +1,6 @@ -- | C-backed thunk memoization cells via FFI. ----- Wraps @cbits/nn_thunk.c@ — an arena-allocated mutable cell that+-- Wraps @cbits/nn_thunk.c@ - an arena-allocated mutable cell that -- holds either a pending expression (StablePtr to (Expr, Env)) or a -- computed value (StablePtr to NixValue).  Replaces Haskell's -- @IORef ThunkCell@ (~56 bytes on GHC heap, all GC-traced) with a@@ -91,7 +91,7 @@ type CThunkPtr = Ptr NnThunk  -- ------------------------------------------------------------------------------ FFI imports (all unsafe — no callbacks, fast data access)+-- FFI imports (all unsafe - no callbacks, fast data access) -- ---------------------------------------------------------------------------  foreign import ccall unsafe "nn_thunk_init"@@ -248,7 +248,7 @@ cthunkNew = c_nn_thunk_new  -- | Allocate a new PENDING thunk with a bytecode index + C env pointer.--- No Haskell heap references — zero GC pressure for pending thunks.+-- No Haskell heap references - zero GC pressure for pending thunks. cthunkNewBc :: Word32 -> Ptr () -> IO CThunkPtr cthunkNewBc = c_nn_thunk_new_bc 
src/Nix/Eval/Compile.hs view
@@ -459,7 +459,7 @@     formals <- decodeFormalEntries (fromIntegral count) (dataOff + 3)     pure (EFNamedSet (symbolText (Symbol nameSym)) formals (ellipsis /= 0))   -- Unreachable: the flag is written only by this module's formals compiler,-  -- which emits 0, 1, or 2 — all matched above.+  -- which emits 0, 1, or 2 - all matched above.   _ -> error "decodeBcFormals: invalid formal type flag"  -- | Decode a list of formal entries from the data buffer.@@ -489,7 +489,7 @@       pairs <- decodePairs (fromIntegral count) (dataOff + 2)       pure (CapturesWithScopes pairs)     -- Unreachable: the tag is written only by this module's capture compiler,-    -- which emits 0, 1, or 2 — all matched above.+    -- which emits 0, 1, or 2 - all matched above.     _ -> error "decodeBcCaptureInfo: invalid capture type tag"  -- | Decode (level, idx) pairs from the data buffer.@@ -553,7 +553,7 @@         then pure (BcInheritFrom fromBcIdx syms, nextOff)         else pure (BcInherit syms, nextOff)     -- Unreachable: the tag is written only by this module's binding compiler,-    -- which emits 0 or 1 — both matched above.+    -- which emits 0 or 1 - both matched above.     _ -> error "decodeOneBinding: invalid binding type tag"  -- | Decode attr path keys: pairs of (is_expr, key_or_bc_idx).
src/Nix/Eval/EvalFormals.hs view
@@ -24,10 +24,10 @@ -- | Formal parameter specification for a lambda. -- Mirrors 'Nix.Expr.Types.Formals' but with bytecode indices for defaults. data EvalFormals-  = -- | @x: body@ — single named parameter+  = -- | @x: body@ - single named parameter     EFName !Text-  | -- | @{ a, b, ... }: body@ — destructuring set pattern+  | -- | @{ a, b, ... }: body@ - destructuring set pattern     EFSet ![EvalFormal] !Bool-  | -- | @args\@{ a, b, ... }: body@ — named set pattern+  | -- | @args\@{ a, b, ... }: body@ - named set pattern     EFNamedSet !Text ![EvalFormal] !Bool   deriving (Eq, Show)
src/Nix/Eval/IO.hs view
@@ -72,7 +72,7 @@  instance Exception NixEvalError --- | Abort error — NOT catchable by tryEval (matches real Nix semantics).+-- | Abort error - NOT catchable by tryEval (matches real Nix semantics). newtype NixAbortError = NixAbortError Text   deriving (Show) @@ -85,25 +85,25 @@ -- | Shared state for IO evaluation. -- -- 'esImportCache' is a shared mutable cache (global across all frames).--- Single-threaded only — switch to @MVar@ or @TVar@ if concurrent+-- Single-threaded only - switch to @MVar@ or @TVar@ if concurrent -- evaluation is ever added. ----- 'esBaseDir' is immutable per frame — @import@ uses 'local' to set it+-- 'esBaseDir' is immutable per frame - @import@ uses 'local' to set it -- for nested evaluations, so it is exception-safe with no save\/restore. -- -- 'esSearchPaths' holds parsed @NIX_PATH@ entries as thunks, populating -- @builtins.nixPath@. data EvalState = EvalState   { esImportCache :: !(IORef (Map FilePath NixValue)),-    -- | Cache of derivation modulo-hashes (drv store path → hex), populated+    -- | Cache of derivation modulo-hashes (drv store path to hex), populated     -- bottom-up by 'builtinDerivationStrict' so input derivations can be     -- substituted by their content hashes when computing output paths.     esDrvModuloCache :: !(IORef (Map Text Text)),-    -- | Accumulated @.drv@ closure (drv store path text → its ATerm), recorded+    -- | Accumulated @.drv@ closure (drv store path text to its ATerm), recorded     -- bottom-up by 'builtinDerivationStrict'.  The build driver reads this after     -- evaluation to write every input @.drv@ to the store before building.     esDrvClosure :: !(IORef (Map Text Text)),-    -- | Cache of source path → its store path (recursive NAR hash), so a path+    -- | Cache of source path to its store path (recursive NAR hash), so a path     -- literal used across many derivations is hashed only once.     esSourcePathCache :: !(IORef (Map Text Text)),     esBaseDir :: !FilePath,@@ -195,7 +195,7 @@             -- captured in closures remain valid after the import scope ends.             let fileDir = takeDirectory target                 expr = resolveRelativePaths fileDir rawExpr-            -- local sets new base dir for nested imports — pure, exception-safe+            -- local sets new base dir for nested imports - pure, exception-safe             let nested =                   EvalIO                     ( local@@ -260,7 +260,7 @@     -- The preimage is built inline against the LIVE storeDir (esStoreDir), not     -- via Nix.Hash.makeStorePath which pins the canonical defaultStoreDir for     -- cross-platform .drv-hash parity.  toFile outputs are host-local, so they-    -- must use the running store dir — keep this format in sync with+    -- must use the running store dir - keep this format in sync with     -- makeStorePath's preimage (type:sha256:hex:storeDir:name).     let contentHash = sha256Hex (encodeUtf8 contents)         inner = "nix-store:sha256:" <> contentHash <> ":" <> T.pack storeDir <> ":" <> name@@ -354,7 +354,7 @@       else pure path    forceThunk evalFn (Thunk ptr) = do-    -- Force protocol: PENDING -> BLACKHOLE -> COMPUTED with memoization.+    -- Force protocol: PENDING to BLACKHOLE to COMPUTED with memoization.     -- Scalar values (int/float/bool/null) are stored inline in the     -- thunk payload (no StablePtr), dispatched via val_tag.     --@@ -362,7 +362,7 @@     -- evaluation begins.  If evaluation re-enters the same thunk, it     -- sees BLACKHOLE and reports infinite recursion.  This is safe with     -- knot-tying (evalRecAttrs, evalLet, matchFormalSet) because those-    -- patterns create distinct thunks sharing an env — no thunk ever+    -- patterns create distinct thunks sharing an env - no thunk ever     -- forces itself.     state <- EvalIO (liftIO (cthunkState ptr))     case state of@@ -370,7 +370,7 @@         EvalIO (liftIO (readComputed ptr))       2 {- BLACKHOLE -} ->         -- Infinite recursion is non-catchable (like abort), matching C++ Nix.-        -- tryEval must NOT catch blackholes — using abortEvaluation ensures+        -- tryEval must NOT catch blackholes - using abortEvaluation ensures         -- the error propagates through tryEval/catchEvalError.         abortEvaluation "infinite recursion encountered"       _ {- PENDING -} -> do@@ -381,7 +381,7 @@         envSp <- EvalIO (liftIO (cthunkPayload ptr))         let pendingSp = castPtrToStablePtr envSp         env <- EvalIO (liftIO (deRefStablePtr pendingSp))-        -- Mark blackhole BEFORE evaluation — any re-entry hits the+        -- Mark blackhole BEFORE evaluation - any re-entry hits the         -- BLACKHOLE branch above.         _ <- EvalIO (liftIO (cthunkMarkBlackhole ptr))         val <- evalFn env bcIdx@@ -462,7 +462,7 @@ -- ---------------------------------------------------------------------------  -- | Classify a filesystem path as @"regular"@, @"directory"@, @"symlink"@,--- or @"unknown"@ — matching Nix's @builtins.readDir@ / @readFileType@.+-- or @"unknown"@ - matching Nix's @builtins.readDir@ / @readFileType@. classifyPath :: FilePath -> IO Text classifyPath fp =   firstMatch
src/Nix/Eval/Operator.hs view
@@ -248,7 +248,7 @@  -- | Merge two 'AttrSet's, right-biased (@//@). -- Delegates to C-side @nn_attrset_union@ which performs a linear merge--- of two sorted arrays — O(n+m) on contiguous, cache-friendly memory.+-- of two sorted arrays - O(n+m) on contiguous, cache-friendly memory. -- -- 'unsafePerformIO' safety: @nn_attrset_union@ is a pure C function -- that allocates a new result set from its two inputs without side
src/Nix/Eval/StringInterp.hs view
@@ -25,8 +25,8 @@  -- | Strip the common indentation from already-evaluated indented-string chunks. -- Each chunk is @(isLiteral, text, context)@.  Indentation is computed and--- stripped from the LITERAL chunks only — interpolated chunks are opaque content--- — the single leading newline is dropped, and the trailing newline is kept.+-- stripped from the LITERAL chunks only - interpolated chunks are opaque content+-- - the single leading newline is dropped, and the trailing newline is kept. -- This matches C++ Nix, which strips at the string-part level (so a multi-line -- interpolated value cannot drag the common indent down). stripIndentedChunks :: [(Bool, Text, StringContext)] -> (Text, StringContext)@@ -110,7 +110,7 @@  -- | Format a float the way C++ Nix does: 6 fixed decimal places -- (@std::to_string@), then strip trailing zeros and unnecessary--- decimal point.  E.g. @1.0@ → @"1"@, @3.14@ → @"3.14"@.+-- decimal point.  E.g. @1.0@ becomes @"1"@, @3.14@ becomes @"3.14"@. formatNixFloat :: Double -> Text formatNixFloat n   | isNaN n = "nan"
src/Nix/Eval/Symbol.hs view
@@ -36,14 +36,14 @@ import Foreign.Ptr (Ptr, nullPtr) import System.IO.Unsafe (unsafePerformIO) --- | An interned symbol — a 'Word32' index into the global symbol table.+-- | An interned symbol - a 'Word32' index into the global symbol table. -- Two symbols are equal iff their indices are equal (O(1) comparison). -- 0 is the invalid sentinel. newtype Symbol = Symbol {unSymbol :: Word32}   deriving (Eq, Ord, Show)  -- ------------------------------------------------------------------------------ FFI imports (unsafe — these never call back to Haskell)+-- FFI imports (unsafe - these never call back to Haskell) -- ---------------------------------------------------------------------------  foreign import ccall unsafe "nn_symbol_init"@@ -85,7 +85,7 @@  -- | Intern a 'Text' value, returning its 'Symbol'. -- If the string was already interned, returns the existing symbol.--- This is the canonical entry point for Text → C conversion.+-- This is the canonical entry point for Text to C conversion. symbolIntern :: Text -> IO Symbol symbolIntern txt =   TF.withCStringLen txt $ \(ptr, len) -> do@@ -93,7 +93,7 @@     pure (Symbol sid)  -- | Retrieve the text of an interned symbol.--- Returns the original string.  The result is safe to use — it copies+-- Returns the original string.  The result is safe to use - it copies -- from the C arena into a fresh 'Text'. symbolText :: Symbol -> Text symbolText (Symbol sid)
src/Nix/Eval/Types.hs view
@@ -186,9 +186,9 @@ -- | A thunk: a C arena-allocated memoization cell. -- -- Each thunk points to an @nn_thunk_t@ in the C arena.--- On first force, the cell transitions PENDING -> BLACKHOLE -> COMPUTED,+-- On first force, the cell transitions PENDING to BLACKHOLE to COMPUTED, -- which detects infinite recursion (BLACKHOLE) and drops the Expr/Env--- references — matching real Nix which mutates thunks in-place.+-- references - matching real Nix which mutates thunks in-place. -- -- The C thunk is allocated via 'unsafePerformIO' in 'mkThunk' (same -- pattern as the former IORef-based approach) so that knot-tying works@@ -227,6 +227,8 @@ pattern ThunkComputed = 1 pattern ThunkBlackhole = 2 +-- | Value-kind tags for the C value layout, mirroring @cbits\/nn_thunk.h@.+-- The tag selects which payload a computed (VALUE) thunk carries. pattern ValueInt, ValueFloat, ValueBool, ValueNull, ValueStr, ValuePath, ValueList, ValueAttrs, ValueCtxStr, ValueLambda :: Word8 pattern ValueInt = 0 pattern ValueFloat = 1@@ -241,7 +243,7 @@  -- | Read a COMPUTED thunk's value without forcing. -- Returns 'Nothing' for PENDING or BLACKHOLE thunks.--- Uses 'unsafePerformIO' — safe because C reads are idempotent.+-- Uses 'unsafePerformIO' - safe because C reads are idempotent. readThunkValue :: Thunk -> Maybe NixValue readThunkValue (Thunk ptr) =   unsafePerformIO $ do@@ -271,7 +273,7 @@             payload <- cthunkPayload ptr             deRefStablePtr (castPtrToStablePtr payload) --- | A Nix value — the result of evaluating an expression.+-- | A Nix value - the result of evaluating an expression. data NixValue   = -- | 64-bit signed integer (matching Nix semantics).     VInt !Int64@@ -297,7 +299,7 @@     -- Accumulated args support curried partial application.     VBuiltin !Text ![NixValue]   | -- | Pre-compiled regex carried in a partially-applied builtin.-    -- Internal only — never exposed to Nix code directly.+    -- Internal only - never exposed to Nix code directly.     VCompiledRegex !CompiledRegex   deriving (Eq, Show) @@ -310,7 +312,7 @@ -- All keys are interned symbols; values are CThunkPtrs in a parallel -- array.  O(log n) binary search on contiguous memory for lookup. -- Replaces the former EagerAttrs\/LazyAttrs\/MappedAttrs\/CAttrs ADT--- with a single C-backed representation — all attr set data lives off+-- with a single C-backed representation - all attr set data lives off -- the GHC heap, dramatically reducing GC pressure for large evaluations. newtype AttrSet = AttrSet {unAttrSet :: CAttrSet} @@ -323,7 +325,7 @@      in "<attrset " ++ show n ++ " entries>"  -- | Look up a single key via symbol interning + C binary search.--- Uses 'unsafePerformIO' — safe because symbolIntern and cattrsetLookup+-- Uses 'unsafePerformIO' - safe because symbolIntern and cattrsetLookup -- are idempotent (same key always yields same symbol and same result). attrSetLookup :: Text -> AttrSet -> Maybe Thunk attrSetLookup key (AttrSet cset) =@@ -357,7 +359,7 @@  -- | Full materialization: build a 'Map Text Thunk' from all entries. -- Iterates the C array and builds a Haskell Map.  Expensive on large--- sets — avoid on the hot path.+-- sets - avoid on the hot path. attrSetToMap :: AttrSet -> Map Text Thunk attrSetToMap (AttrSet cset) =   unsafePerformIO $ do@@ -404,7 +406,7 @@ -- inserts key-value pairs, freezes (sort + dedup).  The canonical entry -- point for all attribute set construction. ----- Uses 'unsafePerformIO' with @NOINLINE@ — safe because C allocation+-- Uses 'unsafePerformIO' with @NOINLINE@ - safe because C allocation -- is idempotent and the resulting CAttrSet is referentially transparent. {-# NOINLINE attrSetFromMap #-} attrSetFromMap :: Map Text Thunk -> AttrSet@@ -447,7 +449,7 @@       Just idx -> cattrsetSetValue cset idx ptr       Nothing -> pure () --- | Evaluation environment — C-backed scope chain.+-- | Evaluation environment - C-backed scope chain. -- -- Arena-allocated @nn_env_t@ struct (48 bytes, zero GC overhead). -- All env data (slots, lazy scope, parent, with-scopes) lives in C.@@ -478,7 +480,7 @@           ++ " withs}"  -- | Empty environment (no variables in scope).--- Points to a static global C struct — valid until 'arenaDestroy'.+-- Points to a static global C struct - valid until 'arenaDestroy'. {-# NOINLINE emptyEnv #-} emptyEnv :: Env emptyEnv = Env (unsafePerformIO cenvEmpty)@@ -515,7 +517,7 @@ -- | Name-based variable lookup: walk the parent chain checking -- lazy scopes; fall back to with-scopes (from the starting env). ----- Positional slots are NOT searched here — used only for+-- Positional slots are NOT searched here - used only for -- 'EVar' lookups (let\/rec bindings, builtins, with-scopes). envLookup :: Text -> Env -> Maybe Thunk envLookup name (Env envPtr) = lexicalLookup envPtr@@ -538,7 +540,7 @@                     else lookupWithScopesC name startWiths startWithCount  -- | Walk with-scopes (C array) innermost to outermost.--- Skips tagged lazy entries (bit 0 set) — those are thunk pointers+-- Skips tagged lazy entries (bit 0 set) - those are thunk pointers -- that can only be resolved by 'evalWithVarScopes' which has monadic -- 'force'.  This is a safety guard: currently unreachable because -- 'resolveVars' ensures EVar behind a with-scope always becomes@@ -613,8 +615,8 @@ -- | Create an unevaluated thunk with a fresh C arena-allocated cell. -- -- Compiles the Expr to bytecode and stores (bc_idx, env_ptr) in the--- C thunk — no StablePtr, zero GHC heap pressure for pending thunks.--- The cell is allocated via 'unsafePerformIO' — safe because C+-- C thunk - no StablePtr, zero GHC heap pressure for pending thunks.+-- The cell is allocated via 'unsafePerformIO' - safe because C -- allocation is a pure side effect, and the @NOINLINE@ + @seq@ -- pattern prevents GHC from floating the allocation to a shared -- top-level CAF.@@ -702,11 +704,11 @@ -- -- @NOINLINE@ prevents inlining so GHC can't see inside or CSE calls. -- @seq@ on the expr creates a data dependency that prevents float-out--- to a top-level CAF — without this, GHC would hoist the+-- to a top-level CAF - without this, GHC would hoist the -- @unsafePerformIO@ and share ONE cell across ALL thunks. -- -- Compiles the Expr to bytecode and stores (bc_idx, StablePtr Env)--- in the C thunk.  The Expr tree is eliminated from the GHC heap —+-- in the C thunk.  The Expr tree is eliminated from the GHC heap - -- only the StablePtr Env (~16 bytes) remains for knot-tying laziness. {-# NOINLINE newBcThunkPtr #-} newBcThunkPtr :: Expr -> Env -> CThunkPtr@@ -728,11 +730,11 @@     cthunkNewBc bcIdx (castStablePtrToPtr sp)  -- | Allocate a fresh C arena thunk cell from a known bytecode index.--- No compilation needed — the bc_idx is already available.+-- No compilation needed - the bc_idx is already available. ----- Uses StablePtr for the Env so it stays lazy — essential for+-- Uses StablePtr for the Env so it stays lazy - essential for -- knot-tying in recursive attrs, let bindings, and matchFormalSet.--- The StablePtr points to an Env (newtype around Ptr NnEnv) — ~16+-- The StablePtr points to an Env (newtype around Ptr NnEnv) - ~16 -- bytes on the GHC heap, negligible compared to the Expr trees we -- eliminated. {-# NOINLINE newBcThunkPtrLazy #-}@@ -967,7 +969,7 @@ -- | Build a C-allocated slot array from a list of 'Thunk' values. -- Each thunk is converted to 'CThunkPtr' via 'thunkToCPtr'. -- Returns the C array pointer and the slot count.--- Uses 'unsafePerformIO' — safe because allocation is idempotent.+-- Uses 'unsafePerformIO' - safe because allocation is idempotent. {-# NOINLINE buildCSlots #-} buildCSlots :: [Thunk] -> (Ptr CThunkPtr, Int) buildCSlots thunks = unsafePerformIO $ do@@ -1088,7 +1090,7 @@   -- IO evaluators should cache results (via per-thunk @IORef@).   -- Pure evaluators re-evaluate each time.   -- The first argument is the bytecode evaluation function (to break-  -- the Eval.Types → Eval circular dependency).+  -- the Eval.Types to Eval circular dependency).   forceThunk :: (Env -> Word32 -> m NixValue) -> Thunk -> m NixValue    -- | Look up a cached derivation modulo-hash (hex) by its @.drv@ store@@ -1109,7 +1111,7 @@   recordDrvAterm :: Text -> Text -> m ()    -- | Compute the store path a source file/directory gets when copied into-  -- the store (recursive NAR sha256 → @source@ fixed-output path), WITHOUT+  -- the store (recursive NAR sha256 to a @source@ fixed-output path), WITHOUT   -- performing the copy.  Used when a path literal is coerced in a derivation   -- argument or environment value.  Unavailable in pure evaluation.   storeSourcePath :: Text -> m Text@@ -1118,7 +1120,7 @@ -- recursion) must escape 'tryEval' exactly as in C++ Nix; 'PThrow' is catchable. data PureError = PThrow !Text | PAbort !Text --- | Pure evaluation monad — wraps @Either PureError@.+-- | Pure evaluation monad - wraps @Either PureError@. -- IO builtins ('readFile', 'import') are unavailable; -- everything else evaluates identically to the IO version. newtype PureEval a = PureEval (Either PureError a)@@ -1164,9 +1166,9 @@   storeSourcePath = pure   resolvePathLiteral = pure   forceThunk evalFn (Thunk ptr) =-    -- Read the C thunk via unsafePerformIO — safe because reads are+    -- Read the C thunk via unsafePerformIO - safe because reads are     -- idempotent and PureEval never writes back (no memoization).-    -- Does NOT mark blackholes — PureEval may re-force the same thunk.+    -- Does NOT mark blackholes - PureEval may re-force the same thunk.     -- Dispatches on val_tag for computed scalars (no StablePtr deref).     case unsafePerformIO (cthunkState ptr) of       ThunkComputed ->
src/Nix/Expr/ClosureTrim.hs view
@@ -148,7 +148,7 @@   -- Default expressions are evaluated in the lambda's own scope,   -- so they get fresh innerNames (just the formals themselves).   -- But we already trimmed nested lambdas, and defaults are part-  -- of the lambda's body — they'll be captured correctly.+  -- of the lambda's body - they'll be captured correctly.   Formal name (fmap (trimExpr Set.empty) defExpr)  -- | Analyze a single lambda body and formals defaults, produce a capture@@ -235,7 +235,7 @@   ELambda formals body_ captures ->     -- Nested lambda creates a LexicalScope (depth + 1).     -- If the inner lambda was already trimmed (has Captures), its capture-    -- coordinates are outer references that we must propagate upward —+    -- coordinates are outer references that we must propagate upward -     -- otherwise an outer lambda won't know it needs those variables.     let (vs1, h1, w1) = foldFormalsDefaults (depth + 1) formals         (vs2, h2, w2) = collectFreeVars (depth + 1) body_
src/Nix/Expr/Resolve.hs view
@@ -22,7 +22,7 @@  -- | Scope entry for static variable resolution. data ScopeEntry-  = -- | Lambda formals: name → positional index.+  = -- | Lambda formals: name -> positional index.     LexicalScope !(Map Text Int)   | -- | Let/rec binding names: blocks resolution (handled by name at runtime).     NameBarrier !(Set Text)@@ -54,7 +54,7 @@             innerStack = scope : stack          in EAttrs True (concatMap (resolveLetBinding stack innerStack) bindings) NoCaptureInfo     | otherwise ->-        -- Fallback: dynamic keys or nested paths — use NameBarrier.+        -- Fallback: dynamic keys or nested paths - use NameBarrier.         let names = collectBindingNames bindings             newStack = NameBarrier names : stack          in EAttrs True (concatMap (resolveBinding newStack) bindings) NoCaptureInfo@@ -78,7 +78,7 @@             innerStack = scope : stack          in ELet (concatMap (resolveLetBinding stack innerStack) bindings) (resolve innerStack body) NoCaptureInfo     | otherwise ->-        -- Fallback: dynamic keys or nested paths — use NameBarrier.+        -- Fallback: dynamic keys or nested paths - use NameBarrier.         let names = collectBindingNames bindings             newStack = NameBarrier names : stack          in ELet (concatMap (resolveBinding newStack) bindings) (resolve newStack body) NoCaptureInfo@@ -92,7 +92,7 @@     EAssert (resolve stack cond) (resolve stack body)   EUnary op operand -> EUnary op (resolve stack operand)   EBinary op l r -> EBinary op (resolve stack l) (resolve stack r)-  -- Desugar: <name> → __findFile __nixPath "name"+  -- Desugar: <name> becomes __findFile __nixPath "name"   -- Matches C++ Nix's parser desugaring.  __findFile and __nixPath are   -- in the root scope (Builtins.hs), so they resolve via name-based   -- lookup at runtime.  This ensures closure trimming captures the@@ -135,9 +135,9 @@ -- -- Index assignment: ----- * @FormalName n@ → @[0: n]@--- * @FormalSet [a, b, c] _@ → @[0: a, 1: b, 2: c]@ (declaration order)--- * @FormalNamedSet n [a, b, c] _@ → @[0: n, 1: a, 2: b, 3: c]@ (\@ name first)+-- * @FormalName n@ becomes @[0: n]@+-- * @FormalSet [a, b, c] _@ becomes @[0: a, 1: b, 2: c]@ (declaration order)+-- * @FormalNamedSet n [a, b, c] _@ becomes @[0: n, 1: a, 2: b, 3: c]@ (\@ name first) lexicalScopeFromFormals :: Formals -> ScopeEntry lexicalScopeFromFormals (FormalName name) =   LexicalScope (Map.singleton name 0)@@ -178,7 +178,7 @@ resolveBinding stack (Inherit (Just fromExpr) names) =   [Inherit (Just (resolve stack fromExpr)) names] resolveBinding stack (Inherit Nothing names) =-  -- Desugar: inherit x y; → x = x; y = y;+  -- Desugar: inherit x y; becomes x = x; y = y;   [NamedBinding [StaticKey name] (resolve stack (EVar name)) | name <- names]  -- | Check if all bindings are eligible for positional resolution:@@ -213,7 +213,7 @@ -- -- Regular bindings resolve their RHS against @innerStack@ (recursive). -- @inherit x@ desugars to @x = x@ where the RHS resolves against--- @outerStack@ — the inherited name must reference the enclosing scope,+-- @outerStack@ - the inherited name must reference the enclosing scope, -- not the let scope being defined. resolveLetBinding :: [ScopeEntry] -> [ScopeEntry] -> Binding -> [Binding] resolveLetBinding _ innerStack (NamedBinding path bodyExpr) =@@ -221,13 +221,13 @@ resolveLetBinding _ innerStack (Inherit (Just fromExpr) names) =   [Inherit (Just (resolve innerStack fromExpr)) names] resolveLetBinding outerStack _ (Inherit Nothing names) =-  -- Desugar @inherit x y;@ → @x = x; y = y;@, resolving each RHS against the+  -- Desugar @inherit x y;@ becomes @x = x; y = y;@, resolving each RHS against the   -- outer scope so it names the enclosing binding, not the one defined here.   -- 'shiftInheritLevel' corrects for the inner env the resulting thunk runs in.   [NamedBinding [StaticKey name] (shiftInheritLevel (resolve outerStack (EVar name))) | name <- names]  -- | A desugared positional @inherit@ RHS is resolved against the outer scope--- but evaluated in the inner (let\/rec) env — one extra parent-chain hop — so+-- but evaluated in the inner (let\/rec) env - one extra parent-chain hop - so -- its de Bruijn level is one too shallow.  Bump it.  'EVar'\/'EWithVar' are -- name-based and need no adjustment. shiftInheritLevel :: Expr -> Expr
src/Nix/Expr/Types.hs view
@@ -1,7 +1,7 @@ -- | Core AST types for the Nix expression language. -- -- Every Nix source file parses into an 'Expr'. The AST is a direct--- representation of the language grammar — no desugaring at parse time.+-- representation of the language grammar - no desugaring at parse time. -- The evaluator ('Nix.Eval') reduces expressions to values. module Nix.Expr.Types   ( -- * Expressions
src/Nix/Hash.hs view
@@ -8,8 +8,8 @@ -- -- That @s66mzx...@ hash encodes ALL inputs that went into building the -- package: source code, compiler version, flags, dependencies (which are--- themselves hashes).  Change any input → different hash → different path →--- completely isolated from the original.+-- themselves hashes).  Change any input and you get a different hash, a+-- different path, completely isolated from the original. -- -- This is why Nix can have multiple versions of the same package installed -- simultaneously without conflict. They live at different store paths@@ -113,7 +113,7 @@    in [hexDigit hi, hexDigit lo]  -- ------------------------------------------------------------------------------ Store path construction — the Nix @makeStorePath@ family+-- Store path construction - the Nix @makeStorePath@ family -- -- These mirror C++ Nix exactly so nova-nix's computed store paths byte-match -- @nix-instantiate@.  All hashing is done against the canonical @\/nix\/store@@@ -121,7 +121,7 @@ -- derivation hashes identically on Windows, Linux, and macOS. -- --------------------------------------------------------------------------- --- | Length in bytes a store-path hash is compressed to (160 bits → 32 base-32+-- | Length in bytes a store-path hash is compressed to (160 bits down to 32 base-32 -- characters). storePathHashBytes :: Int storePathHashBytes = 20@@ -187,8 +187,8 @@    in StorePath (encode (BS.pack compressed)) name  -- | Construct a text store path (used for @.drv@ files and @builtins.toFile@).--- The references are embedded in the @type@ string — @\"text\"@ followed by--- each referenced store path — which is why a derivation's @.drv@ path depends+-- The references are embedded in the @type@ string - @\"text\"@ followed by+-- each referenced store path - which is why a derivation's @.drv@ path depends -- on the paths of all its inputs.  @contentsDigest@ is the SHA-256 of the file -- contents (the ATerm, for a @.drv@). makeTextPath :: Text -> BS.ByteString -> [StorePath] -> StorePath@@ -201,8 +201,8 @@ -- the EXPECTED output hash (e.g. a tarball's SHA-256).  @mode@ is @\"flat\"@ -- or @\"recursive\"@.  Mirrors C++ Nix @makeFixedOutputPath@: ----- * @sha256@ + @recursive@ → @makeStorePath \"source\" foHash name@--- * otherwise → @makeStorePath \"output:out\" sha256(\"fixed:out:\" prefix algo \":\" hex \":\") name@+-- * @sha256@ + @recursive@ yields @makeStorePath \"source\" foHash name@+-- * otherwise, @makeStorePath \"output:out\" sha256(\"fixed:out:\" prefix algo \":\" hex \":\") name@ makeFixedOutputPath :: Text -> Text -> Text -> BS.ByteString -> StorePath makeFixedOutputPath name algo mode foHashDigest   | algo == "sha256" && mode == "recursive" =
src/Nix/Parser.hs view
@@ -66,7 +66,7 @@ -- | Parse a Nix expression from source text. -- -- The input is the full file contents. The file name is used only for--- error messages.  Strips a leading UTF-8 BOM if present — Windows+-- error messages.  Strips a leading UTF-8 BOM if present - Windows -- editors (Notepad, PowerShell) commonly add one. parseNix :: Text -> Text -> Either ParseError Expr parseNix fileName source = do@@ -98,10 +98,10 @@ -- UTF-16 LE.  Nix files are normally UTF-8, but on a Windows-first -- implementation we handle all three BOM variants: ----- * @FF FE@       → UTF-16 LE (PowerShell default)--- * @FE FF@       → UTF-16 BE--- * @EF BB BF@    → UTF-8 (BOM stripped)--- * No BOM        → UTF-8+-- * @FF FE@ is UTF-16 LE (PowerShell default)+-- * @FE FF@ is UTF-16 BE+-- * @EF BB BF@ is UTF-8 (BOM stripped)+-- * No BOM is UTF-8 readFileAutoEncoding :: FilePath -> IO Text readFileAutoEncoding path = decodeAutoEncoding <$> BS.readFile path 
src/Nix/Parser/Expr.hs view
@@ -567,7 +567,7 @@       _ <- advance       expr <- parseExpr       -- In expression context, the lexer doesn't track interpolation-      -- mode — } is TokRBrace rather than TokInterpClose.+      -- mode - } is TokRBrace rather than TokInterpClose.       expect TokRBrace       pure (DynamicKey expr)     _ -> parseError ("expected attribute key, got " <> showToken tok)
src/Nix/Parser/Internal.hs view
@@ -181,6 +181,7 @@       peMessage = "unexpected end of input"     } +-- | Render a token as the human-readable phrase used in parse-error messages. showToken :: Token -> Text showToken TokEOF = "end of input" showToken TokIf = "'if'"
src/Nix/Parser/Lexer.hs view
@@ -1,7 +1,7 @@ -- | Lexer for the Nix language: 'Text' to @['Located']@. -- -- Handles all Nix tokens including string interpolation via a mode stack.--- Entirely pure — no IO.+-- Entirely pure - no IO. module Nix.Parser.Lexer   ( -- * Tokens     Token (..),@@ -309,23 +309,23 @@               -- Check for escape sequences: ''', ''$, ''\x, ''${               case T.uncons rest2 of                 Just ('\'', rest3) ->-                  -- ''' → literal single quote (consume all 3)+                  -- ''' is a literal single quote (consume all 3)                   let litTok = Located (lsLine st) (lsCol st) (TokStringLit "'")                       newSt = advanceCol 3 st {lsInput = rest3}                    in lexIndStringMode newSt (litTok : acc)                 Just ('$', rest3)                   | Just ('{', rest4) <- T.uncons rest3 ->-                      -- ''${ → literal ${+                      -- ''${ is a literal ${                       let litTok = Located (lsLine st) (lsCol st) (TokStringLit "${")                           newSt = advanceCol 4 st {lsInput = rest4}                        in lexIndStringMode newSt (litTok : acc)                 Just ('$', rest3) ->-                  -- ''$ (without brace) → literal $+                  -- ''$ (without brace) is a literal $                   let litTok = Located (lsLine st) (lsCol st) (TokStringLit "$")                       newSt = advanceCol 3 st {lsInput = rest3}                    in lexIndStringMode newSt (litTok : acc)                 Just ('\\', rest3) ->-                  -- ''\x → escape sequence+                  -- ''\x is an escape sequence                   case T.uncons rest3 of                     Just (ec, rest4) ->                       let escaped = case ec of@@ -346,13 +346,13 @@                             peMessage = "unterminated escape in indented string"                           }                 _ ->-                  -- '' followed by non-escape → close indented string+                  -- '' followed by non-escape closes the indented string                   let tok = Located (lsLine st) (lsCol st) TokIndStringClose                       newSt = advanceCol 2 st {lsInput = rest2, lsModes = safeTail (lsModes st)}                    in lexLoop newSt (tok : acc)             Just ('$', rest1)               | Just ('{', rest2) <- T.uncons rest1 ->-                  -- \${ in indented string → interpolation+                  -- \${ in indented string is interpolation                   let tok = Located (lsLine st) (lsCol st) TokInterpOpen                       newSt =                         advanceCol@@ -419,7 +419,7 @@  -- | Lex a literal segment inside an indented string. -- No escape sequences here (those are handled by 'lexIndStringMode'),--- so the chunk is identical to the source text — count chars, then slice+-- so the chunk is identical to the source text - count chars, then slice -- once at the end.  This avoids O(n^2) 'T.snoc' allocation. lexIndStringLiteral :: LexState -> [Located] -> Either ParseError [Located] lexIndStringLiteral st0 acc = go st0 0@@ -505,7 +505,7 @@ readInteger = T.foldl' (\n c -> n * decimalBase + fromIntegral (fromEnum c - zeroOrd)) 0  -- | Read a floating-point number from its integer and decimal parts.--- Total — no 'read', no exceptions.+-- Total - no 'read', no exceptions. readDouble :: Text -> Text -> Double readDouble intPart decPart =   let whole = fromIntegral (readInteger intPart) :: Double@@ -674,7 +674,7 @@  -- | Does the maximal path-char run at the start of the input contain a -- @/@-segment (a slash followed by a non-slash path char)?  If so it lexes--- as a path rather than an identifier, number, or division operator —+-- as a path rather than an identifier, number, or division operator - -- matching Nix, where @a/b@ and @/abs/path@ are paths, @a / b@ (slash -- surrounded by whitespace) is division, and @a // b@ is the update operator. looksLikePath :: Text -> Bool
src/Nix/Push.hs view
@@ -5,7 +5,7 @@ -- == The push choreography -- -- 1. Compute the full reference closure of the requested paths from the---    store database — a cache must never hold a path without its+--    store database - a cache must never hold a path without its --    dependencies, or substitution 404s on cold machines. -- 2. Fetch @\/narinfo-hashes@ from the cache and skip paths already there. -- 3. Upload every missing NAR, then every narinfo.  NARs go first@@ -18,7 +18,7 @@ -- -- The store database and filesystem use the platform store dir -- (@C:\\nix\\store@ on Windows).  Published narinfos always use the--- canonical store dir (@\/nix\/store@) with forward slashes — the form the+-- canonical store dir (@\/nix\/store@) with forward slashes - the form the -- store-path hashes were computed against and the form cache servers -- validate.  'StorePath' itself is directory-agnostic, so translation is -- just a matter of which renderer runs at which boundary.@@ -151,7 +151,7 @@  -- | Compute the full reference closure of the given paths from the store -- database (breadth-first over @Refs@).  Fails if a recorded reference does--- not parse as a store path — that would mean a corrupt database, and+-- not parse as a store path - that would mean a corrupt database, and -- pushing a closure with holes would poison the cache. computeClosure :: Store -> [StorePath] -> IO (Either Text [StorePath]) computeClosure store roots = runExceptT (go Set.empty [] roots)@@ -281,7 +281,7 @@       narHash = Hash.formatNixHash (Hash.hashBytes narBytes)       narSize = BS.length narBytes   -- The database recorded this path's NAR hash at registration; a mismatch-  -- now means the path changed on disk — refuse to publish corruption.+  -- now means the path changed on disk - refuse to publish corruption.   recorded <- liftIO (queryPathInfo (stDB store) sp)   case recorded of     Just info
src/Nix/Store.hs view
@@ -14,7 +14,7 @@ -- -- When Nix SUBSTITUTES (downloads from a binary cache): ----- 1. Fetch @\<hash\>.narinfo@ from cache — contains NAR hash, size, refs+-- 1. Fetch @\<hash\>.narinfo@ from cache - contains NAR hash, size, refs -- 2. Fetch the @.nar.xz@ file -- 3. Verify file hash matches narinfo -- 4. Decompress and unpack NAR into store path@@ -29,7 +29,7 @@ -- profile, per-user profiles, result symlinks from @nix-build@). -- GC walks all roots, follows references transitively, and deletes -- everything not reachable.  Since paths are immutable and reference--- tracking is exact, GC is safe — it never deletes something in use.+-- tracking is exact, GC is safe - it never deletes something in use. module Nix.Store   ( -- * Store operations     Store (..),@@ -227,7 +227,7 @@ -- | Scan an output for references to build-temp output locations. -- -- The builder runs under a temp directory, so an output that embeds its own or--- a sibling output's path embeds the TEMP path — which 'scanReferences' (store+-- a sibling output's path embeds the TEMP path - which 'scanReferences' (store -- prefix only) cannot see.  Given @(tempDir, storePath)@ for every output of -- the derivation, returns the store paths whose temp location is referenced -- from the scanned output, capturing self- and cross-output references.@@ -290,7 +290,7 @@ -- | Recursively mark a store path and its contents read-only after a build. -- -- On Windows the directory read-only attribute does not prevent adding or--- removing entries — only the per-file read-only attribute protects a file.+-- removing entries - only the per-file read-only attribute protects a file. -- Immutability here is therefore enforced at FILE granularity (every file is -- made read-only); hardening the directory itself against entry changes would -- require ACLs and is deferred.
src/Nix/Store/DB.hs view
@@ -6,7 +6,7 @@ -- metadata about each path that isn't in the filesystem: -- -- * __References__: which other store paths does this path depend on?---   (Needed for garbage collection — can't delete a path that others+--   (Needed for garbage collection - can't delete a path that others --   reference.) -- * __Registrant__: who put this path here? (Substituted from cache? --   Built locally?)@@ -169,7 +169,7 @@ -- | Register several store paths as valid in one transaction. -- -- ALL path rows are inserted BEFORE any reference edge, so references among the--- paths in this batch — e.g. intra-derivation cross-output references — are+-- paths in this batch - e.g. intra-derivation cross-output references - are -- never dropped.  (Registering one path at a time loses an edge whenever a -- referrer is registered before its referent.) --@@ -208,7 +208,7 @@       case refRows of         (Only refId : _) ->           execute conn "INSERT OR IGNORE INTO Refs (referrer, reference) VALUES (?, ?)" (referrerId, refId)-        [] -> pure () -- Reference not in this batch or the store yet — skip.+        [] -> pure () -- Reference not in this batch or the store yet - skip.  -- --------------------------------------------------------------------------- -- Queries
src/Nix/Store/Path.hs view
@@ -17,7 +17,7 @@ -- -- * Atomic upgrades (install new version, switch symlink, done) -- * Rollbacks (old version still in store, just switch symlink back)--- * Concurrent installs (no file conflicts — different hashes = different dirs)+-- * Concurrent installs (no file conflicts - different hashes = different dirs) -- * Garbage collection (delete unreferenced paths, everything else stays) -- * Binary substitution (if hash matches, the build output is identical) --@@ -27,7 +27,7 @@ -- binary references its shared libraries, its interpreter, etc.  Nix -- scans the output for store path strings to discover these references -- automatically.  The reference graph is what the garbage collector--- follows — anything reachable from a GC root is kept.+-- follows - anything reachable from a GC root is kept. module Nix.Store.Path   ( -- * Store directory     StoreDir (..),
src/Nix/Substituter.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE ScopedTypeVariables #-} --- | Binary substituter — download pre-built paths from remote caches.+-- | Binary substituter - download pre-built paths from remote caches. -- -- == How substitution works --@@ -27,7 +27,7 @@ -- @ -- -- Our nova-cache server implements this protocol.  The narinfo format,--- NAR serialization, signature verification — all handled by the+-- NAR serialization, signature verification - all handled by the -- @nova-cache@ library.  This module orchestrates the HTTP requests -- and store registration. module Nix.Substituter@@ -50,6 +50,7 @@   ) where +import Control.Concurrent (threadDelay) import Control.Exception (SomeException, try) import Control.Monad (when) import qualified Data.ByteString as BS@@ -99,7 +100,7 @@  -- | Result of a substitution attempt. data SubstResult-  = -- | Successfully substituted — path is now in the store.+  = -- | Successfully substituted - path is now in the store.     SubstSuccess !StorePath   | -- | Cache doesn't have this path.     SubstNotFound@@ -164,7 +165,7 @@                 )             )       | otherwise ->-          -- 2–4. Verify, download, decompress (pure pipeline after fetch)+          -- 2-4. Verify, download, decompress (pure pipeline after fetch)           case verifyAndDecompress cache mgr narInfo of             Left err -> pure (SubstError err)             Right fetchDecompress -> do@@ -184,7 +185,7 @@   verifySigs cache narInfo   let compression = NarInfo.niCompression narInfo   pure $ do-    downloaded <- downloadNar mgr cache narInfo+    downloaded <- downloadNarWithRetry mgr cache narInfo     pure $ downloaded >>= decompressNar compression  -- | Verify the NAR hash, deserialize, unpack to the store, set permissions,@@ -281,6 +282,40 @@       if code == httpNotFound         then pure (Left SubstNotFound)         else pure (Left (SubstError ("narinfo fetch failed: HTTP " <> T.pack (show code))))++-- | How many times to attempt a NAR download before giving up and letting the+-- caller fall back to a local build.  Matches Nix's @download-attempts@ default.+narDownloadAttempts :: Int+narDownloadAttempts = 5++-- | Base delay between NAR download attempts, in microseconds.  The delay grows+-- linearly with each retry (0.5s, 1s, ...).+narRetryBaseDelayMicros :: Int+narRetryBaseDelayMicros = 500000++-- | Download a NAR, retrying transient failures.+--+-- By the time this runs the narinfo has already been fetched and signature-+-- verified, so the cache claims to hold this path: a failed blob fetch (a+-- transient HTTP error, a stale-negative at a CDN edge, or a dropped+-- connection) is far more likely a hiccup than a real miss.  Retrying a few+-- times is much cheaper than the local rebuild a hard failure forces.  A 404+-- on the narinfo itself (a genuine cache miss) is handled earlier in+-- 'fetchNarInfo' and never reaches here.+downloadNarWithRetry :: HTTP.Manager -> CacheConfig -> NarInfo.NarInfo -> IO (Either Text BS.ByteString)+downloadNarWithRetry mgr cache narInfo = attempt narDownloadAttempts+  where+    attempt remaining = do+      outcome <- try (downloadNar mgr cache narInfo)+      case outcome of+        Right (Right bytes) -> pure (Right bytes)+        Right (Left err) -> retryOr err remaining+        Left (e :: SomeException) -> retryOr ("NAR download error: " <> T.pack (show e)) remaining+    retryOr err remaining+      | remaining <= 1 = pure (Left err)+      | otherwise = do+          threadDelay (narRetryBaseDelayMicros * (narDownloadAttempts - remaining + 1))+          attempt (remaining - 1)  -- | Download the NAR file referenced by a narinfo. --
test/Main.hs view
@@ -130,7 +130,7 @@ -- at known Git for Windows locations first, then PATH.  Checks known -- paths first to avoid picking up the WSL launcher at -- @C:\\Windows\\System32\\bash.exe@ which exits 1 when WSL is not--- configured.  Real Nix builders always use bash from the store —+-- configured.  Real Nix builders always use bash from the store - -- this bridges the gap until nova-nix bootstraps its own bash -- derivation. findTestShell :: IO Text@@ -226,7 +226,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Literals+-- Tests: Eval - Literals -- ---------------------------------------------------------------------------  testEvalLiterals :: IO [Bool]@@ -248,7 +248,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Variables+-- Tests: Eval - Variables -- ---------------------------------------------------------------------------  testEvalVariables :: IO [Bool]@@ -264,7 +264,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Arithmetic+-- Tests: Eval - Arithmetic -- ---------------------------------------------------------------------------  testEvalArithmetic :: IO [Bool]@@ -296,7 +296,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Comparison+-- Tests: Eval - Comparison -- ---------------------------------------------------------------------------  testEvalComparison :: IO [Bool]@@ -316,7 +316,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Logic+-- Tests: Eval - Logic -- ---------------------------------------------------------------------------  testEvalLogic :: IO [Bool]@@ -340,7 +340,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Strings+-- Tests: Eval - Strings -- ---------------------------------------------------------------------------  testEvalStrings :: IO [Bool]@@ -358,7 +358,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — If/Assert+-- Tests: Eval - If/Assert -- ---------------------------------------------------------------------------  testEvalIfAssert :: IO [Bool]@@ -376,7 +376,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Let+-- Tests: Eval - Let -- ---------------------------------------------------------------------------  testEvalLet :: IO [Bool]@@ -392,7 +392,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Attribute sets+-- Tests: Eval - Attribute sets -- ---------------------------------------------------------------------------  testEvalAttrs :: IO [Bool]@@ -414,7 +414,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Recursive attribute sets+-- Tests: Eval - Recursive attribute sets -- ---------------------------------------------------------------------------  testEvalRecAttrs :: IO [Bool]@@ -428,7 +428,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Lists+-- Tests: Eval - Lists -- ---------------------------------------------------------------------------  testEvalLists :: IO [Bool]@@ -444,7 +444,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Lambda+-- Tests: Eval - Lambda -- ---------------------------------------------------------------------------  testEvalLambda :: IO [Bool]@@ -462,7 +462,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — With+-- Tests: Eval - With -- ---------------------------------------------------------------------------  testEvalWith :: IO [Bool]@@ -527,7 +527,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Builtins+-- Tests: Eval - Builtins -- ---------------------------------------------------------------------------  testEvalBuiltins :: IO [Bool]@@ -549,7 +549,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Errors+-- Tests: Eval - Errors -- ---------------------------------------------------------------------------  testEvalErrors :: IO [Bool]@@ -565,7 +565,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Eval — Higher-order builtins+-- Tests: Eval - Higher-order builtins -- ---------------------------------------------------------------------------  testEvalHigherOrder :: IO [Bool]@@ -880,8 +880,8 @@           (EBinary OpUpdate (EVar "a") (EBinary OpUpdate (EVar "b") (EVar "c"))),       -- Lambda       -- After variable resolution, lambda-bound vars become EResolvedVar.-      -- FormalName "x" → slot 0; FormalSet [a,b] → a=0, b=1;-      -- FormalNamedSet "args" [a] → args=0, a=1.+      -- FormalName "x" maps to slot 0; FormalSet [a,b] maps to a=0, b=1;+      -- FormalNamedSet "args" [a] maps to args=0, a=1.       runTest "parse simple lambda" $         assertParse "lambda" "x: x" (ELambda (FormalName "x") (EResolvedVar 0 0) NoCaptureInfo),       runTest "parse set pattern lambda" $@@ -1109,7 +1109,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch 1 — Trivial pure builtins + constants+-- Tests: Batch 1 - Trivial pure builtins + constants -- ---------------------------------------------------------------------------  testBatch1 :: IO [Bool]@@ -1189,7 +1189,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch 2 — Arithmetic + bitwise builtins+-- Tests: Batch 2 - Arithmetic + bitwise builtins -- ---------------------------------------------------------------------------  testBatch2 :: IO [Bool]@@ -1234,7 +1234,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch 3 — Attrset higher-order builtins+-- Tests: Batch 3 - Attrset higher-order builtins -- ---------------------------------------------------------------------------  testBatch3 :: IO [Bool]@@ -1273,7 +1273,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch 4 — String operations+-- Tests: Batch 4 - String operations -- ---------------------------------------------------------------------------  testBatch4 :: IO [Bool]@@ -1317,7 +1317,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch 5 — Serialization + hashing+-- Tests: Batch 5 - Serialization + hashing -- ---------------------------------------------------------------------------  testBatch5 :: IO [Bool]@@ -1370,7 +1370,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch 6 — tryEval + deepSeq+-- Tests: Batch 6 - tryEval + deepSeq -- ---------------------------------------------------------------------------  testBatch6 :: IO [Bool]@@ -1400,7 +1400,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch 7 — genericClosure+-- Tests: Batch 7 - genericClosure -- ---------------------------------------------------------------------------  testBatch7 :: IO [Bool]@@ -1460,7 +1460,7 @@     st <- newEvalState baseDir     runEvalIO st (eval (builtinEnv (esTimestamp st) (esSearchPaths st)) expr) --- | Run a named IO eval test — single label, no double-wrapping.+-- | Run a named IO eval test - single label, no double-wrapping. runTestIO :: Text -> FilePath -> Text -> NixValue -> IO Bool runTestIO label baseDir source expected = do   result <- evalNixIO baseDir source@@ -1547,7 +1547,7 @@       ]  -- ------------------------------------------------------------------------------ Tests: Batch A — getEnv, currentTime, toPath+-- Tests: Batch A - getEnv, currentTime, toPath -- ---------------------------------------------------------------------------  testBatchA :: IO [Bool]@@ -1580,7 +1580,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch A — IO tests (getEnv)+-- Tests: Batch A - IO tests (getEnv) -- ---------------------------------------------------------------------------  testBatchAIO :: IO [Bool]@@ -1612,7 +1612,7 @@       ]  -- ------------------------------------------------------------------------------ Tests: Batch B — placeholder, storePath+-- Tests: Batch B - placeholder, storePath -- ---------------------------------------------------------------------------  testBatchB :: IO [Bool]@@ -1646,7 +1646,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch C — findFile+-- Tests: Batch C - findFile -- ---------------------------------------------------------------------------  testBatchC :: IO [Bool]@@ -1709,7 +1709,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch D — toFile+-- Tests: Batch D - toFile -- ---------------------------------------------------------------------------  testBatchD :: IO [Bool]@@ -1725,7 +1725,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch E — scopedImport+-- Tests: Batch E - scopedImport -- ---------------------------------------------------------------------------  testBatchE :: IO [Bool]@@ -1763,7 +1763,7 @@       ]  -- ------------------------------------------------------------------------------ Tests: Batch F — fetchurl, fetchTarball, fetchGit+-- Tests: Batch F - fetchurl, fetchTarball, fetchGit -- ---------------------------------------------------------------------------  testBatchF :: IO [Bool]@@ -1785,7 +1785,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch G — ATerm serialization+-- Tests: Batch G - ATerm serialization -- ---------------------------------------------------------------------------  testBatchG :: IO [Bool]@@ -1859,7 +1859,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Batch H — derivation+-- Tests: Batch H - derivation -- ---------------------------------------------------------------------------  testBatchH :: IO [Bool]@@ -2183,7 +2183,7 @@       runTest "single node graph" $ case DepGraph.buildDepGraph readSingle drvA spA of         Right (DepGraph.DepGraph g) -> assertEqual "single-size" 1 (Map.size g)         Left err -> Fail ("unexpected error: " <> err),-      -- Linear chain A->C: topoSort should give [C, A]+      -- Linear chain A to C: topoSort should give [C, A]       runTest "linear chain topo" $ case DepGraph.buildDepGraph readChain drvB spB of         Right graph -> case DepGraph.topoSort graph of           DepGraph.TopoSorted order ->@@ -2192,7 +2192,7 @@               _ -> Fail ("bad order: " <> T.pack (show order))           DepGraph.TopoCycle cyc -> Fail ("unexpected cycle: " <> T.pack (show cyc))         Left err -> Fail ("graph build failed: " <> err),-      -- Diamond D->B,C; B->C: topoSort should have C first, D last+      -- Diamond D to B,C; B to C: topoSort should have C first, D last       runTest "diamond topo" $ case DepGraph.buildDepGraph readDiamond drvD spD of         Right graph -> case DepGraph.topoSort graph of           DepGraph.TopoSorted order ->@@ -2215,7 +2215,7 @@           let deps = DepGraph.directDeps graph spD            in assertEqual "direct-count" 2 (length deps)         Left err -> Fail ("graph build failed: " <> err),-      -- Missing .drv -> failure+      -- Missing .drv causes failure       runTest "missing drv fails" $ case DepGraph.buildDepGraph readSingle drvB spB of         Left _ -> Pass         Right _ -> Fail "expected failure for missing drv",@@ -2430,7 +2430,7 @@                 DepGraph.TopoCycle _ -> Pass                 DepGraph.TopoSorted order -> Fail ("expected cycle, got sorted: " <> T.pack (show order))               Left err -> Fail ("expected graph to build, got: " <> err),-      -- missing .drv -> failure in dep graph+      -- missing .drv causes failure in dep graph       runTest "missing drv in dep graph" $         let sp = StorePath "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "missing.drv"             drv =@@ -2743,9 +2743,9 @@     Dir.setPermissions path (Dir.setOwnerWritable True perms)  -- | Step 1 of the Windows-stdenv ladder: prove the Builder can run a--- trivial derivation end-to-end natively — a recipe that writes to @$out@,+-- trivial derivation end-to-end natively - a recipe that writes to @$out@, -- with the output landing in the store.  No stdenv, no dependencies: just--- the raw build path (process spawn -> output capture -> addToStore),+-- the raw build path (process spawn, output capture, addToStore), -- exercised on the host platform (@cmd.exe@ on Windows, @\/bin\/sh@ elsewhere). testTrivialBuildIO :: IO [Bool] testTrivialBuildIO = do@@ -3036,7 +3036,7 @@ -- | Step 2 of the ladder: a dependency-aware build end-to-end.  A root -- derivation depends on a leaf; both @.drv@ files are pre-written to the store -- (as the build driver's closure-writing does), and 'buildWithDeps' must read--- the closure, topologically order it, build the leaf first, then the root —+-- the closure, topologically order it, build the leaf first, then the root - -- whose 'validateInputs' requires the leaf's realized output to be valid. -- -- This is the regression guard for the input-@.drv@-closure fix: before it, no@@ -3105,7 +3105,7 @@           ]  -- | Regression tests for the 2026-06-09 audit eval-fidelity fixes.  All are--- parity-safe — none affects a derivation or store-path hash.+-- parity-safe - none affects a derivation or store-path hash. testEvalFidelity :: IO [Bool] testEvalFidelity = do   putStrLn "eval/audit-fidelity"@@ -3156,11 +3156,11 @@         assertEval "leading-dot-type" "builtins.typeOf .5" (mkStr "float"),       runTest "leading-dot float value" $         assertEval "leading-dot-val" ".5 == 0.5" (VBool True),-      -- PureEval tryEval must NOT catch abort — it propagates (matches C++ Nix)+      -- PureEval tryEval must NOT catch abort - it propagates (matches C++ Nix)       runTest "tryEval does not catch abort" $         assertEvalFail "tryeval-abort" "(builtins.tryEval (builtins.abort \"x\")).success",       -- every builtin in the registry (the source of builtinNames/builtinArity)-      -- is actually exposed in the builtins set — guards against builtinRegistry+      -- is actually exposed in the builtins set - guards against builtinRegistry       -- drifting from the exposed builtins       runTest "every registered builtin is exposed" $         let missing = [n | n <- builtinNames, evalNix ("builtins ? \"" <> n <> "\"") /= Right (VBool True)]@@ -3230,7 +3230,7 @@           createDirectoryIfMissing True scanDir           let candidate = StorePath "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "dep1"               prefix = unStoreDir sd-              -- Only 20 chars of hash — should not match 32-char candidate+              -- Only 20 chars of hash - should not match 32-char candidate               partialRef = prefix <> "/aaaaaaaaaaaaaaaaaaaa"           BS.writeFile (scanDir </> "output.txt") (TE.encodeUtf8 (T.pack partialRef))           refs <- scanReferences sd [candidate] scanDir@@ -3444,9 +3444,9 @@                   then Pass                   else Fail ("output names: " <> T.pack (show names))           _ -> Fail ("expected VDerivation, got " <> T.pack (show val)),-      -- builtinDerivation populates drvEnv with the output paths ($out, …)+      -- builtinDerivation populates drvEnv with the output paths ($out, ...)       -- and the build attributes.  Note: the .drv env does NOT contain a-      -- "drvPath" key — matching C++ Nix, which never writes one.+      -- "drvPath" key - matching C++ Nix, which never writes one.       runTest "builtinDerivation populates drvEnv"         $ assertRight           "drvEnv"@@ -3469,7 +3469,7 @@  -- | Create a minimal Derivation for builder tests. -- The shell path is discovered once via 'findTestShell' and threaded through.--- All scripts are POSIX shell — bash is used on every platform.+-- All scripts are POSIX shell - bash is used on every platform. mkTestBuildDrv :: Text -> StorePath -> Text -> Derivation mkTestBuildDrv shell outSP script =   Derivation@@ -3650,7 +3650,7 @@         forceRemoveIfExists tmpStore         forceRemoveIfExists (bcTmpDir config)         pure ret,-      -- Builder succeeds but doesn't create $out -> build fails+      -- Builder succeeds but doesn't create $out, so the build fails       runTestM "missing output fails build" $ do         tmpBase <- getTemporaryDirectory         let tmpStore = tmpBase </> "nova-nix-test-builder-noout"@@ -3709,7 +3709,7 @@ -- Tests: CLI Integration (Phase 2, Batch 5) -- --------------------------------------------------------------------------- --- | End-to-end: eval .nix source -> extract derivation -> build -> verify output.+-- | End-to-end: eval .nix source, extract derivation, build, verify output. evalAndBuild :: StoreDir -> Text -> IO (Either Text (BuildResult, Store)) evalAndBuild storeDir source = do   case parseNix "<test>" source of@@ -3822,7 +3822,7 @@     ]  -- ------------------------------------------------------------------------------ Tests: Phase 4 — search paths, dynamic keys, directory import+-- Tests: Phase 4 - search paths, dynamic keys, directory import -- ---------------------------------------------------------------------------  testPhase4 :: IO [Bool]@@ -3961,7 +3961,7 @@ -- ---------------------------------------------------------------------------  -- | Cast a StablePtr to CThunkPtr for CAttrSet tests.--- CAttrSet stores void* — we use StablePtrs as opaque values in tests.+-- CAttrSet stores void* - we use StablePtrs as opaque values in tests. spToCPtr :: StablePtr a -> CThunkPtr spToCPtr = castPtr . castStablePtrToPtr @@ -4120,7 +4120,7 @@ testCThunk = do   putStrLn "cthunk"   -- Arena is already initialized by main's bracket.-  -- Each test uses the shared arena (thunks accumulate — that's fine).+  -- Each test uses the shared arena (thunks accumulate - that's fine).   sequence     [ runTestM "new pending + state" $ do         sp <- newStablePtr ("pending" :: Text)@@ -4414,7 +4414,7 @@         op <- cbcOpcode idx         count <- cbcShortArg idx         dataOff <- cbcArg1 idx-        -- 3 parts × 2 words each = 6 data words+        -- 3 parts x 2 words each = 6 data words         tag0 <- cbcData dataOff         _val0 <- cbcData (dataOff + 1)         tag1 <- cbcData (dataOff + 2)