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";
throw new TypeError(`Unknown Nix type: ${typeof val}`);
}
};
/**
* 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 => {
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)}`);
}
return forced;

View File

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

View File

@@ -18,7 +18,7 @@ mod downgrade;
pub struct Context {
ctx: Pin<Box<Ctx>>,
pub(crate) runtime: Runtime,
runtime: Runtime,
}
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 {
pub fn new() -> Self {
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);
Self { ctx, runtime }
@@ -45,16 +55,24 @@ impl Context {
if !root.errors().is_empty() {
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 = format!("Nix.force({})", code);
println!("[DEBUG] generated code: {}", &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)]
pub struct Ctx {
pub(crate) struct Ctx {
irs: Vec<Ir>,
symbols: DefaultStringInterner,
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>,
}
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);
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 virtual_file = cwd.join("__eval__.nix");
ctx.as_mut().project().path_stack.push(virtual_file);
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
}
}
@@ -165,7 +183,6 @@ impl Ctx {
}
pub fn downgrade_ctx<'a>(self: Pin<&'a mut Self>) -> DowngradeCtx<'a> {
// SAFETY: `global` is readonly
let global_ref = unsafe { self.global.as_ref() };
DowngradeCtx::new(self, global_ref)
}
@@ -827,14 +844,14 @@ mod test {
let main_path = temp_dir.path().join("main.nix");
let main_content = r#"
let
lib = import ./lib.nix;
helper = import ./subdir/helper.nix;
in {
result1 = lib.multiply 3 4;
result2 = helper.subtract 10 3;
}
"#;
let
lib = import ./lib.nix;
helper = import ./subdir/helper.nix;
in {
result1 = lib.multiply 3 4;
result2 = helper.subtract 10 3;
}
"#;
std::fs::write(&main_path, main_content).unwrap();
let expr = format!(r#"let x = import "{}"; in x.result1"#, main_path.display());

View File

@@ -1,36 +1,37 @@
use std::borrow::Cow;
use std::cell::RefCell;
use std::pin::Pin;
use std::ptr::NonNull;
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 crate::codegen::{CodegenContext, Compile};
use crate::context::{Ctx, PathDropGuard};
use crate::context::{CtxPtr, PathDropGuard};
use crate::error::{Error, Result};
use crate::ir::DowngradeContext;
use crate::value::{AttrSet, Const, List, Symbol, Value};
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] =
&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 {
name: "nix_runtime",
esm_files: Cow::Borrowed(ESM),
esm_entry_point: Some("ext:nix_runtime/runtime.js"),
ops: Cow::Owned(vec![
op_import(),
op_read_file(),
op_path_exists(),
op_resolve_path(),
]),
ops: Cow::Borrowed(OPS),
op_state_fn: Some(Box::new(move |state| {
state.put(RefCell::new(ctx));
state.put(ctx);
})),
enabled: true,
..Default::default()
@@ -58,19 +59,18 @@ impl std::error::Error for SimpleErrorWrapper {
}
}
js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError");
impl From<String> for NixError {
fn from(value: String) -> Self {
NixError(SimpleErrorWrapper(value))
}
}
js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError");
#[deno_core::op2]
#[string]
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 ctx = unsafe { Pin::new_unchecked(ptr.as_mut()) };
let ptr = state.borrow_mut::<CtxPtr>();
let ctx = unsafe { ptr.as_mut() };
let current_dir = ctx.get_current_dir();
let absolute_path = current_dir
@@ -126,7 +126,7 @@ fn op_resolve_path(
state: &mut OpState,
#[string] path: String,
) -> std::result::Result<String, NixError> {
let ptr = state.borrow::<RefCell<NonNull<Ctx>>>().borrow();
let ptr = state.borrow::<CtxPtr>();
let ctx = unsafe { ptr.as_ref() };
// 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() })
}
pub struct Runtime {
pub(crate) struct Runtime {
js_runtime: JsRuntime,
is_thunk_symbol: v8::Global<v8::Symbol>,
primop_metadata_symbol: v8::Global<v8::Symbol>,
}
impl Runtime {
pub fn new(ctx: NonNull<Ctx>) -> Self {
pub(crate) fn new(ctx: CtxPtr) -> Self {
// Initialize V8 once
static INIT: Once = Once::new();
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
.js_runtime
.execute_script("<eval>", script)
@@ -190,13 +190,16 @@ impl Runtime {
let is_thunk_symbol = v8::Local::new(scope, &self.is_thunk_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)
fn get_symbols(
scope: &v8::PinnedRef<'_, v8::HandleScope<'_>>,
) -> (v8::Global<v8::Symbol>, v8::Global<v8::Symbol>) {
fn get_symbols(scope: &ScopeRef) -> (v8::Global<v8::Symbol>, v8::Global<v8::Symbol>) {
let global = scope.get_current_context().global(scope);
let nix_key = v8::String::new(scope, "Nix").unwrap();
let nix_obj = global
@@ -220,10 +223,10 @@ impl Runtime {
}
fn to_value<'a>(
val: v8::Local<'a, v8::Value>,
val: LocalValue<'a>,
scope: &ScopeRef<'a, '_>,
is_thunk_symbol: v8::Local<'a, v8::Symbol>,
primop_metadata_symbol: v8::Local<'a, v8::Symbol>,
is_thunk_symbol: LocalSymbol<'a>,
primop_metadata_symbol: LocalSymbol<'a>,
) -> Value {
match () {
_ if val.is_big_int() => {
@@ -257,10 +260,8 @@ fn to_value<'a>(
Value::List(List::new(list))
}
_ if val.is_function() => {
if let Some(name) = primop_app_name(val, scope, primop_metadata_symbol) {
Value::PrimOpApp(name)
} else if let Some(name) = primop_name(val, scope, primop_metadata_symbol) {
Value::PrimOp(name)
if let Some(primop) = to_primop(val, scope, primop_metadata_symbol) {
primop
} else {
Value::Func
}
@@ -280,7 +281,10 @@ fn to_value<'a>(
let key = keys.get_index(scope, i).unwrap();
let val = val.get(scope, key).unwrap();
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();
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() {
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())
}
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() {
return None;
}
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 Some(metadata_obj) = metadata.to_object(scope)
&& let Some(name_key) = v8::String::new(scope, "name")
&& let Some(name_val) = metadata_obj.get(scope, name_key.into())
{
Some(name_val.to_rust_string_lossy(scope))
let name_key = v8::String::new(scope, "name")?;
let name = metadata
.get(scope, name_key.into())?
.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 {
None
}
}
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
Some(Value::PrimOpApp(name))
}
}
@@ -343,10 +339,10 @@ mod test {
fn to_value_working() {
let mut ctx = Context::new();
assert_eq!(
ctx.runtime.eval(
ctx.eval_js(
"({
test: [1., 9223372036854775807n, true, false, 'hello world!']
})"
test: [1., 9223372036854775807n, true, false, 'hello world!']
})"
.into(),
)
.unwrap(),