From fe96f6d9c5e8037fc33bb2ea9321793c129a5445 Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Mon, 27 Apr 2026 18:17:19 +0800 Subject: [PATCH] implement pattern calling --- fix-builtins/src/lib.rs | 3 ++ fix-vm/src/instructions/builtins.rs | 54 ++++++++++++++++++++++++++++- fix-vm/src/instructions/calls.rs | 29 +++++++++++----- flake.nix | 1 + 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/fix-builtins/src/lib.rs b/fix-builtins/src/lib.rs index 0a43f8d..5496693 100644 --- a/fix-builtins/src/lib.rs +++ b/fix-builtins/src/lib.rs @@ -242,6 +242,9 @@ pub enum PrimOpPhase { ForceResultShallowLoop, ForceResultDeepFinish, + // TODO: split into separate enums + CallPattern, + Illegal, } diff --git a/fix-vm/src/instructions/builtins.rs b/fix-vm/src/instructions/builtins.rs index c24b077..a86d1d0 100644 --- a/fix-vm/src/instructions/builtins.rs +++ b/fix-vm/src/instructions/builtins.rs @@ -1,6 +1,6 @@ use fix_builtins::PrimOpPhase; use fix_error::Error; -use gc_arena::{Gc, Mutation}; +use gc_arena::{Gc, Mutation, RefLock}; use num_enum::TryFromPrimitive as _; use smallvec::SmallVec; @@ -37,6 +37,8 @@ impl<'gc> Vm<'gc> { ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc), ForceResultDeepFinish => self.primop_force_result_deep_finish(ctx, reader, mc), + CallPattern => self.primop_call_pattern(ctx, reader, mc), + phase => todo!("primop phase {phase:?}"), } } @@ -368,6 +370,56 @@ impl<'gc> Vm<'gc> { self.finish_ok(ctx.convert_value(val.relax())) } + pub(crate) fn primop_call_pattern( + &mut self, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + let (func, attrset) = self.try_force::<(Gc, Gc)>(reader, mc)?; + + let Closure { + ip, + n_locals, + env, + pattern, + } = *func; + let Some(pattern) = pattern else { + unreachable!() + }; + // TODO: get function name + // TODO: param spans + if !pattern.ellipsis { + for key in pattern.required.iter().copied() { + if attrset.lookup(key).is_none() { + let name = ctx.resolve_string(key); + return self.finish_err(Error::eval_error(format!( + "function 'anonymous lambda' called without required argument '{name}'" + ))); + } + } + for &(key, _) in attrset.iter() { + let is_expected = + pattern.required.contains(&key) || pattern.optional.contains(&key); + if !is_expected { + let name = ctx.resolve_string(key); + return self.finish_err(Error::eval_error(format!( + "function 'anonymous lambda' called with unexpected argument '{name}'" + ))); + } + } + } + + let new_env = Gc::new( + mc, + RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)), + ); + reader.set_pc(ip as usize); + self.env = new_env; + + Step::Continue(()) + } + fn is_value_in_seen(&self, seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool { if !is_container(val) { return false; diff --git a/fix-vm/src/instructions/calls.rs b/fix-vm/src/instructions/calls.rs index 6093f7e..78a59ad 100644 --- a/fix-vm/src/instructions/calls.rs +++ b/fix-vm/src/instructions/calls.rs @@ -23,13 +23,10 @@ impl<'gc> crate::Vm<'gc> { } self.call_depth += 1; if let Some(closure) = func.as_gc::() { - let ip = closure.ip; - let n_locals = closure.n_locals; - let env = closure.env; - if let Some(ref _pattern) = closure.pattern { - todo!("pattern call") - } else { - let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env))); + if closure.pattern.is_some() { + // FIXME: better DX... + self.push(func.relax()); + self.push(arg); self.call_stack.push(CallFrame { pc: resume_pc, stack_depth: 0, @@ -37,9 +34,23 @@ impl<'gc> crate::Vm<'gc> { env: self.env, with_env: self.with_env, }); - reader.set_pc(ip as usize); - self.env = new_env; + reader.set_pc(PrimOpPhase::CallPattern.ip() as usize); + return Step::Continue(()); } + + let ip = closure.ip; + let n_locals = closure.n_locals; + let env = closure.env; + let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env))); + self.call_stack.push(CallFrame { + pc: resume_pc, + stack_depth: 0, + thunk: None, + env: self.env, + with_env: self.with_env, + }); + reader.set_pc(ip as usize); + self.env = new_env; } else if let Some(primop) = func.as_inline::() { if primop.arity == 1 { self.push(arg); diff --git a/flake.nix b/flake.nix index c31b569..72fedc4 100644 --- a/flake.nix +++ b/flake.nix @@ -50,6 +50,7 @@ llm-agents.claude-code llm-agents.opencode + llm-agents.forge ]; }; }