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