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";
export const fromJSON = (e: NixValue): never => {
throw "Not implemented: fromJSON";
throw new Error("Not implemented: fromJSON");
};
export const fromTOML = (e: NixValue): never => {
throw "Not implemented: fromTOML";
throw new Error("Not implemented: fromTOML");
};
export const toJSON = (e: NixValue): never => {
throw "Not implemented: toJSON";
throw new Error("Not implemented: toJSON");
};
export const toXML = (e: NixValue): never => {
throw "Not implemented: toXML";
throw new Error("Not implemented: toXML");
};
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";
export const derivation = (args: NixValue) => {
throw "Not implemented: derivation";
throw new Error("Not implemented: derivation");
};
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
*/
import type { NixValue } from "../types";
import { CatchableError, type NixValue } from "../types";
import { force } from "../thunk";
import { force_string } from "../type-assert";
export const seq =
(e1: NixValue) =>
@@ -15,15 +16,15 @@ export const seq =
export const deepSeq =
(e1: NixValue) =>
(e2: NixValue): never => {
throw "Not implemented: deepSeq";
throw new Error("Not implemented: deepSeq");
};
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 => {
throw force(s);
throw new CatchableError(force_string(s));
};
export const trace = (e1: NixValue, e2: NixValue): NixValue => {

View File

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

View File

@@ -25,39 +25,39 @@ export const importFunc = (path: NixValue): NixValue => {
export const scopedImport =
(scope: NixValue) =>
(path: NixValue): never => {
throw "Not implemented: scopedImport";
throw new Error("Not implemented: scopedImport");
};
export const storePath = (args: NixValue): never => {
throw "Not implemented: storePath";
throw new Error("Not implemented: storePath");
};
export const fetchClosure = (args: NixValue): never => {
throw "Not implemented: fetchClosure";
throw new Error("Not implemented: fetchClosure");
};
export const fetchMercurial = (args: NixValue): never => {
throw "Not implemented: fetchMercurial";
throw new Error("Not implemented: fetchMercurial");
};
export const fetchGit = (args: NixValue): never => {
throw "Not implemented: fetchGit";
throw new Error("Not implemented: fetchGit");
};
export const fetchTarball = (args: NixValue): never => {
throw "Not implemented: fetchTarball";
throw new Error("Not implemented: fetchTarball");
};
export const fetchTree = (args: NixValue): never => {
throw "Not implemented: fetchTree";
throw new Error("Not implemented: fetchTree");
};
export const fetchurl = (args: NixValue): never => {
throw "Not implemented: fetchurl";
throw new Error("Not implemented: fetchurl");
};
export const readDir = (path: NixValue): never => {
throw "Not implemented: readDir";
throw new Error("Not implemented: readDir");
};
export const readFile = (path: NixValue): string => {
@@ -66,7 +66,7 @@ export const readFile = (path: NixValue): string => {
};
export const readFileType = (path: NixValue): never => {
throw "Not implemented: readFileType";
throw new Error("Not implemented: readFileType");
};
export const pathExists = (path: NixValue): boolean => {
@@ -75,27 +75,27 @@ export const pathExists = (path: NixValue): boolean => {
};
export const path = (args: NixValue): never => {
throw "Not implemented: path";
throw new Error("Not implemented: path");
};
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 => {
throw "Not implemented: toPath";
throw new Error("Not implemented: toPath");
};
export const filterSource = (args: NixValue): never => {
throw "Not implemented: filterSource";
throw new Error("Not implemented: filterSource");
};
export const findFile =
(search: NixValue) =>
(lookup: NixValue): never => {
throw "Not implemented: findFile";
throw new Error("Not implemented: findFile");
};
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
*/
import type { NixValue } from "../types";
import { force } from "../thunk";
import { CatchableError } from "../types";
import type { NixBool, NixStrictValue, NixValue } from "../types";
export const addErrorContext =
(e1: NixValue) =>
(e2: NixValue): never => {
throw "Not implemented: addErrorContext";
throw new Error("Not implemented: addErrorContext");
};
export const appendContext =
(e1: NixValue) =>
(e2: NixValue): never => {
throw "Not implemented: appendContext";
throw new Error("Not implemented: appendContext");
};
export const getContext = (s: NixValue): never => {
throw "Not implemented: getContext";
throw new Error("Not implemented: getContext");
};
export const hasContext = (s: NixValue): never => {
throw "Not implemented: hasContext";
throw new Error("Not implemented: hasContext");
};
export const hashFile =
(type: NixValue) =>
(p: NixValue): never => {
throw "Not implemented: hashFile";
throw new Error("Not implemented: hashFile");
};
export const hashString =
(type: NixValue) =>
(p: NixValue): never => {
throw "Not implemented: hashString";
throw new Error("Not implemented: hashString");
};
export const convertHash = (args: NixValue): never => {
throw "Not implemented: convertHash";
throw new Error("Not implemented: convertHash");
};
export const unsafeDiscardOutputDependency = (s: NixValue): never => {
throw "Not implemented: unsafeDiscardOutputDependency";
throw new Error("Not implemented: unsafeDiscardOutputDependency");
};
export const unsafeDiscardStringContext = (s: NixValue): never => {
throw "Not implemented: unsafeDiscardStringContext";
throw new Error("Not implemented: unsafeDiscardStringContext");
};
export const unsafeGetAttrPos = (s: NixValue): never => {
throw "Not implemented: unsafeGetAttrPos";
throw new Error("Not implemented: unsafeGetAttrPos");
};
export const addDrvOutputDependencies = (s: NixValue): never => {
throw "Not implemented: addDrvOutputDependencies";
throw new Error("Not implemented: addDrvOutputDependencies");
};
export const compareVersions =
(s1: NixValue) =>
(s2: NixValue): never => {
throw "Not implemented: compareVersions";
throw new Error("Not implemented: compareVersions");
};
export const dirOf = (s: NixValue): never => {
throw "Not implemented: dirOf";
throw new Error("Not implemented: dirOf");
};
export const flakeRefToString = (attrs: NixValue): never => {
throw "Not implemented: flakeRefToString";
throw new Error("Not implemented: flakeRefToString");
};
export const functionArgs = (f: NixValue): never => {
throw "Not implemented: functionArgs";
throw new Error("Not implemented: functionArgs");
};
export const genericClosure = (args: NixValue): never => {
throw "Not implemented: genericClosure";
throw new Error("Not implemented: genericClosure");
};
export const getFlake = (attrs: NixValue): never => {
throw "Not implemented: getFlake";
throw new Error("Not implemented: getFlake");
};
export const match =
(regex: NixValue) =>
(str: NixValue): never => {
throw "Not implemented: match";
throw new Error("Not implemented: match");
};
export const outputOf =
(drv: NixValue) =>
(out: NixValue): never => {
throw "Not implemented: outputOf";
throw new Error("Not implemented: outputOf");
};
export const parseDrvName = (s: NixValue): never => {
throw "Not implemented: parseDrvName";
throw new Error("Not implemented: parseDrvName");
};
export const parseFlakeName = (s: NixValue): never => {
throw "Not implemented: parseFlakeName";
throw new Error("Not implemented: parseFlakeName");
};
export const parseFlakeRef = (s: NixValue): never => {
throw "Not implemented: parseFlakeRef";
throw new Error("Not implemented: parseFlakeRef");
};
export const placeholder = (output: NixValue): never => {
throw "Not implemented: placeholder";
throw new Error("Not implemented: placeholder");
};
export const replaceStrings =
(from: NixValue) =>
(to: NixValue) =>
(s: NixValue): never => {
throw "Not implemented: replaceStrings";
throw new Error("Not implemented: replaceStrings");
};
export const split = (regex: NixValue, str: NixValue): never => {
throw "Not implemented: split";
throw new Error("Not implemented: split");
};
export const splitVersion = (s: NixValue): never => {
throw "Not implemented: splitVersion";
throw new Error("Not implemented: splitVersion");
};
export const traceVerbose = (e1: NixValue, e2: NixValue): never => {
throw "Not implemented: traceVerbose";
throw new Error("Not implemented: traceVerbose");
};
export const tryEval =
(e1: NixValue) =>
(e2: NixValue): never => {
throw "Not implemented: tryEval";
};
export const tryEval = (e: NixValue): { success: NixBool; value: NixStrictValue } => {
try {
return {
success: true,
value: force(e),
};
} catch (err) {
if (err instanceof CatchableError) {
return {
success: false,
value: false,
};
} else {
throw err;
}
}
};
export const zipAttrsWith =
(f: NixValue) =>
(list: NixValue): never => {
throw "Not implemented: zipAttrsWith";
throw new Error("Not implemented: zipAttrsWith");
};

View File

@@ -10,6 +10,7 @@ import type {
NixInt,
NixList,
NixNull,
NixStrictValue,
NixString,
NixValue,
} 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 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";

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 => {
if (!isAttrs(obj)) {
return false
return false;
}
let attrs = obj;

View File

@@ -3,7 +3,7 @@
* 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
@@ -20,12 +20,12 @@ export const IS_THUNK = Symbol("is_thunk");
export class NixThunk implements NixThunkInterface {
[key: symbol]: any;
readonly [IS_THUNK] = true as const;
func: (() => NixValue) | null;
result: Exclude<NixValue, NixThunkInterface> | null;
func: (() => NixValue) | undefined;
result: NixStrictValue | undefined;
constructor(func: () => NixValue) {
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)
* @returns The forced/evaluated value
*/
export const force = (value: NixValue): Exclude<NixValue, NixThunkInterface> => {
export const force = (value: NixValue): NixStrictValue => {
if (!is_thunk(value)) {
return value;
}
// Already evaluated - return cached result
if (value.func === null) {
if (value.func === undefined) {
return value.result!;
}
// Evaluate and cache
const result = force(value.func());
value.result = result;
value.func = null;
value.func = undefined;
return result;
};

View File

@@ -2,6 +2,8 @@
* Core TypeScript type definitions for nix-js runtime
*/
import { IS_THUNK } from "./thunk";
// Nix primitive types
export type NixInt = bigint;
export type NixFloat = number;
@@ -20,9 +22,9 @@ export type NixFunction = (...args: any[]) => any;
* Thunks delay evaluation until forced
*/
export interface NixThunkInterface {
readonly [key: symbol]: true; // IS_THUNK marker
func: (() => NixValue) | null;
result: Exclude<NixValue, NixThunkInterface> | null;
readonly [IS_THUNK]: true;
func: (() => NixValue) | undefined;
result: NixStrictValue | undefined;
}
// 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 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
export type BinaryOp<T = NixValue, U = NixValue, R = NixValue> = (a: T, b: U) => 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 deno_core::{Extension, ExtensionFileSource, JsRuntime, OpDecl, OpState, RuntimeOptions, v8};
use deno_error::JsErrorClass;
use crate::codegen::{CodegenContext, Compile};
use crate::context::{CtxPtr, PathDropGuard};
@@ -44,12 +45,12 @@ mod private {
pub struct SimpleErrorWrapper(pub(crate) String);
impl std::fmt::Display for SimpleErrorWrapper {
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 {}
js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError");
js_error_wrapper!(SimpleErrorWrapper, NixError, "Error");
impl From<String> for NixError {
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 current_dir = ctx.get_current_dir();
let absolute_path = current_dir
let mut absolute_path = current_dir
.join(&path)
.canonicalize()
.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 ctx = guard.as_ctx();
@@ -175,7 +179,7 @@ impl Runtime {
let global_value = self
.js_runtime
.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
deno_core::scope!(scope, self.js_runtime);