1use std::{collections::BTreeMap, error::Error, fmt};
2
3use sim_kernel::{ShapeId, Symbol};
4
5#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
7pub struct CallMode {
8 positional: bool,
9 named: bool,
10}
11
12impl CallMode {
13 pub const POSITIONAL: Self = Self::new(true, false);
15 pub const NAMED: Self = Self::new(false, true);
17 pub const POSITIONAL_OR_NAMED: Self = Self::new(true, true);
19
20 pub const fn new(positional: bool, named: bool) -> Self {
25 Self { positional, named }
26 }
27
28 pub const fn is_positional(self) -> bool {
30 self.positional
31 }
32
33 pub const fn is_named(self) -> bool {
35 self.named
36 }
37}
38
39#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
41pub enum ParameterKind {
42 Required,
44 Optional,
46 Remainder,
48}
49
50#[derive(Clone, Debug, Eq, Hash, PartialEq)]
52pub struct ParameterDescriptor {
53 name: Symbol,
54 kind: ParameterKind,
55 call_mode: CallMode,
56 shape: Option<ShapeId>,
57}
58
59impl ParameterDescriptor {
60 pub fn new(
62 name: Symbol,
63 kind: ParameterKind,
64 call_mode: CallMode,
65 shape: Option<ShapeId>,
66 ) -> Self {
67 Self {
68 name,
69 kind,
70 call_mode,
71 shape,
72 }
73 }
74
75 pub fn name(&self) -> &Symbol {
77 &self.name
78 }
79 pub const fn kind(&self) -> ParameterKind {
81 self.kind
82 }
83 pub const fn call_mode(&self) -> CallMode {
85 self.call_mode
86 }
87 pub const fn shape(&self) -> Option<ShapeId> {
89 self.shape
90 }
91}
92
93#[derive(Clone, Debug, Eq, Hash, PartialEq)]
95pub struct CaptureDescriptor {
96 name: Symbol,
97 shape: Option<ShapeId>,
98}
99
100impl CaptureDescriptor {
101 pub fn new(name: Symbol, shape: Option<ShapeId>) -> Self {
103 Self { name, shape }
104 }
105 pub fn name(&self) -> &Symbol {
107 &self.name
108 }
109 pub const fn shape(&self) -> Option<ShapeId> {
111 self.shape
112 }
113}
114
115#[derive(Clone, Debug, Eq, PartialEq)]
117pub struct BrowseProjection {
118 parameters: Vec<(Symbol, Option<ShapeId>)>,
119 result: Option<ShapeId>,
120}
121
122impl BrowseProjection {
123 pub fn parameters(&self) -> &[(Symbol, Option<ShapeId>)] {
125 &self.parameters
126 }
127 pub const fn result(&self) -> Option<ShapeId> {
129 self.result
130 }
131}
132
133#[derive(Clone, Debug, Eq, PartialEq)]
135pub struct PlanError {
136 message: String,
137}
138
139impl PlanError {
140 fn new(message: impl Into<String>) -> Self {
141 Self {
142 message: message.into(),
143 }
144 }
145}
146
147impl fmt::Display for PlanError {
148 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
149 formatter.write_str(&self.message)
150 }
151}
152
153impl Error for PlanError {}
154
155#[derive(Clone, Debug, Eq, Hash, PartialEq)]
160pub struct FunctionPlan {
161 display_identity: Symbol,
162 parameters: Vec<ParameterDescriptor>,
163 captures: Vec<CaptureDescriptor>,
164 result_shape: Option<ShapeId>,
165}
166
167impl FunctionPlan {
168 pub fn new(
170 display_identity: Symbol,
171 parameters: Vec<ParameterDescriptor>,
172 captures: Vec<CaptureDescriptor>,
173 result_shape: Option<ShapeId>,
174 ) -> Result<Self, PlanError> {
175 validate_parameters(¶meters)?;
176 validate_captures(&captures)?;
177 Ok(Self {
178 display_identity,
179 parameters,
180 captures,
181 result_shape,
182 })
183 }
184
185 pub fn display_identity(&self) -> &Symbol {
187 &self.display_identity
188 }
189 pub fn parameters(&self) -> &[ParameterDescriptor] {
191 &self.parameters
192 }
193 pub fn captures(&self) -> &[CaptureDescriptor] {
195 &self.captures
196 }
197 pub const fn result_shape(&self) -> Option<ShapeId> {
199 self.result_shape
200 }
201
202 pub fn browse(&self) -> BrowseProjection {
204 BrowseProjection {
205 parameters: self
206 .parameters
207 .iter()
208 .map(|p| (p.name.clone(), p.shape))
209 .collect(),
210 result: self.result_shape,
211 }
212 }
213}
214
215fn validate_parameters(parameters: &[ParameterDescriptor]) -> Result<(), PlanError> {
216 let mut names = BTreeMap::new();
217 let mut positional_remainder: Option<&Symbol> = None;
218 for parameter in parameters {
219 if let Some(first) = names.insert(parameter.name.clone(), parameter.name.clone()) {
220 return Err(PlanError::new(format!(
221 "duplicate parameter names {first} and {}",
222 parameter.name
223 )));
224 }
225 if !parameter.call_mode.positional && !parameter.call_mode.named {
226 return Err(PlanError::new(format!(
227 "parameter {} has contradictory call modes",
228 parameter.name
229 )));
230 }
231 if let Some(remainder) = positional_remainder
232 && parameter.kind == ParameterKind::Required
233 && parameter.call_mode.positional
234 {
235 return Err(PlanError::new(format!(
236 "positional remainder {remainder} cannot precede required parameter {}",
237 parameter.name
238 )));
239 }
240 if parameter.kind == ParameterKind::Remainder {
241 if parameter.call_mode == CallMode::POSITIONAL_OR_NAMED {
242 return Err(PlanError::new(format!(
243 "remainder parameter {} has contradictory call modes",
244 parameter.name
245 )));
246 }
247 if parameter.call_mode.positional {
248 if let Some(first) = positional_remainder {
249 return Err(PlanError::new(format!(
250 "positional remainders {first} and {} conflict",
251 parameter.name
252 )));
253 }
254 positional_remainder = Some(¶meter.name);
255 }
256 }
257 }
258 Ok(())
259}
260
261fn validate_captures(captures: &[CaptureDescriptor]) -> Result<(), PlanError> {
262 let mut names = BTreeMap::new();
263 for capture in captures {
264 if let Some(first) = names.insert(capture.name.clone(), capture.name.clone()) {
265 return Err(PlanError::new(format!(
266 "duplicate capture names {first} and {}",
267 capture.name
268 )));
269 }
270 }
271 Ok(())
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 fn parameter(name: &str, kind: ParameterKind, mode: CallMode) -> ParameterDescriptor {
279 ParameterDescriptor::new(Symbol::new(name), kind, mode, None)
280 }
281
282 #[test]
283 fn remainder_before_required_names_both_parameters() {
284 let error = FunctionPlan::new(
285 Symbol::new("example"),
286 vec![
287 parameter("rest", ParameterKind::Remainder, CallMode::POSITIONAL),
288 parameter("needed", ParameterKind::Required, CallMode::POSITIONAL),
289 ],
290 vec![],
291 None,
292 )
293 .unwrap_err();
294 assert!(error.to_string().contains("rest"));
295 assert!(error.to_string().contains("needed"));
296 }
297
298 #[test]
299 fn equal_declarations_have_equal_identity() {
300 let build = || {
301 FunctionPlan::new(
302 Symbol::qualified("guest", "work"),
303 vec![parameter(
304 "value",
305 ParameterKind::Required,
306 CallMode::POSITIONAL_OR_NAMED,
307 )],
308 vec![CaptureDescriptor::new(
309 Symbol::new("scope"),
310 Some(ShapeId(7)),
311 )],
312 Some(ShapeId(9)),
313 )
314 .unwrap()
315 };
316 assert_eq!(build(), build());
317 }
318
319 #[test]
320 fn construction_rejects_duplicates_and_contradictory_modes() {
321 let duplicate = FunctionPlan::new(
322 Symbol::new("duplicate"),
323 vec![
324 parameter("same", ParameterKind::Required, CallMode::NAMED),
325 parameter("same", ParameterKind::Optional, CallMode::NAMED),
326 ],
327 vec![],
328 None,
329 )
330 .unwrap_err();
331 assert!(duplicate.to_string().contains("same"));
332
333 let contradictory = FunctionPlan::new(
334 Symbol::new("contradictory"),
335 vec![parameter(
336 "lost",
337 ParameterKind::Required,
338 CallMode::new(false, false),
339 )],
340 vec![],
341 None,
342 )
343 .unwrap_err();
344 assert!(contradictory.to_string().contains("lost"));
345 }
346
347 #[test]
348 fn browse_projection_preserves_shape_identifiers() {
349 let plan = FunctionPlan::new(
350 Symbol::new("browse"),
351 vec![ParameterDescriptor::new(
352 Symbol::new("input"),
353 ParameterKind::Required,
354 CallMode::POSITIONAL,
355 Some(ShapeId(3)),
356 )],
357 vec![],
358 Some(ShapeId(4)),
359 )
360 .unwrap();
361 assert_eq!(
362 plan.browse().parameters(),
363 &[(Symbol::new("input"), Some(ShapeId(3)))]
364 );
365 assert_eq!(plan.browse().result(), Some(ShapeId(4)));
366 }
367}