microcad_lang/syntax/
attribute.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Attribute syntax entities.
5
6use crate::{src_ref::*, syntax::*};
7use derive_more::{Deref, DerefMut};
8
9/// *Command syntax* within an attribute.
10#[derive(Clone)]
11pub enum AttributeCommand {
12    /// A command with an optional identifier and optional arguments: `width(offset = 30mm)`.
13    Call(Option<Identifier>, Option<ArgumentList>),
14    /// A format string subcommand: `"test.svg"`.
15    Expression(Expression),
16}
17
18impl std::fmt::Display for AttributeCommand {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match &self {
21            AttributeCommand::Call(id, argument_list) => {
22                write!(
23                    f,
24                    "{id}{argument_list}",
25                    id = match id {
26                        Some(id) => format!("{id}"),
27                        None => String::new(),
28                    },
29                    argument_list = match argument_list {
30                        Some(argument_list) => format!("({argument_list})"),
31                        None => String::new(),
32                    }
33                )
34            }
35            AttributeCommand::Expression(expression) => write!(f, "{expression}"),
36        }
37    }
38}
39
40impl std::fmt::Debug for AttributeCommand {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        match &self {
43            AttributeCommand::Call(id, argument_list) => {
44                write!(
45                    f,
46                    "{id:?}{argument_list:?}",
47                    id = match id {
48                        Some(id) => format!("{id:?}"),
49                        None => String::new(),
50                    },
51                    argument_list = match argument_list {
52                        Some(argument_list) => format!("({argument_list:?})"),
53                        None => String::new(),
54                    }
55                )
56            }
57            AttributeCommand::Expression(expression) => write!(f, "{expression:?}"),
58        }
59    }
60}
61
62impl SrcReferrer for AttributeCommand {
63    fn src_ref(&self) -> SrcRef {
64        match &self {
65            AttributeCommand::Call(identifier, argument_list) => {
66                match (identifier, argument_list) {
67                    (None, None) => unreachable!("Invalid AttributeCommand::Call"),
68                    (None, Some(arguments)) => arguments.src_ref(),
69                    (Some(identifier), None) => identifier.src_ref(),
70                    (Some(identifier), Some(arguments)) => SrcRef::merge(identifier, arguments),
71                }
72            }
73            AttributeCommand::Expression(expression) => expression.src_ref(),
74        }
75    }
76}
77
78/// An attribute item.
79#[derive(Clone)]
80pub struct Attribute {
81    /// The id of the attribute.
82    pub id: Identifier,
83    /// Attribute commands: `width, height(30mm)`.
84    pub commands: Vec<AttributeCommand>,
85    /// Tells if the attribute is an inner attribute: `#[...]` (outer) vs `#![...]` (inner).
86    pub is_inner: bool,
87    /// Source reference
88    pub src_ref: SrcRef,
89}
90
91impl Attribute {
92    /// Return some command it is the only one in the list.
93    pub fn single_command(&self) -> Option<&AttributeCommand> {
94        match self.commands.len() {
95            1 => self.commands.first(),
96            _ => None,
97        }
98    }
99}
100
101impl TreeDisplay for Attribute {
102    fn tree_print(&self, f: &mut std::fmt::Formatter, depth: TreeState) -> std::fmt::Result {
103        writeln!(f, "{:depth$}Attribute: {self}", "")
104    }
105}
106
107impl std::fmt::Display for Attribute {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        match self.is_inner {
110            true => write!(f, "#![")?,
111            false => write!(f, "#[")?,
112        }
113        match self.commands.is_empty() {
114            true => write!(f, "{}", self.id)?,
115            false => write!(
116                f,
117                "{} = {}",
118                self.id,
119                self.commands
120                    .iter()
121                    .map(|command| command.to_string())
122                    .collect::<Vec<_>>()
123                    .join(", ")
124            )?,
125        }
126        writeln!(f, "]")
127    }
128}
129
130impl std::fmt::Debug for Attribute {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        write!(f, "{self}")
133    }
134}
135
136impl SrcReferrer for Attribute {
137    fn src_ref(&self) -> crate::src_ref::SrcRef {
138        self.src_ref.clone()
139    }
140}
141
142/// A list of attributes, e.g. `#foo #[bar, baz = 42]`
143#[derive(Clone, Default, Deref, DerefMut)]
144pub struct AttributeList(Vec<Attribute>);
145
146impl std::fmt::Display for AttributeList {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        self.0.iter().try_for_each(|attr| writeln!(f, "{attr}"))
149    }
150}
151
152impl std::fmt::Debug for AttributeList {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        write!(f, "{self}")
155    }
156}
157
158impl SrcReferrer for AttributeList {
159    fn src_ref(&self) -> SrcRef {
160        if self.0.is_empty() {
161            SrcRef(None)
162        } else {
163            SrcRef::merge(
164                &self.0.first().expect("One element").src_ref(),
165                &self.0.last().expect("Second element").src_ref(),
166            )
167        }
168    }
169}