diff --git a/fix-builtins/src/lib.rs b/fix-builtins/src/lib.rs index 8d649ba..7ada8c2 100644 --- a/fix-builtins/src/lib.rs +++ b/fix-builtins/src/lib.rs @@ -171,6 +171,10 @@ pub enum PrimOpPhase { FindFile, Floor, FoldlStrict, + FoldlStrictEmpty, + FoldlStrictCall1, + FoldlStrictCall2, + FoldlStrictUpdate, FromJSON, FromTOML, FunctionArgs, diff --git a/fix-vm/src/instructions/misc.rs b/fix-vm/src/instructions/misc.rs index 59895b8..4bb6a55 100644 --- a/fix-vm/src/instructions/misc.rs +++ b/fix-vm/src/instructions/misc.rs @@ -51,9 +51,7 @@ impl<'gc> crate::Vm<'gc> { } }; let Some(attrs) = scope.as_gc::() else { - return self.finish_err(Error::eval_error( - "internal: scope slot is not an attrset", - )); + return self.finish_err(Error::eval_error("internal: scope slot is not an attrset")); }; match attrs.lookup(name) { Some(val) => { @@ -152,8 +150,8 @@ fn resolve_path_str(current_dir: &str, path: &str) -> Result> return Ok(path.to_owned()); } else if let Some(rest) = path.strip_prefix("~/") { #[allow(deprecated)] - let mut dir = std::env::home_dir() - .ok_or_else(|| Error::eval_error("home dir not defined"))?; + let mut dir = + std::env::home_dir().ok_or_else(|| Error::eval_error("home dir not defined"))?; dir.push(rest); dir } else { diff --git a/fix-vm/src/primops/list.rs b/fix-vm/src/primops/list.rs index ce1eb45..ca17b6b 100644 --- a/fix-vm/src/primops/list.rs +++ b/fix-vm/src/primops/list.rs @@ -70,4 +70,100 @@ impl<'gc> Vm<'gc> { reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize); Step::Continue(()) } + + // foldl' op nul list + // + // Stack layouts across phases: + // Entry: [op, nul, list] + // Empty: [op, nul] + // Call1: [op, list, idx, acc] + // Call2: [op, list, idx, acc, intermediate] + // Update: [op, list, idx, acc, result] + pub(crate) fn primop_foldl_strict_entry( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + self.force_slot(0, reader, mc)?; + let list_val = self.peek_forced(0); + let Some(list) = list_val.as_gc::() else { + return self.finish_type_err(NixType::List, list_val.ty()); + }; + if list.inner.borrow().is_empty() { + let _ = self.pop(); // list + reader.set_pc(PrimOpPhase::FoldlStrictEmpty.ip() as usize); + return Step::Continue(()); + } + let list_val = self.pop(); + let nul_val = self.pop(); + self.push(list_val); + self.push(Value::new_inline(0i32)); + self.push(nul_val); + reader.set_pc(PrimOpPhase::FoldlStrictCall1.ip() as usize); + Step::Continue(()) + } + + pub(crate) fn primop_foldl_strict_empty( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + let nul = self.force_and_retry::(reader, mc)?; + let _ = self.pop(); // op + self.return_from_primop(nul.relax(), reader) + } + + pub(crate) fn primop_foldl_strict_call1( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + self.force_slot(3, reader, mc)?; + let op = self.peek_forced(3); + let acc = self.peek(0); + self.push(op.relax()); + self.call(reader, mc, acc, PrimOpPhase::FoldlStrictCall2.ip() as usize) + } + + pub(crate) fn primop_foldl_strict_call2( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + #[allow(clippy::unwrap_used)] + let idx = self.peek(2).as_inline::().unwrap(); + #[allow(clippy::unwrap_used)] + let list = self.peek_forced(3).as_gc::().unwrap(); + let elem = list.inner.borrow()[idx as usize]; + self.call( + reader, + mc, + elem, + PrimOpPhase::FoldlStrictUpdate.ip() as usize, + ) + } + + pub(crate) fn primop_foldl_strict_update( + &mut self, + reader: &mut BytecodeReader<'_>, + _mc: &Mutation<'gc>, + ) -> Step { + let result = self.pop(); + self.replace(0, result); + #[allow(clippy::unwrap_used)] + let idx = self.peek(1).as_inline::().unwrap(); + #[allow(clippy::unwrap_used)] + let list = self.peek_forced(2).as_gc::().unwrap(); + let len = list.inner.borrow().len(); + if (idx as usize) + 1 == len { + let acc = self.pop(); + let _ = self.pop(); // idx + let _ = self.pop(); // list + let _ = self.pop(); // op + return self.return_from_primop(acc, reader); + } + self.replace(1, Value::new_inline(idx + 1)); + reader.set_pc(PrimOpPhase::FoldlStrictCall1.ip() as usize); + Step::Continue(()) + } } diff --git a/fix-vm/src/primops/mod.rs b/fix-vm/src/primops/mod.rs index a0689d8..90e7cf0 100644 --- a/fix-vm/src/primops/mod.rs +++ b/fix-vm/src/primops/mod.rs @@ -41,6 +41,12 @@ impl<'gc> Vm<'gc> { FilterCallPred => self.primop_filter_call_pred(reader, mc), FilterCheck => self.primop_filter_check(reader, mc), + FoldlStrict => self.primop_foldl_strict_entry(reader, mc), + FoldlStrictEmpty => self.primop_foldl_strict_empty(reader, mc), + FoldlStrictCall1 => self.primop_foldl_strict_call1(reader, mc), + FoldlStrictCall2 => self.primop_foldl_strict_call2(reader, mc), + FoldlStrictUpdate => self.primop_foldl_strict_update(reader, mc), + ForceResultShallow => self.primop_force_result_shallow(ctx, reader, mc), ForceResultShallowPush => self.primop_force_result_shallow_push(ctx, reader, mc), ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc), diff --git a/fix/src/lib.rs b/fix/src/lib.rs index 80cb3a5..42ea6af 100644 --- a/fix/src/lib.rs +++ b/fix/src/lib.rs @@ -636,7 +636,10 @@ enum Scope<'ctx, 'id, 'ir> { slot_id: u32, }, Let(HashMap>), - Param { sym: StringId, abs_layer: u8 }, + Param { + sym: StringId, + abs_layer: u8, + }, } pub enum ExtraScope<'ctx> {