jinx Nix, transpiled to TypeScript

Semantics

The mapping from Nix to the emitted TypeScript, construct by construct, with the runtime helper each one calls. nix is the runtime namespace (src/runtime/index.ts). S(e) is the emission of e in a strict position (a value), L(e) in a lazy position (a value or a thunk).

Expressions

Nix TypeScript
1 1n
1.5 1.5; 1.0 is 1.0
"abc" "abc"
"a${b}c" nix.str`a${S(b)}c`
./foo nix.path(here, "./foo")
/etc nix.path("", "/etc")
<nixpkgs> nix.builtins.findFile(nix.builtins.nixPath, "nixpkgs")
[ a b ] [L(a), L(b)]
{ a = e; } { a: L(e) }
{ ${k} = e; } nix.attrsDyn({...}, [[S(k), L(e)]])
rec { a = e; } (() => { const a = L(e); return { a }; })()
e.a.b nix.sel(S(e), "a", "b")
e.a or d nix.selOr(S(e), () => S(d), "a")
e ? a.b nix.has(S(e), "a", "b")
x: e (x) => S(e)
{ a, b ? d, ... }: e nix.lambda((args) => { ... }, formals$n)
f a b nix.app(f, L(a), L(b))
builtins.map f xs nix.builtins.map(L(f), L(xs))
let a = e; in b (() => { const a = L(e); return S(b); })()
with e; b (() => { const w = nix.withScope(L(e), outer); return S(b); })()
assert c; b (nix.assert_(S(c), "file:line:col"), S(b))
if c then a else b (nix.asBool(S(c)) ? S(a) : S(b))
-e, !e nix.neg(S(e)), !nix.asBool(S(e))
a + b etc. nix.add(S(a), S(b)), sub, mul, div
a ++ b, a // b nix.concat(...), nix.update(...)
a == b, a < b nix.eq(...), nix.lt(...), le, gt, ge
a && b, a -> b (nix.asBool(S(a)) && nix.asBool(S(b))), (!nix.asBool(S(a)) || nix.asBool(S(b)))
__curPos { file, line, column }

A lambda whose body is a let, with or assert chain is emitted as a block body rather than nested immediately-invoked functions, so the common { lib, ... }: let ... in ... reads as a function with consts.

Lazy positions

L(e) is S(e) for literals, variables, lambdas and the empty list, and nix.thunk(() => S(e)) for everything else. A with-bound variable in a lazy position is nix.thunk(() => nix.lookup(w, "x")), because the set it comes from may not be forceable yet.

Variables

Nix binding TypeScript
lambda parameter const, holds a Lazy; read as nix.force(x)
formal a const a = nix.arg($, "a"), the attribute's thunk
formal a ? d const a = nix.argOr($, "a", () => S(d))
args@{...} const args = <the argument>
let / rec binding const; a value when L(e) is one, else a thunk
inherit x the outer x
inherit (e) x nix.thunk(() => nix.attr(e, "x"))
under with nix.lookup(w, "x")
global nix.builtins.x, or true, false, null

Names are mangled to JavaScript identifiers (- to _, ' to $, anything else to $ and a hex code), made unique against enclosing scopes, and kept away from JavaScript's reserved words.

Values at runtime

See design.md. In short: bigint, number, boolean, null, string | NixString, Path, Lazy[], plain objects, functions. nix.typeOf gives the Nix type name; nix.force turns a Lazy into a value.

Builtins

All 116 of Nix 2.35's builtins are present (src/runtime/builtins.ts), with their arities, so partial application works. Those that need impurity (fetchTarball, currentTime, getEnv, ...) behave as under --pure-eval or raise an error; constraints.md has the list. derivation is the Nix function from Nix's derivation.nix, transcribed; derivationStrict is in src/runtime/derivation.ts.

Edit this page on GitHub