146 lines
3.8 KiB
JavaScript
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
|
|
};
|
|
})();
|