refactor: tidy

This commit is contained in:
2026-01-09 16:52:15 +08:00
parent 23950da6ea
commit 9cfffc440f
4 changed files with 94 additions and 83 deletions

View File

@@ -20,7 +20,7 @@ const typeName = (value: NixValue): string => {
if (typeof val === "object") return "attribute set"; if (typeof val === "object") return "attribute set";
throw new TypeError(`Unknown Nix type: ${typeof val}`); throw new TypeError(`Unknown Nix type: ${typeof val}`);
} };
/** /**
* Force a value and assert it's a list * Force a value and assert it's a list
@@ -52,7 +52,7 @@ export const force_function = (value: NixValue): NixFunction => {
*/ */
export const force_attrs = (value: NixValue): NixAttrs => { export const force_attrs = (value: NixValue): NixAttrs => {
const forced = force(value); const forced = force(value);
if (!isAttrs(forced)) { if (typeof forced !== "object" || Array.isArray(forced) || forced === null) {
throw new TypeError(`Expected attribute set, got ${typeName(forced)}`); throw new TypeError(`Expected attribute set, got ${typeName(forced)}`);
} }
return forced; return forced;

View File

@@ -1,7 +1,5 @@
import type { NixRuntime } from ".."; import type { NixRuntime } from "..";
export {};
declare global { declare global {
var Nix: NixRuntime; var Nix: NixRuntime;
namespace Deno { namespace Deno {

View File

@@ -18,7 +18,7 @@ mod downgrade;
pub struct Context { pub struct Context {
ctx: Pin<Box<Ctx>>, ctx: Pin<Box<Ctx>>,
pub(crate) runtime: Runtime, runtime: Runtime,
} }
impl Default for Context { impl Default for Context {
@@ -27,10 +27,20 @@ impl Default for Context {
} }
} }
pub(crate) struct CtxPtr(NonNull<Ctx>);
impl CtxPtr {
pub(crate) unsafe fn as_ref(&self) -> &Ctx {
unsafe { self.0.as_ref() }
}
pub(crate) unsafe fn as_mut(&mut self) -> Pin<&mut Ctx> {
unsafe { Pin::new_unchecked(self.0.as_mut()) }
}
}
impl Context { impl Context {
pub fn new() -> Self { pub fn new() -> Self {
let mut ctx = Box::pin(Ctx::new()); let mut ctx = Box::pin(Ctx::new());
let ptr = unsafe { NonNull::new_unchecked(Pin::get_unchecked_mut(ctx.as_mut())) }; let ptr = unsafe { CtxPtr(NonNull::new_unchecked(Pin::get_unchecked_mut(ctx.as_mut()))) };
let runtime = Runtime::new(ptr); let runtime = Runtime::new(ptr);
Self { ctx, runtime } Self { ctx, runtime }
@@ -45,16 +55,24 @@ impl Context {
if !root.errors().is_empty() { if !root.errors().is_empty() {
return Err(Error::parse_error(root.errors().iter().join("; "))); return Err(Error::parse_error(root.errors().iter().join("; ")));
} }
let root = ctx.as_mut().downgrade_ctx().downgrade(root.tree().expr().unwrap())?; let root = ctx
.as_mut()
.downgrade_ctx()
.downgrade(root.tree().expr().unwrap())?;
let code = ctx.get_ir(root).compile(Pin::get_ref(ctx.as_ref())); let code = ctx.get_ir(root).compile(Pin::get_ref(ctx.as_ref()));
let code = format!("Nix.force({})", code); let code = format!("Nix.force({})", code);
println!("[DEBUG] generated code: {}", &code); println!("[DEBUG] generated code: {}", &code);
self.runtime.eval(code) self.runtime.eval(code)
} }
#[allow(dead_code)]
pub(crate) fn eval_js(&mut self, code: String) -> Result<Value> {
self.runtime.eval(code)
}
} }
#[pin_project::pin_project(PinnedDrop)] #[pin_project::pin_project(PinnedDrop)]
pub struct Ctx { pub(crate) struct Ctx {
irs: Vec<Ir>, irs: Vec<Ir>,
symbols: DefaultStringInterner, symbols: DefaultStringInterner,
global: NonNull<HashMap<SymId, ExprId>>, global: NonNull<HashMap<SymId, ExprId>>,
@@ -72,22 +90,22 @@ impl PinnedDrop for Ctx {
} }
} }
pub struct PathDropGuard<'ctx> { pub(crate) struct PathDropGuard<'ctx> {
ctx: Pin<&'ctx mut Ctx>, ctx: Pin<&'ctx mut Ctx>,
} }
impl<'ctx> PathDropGuard<'ctx> { impl<'ctx> PathDropGuard<'ctx> {
pub fn new(path: PathBuf, mut ctx: Pin<&'ctx mut Ctx>) -> Self { pub(crate) fn new(path: PathBuf, mut ctx: Pin<&'ctx mut Ctx>) -> Self {
ctx.as_mut().project().path_stack.push(path); ctx.as_mut().project().path_stack.push(path);
Self { ctx } Self { ctx }
} }
pub fn new_cwd(mut ctx: Pin<&'ctx mut Ctx>) -> Self { pub(crate) fn new_cwd(mut ctx: Pin<&'ctx mut Ctx>) -> Self {
let cwd = std::env::current_dir().unwrap(); let cwd = std::env::current_dir().unwrap();
let virtual_file = cwd.join("__eval__.nix"); let virtual_file = cwd.join("__eval__.nix");
ctx.as_mut().project().path_stack.push(virtual_file); ctx.as_mut().project().path_stack.push(virtual_file);
Self { ctx } Self { ctx }
} }
pub fn as_ctx<'a>(&'a mut self) -> &'a mut Pin<&'ctx mut Ctx> { pub(crate) fn as_ctx<'a>(&'a mut self) -> &'a mut Pin<&'ctx mut Ctx> {
&mut self.ctx &mut self.ctx
} }
} }
@@ -165,7 +183,6 @@ impl Ctx {
} }
pub fn downgrade_ctx<'a>(self: Pin<&'a mut Self>) -> DowngradeCtx<'a> { pub fn downgrade_ctx<'a>(self: Pin<&'a mut Self>) -> DowngradeCtx<'a> {
// SAFETY: `global` is readonly
let global_ref = unsafe { self.global.as_ref() }; let global_ref = unsafe { self.global.as_ref() };
DowngradeCtx::new(self, global_ref) DowngradeCtx::new(self, global_ref)
} }

View File

@@ -1,36 +1,37 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::RefCell;
use std::pin::Pin; use std::pin::Pin;
use std::ptr::NonNull;
use std::sync::Once; use std::sync::Once;
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8}; use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpDecl, OpState, RuntimeOptions, v8};
use deno_error::js_error_wrapper; use deno_error::js_error_wrapper;
use crate::codegen::{CodegenContext, Compile}; use crate::codegen::{CodegenContext, Compile};
use crate::context::{Ctx, PathDropGuard}; use crate::context::{CtxPtr, PathDropGuard};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::ir::DowngradeContext; use crate::ir::DowngradeContext;
use crate::value::{AttrSet, Const, List, Symbol, Value}; use crate::value::{AttrSet, Const, List, Symbol, Value};
type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>; type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>;
type LocalValue<'a> = v8::Local<'a, v8::Value>;
type LocalSymbol<'a> = v8::Local<'a, v8::Symbol>;
fn runtime_extension(ctx: NonNull<Ctx>) -> Extension { fn runtime_extension(ctx: CtxPtr) -> Extension {
const ESM: &[ExtensionFileSource] = const ESM: &[ExtensionFileSource] =
&deno_core::include_js_files!(nix_runtime dir "runtime-ts/dist", "runtime.js"); &deno_core::include_js_files!(nix_runtime dir "runtime-ts/dist", "runtime.js");
const OPS: &[OpDecl] = &[
op_import(),
op_read_file(),
op_path_exists(),
op_resolve_path(),
];
Extension { Extension {
name: "nix_runtime", name: "nix_runtime",
esm_files: Cow::Borrowed(ESM), esm_files: Cow::Borrowed(ESM),
esm_entry_point: Some("ext:nix_runtime/runtime.js"), esm_entry_point: Some("ext:nix_runtime/runtime.js"),
ops: Cow::Owned(vec![ ops: Cow::Borrowed(OPS),
op_import(),
op_read_file(),
op_path_exists(),
op_resolve_path(),
]),
op_state_fn: Some(Box::new(move |state| { op_state_fn: Some(Box::new(move |state| {
state.put(RefCell::new(ctx)); state.put(ctx);
})), })),
enabled: true, enabled: true,
..Default::default() ..Default::default()
@@ -58,19 +59,18 @@ impl std::error::Error for SimpleErrorWrapper {
} }
} }
js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError");
impl From<String> for NixError { impl From<String> for NixError {
fn from(value: String) -> Self { fn from(value: String) -> Self {
NixError(SimpleErrorWrapper(value)) NixError(SimpleErrorWrapper(value))
} }
} }
js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError");
#[deno_core::op2] #[deno_core::op2]
#[string] #[string]
fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result<String, NixError> { fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result<String, NixError> {
let mut ptr = state.borrow::<RefCell<NonNull<Ctx>>>().borrow_mut(); let ptr = state.borrow_mut::<CtxPtr>();
let ctx = unsafe { Pin::new_unchecked(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 absolute_path = current_dir
@@ -126,7 +126,7 @@ fn op_resolve_path(
state: &mut OpState, state: &mut OpState,
#[string] path: String, #[string] path: String,
) -> std::result::Result<String, NixError> { ) -> std::result::Result<String, NixError> {
let ptr = state.borrow::<RefCell<NonNull<Ctx>>>().borrow(); let ptr = state.borrow::<CtxPtr>();
let ctx = unsafe { ptr.as_ref() }; let ctx = unsafe { ptr.as_ref() };
// If already absolute, return as-is // If already absolute, return as-is
@@ -144,14 +144,14 @@ fn op_resolve_path(
.map_err(|e| -> NixError { format!("Failed to resolve path {}: {}", path, e).into() }) .map_err(|e| -> NixError { format!("Failed to resolve path {}: {}", path, e).into() })
} }
pub struct Runtime { pub(crate) struct Runtime {
js_runtime: JsRuntime, js_runtime: JsRuntime,
is_thunk_symbol: v8::Global<v8::Symbol>, is_thunk_symbol: v8::Global<v8::Symbol>,
primop_metadata_symbol: v8::Global<v8::Symbol>, primop_metadata_symbol: v8::Global<v8::Symbol>,
} }
impl Runtime { impl Runtime {
pub fn new(ctx: NonNull<Ctx>) -> Self { pub(crate) fn new(ctx: CtxPtr) -> Self {
// Initialize V8 once // Initialize V8 once
static INIT: Once = Once::new(); static INIT: Once = Once::new();
INIT.call_once(|| { INIT.call_once(|| {
@@ -178,7 +178,7 @@ impl Runtime {
} }
} }
pub fn eval(&mut self, script: String) -> Result<Value> { pub(crate) fn eval(&mut self, script: String) -> Result<Value> {
let global_value = self let global_value = self
.js_runtime .js_runtime
.execute_script("<eval>", script) .execute_script("<eval>", script)
@@ -190,13 +190,16 @@ impl Runtime {
let is_thunk_symbol = v8::Local::new(scope, &self.is_thunk_symbol); let is_thunk_symbol = v8::Local::new(scope, &self.is_thunk_symbol);
let primop_metadata_symbol = v8::Local::new(scope, &self.primop_metadata_symbol); let primop_metadata_symbol = v8::Local::new(scope, &self.primop_metadata_symbol);
Ok(to_value(local_value, scope, is_thunk_symbol, primop_metadata_symbol)) Ok(to_value(
local_value,
scope,
is_thunk_symbol,
primop_metadata_symbol,
))
} }
/// get (IS_THUNK, PRIMOP_METADATA) /// get (IS_THUNK, PRIMOP_METADATA)
fn get_symbols( fn get_symbols(scope: &ScopeRef) -> (v8::Global<v8::Symbol>, v8::Global<v8::Symbol>) {
scope: &v8::PinnedRef<'_, v8::HandleScope<'_>>,
) -> (v8::Global<v8::Symbol>, v8::Global<v8::Symbol>) {
let global = scope.get_current_context().global(scope); let global = scope.get_current_context().global(scope);
let nix_key = v8::String::new(scope, "Nix").unwrap(); let nix_key = v8::String::new(scope, "Nix").unwrap();
let nix_obj = global let nix_obj = global
@@ -220,10 +223,10 @@ impl Runtime {
} }
fn to_value<'a>( fn to_value<'a>(
val: v8::Local<'a, v8::Value>, val: LocalValue<'a>,
scope: &ScopeRef<'a, '_>, scope: &ScopeRef<'a, '_>,
is_thunk_symbol: v8::Local<'a, v8::Symbol>, is_thunk_symbol: LocalSymbol<'a>,
primop_metadata_symbol: v8::Local<'a, v8::Symbol>, primop_metadata_symbol: LocalSymbol<'a>,
) -> Value { ) -> Value {
match () { match () {
_ if val.is_big_int() => { _ if val.is_big_int() => {
@@ -257,10 +260,8 @@ fn to_value<'a>(
Value::List(List::new(list)) Value::List(List::new(list))
} }
_ if val.is_function() => { _ if val.is_function() => {
if let Some(name) = primop_app_name(val, scope, primop_metadata_symbol) { if let Some(primop) = to_primop(val, scope, primop_metadata_symbol) {
Value::PrimOpApp(name) primop
} else if let Some(name) = primop_name(val, scope, primop_metadata_symbol) {
Value::PrimOp(name)
} else { } else {
Value::Func Value::Func
} }
@@ -280,7 +281,10 @@ fn to_value<'a>(
let key = keys.get_index(scope, i).unwrap(); let key = keys.get_index(scope, i).unwrap();
let val = val.get(scope, key).unwrap(); let val = val.get(scope, key).unwrap();
let key = key.to_rust_string_lossy(scope); let key = key.to_rust_string_lossy(scope);
(Symbol::new(key), to_value(val, scope, is_thunk_symbol, primop_metadata_symbol)) (
Symbol::new(key),
to_value(val, scope, is_thunk_symbol, primop_metadata_symbol),
)
}) })
.collect(); .collect();
Value::AttrSet(AttrSet::new(attrs)) Value::AttrSet(AttrSet::new(attrs))
@@ -289,7 +293,7 @@ fn to_value<'a>(
} }
} }
fn is_thunk<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> bool { fn is_thunk<'a>(val: LocalValue<'a>, scope: &ScopeRef<'a, '_>, symbol: LocalSymbol<'a>) -> bool {
if !val.is_object() { if !val.is_object() {
return false; return false;
} }
@@ -298,39 +302,31 @@ fn is_thunk<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol:
matches!(obj.get(scope, symbol.into()), Some(v) if v.is_true()) matches!(obj.get(scope, symbol.into()), Some(v) if v.is_true())
} }
fn primop_name<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> Option<String> { fn to_primop<'a>(
val: LocalValue<'a>,
scope: &ScopeRef<'a, '_>,
symbol: LocalSymbol<'a>,
) -> Option<Value> {
if !val.is_function() { if !val.is_function() {
return None; return None;
} }
let obj = val.to_object(scope).unwrap(); let obj = val.to_object(scope).unwrap();
let metadata = obj.get(scope, symbol.into())?.to_object(scope)?;
if let Some(metadata) = obj.get(scope, symbol.into()) let name_key = v8::String::new(scope, "name")?;
&& let Some(metadata_obj) = metadata.to_object(scope) let name = metadata
&& let Some(name_key) = v8::String::new(scope, "name") .get(scope, name_key.into())?
&& let Some(name_val) = metadata_obj.get(scope, name_key.into()) .to_rust_string_lossy(scope);
{
Some(name_val.to_rust_string_lossy(scope)) let applied_key = v8::String::new(scope, "applied")?;
let applied_val = metadata.get(scope, applied_key.into())?;
let applied = applied_val.to_number(scope)?.value();
if applied == 0.0 {
Some(Value::PrimOp(name))
} else { } else {
None Some(Value::PrimOpApp(name))
}
}
fn primop_app_name<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> Option<String> {
let name = primop_name(val, scope, symbol)?;
let obj = val.to_object(scope).unwrap();
if let Some(metadata) = obj.get(scope, symbol.into())
&& let Some(metadata_obj) = metadata.to_object(scope)
&& let Some(applied_key) = v8::String::new(scope, "applied")
&& let Some(applied_val) = metadata_obj.get(scope, applied_key.into())
&& let Some(applied_num) = applied_val.to_number(scope)
&& applied_num.value() > 0.0
{
Some(name)
} else {
None
} }
} }
@@ -343,7 +339,7 @@ mod test {
fn to_value_working() { fn to_value_working() {
let mut ctx = Context::new(); let mut ctx = Context::new();
assert_eq!( assert_eq!(
ctx.runtime.eval( ctx.eval_js(
"({ "({
test: [1., 9223372036854775807n, true, false, 'hello world!'] test: [1., 9223372036854775807n, true, false, 'hello world!']
})" })"