feat: runtime error

This commit is contained in:
2026-01-10 01:22:39 +08:00
parent cc53963df0
commit e29e432328
11 changed files with 112 additions and 78 deletions

View File

@@ -5,21 +5,21 @@
import type { NixValue } from "../types"; import type { NixValue } from "../types";
export const fromJSON = (e: NixValue): never => { export const fromJSON = (e: NixValue): never => {
throw "Not implemented: fromJSON"; throw new Error("Not implemented: fromJSON");
}; };
export const fromTOML = (e: NixValue): never => { export const fromTOML = (e: NixValue): never => {
throw "Not implemented: fromTOML"; throw new Error("Not implemented: fromTOML");
}; };
export const toJSON = (e: NixValue): never => { export const toJSON = (e: NixValue): never => {
throw "Not implemented: toJSON"; throw new Error("Not implemented: toJSON");
}; };
export const toXML = (e: NixValue): never => { export const toXML = (e: NixValue): never => {
throw "Not implemented: toXML"; throw new Error("Not implemented: toXML");
}; };
export const toString = (name: NixValue, s: NixValue): never => { export const toString = (name: NixValue, s: NixValue): never => {
throw "Not implemented: toString"; throw new Error("Not implemented: toString");
}; };

View File

@@ -1,9 +1,9 @@
import type { NixValue } from "../types"; import type { NixValue } from "../types";
export const derivation = (args: NixValue) => { export const derivation = (args: NixValue) => {
throw "Not implemented: derivation"; throw new Error("Not implemented: derivation");
}; };
export const derivationStrict = (args: NixValue) => { export const derivationStrict = (args: NixValue) => {
throw "Not implemented: derivationStrict"; throw new Error("Not implemented: derivationStrict");
}; };

View File

@@ -2,8 +2,9 @@
* Functional programming builtin functions * Functional programming builtin functions
*/ */
import type { NixValue } from "../types"; import { CatchableError, type NixValue } from "../types";
import { force } from "../thunk"; import { force } from "../thunk";
import { force_string } from "../type-assert";
export const seq = export const seq =
(e1: NixValue) => (e1: NixValue) =>
@@ -15,15 +16,15 @@ export const seq =
export const deepSeq = export const deepSeq =
(e1: NixValue) => (e1: NixValue) =>
(e2: NixValue): never => { (e2: NixValue): never => {
throw "Not implemented: deepSeq"; throw new Error("Not implemented: deepSeq");
}; };
export const abort = (s: NixValue): never => { export const abort = (s: NixValue): never => {
throw `evaluation aborted with the following error message: '${force(s)}'`; throw new Error(`evaluation aborted with the following error message: '${force(s)}'`);
}; };
export const throwFunc = (s: NixValue): never => { export const throwFunc = (s: NixValue): never => {
throw force(s); throw new CatchableError(force_string(s));
}; };
export const trace = (e1: NixValue, e2: NixValue): NixValue => { export const trace = (e1: NixValue, e2: NixValue): NixValue => {

View File

@@ -249,7 +249,7 @@ export const builtins: any = {
builtins: create_thunk(() => builtins), builtins: create_thunk(() => builtins),
currentSystem: create_thunk(() => { currentSystem: create_thunk(() => {
throw "Not implemented: currentSystem"; throw new Error("Not implemented: currentSystem");
}), }),
currentTime: create_thunk(() => Date.now()), currentTime: create_thunk(() => Date.now()),

View File

@@ -25,39 +25,39 @@ export const importFunc = (path: NixValue): NixValue => {
export const scopedImport = export const scopedImport =
(scope: NixValue) => (scope: NixValue) =>
(path: NixValue): never => { (path: NixValue): never => {
throw "Not implemented: scopedImport"; throw new Error("Not implemented: scopedImport");
}; };
export const storePath = (args: NixValue): never => { export const storePath = (args: NixValue): never => {
throw "Not implemented: storePath"; throw new Error("Not implemented: storePath");
}; };
export const fetchClosure = (args: NixValue): never => { export const fetchClosure = (args: NixValue): never => {
throw "Not implemented: fetchClosure"; throw new Error("Not implemented: fetchClosure");
}; };
export const fetchMercurial = (args: NixValue): never => { export const fetchMercurial = (args: NixValue): never => {
throw "Not implemented: fetchMercurial"; throw new Error("Not implemented: fetchMercurial");
}; };
export const fetchGit = (args: NixValue): never => { export const fetchGit = (args: NixValue): never => {
throw "Not implemented: fetchGit"; throw new Error("Not implemented: fetchGit");
}; };
export const fetchTarball = (args: NixValue): never => { export const fetchTarball = (args: NixValue): never => {
throw "Not implemented: fetchTarball"; throw new Error("Not implemented: fetchTarball");
}; };
export const fetchTree = (args: NixValue): never => { export const fetchTree = (args: NixValue): never => {
throw "Not implemented: fetchTree"; throw new Error("Not implemented: fetchTree");
}; };
export const fetchurl = (args: NixValue): never => { export const fetchurl = (args: NixValue): never => {
throw "Not implemented: fetchurl"; throw new Error("Not implemented: fetchurl");
}; };
export const readDir = (path: NixValue): never => { export const readDir = (path: NixValue): never => {
throw "Not implemented: readDir"; throw new Error("Not implemented: readDir");
}; };
export const readFile = (path: NixValue): string => { export const readFile = (path: NixValue): string => {
@@ -66,7 +66,7 @@ export const readFile = (path: NixValue): string => {
}; };
export const readFileType = (path: NixValue): never => { export const readFileType = (path: NixValue): never => {
throw "Not implemented: readFileType"; throw new Error("Not implemented: readFileType");
}; };
export const pathExists = (path: NixValue): boolean => { export const pathExists = (path: NixValue): boolean => {
@@ -75,27 +75,27 @@ export const pathExists = (path: NixValue): boolean => {
}; };
export const path = (args: NixValue): never => { export const path = (args: NixValue): never => {
throw "Not implemented: path"; throw new Error("Not implemented: path");
}; };
export const toFile = (name: NixValue, s: NixValue): never => { export const toFile = (name: NixValue, s: NixValue): never => {
throw "Not implemented: toFile"; throw new Error("Not implemented: toFile");
}; };
export const toPath = (name: NixValue, s: NixValue): never => { export const toPath = (name: NixValue, s: NixValue): never => {
throw "Not implemented: toPath"; throw new Error("Not implemented: toPath");
}; };
export const filterSource = (args: NixValue): never => { export const filterSource = (args: NixValue): never => {
throw "Not implemented: filterSource"; throw new Error("Not implemented: filterSource");
}; };
export const findFile = export const findFile =
(search: NixValue) => (search: NixValue) =>
(lookup: NixValue): never => { (lookup: NixValue): never => {
throw "Not implemented: findFile"; throw new Error("Not implemented: findFile");
}; };
export const getEnv = (s: NixValue): never => { export const getEnv = (s: NixValue): never => {
throw "Not implemented: getEnv"; throw new Error("Not implemented: getEnv");
}; };

View File

@@ -2,141 +2,155 @@
* Miscellaneous unimplemented builtin functions * Miscellaneous unimplemented builtin functions
*/ */
import type { NixValue } from "../types"; import { force } from "../thunk";
import { CatchableError } from "../types";
import type { NixBool, NixStrictValue, NixValue } from "../types";
export const addErrorContext = export const addErrorContext =
(e1: NixValue) => (e1: NixValue) =>
(e2: NixValue): never => { (e2: NixValue): never => {
throw "Not implemented: addErrorContext"; throw new Error("Not implemented: addErrorContext");
}; };
export const appendContext = export const appendContext =
(e1: NixValue) => (e1: NixValue) =>
(e2: NixValue): never => { (e2: NixValue): never => {
throw "Not implemented: appendContext"; throw new Error("Not implemented: appendContext");
}; };
export const getContext = (s: NixValue): never => { export const getContext = (s: NixValue): never => {
throw "Not implemented: getContext"; throw new Error("Not implemented: getContext");
}; };
export const hasContext = (s: NixValue): never => { export const hasContext = (s: NixValue): never => {
throw "Not implemented: hasContext"; throw new Error("Not implemented: hasContext");
}; };
export const hashFile = export const hashFile =
(type: NixValue) => (type: NixValue) =>
(p: NixValue): never => { (p: NixValue): never => {
throw "Not implemented: hashFile"; throw new Error("Not implemented: hashFile");
}; };
export const hashString = export const hashString =
(type: NixValue) => (type: NixValue) =>
(p: NixValue): never => { (p: NixValue): never => {
throw "Not implemented: hashString"; throw new Error("Not implemented: hashString");
}; };
export const convertHash = (args: NixValue): never => { export const convertHash = (args: NixValue): never => {
throw "Not implemented: convertHash"; throw new Error("Not implemented: convertHash");
}; };
export const unsafeDiscardOutputDependency = (s: NixValue): never => { export const unsafeDiscardOutputDependency = (s: NixValue): never => {
throw "Not implemented: unsafeDiscardOutputDependency"; throw new Error("Not implemented: unsafeDiscardOutputDependency");
}; };
export const unsafeDiscardStringContext = (s: NixValue): never => { export const unsafeDiscardStringContext = (s: NixValue): never => {
throw "Not implemented: unsafeDiscardStringContext"; throw new Error("Not implemented: unsafeDiscardStringContext");
}; };
export const unsafeGetAttrPos = (s: NixValue): never => { export const unsafeGetAttrPos = (s: NixValue): never => {
throw "Not implemented: unsafeGetAttrPos"; throw new Error("Not implemented: unsafeGetAttrPos");
}; };
export const addDrvOutputDependencies = (s: NixValue): never => { export const addDrvOutputDependencies = (s: NixValue): never => {
throw "Not implemented: addDrvOutputDependencies"; throw new Error("Not implemented: addDrvOutputDependencies");
}; };
export const compareVersions = export const compareVersions =
(s1: NixValue) => (s1: NixValue) =>
(s2: NixValue): never => { (s2: NixValue): never => {
throw "Not implemented: compareVersions"; throw new Error("Not implemented: compareVersions");
}; };
export const dirOf = (s: NixValue): never => { export const dirOf = (s: NixValue): never => {
throw "Not implemented: dirOf"; throw new Error("Not implemented: dirOf");
}; };
export const flakeRefToString = (attrs: NixValue): never => { export const flakeRefToString = (attrs: NixValue): never => {
throw "Not implemented: flakeRefToString"; throw new Error("Not implemented: flakeRefToString");
}; };
export const functionArgs = (f: NixValue): never => { export const functionArgs = (f: NixValue): never => {
throw "Not implemented: functionArgs"; throw new Error("Not implemented: functionArgs");
}; };
export const genericClosure = (args: NixValue): never => { export const genericClosure = (args: NixValue): never => {
throw "Not implemented: genericClosure"; throw new Error("Not implemented: genericClosure");
}; };
export const getFlake = (attrs: NixValue): never => { export const getFlake = (attrs: NixValue): never => {
throw "Not implemented: getFlake"; throw new Error("Not implemented: getFlake");
}; };
export const match = export const match =
(regex: NixValue) => (regex: NixValue) =>
(str: NixValue): never => { (str: NixValue): never => {
throw "Not implemented: match"; throw new Error("Not implemented: match");
}; };
export const outputOf = export const outputOf =
(drv: NixValue) => (drv: NixValue) =>
(out: NixValue): never => { (out: NixValue): never => {
throw "Not implemented: outputOf"; throw new Error("Not implemented: outputOf");
}; };
export const parseDrvName = (s: NixValue): never => { export const parseDrvName = (s: NixValue): never => {
throw "Not implemented: parseDrvName"; throw new Error("Not implemented: parseDrvName");
}; };
export const parseFlakeName = (s: NixValue): never => { export const parseFlakeName = (s: NixValue): never => {
throw "Not implemented: parseFlakeName"; throw new Error("Not implemented: parseFlakeName");
}; };
export const parseFlakeRef = (s: NixValue): never => { export const parseFlakeRef = (s: NixValue): never => {
throw "Not implemented: parseFlakeRef"; throw new Error("Not implemented: parseFlakeRef");
}; };
export const placeholder = (output: NixValue): never => { export const placeholder = (output: NixValue): never => {
throw "Not implemented: placeholder"; throw new Error("Not implemented: placeholder");
}; };
export const replaceStrings = export const replaceStrings =
(from: NixValue) => (from: NixValue) =>
(to: NixValue) => (to: NixValue) =>
(s: NixValue): never => { (s: NixValue): never => {
throw "Not implemented: replaceStrings"; throw new Error("Not implemented: replaceStrings");
}; };
export const split = (regex: NixValue, str: NixValue): never => { export const split = (regex: NixValue, str: NixValue): never => {
throw "Not implemented: split"; throw new Error("Not implemented: split");
}; };
export const splitVersion = (s: NixValue): never => { export const splitVersion = (s: NixValue): never => {
throw "Not implemented: splitVersion"; throw new Error("Not implemented: splitVersion");
}; };
export const traceVerbose = (e1: NixValue, e2: NixValue): never => { export const traceVerbose = (e1: NixValue, e2: NixValue): never => {
throw "Not implemented: traceVerbose"; throw new Error("Not implemented: traceVerbose");
}; };
export const tryEval = export const tryEval = (e: NixValue): { success: NixBool; value: NixStrictValue } => {
(e1: NixValue) => try {
(e2: NixValue): never => { return {
throw "Not implemented: tryEval"; success: true,
}; value: force(e),
};
} catch (err) {
if (err instanceof CatchableError) {
return {
success: false,
value: false,
};
} else {
throw err;
}
}
};
export const zipAttrsWith = export const zipAttrsWith =
(f: NixValue) => (f: NixValue) =>
(list: NixValue): never => { (list: NixValue): never => {
throw "Not implemented: zipAttrsWith"; throw new Error("Not implemented: zipAttrsWith");
}; };

View File

@@ -10,6 +10,7 @@ import type {
NixInt, NixInt,
NixList, NixList,
NixNull, NixNull,
NixStrictValue,
NixString, NixString,
NixValue, NixValue,
} from "../types"; } from "../types";
@@ -39,7 +40,7 @@ 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 isNull = (e: NixValue): e is NixNull => force(e) === null;
export const isPath = (e: NixValue): never => { export const isPath = (e: NixValue): never => {
throw "Not implemented: isPath"; throw new Error("Not implemented: isPath");
}; };
export const isString = (e: NixValue): e is NixString => typeof force(e) === "string"; export const isString = (e: NixValue): e is NixString => typeof force(e) === "string";

View File

@@ -60,7 +60,7 @@ export const select_with_default = (obj: NixValue, key: NixValue, default_val: N
export const has_attr = (obj: NixValue, attrpath: NixValue[]): NixBool => { export const has_attr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
if (!isAttrs(obj)) { if (!isAttrs(obj)) {
return false return false;
} }
let attrs = obj; let attrs = obj;

View File

@@ -3,7 +3,7 @@
* Implements thunks for lazy evaluation of Nix expressions * Implements thunks for lazy evaluation of Nix expressions
*/ */
import type { NixValue, NixThunkInterface } from "./types"; import type { NixValue, NixThunkInterface, NixStrictValue } from "./types";
/** /**
* Symbol used to mark objects as thunks * Symbol used to mark objects as thunks
@@ -20,12 +20,12 @@ export const IS_THUNK = Symbol("is_thunk");
export class NixThunk implements NixThunkInterface { export class NixThunk implements NixThunkInterface {
[key: symbol]: any; [key: symbol]: any;
readonly [IS_THUNK] = true as const; readonly [IS_THUNK] = true as const;
func: (() => NixValue) | null; func: (() => NixValue) | undefined;
result: Exclude<NixValue, NixThunkInterface> | null; result: NixStrictValue | undefined;
constructor(func: () => NixValue) { constructor(func: () => NixValue) {
this.func = func; this.func = func;
this.result = null; this.result = undefined;
} }
} }
@@ -46,20 +46,20 @@ export const is_thunk = (value: unknown): value is NixThunkInterface => {
* @param value - Value to force (may be a thunk) * @param value - Value to force (may be a thunk)
* @returns The forced/evaluated value * @returns The forced/evaluated value
*/ */
export const force = (value: NixValue): Exclude<NixValue, NixThunkInterface> => { export const force = (value: NixValue): NixStrictValue => {
if (!is_thunk(value)) { if (!is_thunk(value)) {
return value; return value;
} }
// Already evaluated - return cached result // Already evaluated - return cached result
if (value.func === null) { if (value.func === undefined) {
return value.result!; return value.result!;
} }
// Evaluate and cache // Evaluate and cache
const result = force(value.func()); const result = force(value.func());
value.result = result; value.result = result;
value.func = null; value.func = undefined;
return result; return result;
}; };

View File

@@ -2,6 +2,8 @@
* Core TypeScript type definitions for nix-js runtime * Core TypeScript type definitions for nix-js runtime
*/ */
import { IS_THUNK } from "./thunk";
// Nix primitive types // Nix primitive types
export type NixInt = bigint; export type NixInt = bigint;
export type NixFloat = number; export type NixFloat = number;
@@ -20,9 +22,9 @@ export type NixFunction = (...args: any[]) => any;
* Thunks delay evaluation until forced * Thunks delay evaluation until forced
*/ */
export interface NixThunkInterface { export interface NixThunkInterface {
readonly [key: symbol]: true; // IS_THUNK marker readonly [IS_THUNK]: true;
func: (() => NixValue) | null; func: (() => NixValue) | undefined;
result: Exclude<NixValue, NixThunkInterface> | null; result: NixStrictValue | undefined;
} }
// Union of all Nix primitive types // Union of all Nix primitive types
@@ -34,6 +36,18 @@ export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString;
*/ */
export type NixValue = NixPrimitive | NixList | NixAttrs | NixFunction | NixThunkInterface; export type NixValue = NixPrimitive | NixList | NixAttrs | NixFunction | NixThunkInterface;
export type NixStrictValue = Exclude<NixValue, NixThunkInterface>;
/**
* CatchableError: Error type thrown by `builtins.throw`
* This can be caught by `builtins.tryEval`
*/
export class CatchableError extends Error {
constructor(msg: string) {
super(msg);
}
}
// Operator function signatures // Operator function signatures
export type BinaryOp<T = NixValue, U = NixValue, R = NixValue> = (a: T, b: U) => R; export type BinaryOp<T = NixValue, U = NixValue, R = NixValue> = (a: T, b: U) => R;
export type UnaryOp<T = NixValue, R = NixValue> = (a: T) => R; export type UnaryOp<T = NixValue, R = NixValue> = (a: T) => R;

View File

@@ -3,6 +3,7 @@ use std::pin::Pin;
use std::sync::Once; use std::sync::Once;
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpDecl, OpState, RuntimeOptions, v8}; use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpDecl, OpState, RuntimeOptions, v8};
use deno_error::JsErrorClass;
use crate::codegen::{CodegenContext, Compile}; use crate::codegen::{CodegenContext, Compile};
use crate::context::{CtxPtr, PathDropGuard}; use crate::context::{CtxPtr, PathDropGuard};
@@ -44,12 +45,12 @@ mod private {
pub struct SimpleErrorWrapper(pub(crate) String); pub struct SimpleErrorWrapper(pub(crate) String);
impl std::fmt::Display for SimpleErrorWrapper { impl std::fmt::Display for SimpleErrorWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f) std::fmt::Display::fmt(&self.0, f)
} }
} }
impl std::error::Error for SimpleErrorWrapper {} impl std::error::Error for SimpleErrorWrapper {}
js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError"); js_error_wrapper!(SimpleErrorWrapper, NixError, "Error");
impl From<String> for NixError { impl From<String> for NixError {
fn from(value: String) -> Self { fn from(value: String) -> Self {
@@ -71,10 +72,13 @@ fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result
let ctx = unsafe { ptr.as_mut() }; let ctx = unsafe { ptr.as_mut() };
let current_dir = ctx.get_current_dir(); let current_dir = ctx.get_current_dir();
let absolute_path = current_dir let mut absolute_path = current_dir
.join(&path) .join(&path)
.canonicalize() .canonicalize()
.map_err(|e| format!("Failed to resolve path {}: {}", path, e))?; .map_err(|e| format!("Failed to resolve path {}: {}", path, e))?;
if absolute_path.is_dir() {
absolute_path.push("default.nix")
}
let mut guard = PathDropGuard::new(absolute_path.clone(), ctx); let mut guard = PathDropGuard::new(absolute_path.clone(), ctx);
let ctx = guard.as_ctx(); let ctx = guard.as_ctx();
@@ -175,7 +179,7 @@ impl Runtime {
let global_value = self let global_value = self
.js_runtime .js_runtime
.execute_script("<eval>", script) .execute_script("<eval>", script)
.map_err(|e| Error::eval_error(format!("Execution error: {:?}", e)))?; .map_err(|e| Error::eval_error(format!("{}", e.get_message())))?;
// Retrieve scope from JsRuntime // Retrieve scope from JsRuntime
deno_core::scope!(scope, self.js_runtime); deno_core::scope!(scope, self.js_runtime);