hara_native/vm/
prepared.rs1use std::rc::Rc;
4
5use crate::core::Value;
6use crate::kernel::{NamespaceRegistry, Var};
7use crate::lang::data::Symbol;
8
9use super::fiber::VmFiber;
10use super::opcode::Instruction;
11use super::program::{FunctionPrototype, Program, MAX_PRIMITIVE_ARGUMENTS};
12use super::source_map::SourceMap;
13use super::validate::validate;
14
15#[derive(Debug, Clone)]
21pub struct PreparedCall {
22 symbol: String,
23 arity: u16,
24 var: Var<Value>,
25 program: Rc<Program>,
26}
27
28impl PreparedCall {
29 pub fn symbol(&self) -> &str {
30 &self.symbol
31 }
32
33 pub fn arity(&self) -> u16 {
34 self.arity
35 }
36
37 pub fn start(&self, arguments: Vec<Value>) -> Result<VmFiber, String> {
39 if arguments.len() != usize::from(self.arity) {
40 return Err(format!("{} expects {} arguments", self.symbol, self.arity));
41 }
42 let callable = self.var.deref_value();
43 if !matches!(callable, Value::Function(_)) {
44 return Err(format!("prepared Var is not callable: {}", self.symbol));
45 }
46 let mut locals = Vec::with_capacity(arguments.len() + 1);
47 locals.push(callable);
48 locals.extend(arguments);
49 Ok(VmFiber::start_call(
50 self.program.clone(),
51 0,
52 locals,
53 Vec::new(),
54 ))
55 }
56
57 pub fn invoke(&self, arguments: Vec<Value>) -> Result<Value, String> {
64 if arguments.len() != usize::from(self.arity) {
65 return Err(format!("{} expects {} arguments", self.symbol, self.arity));
66 }
67 let callable = self.var.deref_value();
68 let Value::Function(function) = callable else {
69 return Err(format!("prepared Var is not callable: {}", self.symbol));
70 };
71 crate::core::call_function(&function, arguments)
72 }
73}
74
75pub fn prepare_call(
77 registry: &NamespaceRegistry<Value>,
78 symbol: &str,
79 arity: u16,
80) -> Result<PreparedCall, String> {
81 if usize::from(arity) > MAX_PRIMITIVE_ARGUMENTS {
82 return Err(format!(
83 "prepared call arity exceeds {MAX_PRIMITIVE_ARGUMENTS}"
84 ));
85 }
86 let parsed = Symbol::parse(symbol);
87 let var = registry
88 .resolve(&parsed)
89 .ok_or_else(|| format!("unbound handler Var: {symbol}"))?;
90 if !matches!(var.deref_value(), Value::Function(_)) {
91 return Err(format!("prepared Var is not callable: {symbol}"));
92 }
93
94 let mut code = Vec::with_capacity(usize::from(arity) + 2);
95 code.push(Instruction::LoadLocal(0));
96 for argument in 0..arity {
97 code.push(Instruction::LoadLocal(argument + 1));
98 }
99 code.push(Instruction::Call { argc: arity as u8 });
100 code.push(Instruction::Return);
101 let mut source_map = SourceMap::default();
102 for _ in &code {
103 source_map.record(None);
104 }
105 let program = Program {
106 namespace: None,
107 constants: Vec::new(),
108 var_metadata: Vec::new(),
109 schema_types: Default::default(),
110 function_types: Default::default(),
111 inferred_function_types: Default::default(),
112 functions: vec![FunctionPrototype {
113 name: Some(format!("prepared:{symbol}")),
114 async_function: false,
115 arity: arity + 1,
116 variadic: false,
117 capture_count: 0,
118 local_count: arity + 1,
119 max_stack: arity + 1,
120 code,
121 source_map,
122 handlers: Vec::new(),
123 }],
124 entry: 0,
125 };
126 validate(&program).map_err(|error| error.to_string())?;
127 Ok(PreparedCall {
128 symbol: var.symbol().as_str().to_owned(),
129 arity,
130 var,
131 program: Rc::new(program),
132 })
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::core::native_function;
139
140 #[test]
141 fn prepared_calls_pass_arguments_without_globals_and_observe_redefinition() {
142 let registry = NamespaceRegistry::new("app");
143 let function = registry.current().intern(
144 "handler",
145 native_function("handler", 1, |args| Ok(args[0].clone())),
146 );
147 let prepared = prepare_call(®istry, "app/handler", 1).unwrap();
148
149 assert_eq!(
150 prepared
151 .start(vec![Value::Number(42)])
152 .unwrap()
153 .drive_sync()
154 .unwrap(),
155 Value::Number(42)
156 );
157 assert_eq!(
158 prepared.invoke(vec![Value::Number(42)]).unwrap(),
159 Value::Number(42)
160 );
161
162 function.reset_value(native_function("handler", 1, |_| Ok(Value::Number(7))));
163 assert_eq!(
164 prepared
165 .start(vec![Value::Number(42)])
166 .unwrap()
167 .drive_sync()
168 .unwrap(),
169 Value::Number(7)
170 );
171 assert!(registry
172 .resolve(&Symbol::parse("__hoplite_request"))
173 .is_none());
174 }
175
176 #[test]
177 fn prepared_calls_validate_target_and_arity() {
178 let registry = NamespaceRegistry::new("app");
179 registry.current().intern("value", Value::Number(1));
180 assert!(prepare_call(®istry, "app/missing", 1)
181 .unwrap_err()
182 .contains("unbound handler Var"));
183 assert!(prepare_call(®istry, "app/value", 1)
184 .unwrap_err()
185 .contains("not callable"));
186
187 registry.current().intern(
188 "handler",
189 native_function("handler", 1, |args| Ok(args[0].clone())),
190 );
191 let prepared = prepare_call(®istry, "app/handler", 1).unwrap();
192 assert!(matches!(
193 prepared.start(Vec::new()),
194 Err(message) if message.contains("expects 1")
195 ));
196 }
197}