1use std::collections::{BTreeMap, BTreeSet};
4
5use sim_kernel::{Error, Result, Symbol, Value};
6
7#[derive(Clone, Debug)]
9pub enum CallArgument {
10 Positional(Value),
12 Named(Symbol, Value),
14}
15
16#[derive(Clone, Debug, Default, Eq, PartialEq)]
18pub enum Remainder {
19 #[default]
21 Prohibited,
22 Variadic(Symbol),
24}
25
26#[derive(Clone, Debug)]
28pub struct CallParameter {
29 name: Symbol,
30 default: Option<Value>,
31}
32
33impl CallParameter {
34 pub fn required(name: Symbol) -> Self {
36 Self {
37 name,
38 default: None,
39 }
40 }
41
42 pub fn defaulted(name: Symbol, value: Value) -> Self {
44 Self {
45 name,
46 default: Some(value),
47 }
48 }
49
50 pub fn name(&self) -> &Symbol {
52 &self.name
53 }
54}
55
56#[derive(Clone, Debug, Default)]
62pub struct CallSignature {
63 positional: Vec<CallParameter>,
64 named: Vec<CallParameter>,
65 positional_remainder: Remainder,
66 named_remainder: Remainder,
67}
68
69impl CallSignature {
70 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn with_positional(mut self, parameters: Vec<CallParameter>) -> Self {
77 self.positional = parameters;
78 self
79 }
80
81 pub fn with_named(mut self, parameters: Vec<CallParameter>) -> Self {
83 self.named = parameters;
84 self
85 }
86
87 pub fn with_positional_remainder(mut self, remainder: Remainder) -> Self {
89 self.positional_remainder = remainder;
90 self
91 }
92
93 pub fn with_named_remainder(mut self, remainder: Remainder) -> Self {
95 self.named_remainder = remainder;
96 self
97 }
98
99 pub fn bind(&self, arguments: impl IntoIterator<Item = CallArgument>) -> Result<BoundCall> {
101 self.validate()?;
102 let mut supplied_named = BTreeMap::new();
103 let mut positional_values = Vec::new();
104 let mut saw_named = false;
105
106 for argument in arguments {
107 match argument {
108 CallArgument::Positional(value) => {
109 if saw_named {
110 return Err(call_error(
111 "ordering",
112 "positional argument follows a named argument",
113 ));
114 }
115 positional_values.push(value);
116 }
117 CallArgument::Named(name, value) => {
118 saw_named = true;
119 if supplied_named.insert(name.clone(), value).is_some() {
120 return Err(call_error("duplicate", format!("named argument {name}")));
121 }
122 }
123 }
124 }
125
126 let mut bindings = BTreeMap::new();
127 for (index, parameter) in self.positional.iter().enumerate() {
128 let positional = positional_values.get(index).cloned();
129 let named = supplied_named.remove(¶meter.name);
130 let value = match (positional, named, parameter.default.clone()) {
131 (Some(_), Some(_), _) => {
132 return Err(call_error(
133 "duplicate",
134 format!(
135 "parameter {} supplied positionally and by name",
136 parameter.name
137 ),
138 ));
139 }
140 (Some(value), None, _) | (None, Some(value), _) => value,
141 (None, None, Some(value)) => value,
142 (None, None, None) => {
143 return Err(call_error(
144 "missing",
145 format!("required parameter {}", parameter.name),
146 ));
147 }
148 };
149 bindings.insert(parameter.name.clone(), value);
150 }
151
152 for parameter in &self.named {
153 let value = match supplied_named.remove(¶meter.name) {
154 Some(value) => value,
155 None => parameter.default.clone().ok_or_else(|| {
156 call_error(
157 "missing",
158 format!("required named parameter {}", parameter.name),
159 )
160 })?,
161 };
162 bindings.insert(parameter.name.clone(), value);
163 }
164
165 let extra_positional = positional_values
166 .into_iter()
167 .skip(self.positional.len())
168 .collect::<Vec<_>>();
169 if !extra_positional.is_empty() && self.positional_remainder == Remainder::Prohibited {
170 return Err(call_error(
171 "unexpected",
172 format!("{} positional argument(s)", extra_positional.len()),
173 ));
174 }
175 if !supplied_named.is_empty() && self.named_remainder == Remainder::Prohibited {
176 let names = supplied_named
177 .keys()
178 .map(ToString::to_string)
179 .collect::<Vec<_>>()
180 .join(", ");
181 return Err(call_error(
182 "unexpected",
183 format!("named argument(s): {names}"),
184 ));
185 }
186
187 Ok(BoundCall {
188 bindings,
189 positional_remainder: extra_positional,
190 named_remainder: supplied_named,
191 })
192 }
193
194 fn validate(&self) -> Result<()> {
195 let mut names = BTreeSet::new();
196 for parameter in self.positional.iter().chain(&self.named) {
197 if !names.insert(parameter.name.clone()) {
198 return Err(call_error(
199 "duplicate",
200 format!("parameter declaration {}", parameter.name),
201 ));
202 }
203 }
204 for remainder in [&self.positional_remainder, &self.named_remainder] {
205 if let Remainder::Variadic(name) = remainder
206 && !names.insert(name.clone())
207 {
208 return Err(call_error(
209 "duplicate",
210 format!("variadic declaration {name}"),
211 ));
212 }
213 }
214 Ok(())
215 }
216}
217
218#[derive(Clone, Debug)]
220pub struct BoundCall {
221 bindings: BTreeMap<Symbol, Value>,
222 positional_remainder: Vec<Value>,
223 named_remainder: BTreeMap<Symbol, Value>,
224}
225
226impl BoundCall {
227 pub fn get(&self, name: &Symbol) -> Option<&Value> {
229 self.bindings.get(name)
230 }
231
232 pub fn positional_remainder(&self) -> &[Value] {
234 &self.positional_remainder
235 }
236
237 pub fn named_remainder(&self) -> &BTreeMap<Symbol, Value> {
239 &self.named_remainder
240 }
241}
242
243fn call_error(category: &str, detail: impl std::fmt::Display) -> Error {
244 Error::Eval(format!("call binding {category}: {detail}"))
245}