1use crate::{
5 Id,
6 builtin::ExporterAccess,
7 eval::{self, *},
8 lower::{Identifiable, ir},
9 model::{Attributes, CustomCommand, ExportCommand, MeasureCommand, ResolutionAttribute},
10 parameter,
11};
12
13use microcad_lang_base::{Identifier, PushDiag, SrcRef};
14use miette::Diagnostic;
15use std::str::FromStr;
16
17use microcad_core::{Color, Size2};
18use thiserror::Error;
19
20#[derive(Debug, Error, Diagnostic)]
22pub enum AttributeError {
23 #[error("Cannot assign attribute to expression `{0}`")]
25 CannotAssignAttribute(String),
26
27 #[error("Not found: {0}")]
29 NotFound(Identifier),
30
31 #[error("Invalid command list for attribute `{0}`")]
33 InvalidCommand(Identifier),
34}
35
36impl Eval<Option<ExportCommand>> for ir::AttributeCommand {
37 fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<ExportCommand>> {
38 match self {
39 ir::AttributeCommand::Call(call) => {
40 match ArgumentMatch::find_match(
41 &call.argument_list.eval(context)?,
42 &[
43 parameter!(filename: String),
44 (
45 Identifier::no_ref("size"),
46 eval::ParameterValue {
47 specified_type: Some(Type::Tuple(Box::new(TupleType::new_size2()))),
48 default_value: Some(Value::Tuple(Box::new(Size2::A4.into()))),
49 src_ref: SrcRef::none(),
50 },
51 ),
52 ]
53 .into_iter()
54 .collect(),
55 ) {
56 Ok(arguments) => {
57 let filename: std::path::PathBuf =
58 arguments.get::<String>("filename").into();
59 let id: Option<Id> = if let Ok(id) = arguments.by_str::<String>("id") {
60 Some(id.into())
61 } else {
62 None
63 };
64 match context.find_exporter(&filename, &id) {
65 Ok(exporter) => Ok(Some(ExportCommand { filename, exporter })),
66 Err(err) => {
67 context.warning(self, err)?;
68 Ok(None)
69 }
70 }
71 }
72 Err(err) => {
73 context.warning(self, err)?;
74 Ok(None)
75 }
76 }
77 }
78 ir::AttributeCommand::Assignment { value, .. } => {
79 let value: Value = value.eval(context)?;
80 match value {
81 Value::String(filename) => {
82 let filename = std::path::PathBuf::from(filename);
83 match context.find_exporter(&filename, &None) {
84 Ok(exporter) => Ok(Some(ExportCommand { filename, exporter })),
85 Err(err) => {
86 context.warning(self, err)?;
87 Ok(None)
88 }
89 }
90 }
91 _ => unimplemented!(),
92 }
93 }
94 _ => Ok(None),
95 }
96 }
97}
98
99impl Eval<Vec<ExportCommand>> for ir::Attribute {
100 fn eval(&self, context: &mut EvalContext) -> EvalResult<Vec<ExportCommand>> {
101 self.commands
102 .iter()
103 .try_fold(Vec::new(), |mut commands, attribute| {
104 assert_eq!(attribute.id_as_str(), "export");
105 if let Some(export_command) = attribute.eval(context)? {
106 commands.push(export_command)
107 }
108 Ok(commands)
109 })
110 }
111}
112
113impl Eval<Vec<MeasureCommand>> for ir::Attribute {
114 fn eval(&self, context: &mut EvalContext) -> EvalResult<Vec<MeasureCommand>> {
115 let mut commands = Vec::new();
116
117 for command in &self.commands {
118 match command {
119 ir::AttributeCommand::Call(_) => match command.id_as_str() {
120 "width" => commands.push(MeasureCommand::Width),
121 "height" => commands.push(MeasureCommand::Height),
122 "size" => commands.push(MeasureCommand::Size),
123 _ => context
124 .warning(self, AttributeError::InvalidCommand(command.id().clone()))?,
125 },
126 _ => unimplemented!(),
127 }
128 }
129
130 Ok(commands)
131 }
132}
133
134impl Eval<Vec<CustomCommand>> for ir::Attribute {
135 fn eval(&self, context: &mut EvalContext) -> EvalResult<Vec<CustomCommand>> {
136 let mut commands = Vec::new();
137 for command in &self.commands {
138 match command {
139 ir::AttributeCommand::Call(call) => {
140 match context.exporters().exporter_by_id(command.id().id()) {
141 Ok(exporter) => {
142 match ArgumentMatch::find_match(
143 &call.argument_list.eval(context)?,
144 &exporter.model_parameters(),
145 ) {
146 Ok(tuple) => commands.push(CustomCommand::new(command.id(), tuple)),
147 Err(err) => {
148 context.warning(self, err)?;
149 }
150 }
151 }
152 Err(err) => {
153 context.warning(self, err)?;
154 }
155 }
156 }
157 ir::AttributeCommand::Assignment { name, .. } => {
158 match context.exporters().exporter_by_id(name.id()) {
159 Ok(_) => {
160 commands.push(CustomCommand::new(command.id(), Default::default()))
161 }
162 Err(err) => {
163 context.warning(self, err)?;
164 }
165 }
166 }
167 _ => unimplemented!(),
168 }
169 }
170
171 Ok(commands)
172 }
173}
174
175impl Eval<Option<Color>> for ir::AttributeCommand {
176 fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<Color>> {
177 match self {
178 ir::AttributeCommand::Assignment { value, .. } => {
180 let value: Value = value.eval(context)?;
181 match value {
182 Value::String(s) => match Color::from_str(&s) {
184 Ok(color) => Ok(Some(color)),
185 Err(err) => {
186 context.warning(self, err)?;
187 Ok(None)
188 }
189 },
190 Value::Tuple(tuple) => match Color::try_from(tuple.as_ref()) {
192 Ok(color) => Ok(Some(color)),
193 Err(err) => {
194 context.warning(self, err)?;
195 Ok(None)
196 }
197 },
198 _ => {
199 context.warning(
200 self,
201 AttributeError::InvalidCommand(Identifier::no_ref("color")),
202 )?;
203 Ok(None)
204 }
205 }
206 }
207 _ => todo!(),
208 }
209 }
210}
211
212impl Eval<Option<ResolutionAttribute>> for ir::AttributeCommand {
213 fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<ResolutionAttribute>> {
214 match self {
215 ir::AttributeCommand::Assignment { value, .. } => {
216 let value: Value = value.eval(context)?;
217 match value {
218 Value::Quantity(qty) => match qty.quantity_type {
219 QuantityType::Scalar => Ok(Some(ResolutionAttribute::Relative(qty.value))),
220 QuantityType::Length => Ok(Some(ResolutionAttribute::Absolute(qty.value))),
221 _ => unimplemented!(),
222 },
223 _ => todo!("Error handling"),
224 }
225 }
226 _ => {
227 context.warning(
228 self,
229 AttributeError::InvalidCommand(Identifier::no_ref("resolution")),
230 )?;
231 Ok(None)
232 }
233 }
234 }
235}
236
237impl Eval<Option<Size2>> for ir::AttributeCommand {
238 fn eval(&self, _: &mut EvalContext) -> EvalResult<Option<Size2>> {
239 todo!("Get Size2, e.g. `size = (width = 10mm, height = 10mm) from AttributeCommand")
240 }
241}
242
243macro_rules! eval_to_attribute {
244 ($id:ident: $ty:ty) => {
245 impl Eval<Option<$ty>> for ir::Attribute {
246 fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<$ty>> {
247 let command = self.commands.first().expect("empty attribute");
248 assert_eq!(command.id_ref().to_string(), stringify!($id));
249 match self.single_command() {
250 Some(command) => Ok(command.eval(context)?),
251 None => {
252 context.warning(self, AttributeError::InvalidCommand(command.id()))?;
253 Ok(None)
254 }
255 }
256 }
257 }
258 };
259}
260
261eval_to_attribute!(color: Color);
262eval_to_attribute!(resolution: ResolutionAttribute);
263eval_to_attribute!(size: Size2);
264
265impl Eval<Vec<crate::model::Attribute>> for ir::Attribute {
266 fn eval(&self, context: &mut EvalContext) -> EvalResult<Vec<crate::model::Attribute>> {
267 use crate::model::Attribute as Attr;
268 self.commands
269 .iter()
270 .map(|command| {
271 Ok(match command.id_as_str() {
272 "color" => match self.eval(context)? {
273 Some(color) => vec![Attr::Color(color)],
274 None => Default::default(),
275 },
276 "resolution" => match self.eval(context)? {
277 Some(resolution) => vec![Attr::Resolution(resolution)],
278 None => Default::default(),
279 },
280 "size" => match self.eval(context)? {
281 Some(size) => vec![Attr::Size(size)],
282 None => Default::default(),
283 },
284 "export" => {
285 let exports: Vec<ExportCommand> = self.eval(context)?;
286 exports.iter().cloned().map(Attr::Export).collect()
287 }
288 "measure" => {
289 let measures: Vec<MeasureCommand> = self.eval(context)?;
290 measures.iter().cloned().map(Attr::Measure).collect()
291 }
292 _ => {
293 let commands: Vec<CustomCommand> = self.eval(context)?;
294 commands.iter().cloned().map(Attr::Custom).collect()
295 }
296 })
297 })
298 .flat_map(|res| match res {
299 Ok(res) => res.into_iter().map(Ok).collect(),
300 Err(err) => vec![Err(err)],
301 })
302 .collect()
303 }
304}
305
306impl Eval<crate::model::Attributes> for ir::AttributeList {
307 fn eval(&self, context: &mut EvalContext) -> EvalResult<crate::model::Attributes> {
308 Ok(Attributes(self.iter().try_fold(
309 Vec::new(),
310 |mut attributes, attribute| -> EvalResult<_> {
311 attributes.append(&mut attribute.eval(context)?);
312 Ok(attributes)
313 },
314 )?))
315 }
316}