Files
nix-js/nix-js/src/runtime/runtime.js
2026-01-02 02:54:52 +08:00

146 lines
3.8 KiB
JavaScript

const NixRuntime = (() => {
const IS_THUNK = Symbol("is_thunk");
class NixThunk {
constructor(func) {
this[IS_THUNK] = true;
this.func = func;
this.is_forced = false;
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.is_forced) {
return value.result;
}
const result = force(value.func());
value.result = result;
value.is_forced = true;
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 trace = (msg, value) => {
console.log(`[TRACE] ${msg}`);
return force(value);
};
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 force(default_val);
}
if (!(forced_key in forced_obj)) {
return force(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)
};
return {
create_thunk,
force,
is_thunk,
create_lazy_set,
trace,
select,
select_with_default,
validate_params,
op
};
})();