1use microcad_core::hash::Hashed;
7use microcad_lang_base::{SrcRef, SrcReferrer};
8
9use crate::{eval::*, model::*, symbol::Symbol, syntax::*};
10
11impl WorkbenchDefinition {
12 fn eval_to_model<'a>(
18 &'a self,
19 call_src_ref: SrcRef,
20 creator: Creator,
21 init: Option<&'a InitDefinition>,
22 context: &mut EvalContext,
23 ) -> EvalResult<Model> {
24 log::debug!(
25 "Evaluating model of `{id:?}` {kind}",
26 id = self.id_ref(),
27 kind = self.kind
28 );
29
30 let arguments = creator.arguments.clone();
31
32 let (mut properties, non_properties): (Vec<_>, Vec<_>) = arguments
34 .named_iter()
35 .map(|(id, value)| (id.clone(), value.clone()))
36 .partition(|(id, _)| self.plan.contains_key(id));
37
38 let missing: Vec<_> = self
40 .plan
41 .iter()
42 .filter(|param| !properties.iter().any(|(id, _)| param.id_ref() == id))
43 .map(|param| param.id())
44 .collect();
45 missing
46 .into_iter()
47 .for_each(|id| properties.push((id, Value::None)));
48
49 log::trace!("Properties: {properties:?}");
50 log::trace!("Non-Properties: {non_properties:?}");
51
52 let model = ModelBuilder::new(
54 Element::Workpiece(Workpiece {
55 kind: *self.kind,
56 properties: properties.into_iter().collect(),
58 creator: Hashed::new(creator),
59 }),
60 call_src_ref,
61 )
62 .attributes(self.attribute_list.eval(context)?)
63 .build();
64
65 context.scope(
66 StackFrame::Workbench(model, self.id(), Default::default()),
67 |context| {
68 let model = context.get_model()?;
69
70 if let Some(init) = init {
72 log::trace!(
73 "Initializing`{id:?}` {kind}",
74 id = self.id_ref(),
75 kind = self.kind
76 );
77 if let Err(err) = init.eval(non_properties.into_iter().collect(), context) {
78 context.error(&self.src_ref(), err)?;
79 }
80 }
81
82 log::trace!(
84 "Run body`{id:?}` {kind}",
85 id = self.id_ref(),
86 kind = self.kind
87 );
88 model.append_children(self.body.statements.eval(context)?);
89
90 Ok(model)
91 },
92 )
93 }
94}
95
96impl WorkbenchDefinition {
97 pub fn call(
104 &self,
105 call_src_ref: SrcRef,
106 symbol: Symbol,
107 arguments: &ArgumentValueList,
108 context: &mut EvalContext,
109 ) -> EvalResult<Model> {
110 log::debug!(
111 "{call} workbench {kind} {id:?}({arguments:?})",
112 call = microcad_lang_base::mark!(CALL),
113 id = self.id_ref(),
114 kind = self.kind
115 );
116
117 let mut models = Models::default();
119
120 let matches: Vec<_> = std::iter::once((
122 None,
123 self.plan
124 .eval(context)
125 .and_then(|params| ArgumentMatch::find_multi_match(arguments, ¶ms)),
126 ))
127 .chain(self.inits().map(|init| {
129 (
130 Some(init),
131 init.parameters
132 .eval(context)
133 .and_then(|params| ArgumentMatch::find_multi_match(arguments, ¶ms)),
134 )
135 }))
136 .inspect(|(i, m)| {
138 let result = match m {
139 Ok(m) => format!(
140 "{match_} [{priority:>10}]",
141 priority = m.priority,
142 match_ = microcad_lang_base::mark!(MATCH)
143 ),
144 Err(_) => microcad_lang_base::mark!(NO_MATCH),
145 };
146 if let Some(i) = i {
147 log::debug!("{result} {}::init({})", symbol.full_name(), i.parameters)
148 } else {
149 log::debug!("{result} {}({})", symbol.full_name(), self.plan)
150 }
151 })
152 .filter_map(|(i, m)| if let Ok(m) = m { Some((i, m)) } else { None })
154 .collect();
155
156 let matches = Priority::high_to_low().iter().find_map(|priority| {
158 let matches: Vec<_> = matches
159 .iter()
160 .filter(|(_, m)| m.priority == *priority)
161 .collect();
162 if matches.is_empty() {
163 None
164 } else {
165 Some(matches)
166 }
167 });
168
169 if let Some(mut matches) = matches {
170 if matches.len() > 1 {
171 let ambiguous = matches
172 .iter()
173 .map(|(init, _)| match init {
174 Some(init) => {
175 format!(
176 "{name}::init({params})",
177 name = symbol.full_name(),
178 params = init.parameters
179 )
180 }
181 None => format!(
182 "{name:?}({params})",
183 name = symbol.full_name(),
184 params = self.plan
185 ),
186 })
187 .collect::<Vec<_>>();
188 log::debug!(
189 "{match_} Ambiguous initialization: {name}({arguments})\nCould be one of:\n{ambiguous}",
190 name = symbol.full_name(),
191 ambiguous = ambiguous.join("\n"),
192 match_ = microcad_lang_base::mark!(AMBIGUOUS)
193 );
194 context.error(
195 arguments,
196 EvalError::AmbiguousInitialization {
197 src_ref: call_src_ref,
198 name: self.id(),
199 actual_params: arguments.to_string(),
200 ambiguous_params: ambiguous,
201 },
202 )?;
203 } else if let Some(matched) = matches.pop() {
204 let what = if matched.0.is_none() {
205 "Building plan"
206 } else {
207 "Initializer"
208 };
209 log::debug!(
210 "{match_} {what}: {}",
211 matched
212 .1
213 .args
214 .iter()
215 .map(|m| format!("{m:?}"))
216 .collect::<Vec<_>>()
217 .join("\n"),
218 match_ = microcad_lang_base::mark!(MATCH!)
219 );
220
221 for arguments in matched.1.args.iter() {
223 models.push(self.eval_to_model(
224 call_src_ref.clone(),
225 Creator::new(symbol.clone(), arguments.clone()),
226 matched.0,
227 context,
228 )?);
229 }
230 }
231 } else {
232 log::debug!(
233 "{match_} Neither the building plan nor any initializer matches arguments",
234 match_ = microcad_lang_base::mark!(NO_MATCH!)
235 );
236 context.error(
237 arguments,
238 EvalError::NoInitializationFound {
239 src_ref: call_src_ref,
240 name: self.id(),
241 actual_params: arguments.to_string(),
242 possible_params: self.possible_params(),
243 },
244 )?;
245 }
246
247 Ok(models.to_multiplicity(self.src_ref()))
248 }
249}