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(Debug, 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 SrcReferrer for AttributeCommand {
41    fn src_ref(&self) -> SrcRef {
42        match &self {
43            AttributeCommand::Call(identifier, argument_list) => {
44                match (identifier, argument_list) {
45                    (None, None) => unreachable!("Invalid AttributeCommand::Call"),
46                    (None, Some(arguments)) => arguments.src_ref(),
47                    (Some(identifier), None) => identifier.src_ref(),
48                    (Some(identifier), Some(arguments)) => SrcRef::merge(identifier, arguments),
49                }
50            }
51            AttributeCommand::Expression(expression) => expression.src_ref(),
52        }
53    }
54}
55
56/// An attribute item.
57#[derive(Debug, Clone)]
58pub struct Attribute {
59    /// The id of the attribute.
60    pub id: Identifier,
61    /// Attribute commands: `width, height(30mm)`.
62    pub commands: Vec<AttributeCommand>,
63    /// Tells if the attribute is an inner attribute: `#[...]` (outer) vs `#![...]` (inner).
64    pub is_inner: bool,
65    /// Source reference
66    pub src_ref: SrcRef,
67}
68
69impl Attribute {
70    /// Return some command it is the only one in the list.
71    pub fn single_command(&self) -> Option<&AttributeCommand> {
72        match self.commands.len() {
73            1 => self.commands.first(),
74            _ => None,
75        }
76    }
77}
78
79impl TreeDisplay for Attribute {
80    fn tree_print(&self, f: &mut std::fmt::Formatter, depth: TreeState) -> std::fmt::Result {
81        writeln!(f, "{:depth$}Attribute: {self}", "")
82    }
83}
84
85impl std::fmt::Display for Attribute {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        match self.is_inner {
88            true => write!(f, "#![")?,
89            false => write!(f, "#[")?,
90        }
91        match self.commands.is_empty() {
92            true => write!(f, "{}", self.id)?,
93            false => write!(
94                f,
95                "{} = {}",
96                self.id,
97                self.commands
98                    .iter()
99                    .map(|command| command.to_string())
100                    .collect::<Vec<_>>()
101                    .join(", ")
102            )?,
103        }
104        writeln!(f, "]")
105    }
106}
107
108impl SrcReferrer for Attribute {
109    fn src_ref(&self) -> crate::src_ref::SrcRef {
110        self.src_ref.clone()
111    }
112}
113
114/// A list of attributes, e.g. `#foo #[bar, baz = 42]`
115#[derive(Debug, Clone, Default, Deref, DerefMut)]
116pub struct AttributeList(Vec<Attribute>);
117
118impl std::fmt::Display for AttributeList {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        self.0.iter().try_for_each(|attr| writeln!(f, "{attr}"))
121    }
122}
123
124impl SrcReferrer for AttributeList {
125    fn src_ref(&self) -> SrcRef {
126        if self.0.is_empty() {
127            SrcRef(None)
128        } else {
129            SrcRef::merge(
130                &self.0.first().expect("One element").src_ref(),
131                &self.0.last().expect("Second element").src_ref(),
132            )
133        }
134    }
135}