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