implement Select and HasAttr

This commit is contained in:
2026-04-22 08:32:11 +08:00
parent e469d1b819
commit 21036aba46
9 changed files with 316 additions and 96 deletions
+5 -23
View File
@@ -14,10 +14,7 @@ impl<'gc> crate::Vm<'gc> {
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
if let (Some(ls), Some(rs)) = (
VmContextExt::get_string(ctx, lhs),
VmContextExt::get_string(ctx, rhs),
) {
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}")));
self.push(Value::new_gc(ns));
return Step::Continue(());
@@ -33,20 +30,12 @@ impl<'gc> crate::Vm<'gc> {
}
#[inline(always)]
pub(crate) fn op_sub(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
pub(crate) fn op_sub(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
self.op_arith(reader, mc, i64::wrapping_sub, |a, b| a - b)
}
#[inline(always)]
pub(crate) fn op_mul(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
pub(crate) fn op_mul(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
self.op_arith(reader, mc, i64::wrapping_mul, |a, b| a * b)
}
@@ -70,11 +59,7 @@ impl<'gc> crate::Vm<'gc> {
}
#[inline(always)]
pub(crate) fn op_div(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
match (get_num(rhs), get_num(lhs)) {
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
@@ -235,10 +220,7 @@ impl<'gc> crate::Vm<'gc> {
if lhs.is::<crate::Null>() && rhs.is::<crate::Null>() {
return Ok(true);
}
if let (Some(a), Some(b)) = (
VmContextExt::get_string(ctx, lhs),
VmContextExt::get_string(ctx, rhs),
) {
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
return Ok(a == b);
}
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::List>(), rhs.as_gc::<crate::List>()) {
+2 -8
View File
@@ -28,19 +28,13 @@ impl<'gc> crate::Vm<'gc> {
}
#[inline(always)]
pub(crate) fn op_load_repl_binding(
&mut self,
reader: &mut BytecodeReader<'_>,
) -> Step {
pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let _name = reader.read_string_id();
todo!("LoadReplBinding");
}
#[inline(always)]
pub(crate) fn op_load_scoped_binding(
&mut self,
reader: &mut BytecodeReader<'_>,
) -> Step {
pub(crate) fn op_load_scoped_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let _name = reader.read_string_id();
todo!("LoadScopedBinding");
}
+185 -29
View File
@@ -1,9 +1,11 @@
use fix_common::StringId;
use fix_error::Error;
use gc_arena::Gc;
use smallvec::SmallVec;
use crate::value::NixType;
use crate::{
AttrKeyData, AttrSet, BytecodeReader, List, NixString, OperandData, Step, StrictValue, Value,
AttrKeyData, AttrSet, BytecodeReader, List, OperandData, Step, StrictValue, Value, VmContextExt,
};
impl<'gc> crate::Vm<'gc> {
@@ -63,21 +65,7 @@ impl<'gc> crate::Vm<'gc> {
Some(v) => {
self.push(v);
}
None => loop {
let byte = reader.bytecode()[reader.pc()];
if byte == fix_codegen::Op::SelectStatic as u8 {
reader.set_pc(reader.pc() + 1 + 4 + 4);
} else if byte == fix_codegen::Op::SelectDynamic as u8 {
reader.set_pc(reader.pc() + 1 + 4);
} else if byte == fix_codegen::Op::JumpIfSelectSucceeded as u8 {
reader.set_pc(reader.pc() + 1 + 4);
break;
} else {
let name = ctx.resolve_string(key);
return self
.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
}
},
None => return self.select_skip(key, ctx, reader),
}
Step::Continue(())
}
@@ -93,40 +81,208 @@ impl<'gc> crate::Vm<'gc> {
let (attrset, key_val) = self.try_force::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
let key_sid = if let Some(sid) = key_val.as_inline::<crate::StringId>() {
sid
} else if let Some(ns) = key_val.as_gc::<NixString>() {
ctx.intern_string(ns.as_str())
} else {
return self.finish_err(Error::eval_error("dynamic select key must be a string"));
let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id,
Err(got) => return self.finish_type_err(NixType::String, got),
};
match attrset.lookup(key_sid) {
Some(v) => {
self.push(v);
}
None => {
let name = ctx.resolve_string(key_sid);
return self.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
None => return self.select_skip(key_sid, ctx, reader),
}
Step::Continue(())
}
/// Skip the rest of a **Select** attrpath after a missing attribute.
/// Only recognises Select opcodes and jumps; encountering any other
/// opcode means we've reached the end of the select sequence and
/// should report the missing-attribute error.
fn select_skip(
&mut self,
key: StringId,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
) -> Step {
use fix_codegen::Op::*;
loop {
match reader.read_op() {
SelectStatic => {
reader.set_pc(reader.pc() + 4 + 4);
}
SelectDynamic => {
reader.set_pc(reader.pc() + 4);
}
JumpIfSelectSucceeded => {
reader.set_pc(reader.pc() + 4);
break Step::Continue(());
}
JumpIfSelectFailed => {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
_ => {
let name = ctx.resolve_string(key);
return self
.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
}
}
}
}
/// Skip the rest of a **HasAttr** attrpath after an intermediate
/// lookup failed. Only recognises HasAttr opcodes and jumps.
fn has_attr_skip(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
use fix_codegen::Op::*;
loop {
match reader.read_op() {
HasAttrPathStatic => {
reader.set_pc(reader.pc() + 4 + 4);
}
HasAttrPathDynamic => {
reader.set_pc(reader.pc() + 4);
}
HasAttrStatic => {
reader.set_pc(reader.pc() + 4);
break Step::Continue(());
}
JumpIfSelectFailed => {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
HasAttrDynamic => {
break Step::Continue(());
}
HasAttrResolve => {
reader.set_pc(reader.pc() - 1);
break Step::Continue(());
}
other => {
unreachable!("unexpected opcode {:?} in has_attr_skip", other as u8)
}
}
}
}
#[inline(always)]
pub(crate) fn op_has_attr_path_static(
&mut self,
_ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let key = reader.read_string_id();
let current = self.try_force::<StrictValue>(reader, mc)?;
match current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key))
{
Some(v) => {
self.push(v);
}
None => return self.has_attr_skip(reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_select_succeeded(
pub(crate) fn op_has_attr_path_dynamic(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let (current, key_val) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id,
Err(got) => return self.finish_type_err(NixType::String, got),
};
match current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key_sid))
{
Some(v) => {
self.push(v);
}
None => return self.has_attr_skip(reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_select_failed(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
// No-op
let _offset = reader.read_i32();
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_select_succeeded(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let _n = reader.read_u16() as usize;
todo!("HasAttr");
pub(crate) fn op_has_attr_static(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let key = reader.read_string_id();
let current = self.try_force::<StrictValue>(reader, mc)?;
self.push(Value::new_inline(
current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key))
.is_some(),
));
// Skip HasAttrResolve
reader.set_pc(reader.pc() + 1);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_dynamic(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let (current, dyn_key) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(dyn_key) {
Ok(id) => id,
Err(got) => return self.finish_type_err(NixType::String, got),
};
self.push(Value::new_inline(
current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key_sid))
.is_some(),
));
// Skip HasAttrResolve
reader.set_pc(reader.pc() + 1);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_resolve(&mut self) -> Step {
// If we reach here, has_attr check has failed, push false (AttrSet is already popped)
self.push(Value::new_inline(false));
Step::Continue(())
}
#[inline(always)]
+1 -1
View File
@@ -2,8 +2,8 @@ use fix_common::Symbol;
use fix_error::Error;
use gc_arena::Gc;
use crate::{BytecodeReader, CallFrame, Step, WithEnv};
use crate::value::*;
use crate::{BytecodeReader, CallFrame, Step, WithEnv};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]