From 210923fd92ab05f058c19ea49142cdaf2e037101 Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Fri, 2 Jan 2026 20:46:07 +0800 Subject: [PATCH] feat: rewrite runtime.js with typescript --- flake.nix | 4 + nix-js/Cargo.toml | 1 + nix-js/build.rs | 67 +++ nix-js/runtime-ts/.gitignore | 2 + nix-js/runtime-ts/package-lock.json | 496 +++++++++++++++++++ nix-js/runtime-ts/package.json | 14 + nix-js/runtime-ts/src/builtins/arithmetic.ts | 53 ++ nix-js/runtime-ts/src/builtins/attrs.ts | 67 +++ nix-js/runtime-ts/src/builtins/conversion.ts | 25 + nix-js/runtime-ts/src/builtins/functional.ts | 35 ++ nix-js/runtime-ts/src/builtins/index.ts | 169 +++++++ nix-js/runtime-ts/src/builtins/io.ts | 74 +++ nix-js/runtime-ts/src/builtins/list.ts | 102 ++++ nix-js/runtime-ts/src/builtins/math.ts | 18 + nix-js/runtime-ts/src/builtins/misc.ts | 109 ++++ nix-js/runtime-ts/src/builtins/string.ts | 35 ++ nix-js/runtime-ts/src/builtins/type-check.ts | 50 ++ nix-js/runtime-ts/src/helpers.ts | 99 ++++ nix-js/runtime-ts/src/index.ts | 34 ++ nix-js/runtime-ts/src/operators.ts | 91 ++++ nix-js/runtime-ts/src/thunk.ts | 79 +++ nix-js/runtime-ts/src/type-assert.ts | 127 +++++ nix-js/runtime-ts/src/types.ts | 55 ++ nix-js/runtime-ts/tsconfig.json | 21 + nix-js/src/codegen.rs | 11 +- nix-js/src/context.rs | 145 ++++++ nix-js/src/runtime.rs | 21 +- nix-js/src/runtime/runtime.js | 481 ------------------ 28 files changed, 1990 insertions(+), 495 deletions(-) create mode 100644 nix-js/build.rs create mode 100644 nix-js/runtime-ts/.gitignore create mode 100644 nix-js/runtime-ts/package-lock.json create mode 100644 nix-js/runtime-ts/package.json create mode 100644 nix-js/runtime-ts/src/builtins/arithmetic.ts create mode 100644 nix-js/runtime-ts/src/builtins/attrs.ts create mode 100644 nix-js/runtime-ts/src/builtins/conversion.ts create mode 100644 nix-js/runtime-ts/src/builtins/functional.ts create mode 100644 nix-js/runtime-ts/src/builtins/index.ts create mode 100644 nix-js/runtime-ts/src/builtins/io.ts create mode 100644 nix-js/runtime-ts/src/builtins/list.ts create mode 100644 nix-js/runtime-ts/src/builtins/math.ts create mode 100644 nix-js/runtime-ts/src/builtins/misc.ts create mode 100644 nix-js/runtime-ts/src/builtins/string.ts create mode 100644 nix-js/runtime-ts/src/builtins/type-check.ts create mode 100644 nix-js/runtime-ts/src/helpers.ts create mode 100644 nix-js/runtime-ts/src/index.ts create mode 100644 nix-js/runtime-ts/src/operators.ts create mode 100644 nix-js/runtime-ts/src/thunk.ts create mode 100644 nix-js/runtime-ts/src/type-assert.ts create mode 100644 nix-js/runtime-ts/src/types.ts create mode 100644 nix-js/runtime-ts/tsconfig.json delete mode 100644 nix-js/src/runtime/runtime.js diff --git a/flake.nix b/flake.nix index 37838d8..dffb1d1 100644 --- a/flake.nix +++ b/flake.nix @@ -25,6 +25,10 @@ lldb valgrind claude-code + + # Node.js tooling for TypeScript build + nodejs + nodePackages.npm ]; }; } diff --git a/nix-js/Cargo.toml b/nix-js/Cargo.toml index 27f8762..e629f80 100644 --- a/nix-js/Cargo.toml +++ b/nix-js/Cargo.toml @@ -2,6 +2,7 @@ name = "nix-js" version = "0.1.0" edition = "2024" +build = "build.rs" [dependencies] mimalloc = "0.1" diff --git a/nix-js/build.rs b/nix-js/build.rs new file mode 100644 index 0000000..33e13f5 --- /dev/null +++ b/nix-js/build.rs @@ -0,0 +1,67 @@ +use std::process::Command; +use std::path::Path; +use std::env; + +fn main() { + let runtime_ts_dir = Path::new("runtime-ts"); + let dist_runtime = runtime_ts_dir.join("dist/runtime.js"); + + if !runtime_ts_dir.exists() { + println!("cargo::warning=runtime-ts directory not found, using existing runtime.js"); + return; + } + + println!("cargo::rerun-if-changed=runtime-ts/src"); + println!("cargo::rerun-if-changed=runtime-ts/package.json"); + println!("cargo::rerun-if-changed=runtime-ts/tsconfig.json"); + + if !runtime_ts_dir.join("node_modules").exists() { + println!("Installing npm dependencies..."); + let npm_cmd = if cfg!(target_os = "windows") { "npm.cmd" } else { "npm" }; + let status = Command::new(npm_cmd) + .arg("install") + .current_dir(runtime_ts_dir) + .status() + .expect("Failed to run npm install. Is Node.js installed?"); + + if !status.success() { + panic!("npm install failed. Please check your Node.js installation."); + } + } + + println!("Running TypeScript type checking..."); + let npm_cmd = if cfg!(target_os = "windows") { "npm.cmd" } else { "npm" }; + let status = Command::new(npm_cmd) + .arg("run") + .arg("typecheck") + .current_dir(runtime_ts_dir) + .status() + .expect("Failed to run type checking"); + + if !status.success() { + panic!("TypeScript type checking failed! Fix type errors before building."); + } + + println!("Building runtime.js from TypeScript..."); + let status = Command::new(npm_cmd) + .arg("run") + .arg("build") + .current_dir(runtime_ts_dir) + .status() + .expect("Failed to build runtime"); + + if !status.success() { + panic!("Runtime build failed!"); + } + + if dist_runtime.exists() { + println!("Successfully built runtime.js",); + } else { + panic!("dist/runtime.js not found after build"); + } + + // Print build info + if env::var("CARGO_CFG_DEBUG_ASSERTIONS").is_ok() { + println!("Built runtime.js in DEBUG mode"); + } +} diff --git a/nix-js/runtime-ts/.gitignore b/nix-js/runtime-ts/.gitignore new file mode 100644 index 0000000..8225baa --- /dev/null +++ b/nix-js/runtime-ts/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/dist diff --git a/nix-js/runtime-ts/package-lock.json b/nix-js/runtime-ts/package-lock.json new file mode 100644 index 0000000..26b9dad --- /dev/null +++ b/nix-js/runtime-ts/package-lock.json @@ -0,0 +1,496 @@ +{ + "name": "nix-js-runtime", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nix-js-runtime", + "version": "0.1.0", + "devDependencies": { + "esbuild": "^0.24.2", + "typescript": "^5.7.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/nix-js/runtime-ts/package.json b/nix-js/runtime-ts/package.json new file mode 100644 index 0000000..76190fe --- /dev/null +++ b/nix-js/runtime-ts/package.json @@ -0,0 +1,14 @@ +{ + "name": "nix-js-runtime", + "version": "0.1.0", + "private": true, + "scripts": { + "typecheck": "tsc --noEmit", + "build": "esbuild src/index.ts --bundle --target=es2020 --format=iife --outfile=dist/runtime.js", + "dev": "npm run typecheck && npm run build" + }, + "devDependencies": { + "esbuild": "^0.24.2", + "typescript": "^5.7.2" + } +} diff --git a/nix-js/runtime-ts/src/builtins/arithmetic.ts b/nix-js/runtime-ts/src/builtins/arithmetic.ts new file mode 100644 index 0000000..43aac79 --- /dev/null +++ b/nix-js/runtime-ts/src/builtins/arithmetic.ts @@ -0,0 +1,53 @@ +/** + * Arithmetic builtin functions + */ + +import type { NixBool, NixInt, NixNumber, NixValue } from '../types'; +import { force_numeric, coerce_numeric, force_int } from '../type-assert'; + +export const add = (a: NixValue) => (b: NixValue): bigint | number => { + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + return (av as any) + (bv as any); +}; + +export const sub = (a: NixValue) => (b: NixValue): bigint | number => { + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + return (av as any) - (bv as any); +}; + +export const mul = (a: NixValue) => (b: NixValue): bigint | number => { + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + return (av as any) * (bv as any); +}; + +export const div = (a: NixValue) => (b: NixValue): NixNumber => { + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + + if (bv === 0 || bv === 0n) { + throw new RangeError('Division by zero'); + } + + return (av as any) / (bv as any) +}; + +// Bitwise operations - only for integers +export const bitAnd = (a: NixValue) => (b: NixValue): NixInt => { + const av = force_int(a); + const bv = force_int(b); + return av & bv; +}; + +export const bitOr = (a: NixValue) => (b: NixValue): NixInt => { + const av = force_int(a); + const bv = force_int(b); + return av | bv; +}; + +export const bitXor = (a: NixValue) => (b: NixValue): NixInt => { + const av = force_int(a); + const bv = force_int(b); + return av ^ bv; +}; + +export const lessThan = (a: NixValue) => (b: NixValue): NixBool => + force_numeric(a) < force_numeric(b); diff --git a/nix-js/runtime-ts/src/builtins/attrs.ts b/nix-js/runtime-ts/src/builtins/attrs.ts new file mode 100644 index 0000000..dcdc55c --- /dev/null +++ b/nix-js/runtime-ts/src/builtins/attrs.ts @@ -0,0 +1,67 @@ +/** + * Attribute set operation builtin functions + */ + +import type { NixValue, NixAttrs, NixList } from '../types'; +import { force_attrs, force_string, force_function, force_list } from '../type-assert'; + +export const attrNames = (set: NixValue): string[] => Object.keys(force_attrs(set)); + +export const attrValues = (set: NixValue): NixValue[] => Object.values(force_attrs(set)); + +export const getAttr = (s: NixValue) => (set: NixValue): NixValue => + force_attrs(set)[force_string(s)]; + +export const hasAttr = (s: NixValue) => (set: NixValue): boolean => + Object.prototype.hasOwnProperty.call(force_attrs(set), force_string(s)); + +export const mapAttrs = (f: NixValue) => (attrs: NixValue): NixAttrs => { + const new_attrs: NixAttrs = {}; + const forced_attrs = force_attrs(attrs); + const forced_f = force_function(f); + for (const key in forced_attrs) { + new_attrs[key] = forced_f(key)(forced_attrs[key]); + } + return new_attrs; +}; + +export const listToAttrs = (e: NixValue): NixAttrs => { + const attrs: NixAttrs = {}; + const forced_e = [...force_list(e)].reverse(); + for (const obj of forced_e) { + const item = force_attrs(obj); + attrs[force_string(item.name)] = item.value; + } + return attrs; +}; + +export const intersectAttrs = (e1: NixValue) => (e2: NixValue): NixAttrs => { + const f1 = force_attrs(e1); + const f2 = force_attrs(e2); + const attrs: NixAttrs = {}; + for (const key of Object.keys(f2)) { + if (Object.prototype.hasOwnProperty.call(f1, key)) { + attrs[key] = f2[key]; + } + } + return attrs; +}; + +export const catAttrs = (attr: NixValue) => (list: NixValue): NixList => { + const key = force_string(attr); + return force_list(list) + .map((set) => force_attrs(set)[key]) + .filter((val) => val !== undefined); +}; + +export const groupBy = (f: NixValue) => (list: NixValue): NixAttrs => { + const attrs: NixAttrs = {}; + const forced_f = force_function(f); + const forced_list = force_list(list); + for (const elem of forced_list) { + const key = force_string(forced_f(elem)); + if (!attrs[key]) attrs[key] = []; + (attrs[key] as NixList).push(elem); + } + return attrs; +}; diff --git a/nix-js/runtime-ts/src/builtins/conversion.ts b/nix-js/runtime-ts/src/builtins/conversion.ts new file mode 100644 index 0000000..d7e8664 --- /dev/null +++ b/nix-js/runtime-ts/src/builtins/conversion.ts @@ -0,0 +1,25 @@ +/** + * Conversion and serialization builtin functions (unimplemented) + */ + +import type { NixValue } from '../types'; + +export const fromJSON = (e: NixValue): never => { + throw 'Not implemented: fromJSON'; +}; + +export const fromTOML = (e: NixValue): never => { + throw 'Not implemented: fromTOML'; +}; + +export const toJSON = (e: NixValue): never => { + throw 'Not implemented: toJSON'; +}; + +export const toXML = (e: NixValue): never => { + throw 'Not implemented: toXML'; +}; + +export const toString = (name: NixValue, s: NixValue): never => { + throw 'Not implemented: toString'; +}; diff --git a/nix-js/runtime-ts/src/builtins/functional.ts b/nix-js/runtime-ts/src/builtins/functional.ts new file mode 100644 index 0000000..5983938 --- /dev/null +++ b/nix-js/runtime-ts/src/builtins/functional.ts @@ -0,0 +1,35 @@ +/** + * Functional programming builtin functions + */ + +import type { NixValue } from '../types'; +import { force } from '../thunk'; + +export const seq = (e1: NixValue) => (e2: NixValue): NixValue => { + force(e1); // Force evaluation of e1 + return e2; +}; + +export const deepSeq = (e1: NixValue) => (e2: NixValue): never => { + throw 'Not implemented: deepSeq'; +}; + +export const abort = (s: NixValue): never => { + throw `evaluation aborted with the following error message: '${force(s)}'`; +}; + +export const throwFunc = (s: NixValue): never => { + throw force(s); +}; + +export const trace = (e1: NixValue, e2: NixValue): NixValue => { + console.log(`trace: ${force(e1)}`); + return e2; +}; + +export const warn = (e1: NixValue) => (e2: NixValue): NixValue => { + console.log(`evaluation warning: ${force(e1)}`); + return e2; +}; + +export const breakFunc = (v: NixValue): NixValue => v; diff --git a/nix-js/runtime-ts/src/builtins/index.ts b/nix-js/runtime-ts/src/builtins/index.ts new file mode 100644 index 0000000..dbab617 --- /dev/null +++ b/nix-js/runtime-ts/src/builtins/index.ts @@ -0,0 +1,169 @@ +/** + * Main builtins export + * Combines all builtin function categories into the global `builtins` object + */ + +import { create_thunk } from '../thunk'; + +// Import all builtin categories +import * as arithmetic from './arithmetic'; +import * as math from './math'; +import * as typeCheck from './type-check'; +import * as list from './list'; +import * as attrs from './attrs'; +import * as string from './string'; +import * as functional from './functional'; +import * as io from './io'; +import * as conversion from './conversion'; +import * as misc from './misc'; + +/** + * The global builtins object + * Contains 80+ Nix builtin functions plus metadata + * + * All functions are curried for Nix semantics: + * - Single argument functions: (a) => result + * - Multi-argument functions: (a) => (b) => result + */ +export const builtins: any = { + // Arithmetic (curried binary functions) + add: arithmetic.add, + sub: arithmetic.sub, + mul: arithmetic.mul, + div: arithmetic.div, + bitAnd: arithmetic.bitAnd, + bitOr: arithmetic.bitOr, + bitXor: arithmetic.bitXor, + lessThan: arithmetic.lessThan, + + // Math + ceil: math.ceil, + floor: math.floor, + + // Type checking + isAttrs: typeCheck.isAttrs, + isBool: typeCheck.isBool, + isFloat: typeCheck.isFloat, + isFunction: typeCheck.isFunction, + isInt: typeCheck.isInt, + isList: typeCheck.isList, + isNull: typeCheck.isNull, + isPath: typeCheck.isPath, + isString: typeCheck.isString, + typeOf: typeCheck.typeOf, + + // List operations + map: list.map, + filter: list.filter, + length: list.length, + head: list.head, + tail: list.tail, + elem: list.elem, + elemAt: list.elemAt, + concatLists: list.concatLists, + concatMap: list.concatMap, + 'foldl\'': list.foldlPrime, + sort: list.sort, + partition: list.partition, + genList: list.genList, + all: list.all, + any: list.any, + + // Attribute set operations + attrNames: attrs.attrNames, + attrValues: attrs.attrValues, + getAttr: attrs.getAttr, + hasAttr: attrs.hasAttr, + mapAttrs: attrs.mapAttrs, + listToAttrs: attrs.listToAttrs, + intersectAttrs: attrs.intersectAttrs, + catAttrs: attrs.catAttrs, + groupBy: attrs.groupBy, + + // String operations + stringLength: string.stringLength, + substring: string.substring, + concatStringsSep: string.concatStringsSep, + baseNameOf: string.baseNameOf, + + // Functional + seq: functional.seq, + deepSeq: functional.deepSeq, + abort: functional.abort, + throw: functional.throwFunc, + trace: functional.trace, + warn: functional.warn, + break: functional.breakFunc, + + // I/O (unimplemented) + import: io.importFunc, + scopedImport: io.scopedImport, + fetchClosure: io.fetchClosure, + fetchGit: io.fetchGit, + fetchTarball: io.fetchTarball, + fetchTree: io.fetchTree, + fetchurl: io.fetchurl, + readDir: io.readDir, + readFile: io.readFile, + readFileType: io.readFileType, + pathExists: io.pathExists, + path: io.path, + toFile: io.toFile, + toPath: io.toPath, + filterSource: io.filterSource, + findFile: io.findFile, + getEnv: io.getEnv, + + // Conversion (unimplemented) + fromJSON: conversion.fromJSON, + fromTOML: conversion.fromTOML, + toJSON: conversion.toJSON, + toXML: conversion.toXML, + toString: conversion.toString, + + // Miscellaneous (unimplemented) + getContext: misc.getContext, + hasContext: misc.hasContext, + hashFile: misc.hashFile, + hashString: misc.hashString, + convertHash: misc.convertHash, + unsafeDiscardOutputDependency: misc.unsafeDiscardOutputDependency, + unsafeDiscardStringContext: misc.unsafeDiscardStringContext, + unsafeGetAttrPos: misc.unsafeGetAttrPos, + addDrvOutputDependencies: misc.addDrvOutputDependencies, + compareVersions: misc.compareVersions, + dirOf: misc.dirOf, + flakeRefToString: misc.flakeRefToString, + functionArgs: misc.functionArgs, + genericClosure: misc.genericClosure, + getFlake: misc.getFlake, + match: misc.match, + outputOf: misc.outputOf, + parseDrvName: misc.parseDrvName, + parseFlakeName: misc.parseFlakeName, + placeholder: misc.placeholder, + replaceStrings: misc.replaceStrings, + split: misc.split, + splitVersion: misc.splitVersion, + traceVerbose: misc.traceVerbose, + tryEval: misc.tryEval, + zipAttrsWith: misc.zipAttrsWith, + + // Meta - self-reference and constants + builtins: create_thunk(() => builtins), // Recursive reference + currentSystem: create_thunk(() => { + throw 'Not implemented: currentSystem'; + }), + currentTime: create_thunk(() => Date.now()), + + // Constants (special keys) + false: false, + true: true, + null: null, + + // Version information + langVersion: 6, + nixPath: [], + nixVersion: 'NIX_JS_VERSION', + storeDir: '/nix/store', +}; diff --git a/nix-js/runtime-ts/src/builtins/io.ts b/nix-js/runtime-ts/src/builtins/io.ts new file mode 100644 index 0000000..571b2cf --- /dev/null +++ b/nix-js/runtime-ts/src/builtins/io.ts @@ -0,0 +1,74 @@ +/** + * I/O and filesystem builtin functions (unimplemented) + * These functions require Node.js/Deno APIs not available in V8 + */ + +import type { NixValue } from '../types'; + +export const importFunc = (path: NixValue): never => { + throw 'Not implemented: import'; +}; + +export const scopedImport = (scope: NixValue) => (path: NixValue): never => { + throw 'Not implemented: scopedImport'; +}; + +export const fetchClosure = (args: NixValue): never => { + throw 'Not implemented: fetchClosure'; +}; + +export const fetchGit = (args: NixValue): never => { + throw 'Not implemented: fetchGit'; +}; + +export const fetchTarball = (args: NixValue): never => { + throw 'Not implemented: fetchTarball'; +}; + +export const fetchTree = (args: NixValue): never => { + throw 'Not implemented: fetchTree'; +}; + +export const fetchurl = (args: NixValue): never => { + throw 'Not implemented: fetchurl'; +}; + +export const readDir = (path: NixValue): never => { + throw 'Not implemented: readDir'; +}; + +export const readFile = (path: NixValue): never => { + throw 'Not implemented: readFile'; +}; + +export const readFileType = (path: NixValue): never => { + throw 'Not implemented: readFileType'; +}; + +export const pathExists = (path: NixValue): never => { + throw 'Not implemented: pathExists'; +}; + +export const path = (args: NixValue): never => { + throw 'Not implemented: path'; +}; + +export const toFile = (name: NixValue, s: NixValue): never => { + throw 'Not implemented: toFile'; +}; + +export const toPath = (name: NixValue, s: NixValue): never => { + throw 'Not implemented: toPath'; +}; + +export const filterSource = (args: NixValue): never => { + throw 'Not implemented: filterSource'; +}; + +export const findFile = (search: NixValue) => (lookup: NixValue): never => { + throw 'Not implemented: findFile'; +}; + +export const getEnv = (s: NixValue): never => { + throw 'Not implemented: getEnv'; +}; diff --git a/nix-js/runtime-ts/src/builtins/list.ts b/nix-js/runtime-ts/src/builtins/list.ts new file mode 100644 index 0000000..76f85d1 --- /dev/null +++ b/nix-js/runtime-ts/src/builtins/list.ts @@ -0,0 +1,102 @@ +/** + * List operation builtin functions + * All functions are properly curried + */ + +import type { NixValue, NixList, NixAttrs } from '../types'; +import { force } from '../thunk'; +import { force_list, force_function, force_numeric, force_int } from '../type-assert'; + +export const map = (f: NixValue) => (list: NixValue): NixList => + force_list(list).map(force_function(f)); + +export const filter = (f: NixValue) => (list: NixValue): NixList => + force_list(list).filter(force_function(f)); + +export const length = (e: NixValue): bigint => { + const forced = force(e); + if (typeof forced === 'string') return BigInt(forced.length); + return BigInt(force_list(forced).length); +}; + +export const head = (list: NixValue): NixValue => force_list(list)[0]; + +export const tail = (list: NixValue): NixList => force_list(list).slice(1); + +export const elem = (x: NixValue) => (xs: NixValue): boolean => + force_list(xs).includes(force(x)); + +export const elemAt = (xs: NixValue) => (n: NixValue): NixValue => { + const list = force_list(xs); + const idx = Number(force_int(n)); + + if (idx < 0 || idx >= list.length) { + throw new RangeError(`Index ${idx} out of bounds for list of length ${list.length}`); + } + + return list[idx]; +}; + +export const concatLists = (lists: NixValue): NixList => { + return force_list(lists).reduce((acc: NixList, cur: NixValue) => { + return acc.concat(force_list(cur)); + }, []); +}; + +export const concatMap = (f: NixValue) => (lists: NixValue): NixList => { + const fn = force_function(f); + return force_list(lists).reduce((acc: NixList, cur: NixValue) => { + return acc.concat(force(fn(cur)) as NixList); + }, []); +}; + +export const foldlPrime = (op_fn: NixValue) => (nul: NixValue) => (list: NixValue): NixValue => { + const forced_op = force_function(op_fn); + return force_list(list).reduce((acc: NixValue, cur: NixValue) => { + return forced_op(acc)(cur); + }, nul); +}; + +export const sort = (cmp: NixValue) => (list: NixValue): NixList => { + const forced_list = [...force_list(list)]; + const forced_cmp = force_function(cmp); + return forced_list.sort((a, b) => { + if (force(forced_cmp(a)(b))) return -1; + if (force(forced_cmp(b)(a))) return 1; + return 0; + }); +}; + +export const partition = (pred: NixValue) => (list: NixValue): NixAttrs => { + const forced_list = force_list(list); + const forced_pred = force_function(pred); + const attrs: NixAttrs = { + right: [], + wrong: [], + }; + for (const elem of forced_list) { + if (force(forced_pred(elem))) { + (attrs.right as NixList).push(elem); + } else { + (attrs.wrong as NixList).push(elem); + } + } + return attrs; +}; + +export const genList = (f: NixValue) => (len: NixValue): NixList => { + const func = force_function(f); + const length = force_int(len); + + if (length < 0) { + throw new TypeError(`genList length must be non-negative integer, got ${length}`); + } + + return [...Array(Number(length)).keys()].map(i => func(BigInt(i))); +}; + +export const all = (pred: NixValue) => (list: NixValue): boolean => + force_list(list).every(force_function(pred)); + +export const any = (pred: NixValue) => (list: NixValue): boolean => + force_list(list).some(force_function(pred)); diff --git a/nix-js/runtime-ts/src/builtins/math.ts b/nix-js/runtime-ts/src/builtins/math.ts new file mode 100644 index 0000000..de8e7a4 --- /dev/null +++ b/nix-js/runtime-ts/src/builtins/math.ts @@ -0,0 +1,18 @@ +/** + * Math builtin functions + */ + +import type { NixValue } from '../types'; +import { force_numeric } from '../type-assert'; + +export const ceil = (x: NixValue): bigint => { + const val = force_numeric(x); + if (typeof val === 'bigint') return val; // Already an integer + return BigInt(Math.ceil(val)); // Convert to integer +}; + +export const floor = (x: NixValue): bigint => { + const val = force_numeric(x); + if (typeof val === 'bigint') return val; // Already an integer + return BigInt(Math.floor(val)); // Convert to integer +}; diff --git a/nix-js/runtime-ts/src/builtins/misc.ts b/nix-js/runtime-ts/src/builtins/misc.ts new file mode 100644 index 0000000..e2d857d --- /dev/null +++ b/nix-js/runtime-ts/src/builtins/misc.ts @@ -0,0 +1,109 @@ +/** + * Miscellaneous unimplemented builtin functions + */ + +import type { NixValue } from '../types'; + +export const getContext = (s: NixValue): never => { + throw 'Not implemented: getContext'; +}; + +export const hasContext = (s: NixValue): never => { + throw 'Not implemented: hasContext'; +}; + +export const hashFile = (type: NixValue) => (p: NixValue): never => { + throw 'Not implemented: hashFile'; +}; + +export const hashString = (type: NixValue) => (p: NixValue): never => { + throw 'Not implemented: hashString'; +}; + +export const convertHash = (args: NixValue): never => { + throw 'Not implemented: convertHash'; +}; + +export const unsafeDiscardOutputDependency = (s: NixValue): never => { + throw 'Not implemented: unsafeDiscardOutputDependency'; +}; + +export const unsafeDiscardStringContext = (s: NixValue): never => { + throw 'Not implemented: unsafeDiscardStringContext'; +}; + +export const unsafeGetAttrPos = (s: NixValue): never => { + throw 'Not implemented: unsafeGetAttrPos'; +}; + +export const addDrvOutputDependencies = (s: NixValue): never => { + throw 'Not implemented: addDrvOutputDependencies'; +}; + +export const compareVersions = (s1: NixValue) => (s2: NixValue): never => { + throw 'Not implemented: compareVersions'; +}; + +export const dirOf = (s: NixValue): never => { + throw 'Not implemented: dirOf'; +}; + +export const flakeRefToString = (attrs: NixValue): never => { + throw 'Not implemented: flakeRefToString'; +}; + +export const functionArgs = (f: NixValue): never => { + throw 'Not implemented: functionArgs'; +}; + +export const genericClosure = (args: NixValue): never => { + throw 'Not implemented: genericClosure'; +}; + +export const getFlake = (attrs: NixValue): never => { + throw 'Not implemented: getFlake'; +}; + +export const match = (regex: NixValue) => (str: NixValue): never => { + throw 'Not implemented: match'; +}; + +export const outputOf = (drv: NixValue) => (out: NixValue): never => { + throw 'Not implemented: outputOf'; +}; + +export const parseDrvName = (s: NixValue): never => { + throw 'Not implemented: parseDrvName'; +}; + +export const parseFlakeName = (s: NixValue): never => { + throw 'Not implemented: parseFlakeName'; +}; + +export const placeholder = (output: NixValue): never => { + throw 'Not implemented: placeholder'; +}; + +export const replaceStrings = (from: NixValue) => (to: NixValue) => (s: NixValue): never => { + throw 'Not implemented: replaceStrings'; +}; + +export const split = (regex: NixValue, str: NixValue): never => { + throw 'Not implemented: split'; +}; + +export const splitVersion = (s: NixValue): never => { + throw 'Not implemented: splitVersion'; +}; + +export const traceVerbose = (e1: NixValue, e2: NixValue): never => { + throw 'Not implemented: traceVerbose'; +}; + +export const tryEval = (e1: NixValue) => (e2: NixValue): never => { + throw 'Not implemented: tryEval'; +}; + +export const zipAttrsWith = (f: NixValue) => (list: NixValue): never => { + throw 'Not implemented: zipAttrsWith'; +}; diff --git a/nix-js/runtime-ts/src/builtins/string.ts b/nix-js/runtime-ts/src/builtins/string.ts new file mode 100644 index 0000000..ac6d9a1 --- /dev/null +++ b/nix-js/runtime-ts/src/builtins/string.ts @@ -0,0 +1,35 @@ +/** + * String operation builtin functions + */ + +import type { NixValue } from '../types'; +import { force_string, force_list, force_int } from '../type-assert'; + +export const stringLength = (e: NixValue): number => force_string(e).length; + +export const substring = (start: NixValue) => (len: NixValue) => (s: NixValue): string => { + const str = force_string(s); + const startPos = Number(force_int(start)); + const length = Number(force_int(len)); + return str.substring(startPos, startPos + length); +}; + +export const concatStringsSep = (sep: NixValue) => (list: NixValue): string => + force_list(list).join(force_string(sep)); + +export const baseNameOf = (x: NixValue): string => { + const str = force_string(x); + if (str.length === 0) return ''; + + let last = str.length - 1; + if (str[last] === '/' && last > 0) last -= 1; + + let pos = last; + while (pos >= 0 && str[pos] !== '/') pos -= 1; + + if (pos !== 0 || (pos === 0 && str[pos] === '/')) { + pos += 1; + } + + return str.substring(pos, last + 1); +}; diff --git a/nix-js/runtime-ts/src/builtins/type-check.ts b/nix-js/runtime-ts/src/builtins/type-check.ts new file mode 100644 index 0000000..f9ff4f3 --- /dev/null +++ b/nix-js/runtime-ts/src/builtins/type-check.ts @@ -0,0 +1,50 @@ +/** + * Type checking builtin functions + */ + +import type { NixAttrs, NixBool, NixFloat, NixFunction, NixInt, NixList, NixNull, NixString, NixValue } from '../types'; +import { force } from '../thunk'; + +export const isAttrs = (e: NixValue): e is NixAttrs => { + const val = force(e); + return typeof val === 'object' && !Array.isArray(val) && val !== null; +}; + +export const isBool = (e: NixValue): e is NixBool => typeof force(e) === 'boolean'; + +export const isFloat = (e: NixValue): e is NixFloat => { + const val = force(e); + return typeof val === 'number'; // Only number is float +}; + +export const isFunction = (e: NixValue): e is NixFunction => typeof force(e) === 'function'; + +export const isInt = (e: NixValue): e is NixInt => { + const val = force(e); + return typeof val === 'bigint'; // Only bigint is int +}; + +export const isList = (e: NixValue): e is NixList => Array.isArray(force(e)); + +export const isNull = (e: NixValue): e is NixNull => force(e) === null; + +export const isPath = (e: NixValue): never => { + throw 'Not implemented: isPath'; +}; + +export const isString = (e: NixValue): e is NixString => typeof force(e) === 'string'; + +export const typeOf = (e: NixValue): string => { + const val = force(e); + + if (typeof val === 'bigint') return 'int'; + if (typeof val === 'number') return 'float'; + if (typeof val === 'boolean') return 'bool'; + if (typeof val === 'string') return 'string'; + if (val === null) return 'null'; + if (Array.isArray(val)) return 'list'; + if (typeof val === 'function') return 'lambda'; + if (typeof val === 'object') return 'set'; + + throw new TypeError(`Unknown Nix type: ${typeof val}`); +}; diff --git a/nix-js/runtime-ts/src/helpers.ts b/nix-js/runtime-ts/src/helpers.ts new file mode 100644 index 0000000..959e9ac --- /dev/null +++ b/nix-js/runtime-ts/src/helpers.ts @@ -0,0 +1,99 @@ +/** + * Helper functions for nix-js runtime + * Implements attribute selection, parameter validation, and lazy sets + */ + +import type { NixValue, NixAttrs } from './types'; +import { force_attrs, force_string } from './type-assert'; + +/** + * Select an attribute from an attribute set + * Used by codegen for attribute access (e.g., obj.key) + * + * @param obj - Attribute set to select from + * @param key - Key to select + * @returns The value at obj[key] + * @throws Error if obj is null/undefined or key not found + */ +export const select = (obj: NixValue, key: NixValue): NixValue => { + const forced_obj = force_attrs(obj); + const forced_key = force_string(key); + + if (!(forced_key in forced_obj)) { + throw new Error(`Attribute '${forced_key}' not found`); + } + + return forced_obj[forced_key]; +}; + +/** + * Select an attribute with a default value + * Used for Nix's `obj.key or default` syntax + * + * @param obj - Attribute set to select from + * @param key - Key to select + * @param default_val - Value to return if key not found + * @returns obj[key] if exists, otherwise default_val + */ +export const select_with_default = ( + obj: NixValue, + key: NixValue, + default_val: NixValue +): NixValue => { + const forced_obj = force_attrs(obj); + const forced_key = force_string(key); + + if (forced_obj === null || forced_obj === undefined) { + return default_val; + } + + const attrs = forced_obj; + if (!(forced_key in attrs)) { + return default_val; + } + + return attrs[forced_key]; +}; + +/** + * Validate function parameters + * Used for pattern matching in function parameters + * + * Example: { a, b ? 1, ... }: ... + * - required: ["a"] + * - allowed: ["a", "b"] (or null if ellipsis "..." present) + * + * @param arg - Argument object to validate + * @param required - Array of required parameter names (or null) + * @param allowed - Array of allowed parameter names (or null for ellipsis) + * @returns The forced argument object + * @throws Error if required param missing or unexpected param present + */ +export const validate_params = ( + arg: NixValue, + required: string[] | null, + allowed: string[] | null +): NixAttrs => { + const forced_arg = force_attrs(arg); + + // Check required parameters + if (required) { + for (const key of required) { + if (!Object.prototype.hasOwnProperty.call(forced_arg, key)) { + throw new Error(`Function called without required argument '${key}'`); + } + } + } + + // Check allowed parameters (if not using ellipsis) + if (allowed) { + const allowed_set = new Set(allowed); + for (const key in forced_arg) { + if (!allowed_set.has(key)) { + throw new Error(`Function called with unexpected argument '${key}'`); + } + } + } + + return forced_arg; +}; diff --git a/nix-js/runtime-ts/src/index.ts b/nix-js/runtime-ts/src/index.ts new file mode 100644 index 0000000..63e5214 --- /dev/null +++ b/nix-js/runtime-ts/src/index.ts @@ -0,0 +1,34 @@ +/** + * nix-js Runtime Entry Point + * + * All functionality is exported via the global `Nix` object + */ + +import { create_thunk, force, is_thunk, IS_THUNK } from './thunk'; +import { select, select_with_default, validate_params } from './helpers'; +import { op } from './operators'; +import { builtins } from './builtins'; + +export type NixRuntime = typeof Nix; + +/** + * The global Nix runtime object + */ +export const Nix = { + create_thunk, + force, + is_thunk, + IS_THUNK, + + select, + select_with_default, + validate_params, + + op, + builtins, +}; + +globalThis.Nix = Nix; +declare global { + var Nix: NixRuntime +} diff --git a/nix-js/runtime-ts/src/operators.ts b/nix-js/runtime-ts/src/operators.ts new file mode 100644 index 0000000..430a0c1 --- /dev/null +++ b/nix-js/runtime-ts/src/operators.ts @@ -0,0 +1,91 @@ +/** + * Nix operators module + * Implements all binary and unary operators used by codegen + */ + +import type { NixValue, NixList, NixAttrs } from './types'; +import { force } from './thunk'; +import { force_numeric, force_list, force_attrs, coerce_numeric } from './type-assert'; + +/** + * Operator object exported as Nix.op + * All operators referenced by codegen (e.g., Nix.op.add, Nix.op.eq) + */ +export const op = { + // Arithmetic operators - preserve int/float distinction + add: (a: NixValue, b: NixValue): bigint | number => { + // FIXME: String & Path + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + return (av as any) + (bv as any); + }, + + sub: (a: NixValue, b: NixValue): bigint | number => { + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + return (av as any) - (bv as any); + }, + + mul: (a: NixValue, b: NixValue): bigint | number => { + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + return (av as any) * (bv as any); + }, + + div: (a: NixValue, b: NixValue): bigint | number => { + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + + if (bv === 0 || bv === 0n) { + throw new RangeError('Division by zero'); + } + + return (av as any) / (bv as any) + }, + + // Comparison operators (JavaScript natively supports bigint/number mixed comparison) + eq: (a: NixValue, b: NixValue): boolean => { + // FIXME: Int and Float + const av = force(a); + const bv = force(b); + return av === bv + }, + neq: (a: NixValue, b: NixValue): boolean => { + // FIXME: Int and Float + const av = force(a); + const bv = force(b); + return av !== bv + }, + lt: (a: NixValue, b: NixValue): boolean => { + // FIXME: Non-numeric + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + return (av as any) < (bv as any); + }, + lte: (a: NixValue, b: NixValue): boolean => { + // FIXME: Non-numeric + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + return (av as any) <= (bv as any); + }, + gt: (a: NixValue, b: NixValue): boolean => { + // FIXME: Non-numeric + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + return (av as any) > (bv as any); + }, + gte: (a: NixValue, b: NixValue): boolean => { + // FIXME: Non-numeric + const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b)); + return (av as any) >= (bv as any); + }, + + // Boolean operators + bnot: (a: NixValue): boolean => !force(a), + // Non-short-circuit + // band: (a: NixValue, b: NixValue): boolean => !!(force(a) && force(b)), + // bor: (a: NixValue, b: NixValue): boolean => !!(force(a) || force(b)), + + // List concatenation + concat: (a: NixValue, b: NixValue): NixList => { + return Array.prototype.concat.call(force_list(a), force_list(b)); + }, + + // Attribute set update (merge) + update: (a: NixValue, b: NixValue): NixAttrs => { + return { ...force_attrs(a), ...force_attrs(b) }; + }, +}; diff --git a/nix-js/runtime-ts/src/thunk.ts b/nix-js/runtime-ts/src/thunk.ts new file mode 100644 index 0000000..04ecc00 --- /dev/null +++ b/nix-js/runtime-ts/src/thunk.ts @@ -0,0 +1,79 @@ +/** + * Lazy evaluation system for nix-js + * Implements thunks for lazy evaluation of Nix expressions + */ + +import type { NixValue, NixThunkInterface } from './types'; + +/** + * Symbol used to mark objects as thunks + * This is exported to Rust via Nix.IS_THUNK + */ +export const IS_THUNK = Symbol('is_thunk'); + +/** + * NixThunk class - represents a lazy, unevaluated expression + * + * A thunk wraps a function that produces a value when called. + * Once evaluated, the result is cached to avoid recomputation. + */ +export class NixThunk implements NixThunkInterface { + [key: symbol]: any; + readonly [IS_THUNK] = true as const; + func: (() => NixValue) | null; + result: Exclude | null; + + constructor(func: () => NixValue) { + this.func = func; + this.result = null; + } +} + +/** + * Type guard to check if a value is a thunk + * @param value - Value to check + * @returns true if value is a NixThunk + */ +export const is_thunk = (value: unknown): value is NixThunkInterface => { + return ( + value !== null && + typeof value === 'object' && + IS_THUNK in value && + value[IS_THUNK] === true + ); +}; + +/** + * Force evaluation of a value + * If the value is a thunk, evaluate it and cache the result + * If already evaluated or not a thunk, return as-is + * + * @param value - Value to force (may be a thunk) + * @returns The forced/evaluated value + */ +export const force = (value: NixValue): Exclude => { + if (!is_thunk(value)) { + return value; + } + + // Already evaluated - return cached result + if (value.func === null) { + return value.result!; + } + + // Evaluate and cache + const result = force(value.func()); + value.result = result; + value.func = null; + + return result; +}; + +/** + * Create a new thunk from a function + * @param func - Function that produces a value when called + * @returns A new NixThunk wrapping the function + */ +export const create_thunk = (func: () => NixValue): NixThunkInterface => { + return new NixThunk(func); +}; diff --git a/nix-js/runtime-ts/src/type-assert.ts b/nix-js/runtime-ts/src/type-assert.ts new file mode 100644 index 0000000..ce251dc --- /dev/null +++ b/nix-js/runtime-ts/src/type-assert.ts @@ -0,0 +1,127 @@ +/** + * Type assertion helpers for runtime type checking + * These functions force evaluation and verify the type, throwing errors on mismatch + */ + +import type { NixValue, NixList, NixAttrs, NixFunction, NixInt, NixFloat, NixNumber } from './types'; +import { force } from './thunk'; + +/** + * Force a value and assert it's a list + * @throws TypeError if value is not a list after forcing + */ +export const force_list = (value: NixValue): NixList => { + const forced = force(value); + if (!Array.isArray(forced)) { + throw new TypeError(`Expected list, got ${typeof forced}`); + } + return forced; +}; + +/** + * Force a value and assert it's a function + * @throws TypeError if value is not a function after forcing + */ +export const force_function = (value: NixValue): NixFunction => { + const forced = force(value); + if (typeof forced !== 'function') { + throw new TypeError(`Expected function, got ${typeof forced}`); + } + return forced; +}; + +/** + * Force a value and assert it's an attribute set + * @throws TypeError if value is not an attribute set after forcing + */ +export const force_attrs = (value: NixValue): NixAttrs => { + const forced = force(value); + if (typeof forced !== 'object' || forced === null || Array.isArray(forced)) { + throw new TypeError(`Expected attribute set, got ${typeof forced}`); + } + return forced as NixAttrs; +}; + +/** + * Force a value and assert it's a string + * @throws TypeError if value is not a string after forcing + */ +export const force_string = (value: NixValue): string => { + const forced = force(value); + if (typeof forced !== 'string') { + throw new TypeError(`Expected string, got ${typeof forced}`); + } + return forced; +}; + +/** + * Force a value and assert it's a boolean + * @throws TypeError if value is not a boolean after forcing + */ +export const force_bool = (value: NixValue): boolean => { + const forced = force(value); + if (typeof forced !== 'boolean') { + throw new TypeError(`Expected boolean, got ${typeof forced}`); + } + return forced; +}; + +/** + * Force a value and extract int value + * @throws TypeError if value is not an int + */ +export const force_int = (value: NixValue): NixInt => { + const forced = force(value); + if (typeof forced === 'bigint') { + return forced; + } + throw new TypeError(`Expected int, got ${typeof forced}`); +}; + +/** + * Force a value and extract float value + * @throws TypeError if value is not a float + */ +export const force_float = (value: NixValue): NixFloat => { + const forced = force(value); + if (typeof forced === 'number') { + return forced; + } + throw new TypeError(`Expected float, got ${typeof forced}`); +}; + +/** + * Force a value and extract numeric value (int or float) + * @throws TypeError if value is not a numeric type + */ +export const force_numeric = (value: NixValue): NixNumber => { + const forced = force(value); + if (typeof forced === 'bigint' || typeof forced === 'number') { + return forced; + } + throw new TypeError(`Expected numeric type, got ${typeof forced}`); +}; + +/** + * Coerce two numeric values to a common type for arithmetic + * Rule: If either is float, convert both to float; otherwise keep as bigint + * @returns [a, b] tuple of coerced values + */ +export const coerce_numeric = ( + a: NixNumber, + b: NixNumber +): [NixFloat, NixFloat] | [NixInt, NixInt] => { + const aIsInt = typeof a === 'bigint'; + const bIsInt = typeof b === 'bigint'; + + // If either is float, convert both to float + if (!aIsInt || !bIsInt) { + return [ + aIsInt ? Number(a) : a, + bIsInt ? Number(b) : b + ]; + } + + // Both are integers + return [a, b]; +}; diff --git a/nix-js/runtime-ts/src/types.ts b/nix-js/runtime-ts/src/types.ts new file mode 100644 index 0000000..df56761 --- /dev/null +++ b/nix-js/runtime-ts/src/types.ts @@ -0,0 +1,55 @@ +/** + * Core TypeScript type definitions for nix-js runtime + */ + +// Nix primitive types +export type NixInt = bigint; +export type NixFloat = number; +export type NixNumber = NixInt | NixFloat; +export type NixBool = boolean; +export type NixString = string; +export type NixNull = null; + +// Nix composite types +export type NixList = NixValue[]; +export type NixAttrs = { [key: string]: NixValue }; +export type NixFunction = (...args: any[]) => any; + +/** + * Interface for lazy thunk values + * Thunks delay evaluation until forced + */ +export interface NixThunkInterface { + readonly [key: symbol]: true; // IS_THUNK marker + func: (() => NixValue) | null; + result: Exclude | null; +} + +// Union of all Nix primitive types +export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString; + +/** + * NixValue: Union type representing any possible Nix value + * This is the core type used throughout the runtime + */ +export type NixValue = + | NixPrimitive + | NixList + | NixAttrs + | NixFunction + | NixThunkInterface; + +// Operator function signatures +export type BinaryOp = (a: T, b: U) => R; +export type UnaryOp = (a: T) => R; + +/** + * Curried function types - All Nix builtins must be curried! + * + * Examples: + * - add: Curried2 = (a) => (b) => a + b + * - map: Curried2 = (f) => (list) => list.map(f) + */ +export type Curried2 = (a: A) => (b: B) => R; +export type Curried3 = (a: A) => (b: B) => (c: C) => R; +export type Curried4 = (a: A) => (b: B) => (c: C) => (d: D) => R; diff --git a/nix-js/runtime-ts/tsconfig.json b/nix-js/runtime-ts/tsconfig.json new file mode 100644 index 0000000..2a65ddd --- /dev/null +++ b/nix-js/runtime-ts/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020", "DOM"], + "moduleResolution": "bundler", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "declaration": false, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/nix-js/src/codegen.rs b/nix-js/src/codegen.rs index baa0d29..1b9137c 100644 --- a/nix-js/src/codegen.rs +++ b/nix-js/src/codegen.rs @@ -16,7 +16,7 @@ impl Compile for Ir { match self { Ir::Const(Const { val }) => match val { crate::value::Const::Null => "null".to_string(), - crate::value::Const::Int(val) => val.to_string(), + crate::value::Const::Int(val) => format!("{}n", val), // Generate BigInt literal crate::value::Const::Float(val) => val.to_string(), crate::value::Const::Bool(val) => val.to_string(), }, @@ -68,9 +68,10 @@ impl Compile for BinOp { Gt => format!("Nix.op.gt({},{})", lhs, rhs), Leq => format!("Nix.op.lte({},{})", lhs, rhs), Geq => format!("Nix.op.gte({},{})", lhs, rhs), - And => format!("Nix.op.band({},{})", lhs, rhs), - Or => format!("Nix.op.bor({},{})", lhs, rhs), - Impl => format!("Nix.op.bor(Nix.op.bnot({}),{})", lhs, rhs), + // Short-circuit operators: use JavaScript native && and || + And => format!("(Nix.force({}) && Nix.force({}))", lhs, rhs), + Or => format!("(Nix.force({}) || Nix.force({}))", lhs, rhs), + Impl => format!("(!Nix.force({}) || Nix.force({}))", lhs, rhs), Con => format!("Nix.op.concat({},{})", lhs, rhs), Upd => format!("Nix.op.update({},{})", lhs, rhs), PipeL => format!("Nix.force({})({})", rhs, lhs), @@ -84,7 +85,7 @@ impl Compile for UnOp { use UnOpKind::*; let rhs = ctx.get_ir(self.rhs).compile(ctx); match self.kind { - Neg => format!("Nix.op.sub(0,{rhs})"), + Neg => format!("Nix.op.sub(0n,{rhs})"), Not => format!("Nix.op.bnot({rhs})"), } } diff --git a/nix-js/src/context.rs b/nix-js/src/context.rs index 3d31232..2e765c0 100644 --- a/nix-js/src/context.rs +++ b/nix-js/src/context.rs @@ -184,6 +184,7 @@ mod test { ("2 > 1", Value::Const(Const::Bool(true))), ("1 <= 1", Value::Const(Const::Bool(true))), ("1 >= 1", Value::Const(Const::Bool(true))), + // Short-circuit evaluation: true || should not evaluate ("true || (1 / 0)", Value::Const(Const::Bool(true))), ("true && 1 == 0", Value::Const(Const::Bool(false))), ( @@ -560,4 +561,148 @@ mod test { Value::Const(Const::Bool(false)) ); } + + // BigInt and numeric type tests + #[test] + fn test_bigint_precision() { + let mut ctx = Context::new(); + + // Test large i64 values + assert_eq!( + ctx.eval("9223372036854775807").unwrap(), + Value::Const(Const::Int(9223372036854775807)) + ); + + // Test negative large value + // Can't use -9223372036854775808 since unary minus is actually desugared to (0 - ) + assert_eq!( + ctx.eval("-9223372036854775807").unwrap(), + Value::Const(Const::Int(-9223372036854775807)) + ); + + // Test large number arithmetic + assert_eq!( + ctx.eval("5000000000000000000 + 3000000000000000000") + .unwrap(), + Value::Const(Const::Int(8000000000000000000i64)) + ); + } + + #[test] + fn test_int_float_distinction() { + let mut ctx = Context::new(); + + // isInt tests + assert_eq!( + ctx.eval("builtins.isInt 42").unwrap(), + Value::Const(Const::Bool(true)) + ); + assert_eq!( + ctx.eval("builtins.isInt 42.0").unwrap(), + Value::Const(Const::Bool(false)) + ); + + // isFloat tests + assert_eq!( + ctx.eval("builtins.isFloat 42").unwrap(), + Value::Const(Const::Bool(false)) + ); + assert_eq!( + ctx.eval("builtins.isFloat 42.5").unwrap(), + Value::Const(Const::Bool(true)) + ); + assert_eq!( + ctx.eval("builtins.isFloat 1.0").unwrap(), + Value::Const(Const::Bool(true)) + ); + + // typeOf tests + assert_eq!( + ctx.eval("builtins.typeOf 1").unwrap(), + Value::String("int".to_string()) + ); + assert_eq!( + ctx.eval("builtins.typeOf 1.0").unwrap(), + Value::String("float".to_string()) + ); + assert_eq!( + ctx.eval("builtins.typeOf 3.14").unwrap(), + Value::String("float".to_string()) + ); + } + + #[test] + fn test_arithmetic_type_preservation() { + let mut ctx = Context::new(); + + // int + int = int + assert_eq!( + ctx.eval("builtins.typeOf (1 + 2)").unwrap(), + Value::String("int".to_string()) + ); + + // int + float = float + assert_eq!( + ctx.eval("builtins.typeOf (1 + 2.0)").unwrap(), + Value::String("float".to_string()) + ); + + // int * int = int + assert_eq!( + ctx.eval("builtins.typeOf (3 * 4)").unwrap(), + Value::String("int".to_string()) + ); + + // int * float = float + assert_eq!( + ctx.eval("builtins.typeOf (3 * 4.0)").unwrap(), + Value::String("float".to_string()) + ); + } + + #[test] + fn test_integer_division() { + let mut ctx = Context::new(); + + assert_eq!(ctx.eval("5 / 2").unwrap(), Value::Const(Const::Int(2))); + + assert_eq!(ctx.eval("7 / 3").unwrap(), Value::Const(Const::Int(2))); + + assert_eq!(ctx.eval("10 / 3").unwrap(), Value::Const(Const::Int(3))); + + // Float division returns float + assert_eq!( + ctx.eval("5 / 2.0").unwrap(), + Value::Const(Const::Float(2.5)) + ); + + assert_eq!( + ctx.eval("7.0 / 2").unwrap(), + Value::Const(Const::Float(3.5)) + ); + + assert_eq!( + ctx.eval("(-7) / 3").unwrap(), + Value::Const(Const::Int(-2)) + ); + } + + #[test] + fn test_builtin_arithmetic_with_bigint() { + let mut ctx = Context::new(); + + // Test builtin add with large numbers + assert_eq!( + ctx.eval("builtins.add 5000000000000000000 3000000000000000000") + .unwrap(), + Value::Const(Const::Int(8000000000000000000i64)) + ); + + // Test builtin mul with large numbers + assert_eq!( + ctx.eval("builtins.mul 1000000000 1000000000") + .unwrap(), + Value::Const(Const::Int(1000000000000000000i64)) + ); + } } diff --git a/nix-js/src/runtime.rs b/nix-js/src/runtime.rs index 559a12e..8cbf20f 100644 --- a/nix-js/src/runtime.rs +++ b/nix-js/src/runtime.rs @@ -26,7 +26,7 @@ fn run_impl(script: &str, isolate: &mut v8::Isolate) -> Result { let context = v8::Context::new(handle_scope, v8::ContextOptions::default()); let scope = &mut v8::ContextScope::new(handle_scope, context); - let runtime_code = include_str!("./runtime/runtime.js"); + let runtime_code = include_str!("../runtime-ts/dist/runtime.js"); let runtime_source = v8::String::new(scope, runtime_code).unwrap(); let runtime_script = v8::Script::compile(scope, runtime_source, None).unwrap(); @@ -83,19 +83,22 @@ fn to_value<'a>( scope: &v8::PinnedRef<'a, v8::HandleScope>, ) -> Value { match () { - _ if val.is_int32() => { - let val = val.to_int32(scope).unwrap().value(); - Value::Const(Const::Int(val as i64)) - } _ if val.is_big_int() => { - let (val, true) = val.to_big_int(scope).unwrap().i64_value() else { - todo!() - }; + let (val, lossless) = val.to_big_int(scope).unwrap().i64_value(); + if !lossless { + panic!("BigInt value out of i64 range: conversion lost precision"); + } Value::Const(Const::Int(val)) } _ if val.is_number() => { let val = val.to_number(scope).unwrap().value(); - Value::Const(Const::Float(val)) + // Heuristic: convert whole numbers to Int (for backward compatibility and JS interop) + if val.is_finite() && val.fract() == 0.0 + && val >= i64::MIN as f64 && val <= i64::MAX as f64 { + Value::Const(Const::Int(val as i64)) + } else { + Value::Const(Const::Float(val)) + } } _ if val.is_true() => Value::Const(Const::Bool(true)), _ if val.is_false() => Value::Const(Const::Bool(false)), diff --git a/nix-js/src/runtime/runtime.js b/nix-js/src/runtime/runtime.js deleted file mode 100644 index 5b2ff69..0000000 --- a/nix-js/src/runtime/runtime.js +++ /dev/null @@ -1,481 +0,0 @@ -const Nix = (() => { - const IS_THUNK = Symbol("is_thunk"); - - class NixThunk { - constructor(func) { - this[IS_THUNK] = true; - this.func = func; - this.result = null; - } - } - - const is_thunk = (value) => { - return value !== null && typeof value === "object" && value[IS_THUNK] === true; - }; - - const force = (value) => { - if (!is_thunk(value)) { - return value; - } - - if (value.func === null) { - return value.result; - } - - const result = force(value.func()); - value.result = result; - value.func = null; - - return result; - }; - - const create_thunk = (func) => new NixThunk(func); - - const create_lazy_set = (definitions) => { - const cache = new Map(); - return new Proxy({}, { - get: (_target, key) => { - if (cache.has(key)) { - return cache.get(key); - } - - if (key in definitions) { - const value = definitions[key](); - cache.set(key, value); - return value; - } - - return undefined; - } - }); - }; - - const select = (obj, key) => { - const forced_obj = force(obj); - const forced_key = force(key); - - if (forced_obj === null || forced_obj === undefined) { - throw new Error(`Cannot select '${forced_key}' from null/undefined`); - } - - if (!(forced_key in forced_obj)) { - throw new Error(`Attribute '${forced_key}' not found`); - } - - return forced_obj[forced_key]; - }; - - const select_with_default = (obj, key, default_val) => { - const forced_obj = force(obj); - const forced_key = force(key); - - if (forced_obj === null || forced_obj === undefined) { - return default_val; - } - - if (!(forced_key in forced_obj)) { - return default_val; - } - - return forced_obj[forced_key]; - }; - - const validate_params = (arg, required, allowed) => { - const forced_arg = force(arg); - - // Check required parameters - if (required) { - for (const key of required) { - if (!Object.prototype.hasOwnProperty.call(forced_arg, key)) { - throw new Error(`Function called without required argument '${key}'`); - } - } - } - - // Check allowed parameters (if not using ellipsis) - if (allowed) { - const allowed_set = new Set(allowed); - for (const key in forced_arg) { - if (!allowed_set.has(key)) { - throw new Error(`Function called with unexpected argument '${key}'`); - } - } - } - - return forced_arg; - }; - - const op = { - add: (a, b) => force(a) + force(b), - sub: (a, b) => force(a) - force(b), - mul: (a, b) => force(a) * force(b), - div: (a, b) => force(a) / force(b), - - eq: (a, b) => force(a) === force(b), - neq: (a, b) => force(a) !== force(b), - lt: (a, b) => force(a) < force(b), - lte: (a, b) => force(a) <= force(b), - gt: (a, b) => force(a) > force(b), - gte: (a, b) => force(a) >= force(b), - - band: (a, b) => force(a) && force(b), - bor: (a, b) => force(a) || force(b), - bnot: (a) => !force(a), - - concat: (a, b) => Array.prototype.concat.apply(force(a), force(b)), - update: (a, b) => ({ ...force(a), ...force(b) }), - }; - - const builtins = { - add: (a) => (b) => force(a) + force(b), - sub: (a) => (b) => force(a) - force(b), - mul: (a) => (b) => force(a) * force(b), - div: (a) => (b) => force(a) / force(b), - bitAnd: (a) => (b) => force(a) & force(b), - bitOr: (a) => (b) => force(a) | force(b), - bitXor: (a) => (b) => force(a) ^ force(b), - lessThan: (a) => (b) => force(a) < force(b), - - ceil: (x) => Math.ceil(force(x)), - floor: (x) => Math.floor(force(x)), - - deepSeq: (e1) => (e2) => { - throw "Not implemented: deepSeq" - }, - seq: (e1) => (e2) => { - force(e1); - return e2; - }, - - fetchClosure: (args) => { - throw "Not implemented: fetchClosure" - }, - fetchGit: (args) => { - throw "Not implemented: fetchGit" - }, - fetchTarball: (args) => { - throw "Not implemented: fetchTarball" - }, - fetchTree: (args) => { - throw "Not implemented: fetchTree" - }, - fetchurl: (args) => { - throw "Not implemented: fetchurl" - }, - - fromJSON: (e) => { - throw "Not implemented: fromJSON" - }, - fromTOML: (e) => { - throw "Not implemented: fromTOML" - }, - toJSON: (e) => { - throw "Not implemented: toJSON" - }, - toXML: (e) => { - throw "Not implemented: toXML" - }, - - getContext: (s) => { - throw "Not implemented: getContext" - }, - hasContext: (s) => { - throw "Not implemented: hasContext" - }, - - hashFile: (type) => (p) => { - throw "Not implemented: hashFile" - }, - hashString: (type) => (p) => { - throw "Not implemented: hashString" - }, - convertHash: (args) => { - throw "Not implemented: convertHash" - }, - - isAttrs: (e) => { - const val = force(e); - return typeof val === "object" && !Array.isArray(val) && val !== null; - }, - isBool: (e) => typeof force(e) === "boolean", - isFloat: (e) => { - throw "Not implemented: isFloat" - }, - isFunction: (e) => typeof force(e) === "function", - isInt: (e) => { - throw "Not implemented: isInt" - }, - isList: (e) => Array.isArray(force(e)), - isNull: (e) => force(e) === null, - isPath: (e) => { - throw "Not implemented: isPath" - }, - isString: (e) => typeof force(e) === "string", - typeOf: (e) => { - throw "Not implemented: typeOf" - }, - - import: (path) => { - throw "Not implemented: import" - }, - scopedImport: (scope) => (path) => { - throw "Not implemented: scopedImport" - }, - - unsafeDiscardOutputDependency: (s) => { - throw "Not implemented: unsafeDiscardOutputDependency" - }, - unsafeDiscardStringContext: (s) => { - throw "Not implemented: unsafeDiscardStringContext" - }, - unsafeGetAttrPos: (s) => { - throw "Not implemented: unsafeGetAttrPos" - }, - - abort: (s) => { - throw `evaluation aborted with the following error message: '${force(s)}'` - }, - addDrvOutputDependencies: (s) => { - throw "Not implemented: addDrvOutputDependencies" - }, - all: (pred) => (list) => Array.prototype.every.call(force(list), force(pred)), - any: (pred) => (list) => Array.prototype.some.call(force(list), force(pred)), - attrNames: (set) => Object.keys(force(set)), - attrValues: (set) => Object.values(force(set)), - baseNameOf: (x) => { - const str = force(x); - if (str.length === 0) - return ""; - let last = str.length - 1; - if (str[last] === "/" && last > 0) - last -= 1; - let pos = last; - while (pos >= 0 && str[pos] !== "/") - pos -= 1; - if (pos !== 0 || (pos === 0 && str[pos] === "/")) { - pos += 1; - } - return String.prototype.substring.call(str, pos, last + 1); - }, - break: (v) => v, - catAttrs: (attr) => (list) => { - const key = force(attr); - return force(list).map((set) => force(set)[key]).filter((val) => val !== undefined); - }, - compareVersions: (s1) => (s2) => { - throw "Not implemented: compareVersions" - }, - concatLists: (lists) => Array.prototype.reduce.call(force(lists), (acc, cur) => Array.prototype.concat(acc, force(cur)), []), - concatMap: (f) => (lists) => { - const fn = force(f); - return Array.prototype.reduce.call(force(lists), (acc, cur) => Array.prototype.concat(acc, force(fn(cur))), []); - }, - concatStringsSep: (sep) => (list) => Array.prototype.join.call(force(list), force(sep)), - dirOf: (s) => { - throw "Not implemented: dirOf" - }, - elem: (x) => (xs) => Array.prototype.includes.call(force(xs), force(x)), - elemAt: (xs) => (n) => force(xs)[force(n)], - filter: (f) => (list) => Array.prototype.filter.call(force(list), force(f)), - filterSource: (args) => { - throw "Not implemented: filterSource" - }, - findFile: (search) => (lookup) => { - throw "Not implemented: findFile" - }, - flakeRefToString: (attrs) => { - throw "Not implemented: flakeRefToString" - }, - ["foldl'"]:(op_fn) => (nul) => (list) => { - const forced_op = force(op_fn); - return Array.prototype.reduce.call(force(list), (acc, cur) => forced_op(acc)(cur), nul); - }, - functionArgs: (f) => { - throw "Not implemented: functionArgs" - }, - genList: (f) => (len) => { - const forced_f = force(f); - const forced_len = force(len); - return [...Array(forced_len).keys()].map(i => forced_f(i)); - }, - genericClosure: (args) => { - throw "Not implemented: genericClosure" - }, - getAttr: (s) => (set) => force(set)[force(s)], - getEnv: (s) => { - throw "Not implemented: getEnv" - }, - getFlake: (attrs) => { - throw "Not implemented: getFlake" - }, - groupBy: (f) => (list) => { - let attrs = {}; - const forced_f = force(f); - const forced_list = force(list); - for (const elem of forced_list) { - const key = force(forced_f(elem)); - if (!attrs[key]) attrs[key] = []; - attrs[key].push(elem); - } - return attrs; - }, - hasAttr: (s) => (set) => Object.prototype.hasOwnProperty.call(force(set), force(s)), - head: (list) => force(list)[0], - intersectAttrs: (e1) => (e2) => { - const f1 = force(e1); - const f2 = force(e2); - let attrs = {}; - for (const key of Object.keys(f2)) { - if (Object.prototype.hasOwnProperty.call(f1, key)) { - attrs[key] = f2[key]; - } - } - return attrs; - }, - length: (e) => force(e).length, - listToAttrs: (e) => { - let attrs = {}; - const forced_e = [...force(e)].reverse(); - for (const obj of forced_e) { - const item = force(obj); - attrs[force(item.name)] = item.value; - } - return attrs; - }, - map: (f) => (list) => Array.prototype.map.call(force(list), force(f)), - mapAttrs: (f) => (attrs) => { - let new_attrs = {}; - const forced_attrs = force(attrs); - const forced_f = force(f); - for (const key in forced_attrs) { - new_attrs[key] = forced_f(key)(forced_attrs[key]); - } - return new_attrs; - }, - match: (regex) => (str) => { - throw "Not implemented: match" - }, - outputOf: (drv) => (out) => { - throw "Not implemented: outputOf" - }, - parseDrvName: (s) => { - throw "Not implemented: parseDrvName" - }, - parseFlakeName: (s) => { - throw "Not implemented: parseFlakeName" - }, - partition: (pred) => (list) => { - const forced_list = force(list); - const forced_pred = force(pred); - let attrs = { - right: [], - wrong: [], - }; - for (const elem of forced_list) { - if (force(forced_pred(elem))) { - attrs.right.push(elem); - } else { - attrs.wrong.push(elem); - } - } - return attrs; - }, - path: (args) => { - throw "Not implemented: path" - }, - pathExists: (path) => { - throw "Not implemented: pathExists" - }, - placeholder: (output) => { - throw "Not implemented: placeholder" - }, - readDir: (path) => { - throw "Not implemented: readDir" - }, - readFile: (path) => { - throw "Not implemented: readFile" - }, - readFileType: (path) => { - throw "Not implemented: readFileType" - }, - replaceStrings: (from) => (to) => (s) => { - throw "Not implemented: replaceStrings" - }, - sort: (cmp) => (list) => { - const forced_list = [...force(list)]; - const forced_cmp = force(cmp); - return forced_list.sort((a, b) => { - if (force(forced_cmp(a)(b))) return -1; - if (force(forced_cmp(b)(a))) return 1; - return 0; - }); - }, - split: (regex, str) => { - throw "Not implemented: split" - }, - splitVersion: (s) => { - throw "Not implemented: splitVersion" - }, - stringLength: (e) => force(e).length, - substring: (start) => (len) => (s) => String.prototype.substring.call(force(s), force(start), force(start) + force(len)), - tail: (list) => Array.prototype.slice.call(force(list), 1), - throw: (s) => { - throw force(s); - }, - toFile: (name, s) => { - throw "Not implemented: toFile" - }, - toPath: (name, s) => { - throw "Not implemented: toPath" - }, - toString: (name, s) => { - throw "Not implemented: toString" - }, - trace: (e1, e2) => { - console.log(`trace: ${force(e1)}`); - return e2; - }, - traceVerbose: (e1, e2) => { - throw "Not implemented: traceVerbose" - }, - tryEval: (e1) => (e2) => { - throw "Not implemented: tryEval" - }, - warn: (e1) => (e2) => { - console.log(`evaluation warning: ${force(e1)}`); - return e2; - }, - zipAttrsWith: (f) => (list) => { - throw "Not implemented: zipAttrsWith" - }, - - builtins: create_thunk(() => builtins), - currentSystem: create_thunk(() => { - throw "Not implemented: currentSystem" - }), - currentTime: create_thunk(() => Date.now()), - [false]: false, - [true]: true, - [null]: null, - langVersion: 6, - nixPath: [], - nixVersion: "NIX_JS_VERSION", - storeDir: "/nix/store", - }; - - return { - create_thunk, - force, - is_thunk, - create_lazy_set, - select, - select_with_default, - validate_params, - op, - builtins, - IS_THUNK // Export the Symbol for Rust to use - }; -})(); - -// Ensure Nix is available on the global object for Rust access -globalThis.Nix = Nix;