microcad_lang/eval/workbench.rs
1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Workbench definition syntax element evaluation
5
6use crate::{eval::*, model::*, syntax::*};
7
8impl WorkbenchDefinition {
9 /// Try to evaluate a single call into a [`Model`].
10 ///
11 /// - `arguments`: Single argument tuple (will not be multiplied).
12 /// - `init`: Initializer to call with given `arguments`.
13 /// - `context`: Current evaluation context.
14 fn eval_to_model<'a>(
15 &'a self,
16 call_src_ref: SrcRef,
17 creator: Creator,
18 init: Option<&'a InitDefinition>,
19 context: &mut Context,
20 ) -> EvalResult<Model> {
21 log::debug!(
22 "Evaluating model of `{id:?}` {kind}",
23 id = self.id,
24 kind = self.kind
25 );
26
27 let arguments = creator.arguments.clone();
28
29 // copy all arguments which are part of the building plan into properties
30 let (mut properties, non_properties): (Vec<_>, Vec<_>) = arguments
31 .named_iter()
32 .map(|(id, value)| (id.clone(), value.clone()))
33 .partition(|(id, _)| self.plan.contains_key(id));
34
35 // create uninitialized values for all missing building plan properties
36 let missing: Vec<_> = self
37 .plan
38 .iter()
39 .filter(|param| !properties.iter().any(|(id, _)| param.id == *id))
40 .map(|param| param.id.clone())
41 .collect();
42 missing
43 .into_iter()
44 .for_each(|id| properties.push((id, Value::None)));
45
46 log::trace!("Properties: {properties:?}");
47 log::trace!("Non-Properties: {non_properties:?}");
48
49 // Create model
50 let model = ModelBuilder::new(
51 Element::Workpiece(Workpiece {
52 kind: *self.kind,
53 // copy all arguments which are part of the building plan to properties
54 properties: properties.into_iter().collect(),
55 creator,
56 }),
57 call_src_ref,
58 )
59 .attributes(self.attribute_list.eval(context)?)
60 .build();
61
62 context.scope(
63 StackFrame::Workbench(
64 model,
65 self.id.clone(),
66 non_properties.clone().into_iter().collect(),
67 ),
68 |context| {
69 let model = context.get_model()?;
70
71 // run init code
72 if let Some(init) = init {
73 log::trace!(
74 "Initializing`{id:?}` {kind}",
75 id = self.id,
76 kind = self.kind
77 );
78 if let Err(err) = init.eval(non_properties.into_iter().collect(), context) {
79 context.error(&self.src_ref_head(), err)?;
80 }
81 }
82
83 // At this point, all properties must have a value
84 log::trace!("Run body`{id:?}` {kind}", id = self.id, kind = self.kind);
85 model.append_children(self.body.statements.eval(context)?);
86
87 // We have to deduce the output type of this model, otherwise the model is incomplete.
88 {
89 let model_ = model.borrow();
90 match &*model_.element {
91 Element::Workpiece(workpiece) => {
92 let output_type = model.deduce_output_type();
93
94 let result = workpiece.check_output_type(output_type);
95 match result {
96 Ok(()) => {}
97 Err(EvalError::WorkbenchNoOutput(..)) => {
98 context.warning(
99 &self.src_ref_head(),
100 result.expect_err("Error"),
101 )?;
102 }
103 result => {
104 context
105 .error(&self.src_ref_head(), result.expect_err("Error"))?;
106 }
107 }
108 }
109 _ => panic!("A workbench must produce a workpiece."),
110 }
111 }
112
113 Ok(model)
114 },
115 )
116 }
117}
118
119impl WorkbenchDefinition {
120 /// Evaluate the call of a workbench with given arguments.
121 ///
122 /// - `args`: Arguments which will be matched with the building plan and the initializers using parameter multiplicity.
123 /// - `context`: Current evaluation context.
124 ///
125 /// Return evaluated nodes (multiple nodes might be created by parameter multiplicity).
126 pub fn call(
127 &self,
128 call_src_ref: SrcRef,
129 symbol: Symbol,
130 arguments: &ArgumentValueList,
131 context: &mut Context,
132 ) -> EvalResult<Model> {
133 log::debug!(
134 "Workbench {call} {kind} {id:?}({arguments})",
135 call = crate::mark!(CALL),
136 id = self.id,
137 kind = self.kind
138 );
139
140 // prepare models
141 let mut models = Models::default();
142 // prepare building plan
143 let plan = self.plan.eval(context)?;
144
145 // try to match arguments with the building plan
146 match ArgumentMatch::find_multi_match(arguments, &plan) {
147 Ok(matches) => {
148 log::debug!(
149 "Building plan matches: {}",
150 matches
151 .iter()
152 .map(|m| m.to_string())
153 .collect::<Vec<_>>()
154 .join("\n")
155 );
156 // evaluate models for all multiplicity matches
157 for arguments in matches {
158 models.push(self.eval_to_model(
159 call_src_ref.clone(),
160 Creator::new(symbol.clone(), arguments),
161 None,
162 context,
163 )?);
164 }
165 }
166 _ => {
167 log::trace!("Building plan did not match, finding initializer");
168
169 // at the end: check if initialization was successful
170 let mut initialized = false;
171
172 // find an initializer that matches the arguments
173 for init in self.inits() {
174 if let Ok(matches) =
175 ArgumentMatch::find_multi_match(arguments, &init.parameters.eval(context)?)
176 {
177 log::debug!(
178 "Initializer matches: {}",
179 matches
180 .iter()
181 .map(|m| m.to_string())
182 .collect::<Vec<_>>()
183 .join("\n")
184 );
185 // evaluate models for all multiplicity matches
186 for arguments in matches {
187 models.push(self.eval_to_model(
188 call_src_ref.clone(),
189 Creator::new(symbol.clone(), arguments),
190 Some(init),
191 context,
192 )?);
193 }
194 initialized = true;
195 break;
196 }
197 }
198 if !initialized {
199 context.error(arguments, EvalError::NoInitializationFound(self.id.clone()))?;
200 }
201 }
202 }
203
204 Ok(models.to_multiplicity(self.src_ref.clone()))
205 }
206}