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