1use std::str::FromStr;
5
6use crate::{
7 builtin::ExporterAccess,
8 eval::{self, *},
9 model::{Attributes, CustomCommand, ExportCommand, MeasureCommand, ResolutionAttribute},
10 parameter,
11 syntax::{self, *},
12 Id,
13};
14
15use microcad_core::{theme::Theme, Color, RenderResolution, Size2};
16use thiserror::Error;
17
18#[derive(Debug, Error)]
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(_, Some(argument_list)) => {
42 match ArgumentMatch::find_match(
43 &argument_list.eval(context)?,
44 &[
45 parameter!(filename: String),
46 parameter!(resolution: Length = 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::Expression(expression) => {
90 let value: Value = expression.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 assert_eq!(self.id.id().as_str(), "export");
117
118 self.commands
119 .iter()
120 .try_fold(Vec::new(), |mut commands, attribute| {
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(Some(id), _) => match id.id().as_str() {
136 "width" => commands.push(MeasureCommand::Width),
137 "height" => commands.push(MeasureCommand::Height),
138 "size" => commands.push(MeasureCommand::Size),
139 _ => context.warning(self, AttributeError::InvalidCommand(id.clone()))?,
140 },
141 _ => unimplemented!(),
142 }
143 }
144
145 Ok(commands)
146 }
147}
148
149impl Eval<Vec<CustomCommand>> for syntax::Attribute {
150 fn eval(&self, context: &mut EvalContext) -> EvalResult<Vec<CustomCommand>> {
151 match context.exporters().exporter_by_id(self.id.id()) {
152 Ok(exporter) => {
153 let mut commands = Vec::new();
154 for command in &self.commands {
155 match command {
156 AttributeCommand::Call(None, Some(argument_list)) => {
157 match ArgumentMatch::find_match(
158 &argument_list.eval(context)?,
159 &exporter.model_parameters(),
160 ) {
161 Ok(tuple) => commands.push(CustomCommand {
162 id: self.id.clone(),
163 arguments: Box::new(tuple),
164 }),
165 Err(err) => {
166 context.warning(self, err)?;
167 }
168 }
169 }
170 _ => unimplemented!(),
171 }
172 }
173
174 Ok(commands)
175 }
176 Err(err) => {
177 context.warning(self, err)?;
178 Ok(Vec::default())
179 }
180 }
181 }
182}
183
184impl Eval<Option<Color>> for syntax::AttributeCommand {
185 fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<Color>> {
186 match self {
187 AttributeCommand::Call(_, _) => todo!(),
188 AttributeCommand::Expression(expression) => {
190 let value: Value = expression.eval(context)?;
191 match value {
192 Value::String(s) => match Color::from_str(&s) {
194 Ok(color) => Ok(Some(color)),
195 Err(err) => {
196 context.warning(self, err)?;
197 Ok(None)
198 }
199 },
200 Value::Tuple(tuple) => match Color::try_from(tuple.as_ref()) {
202 Ok(color) => Ok(Some(color)),
203 Err(err) => {
204 context.warning(self, err)?;
205 Ok(None)
206 }
207 },
208 _ => {
209 context.warning(
210 self,
211 AttributeError::InvalidCommand(Identifier::no_ref("color")),
212 )?;
213 Ok(None)
214 }
215 }
216 }
217 }
218 }
219}
220
221impl Eval<Option<ResolutionAttribute>> for syntax::AttributeCommand {
222 fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<ResolutionAttribute>> {
223 match self {
224 AttributeCommand::Expression(expression) => {
225 let value: Value = expression.eval(context)?;
226 match value {
227 Value::Quantity(qty) => match qty.quantity_type {
228 QuantityType::Scalar => Ok(Some(ResolutionAttribute::Relative(qty.value))),
229 QuantityType::Length => Ok(Some(ResolutionAttribute::Linear(qty.value))),
230 _ => unimplemented!(),
231 },
232 _ => todo!("Error handling"),
233 }
234 }
235 AttributeCommand::Call(_, _) => {
236 context.warning(
237 self,
238 AttributeError::InvalidCommand(Identifier::no_ref("resolution")),
239 )?;
240 Ok(None)
241 }
242 }
243 }
244}
245
246impl Eval<Option<std::rc::Rc<Theme>>> for syntax::AttributeCommand {
247 fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<std::rc::Rc<Theme>>> {
248 match self {
249 AttributeCommand::Expression(_) => todo!(),
250 AttributeCommand::Call(_, _) => {
251 context.warning(
252 self,
253 AttributeError::InvalidCommand(Identifier::no_ref("resolution")),
254 )?;
255 Ok(None)
256 }
257 }
258 }
259}
260
261impl Eval<Option<Size2>> for syntax::AttributeCommand {
262 fn eval(&self, _: &mut EvalContext) -> EvalResult<Option<Size2>> {
263 todo!("Get Size2, e.g. `size = (width = 10mm, height = 10mm) from AttributeCommand")
264 }
265}
266
267macro_rules! eval_to_attribute {
268 ($id:ident: $ty:ty) => {
269 impl Eval<Option<$ty>> for syntax::Attribute {
270 fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<$ty>> {
271 assert_eq!(self.id.id().as_str(), stringify!($id));
272 match self.single_command() {
273 Some(command) => Ok(command.eval(context)?),
274 None => {
275 context.warning(self, AttributeError::InvalidCommand(self.id.clone()))?;
276 Ok(None)
277 }
278 }
279 }
280 }
281 };
282}
283
284eval_to_attribute!(color: Color);
285eval_to_attribute!(resolution: ResolutionAttribute);
286eval_to_attribute!(theme: std::rc::Rc<Theme>);
287eval_to_attribute!(size: Size2);
288
289impl Eval<Vec<crate::model::Attribute>> for syntax::Attribute {
290 fn eval(&self, context: &mut EvalContext) -> EvalResult<Vec<crate::model::Attribute>> {
291 let id = self.id.id().as_str();
292 use crate::model::Attribute as Attr;
293 Ok(match id {
294 "color" => match self.eval(context)? {
295 Some(color) => vec![Attr::Color(color)],
296 None => Default::default(),
297 },
298 "resolution" => match self.eval(context)? {
299 Some(resolution) => vec![Attr::Resolution(resolution)],
300 None => Default::default(),
301 },
302 "theme" => match self.eval(context)? {
303 Some(theme) => vec![Attr::Theme(theme)],
304 None => Default::default(),
305 },
306 "size" => match self.eval(context)? {
307 Some(size) => vec![Attr::Size(size)],
308 None => Default::default(),
309 },
310 "export" => {
311 let exports: Vec<ExportCommand> = self.eval(context)?;
312 exports.iter().cloned().map(Attr::Export).collect()
313 }
314 "measure" => {
315 let measures: Vec<MeasureCommand> = self.eval(context)?;
316 measures.iter().cloned().map(Attr::Measure).collect()
317 }
318 _ => {
319 let commands: Vec<CustomCommand> = self.eval(context)?;
320 commands.iter().cloned().map(Attr::Custom).collect()
321 }
322 })
323 }
324}
325
326impl Eval<crate::model::Attributes> for AttributeList {
327 fn eval(&self, context: &mut EvalContext) -> EvalResult<crate::model::Attributes> {
328 Ok(Attributes(self.iter().try_fold(
329 Vec::new(),
330 |mut attributes, attribute| -> EvalResult<_> {
331 attributes.append(&mut attribute.eval(context)?);
332 Ok(attributes)
333 },
334 )?))
335 }
336}