feat: IS_PRIMOP

This commit is contained in:
2026-01-02 23:07:43 +08:00
parent f1670e8397
commit 073c95f2c3
7 changed files with 307 additions and 139 deletions

View File

@@ -23,14 +23,17 @@ pub fn run(script: &str) -> Result<Value> {
struct RuntimeContext<'a, 'b> {
scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>,
is_thunk_symbol: Option<v8::Local<'a, v8::Symbol>>,
is_primop_symbol: Option<v8::Local<'a, v8::Symbol>>,
}
impl<'a, 'b> RuntimeContext<'a, 'b> {
fn new(scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>) -> Self {
let is_thunk_symbol = Self::get_is_thunk_symbol(scope);
let is_primop_symbol = Self::get_is_primop_symbol(scope);
Self {
scope,
is_thunk_symbol,
is_primop_symbol,
}
}
@@ -50,6 +53,23 @@ impl<'a, 'b> RuntimeContext<'a, 'b> {
None
}
}
fn get_is_primop_symbol(
scope: &v8::PinnedRef<'a, v8::HandleScope<'b>>,
) -> Option<v8::Local<'a, v8::Symbol>> {
let global = scope.get_current_context().global(scope);
let nix_key = v8::String::new(scope, "Nix")?;
let nix_obj = global.get(scope, nix_key.into())?.to_object(scope)?;
let is_primop_sym_key = v8::String::new(scope, "IS_PRIMOP")?;
let is_primop_sym = nix_obj.get(scope, is_primop_sym_key.into())?;
if is_primop_sym.is_symbol() {
is_primop_sym.try_cast().ok()
} else {
None
}
}
}
fn run_impl(script: &str, isolate: &mut v8::Isolate) -> Result<Value> {
@@ -127,8 +147,11 @@ fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeContext<'a, 'b>)
_ if val.is_number() => {
let val = val.to_number(scope).unwrap().value();
// Heuristic: convert whole numbers to Int (for backward compatibility and JS interop)
if val.is_finite() && val.fract() == 0.0
&& val >= i64::MIN as f64 && val <= i64::MAX as f64 {
if val.is_finite()
&& val.fract() == 0.0
&& val >= i64::MIN as f64
&& val <= i64::MAX as f64
{
Value::Const(Const::Int(val as i64))
} else {
Value::Const(Const::Float(val))
@@ -152,7 +175,15 @@ fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeContext<'a, 'b>)
.collect();
Value::List(List::new(list))
}
_ if val.is_function() => Value::Func,
_ if val.is_function() => {
if let Some(name) = primop_app_name(val, ctx) {
Value::PrimOpApp(name)
} else if let Some(name) = primop_name(val, ctx) {
Value::PrimOp(name)
} else {
Value::Func
}
}
_ if val.is_object() => {
if is_thunk(val, ctx) {
return Value::Thunk;
@@ -193,6 +224,58 @@ fn is_thunk<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeContext<'a, 'b>)
matches!(obj.get(scope, is_thunk_sym.into()), Some(v) if v.is_true())
}
/// Check if a function is a primop
fn primop_name<'a, 'b>(
val: v8::Local<'a, v8::Value>,
ctx: &RuntimeContext<'a, 'b>,
) -> Option<String> {
if !val.is_function() {
return None;
}
// Use cached IS_PRIMOP symbol from context
let is_primop_sym = ctx.is_primop_symbol?;
let scope = ctx.scope;
let obj = val.to_object(scope).unwrap();
if let Some(metadata) = obj.get(scope, is_primop_sym.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))
} else {
None
}
}
/// Check if a primop is partially applied (has applied > 0)
fn primop_app_name<'a, 'b>(
val: v8::Local<'a, v8::Value>,
ctx: &RuntimeContext<'a, 'b>,
) -> Option<String> {
let name = primop_name(val, ctx)?;
// Get cached IS_PRIMOP symbol
let is_primop_sym = ctx.is_primop_symbol?;
let scope = ctx.scope;
let obj = val.to_object(scope).unwrap();
if let Some(metadata) = obj.get(scope, is_primop_sym.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
}
}
#[test]
fn to_value_working() {
assert_eq!(