Skip to main content

microcad_lang/syntax/
attribute.rs

1// Copyright © 2025-2026 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 bare name
13    Ident(Identifier),
14    /// A command with optional arguments: `width(offset = 30mm)`.
15    Call(Call),
16    /// An assignment: `color = "red"`.
17    Assigment {
18        /// Source code reference.
19        src_ref: SrcRef,
20        /// Name of the assigment.
21        name: Identifier,
22        /// Value name of the assigment.
23        value: Expression,
24    },
25}
26
27impl AttributeCommand {
28    /// Qualified name of the attribute command
29    pub fn name(&self) -> &Identifier {
30        match self {
31            AttributeCommand::Ident(name) => name,
32            AttributeCommand::Call(call) => call
33                .name
34                .as_identifier()
35                .expect("non-identifier attribute call"),
36            AttributeCommand::Assigment { name, .. } => name,
37        }
38    }
39}
40
41impl std::fmt::Display for AttributeCommand {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match &self {
44            AttributeCommand::Ident(name) => write!(f, "{name}"),
45            AttributeCommand::Call(call) => write!(f, "{call}"),
46            AttributeCommand::Assigment { name, value, .. } => write!(f, "{name} = {value}"),
47        }
48    }
49}
50
51impl std::fmt::Debug for AttributeCommand {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match &self {
54            AttributeCommand::Ident(name) => write!(f, "{name:?}"),
55            AttributeCommand::Call(call) => write!(f, "{call:?}"),
56            AttributeCommand::Assigment { name, value, .. } => write!(f, "{name:?} = {value:?}"),
57        }
58    }
59}
60
61impl SrcReferrer for AttributeCommand {
62    fn src_ref(&self) -> SrcRef {
63        match &self {
64            AttributeCommand::Ident(name) => name.src_ref(),
65            AttributeCommand::Call(call) => call.src_ref(),
66            AttributeCommand::Assigment { src_ref, .. } => src_ref.clone(),
67        }
68    }
69}
70
71/// An attribute item.
72#[derive(Clone)]
73pub struct Attribute {
74    /// Attribute commands: `export = "test.stl", height(30mm)`.
75    pub commands: Vec<AttributeCommand>,
76    /// Tells if the attribute is an inner attribute: `#[...]` (outer) vs `#![...]` (inner).
77    pub is_inner: bool,
78    /// Source reference
79    pub src_ref: SrcRef,
80}
81
82impl Attribute {
83    /// Return some command it is the only one in the list.
84    pub fn single_command(&self) -> Option<&AttributeCommand> {
85        match self.commands.len() {
86            1 => self.commands.first(),
87            _ => None,
88        }
89    }
90}
91
92impl TreeDisplay for Attribute {
93    fn tree_print(&self, f: &mut std::fmt::Formatter, depth: TreeState) -> std::fmt::Result {
94        writeln!(f, "{:depth$}Attribute: {self}", "")
95    }
96}
97
98impl std::fmt::Display for Attribute {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        match self.is_inner {
101            true => write!(f, "#![")?,
102            false => write!(f, "#[")?,
103        }
104        write!(
105            f,
106            "{}",
107            self.commands
108                .iter()
109                .map(|command| command.to_string())
110                .collect::<Vec<_>>()
111                .join(", ")
112        )?;
113        writeln!(f, "]")
114    }
115}
116
117impl std::fmt::Debug for Attribute {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        write!(f, "{self}")
120    }
121}
122
123impl SrcReferrer for Attribute {
124    fn src_ref(&self) -> crate::src_ref::SrcRef {
125        self.src_ref.clone()
126    }
127}
128
129/// A list of attributes, e.g. `#foo #[bar, baz = 42]`
130#[derive(Clone, Default, Deref, DerefMut)]
131pub struct AttributeList(Vec<Attribute>);
132
133impl From<Vec<Attribute>> for AttributeList {
134    fn from(value: Vec<Attribute>) -> Self {
135        AttributeList(value)
136    }
137}
138
139impl std::fmt::Display for AttributeList {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        self.0.iter().try_for_each(|attr| writeln!(f, "{attr}"))
142    }
143}
144
145impl std::fmt::Debug for AttributeList {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        write!(f, "{self}")
148    }
149}
150
151impl SrcReferrer for AttributeList {
152    fn src_ref(&self) -> SrcRef {
153        if self.0.is_empty() {
154            SrcRef(None)
155        } else {
156            SrcRef::merge(
157                &self.0.first().expect("One element").src_ref(),
158                &self.0.last().expect("Second element").src_ref(),
159            )
160        }
161    }
162}