implement Select & SelectDefault

This commit is contained in:
2026-03-30 18:23:21 +08:00
parent 6567ed4058
commit e82369695c
2 changed files with 172 additions and 2 deletions
+2
View File
@@ -47,6 +47,7 @@ pub struct Runtime {
fuel: usize, fuel: usize,
error_contexts: Stack<8192, ErrorFrame>, error_contexts: Stack<8192, ErrorFrame>,
arena: Arena<Rootable![GcRoot<'_>]>, arena: Arena<Rootable![GcRoot<'_>]>,
force_mode: ForceMode,
} }
impl Runtime { impl Runtime {
@@ -81,6 +82,7 @@ impl Runtime {
fuel: Self::DEFAULT_FUEL_AMOUNT, fuel: Self::DEFAULT_FUEL_AMOUNT,
error_contexts: Stack::new(), error_contexts: Stack::new(),
arena, arena,
force_mode: ForceMode::default(),
}) })
} }
+170 -2
View File
@@ -578,13 +578,181 @@ impl Runtime {
let n = self.read_u16() as usize; let n = self.read_u16() as usize;
let _span_id = self.read_u32(); let _span_id = self.read_u32();
let keys = self.read_attr_keys(n); let keys = self.read_attr_keys(n);
todo!("implement Select (force + lookup)");
// Move dynamic key values from stack to temp_stack
let dyn_count = keys
.iter()
.filter(|k| matches!(k, SelectKeyData::Dynamic))
.count();
let temp_base = self.arena.mutate_root(|_, root| {
let base = root.temp_stack.len();
for _ in 0..dyn_count {
let v = root.pop_stack();
root.temp_stack.push(v);
}
base
});
// Force the expr (now TOS)
self.force_tos();
for (i, key) in keys.iter().enumerate() {
let key_sid = match key {
&SelectKeyData::Static(sid) => sid,
SelectKeyData::Dynamic => {
self.arena.mutate_root(|_, root| {
let v =
root.temp_stack.pop().expect("missing dynamic key");
root.push_stack(v);
});
self.force_tos();
let key_data: std::result::Result<StringId, String> =
self.arena.mutate_root(|_, root| {
let v = root.pop_stack();
if let Some(sid) = v.as_inline::<StringId>() {
Ok(sid)
} else if let Some(ns) = v.as_gc::<NixString>() {
Err(ns.as_str().to_owned())
} else {
panic!("dynamic select key must be a string")
}
});
match key_data {
Ok(sid) => sid,
Err(s) => StringId(self.strings.get_or_intern(&s)),
}
}
};
let result = self.arena.mutate_root(|_, root| {
let val = root.pop_stack();
let Some(attrset) = val.as_gc::<AttrSet<'_>>() else {
return Err(vm_err(
"value is not a set while a set was expected",
));
};
match attrset.lookup(key_sid) {
Some(v) => {
root.push_stack(v);
Ok(true)
}
None => Ok(false),
}
});
match result {
Err(e) => {
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
return Runtime::handle_vm_error(self, e);
}
Ok(false) => {
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
let name =
self.strings.resolve(key_sid.0).unwrap_or("«unknown»");
return Runtime::handle_vm_error(
self,
vm_err(format!("attribute '{name}' missing")),
);
}
Ok(true) => {
if i < n - 1 {
self.force_tos();
}
}
}
}
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
} }
SelectDefault => { SelectDefault => {
let n = self.read_u16() as usize; let n = self.read_u16() as usize;
let _span_id = self.read_u32(); let _span_id = self.read_u32();
let keys = self.read_attr_keys(n); let keys = self.read_attr_keys(n);
todo!("implement SelectDefault (force + lookup with default)");
let dyn_count = keys
.iter()
.filter(|k| matches!(k, SelectKeyData::Dynamic))
.count();
let temp_base = self.arena.mutate_root(|_, root| {
let base = root.temp_stack.len();
// Default value is on top of the stack (pushed last by codegen)
let default_val = root.pop_stack();
root.temp_stack.push(default_val);
// Then dynamic keys
for _ in 0..dyn_count {
let v = root.pop_stack();
root.temp_stack.push(v);
}
base
});
// Force the expr (now TOS)
self.force_tos();
let mut use_default = false;
for (i, key) in keys.iter().enumerate() {
let key_sid = match key {
&SelectKeyData::Static(sid) => sid,
SelectKeyData::Dynamic => {
self.arena.mutate_root(|_, root| {
let v =
root.temp_stack.pop().expect("missing dynamic key");
root.push_stack(v);
});
self.force_tos();
let key_data: std::result::Result<StringId, String> =
self.arena.mutate_root(|_, root| {
let v = root.pop_stack();
if let Some(sid) = v.as_inline::<StringId>() {
Ok(sid)
} else if let Some(ns) = v.as_gc::<NixString>() {
Err(ns.as_str().to_owned())
} else {
panic!("dynamic select key must be a string")
}
});
match key_data {
Ok(sid) => sid,
Err(s) => StringId(self.strings.get_or_intern(&s)),
}
}
};
let found = self.arena.mutate_root(|_, root| {
let val = root.pop_stack();
if let Some(attrset) = val.as_gc::<AttrSet<'_>>() {
if let Some(v) = attrset.lookup(key_sid) {
root.push_stack(v);
return true;
}
}
// Not a set or key missing → use default
false
});
if !found {
use_default = true;
break;
}
if i < n - 1 {
self.force_tos();
}
}
if use_default {
self.arena.mutate_root(|_, root| {
let default_val = root.temp_stack[temp_base];
root.temp_stack.truncate(temp_base);
root.push_stack(default_val);
});
} else {
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
}
} }
HasAttr => { HasAttr => {
let n = self.read_u16() as usize; let n = self.read_u16() as usize;