Skip to main content

graphcal_compiler/syntax/ast/
common.rs

1use crate::syntax::names::{GenericParamName, ModuleAliasName, NameAtom};
2use crate::syntax::non_empty::NonEmpty;
3use crate::syntax::span::{Span, Spanned};
4
5/// An attribute annotation on a declaration: `#[name]` or `#[name(arg1, arg2)]`.
6#[derive(Debug, Clone)]
7pub struct Attribute {
8    pub name: Ident,
9    pub args: Vec<AttributeArg>,
10    pub span: Span,
11}
12
13/// An argument inside an attribute's parenthesized list.
14///
15/// Supports plain identifiers (`pressure_safe`), qualified paths
16/// (`Index.Variant`), Nat range steps (`#2`), and parenthesized groups
17/// (`(Mode.Boost, Phase.Launch)`, `(Mode.Boost, #2)`).
18#[derive(Debug, Clone)]
19pub enum AttributeArg {
20    /// A path of one or more `.`-separated segments: `foo`, `Index.Variant`.
21    Path {
22        segments: NonEmpty<Ident>,
23        span: Span,
24    },
25    /// A Nat range step key: `#N` — matches the `#N` slice-label syntax of
26    /// `table` expressions for `range(N)` axes.
27    RangeStep { step: u64, span: Span },
28    /// A parenthesized group of args: `(Index.A, Index.B).`
29    Group { elements: Vec<Self>, span: Span },
30}
31
32impl AttributeArg {
33    /// Returns the span of this argument.
34    #[must_use]
35    pub const fn span(&self) -> Span {
36        match self {
37            Self::Path { span, .. } | Self::RangeStep { span, .. } | Self::Group { span, .. } => {
38                *span
39            }
40        }
41    }
42}
43
44/// Visibility annotation for declaration kinds that can be public but cannot be bindable.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum Visibility {
47    Private,
48    Public,
49}
50
51impl Visibility {
52    /// Returns `true` for `Public`.
53    #[must_use]
54    pub const fn is_public(self) -> bool {
55        matches!(self, Self::Public)
56    }
57}
58
59/// Visibility and bindability annotation for declaration kinds that support `pub(bind)`.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum BindableVisibility {
62    Private,
63    Public,
64    PublicBind,
65}
66
67impl BindableVisibility {
68    /// Returns `true` for `Public` and `PublicBind`.
69    #[must_use]
70    pub const fn is_public(self) -> bool {
71        matches!(self, Self::Public | Self::PublicBind)
72    }
73
74    /// Returns `true` for `PublicBind`.
75    #[must_use]
76    pub const fn is_bindable(self) -> bool {
77        matches!(self, Self::PublicBind)
78    }
79}
80
81impl From<Visibility> for BindableVisibility {
82    fn from(visibility: Visibility) -> Self {
83        match visibility {
84            Visibility::Private => Self::Private,
85            Visibility::Public => Self::Public,
86        }
87    }
88}
89/// The kind of an `import` or `include` declaration.
90///
91/// For `import`:
92///   - `Selective(items)`: brace-list form `import path.{X, Y};` — brings only
93///     the listed names. Does NOT also bring the leaf module.
94///   - `Module { alias: None }`: bare form `import path;` — brings the leaf
95///     module under its own name.
96///   - `Module { alias: Some(a) }`: aliased form `import path as a;`.
97///
98/// For `include`:
99///   - `Selective(items)`: brace-list form `include path(args).{y};` — exposes
100///     the listed outputs as nodes.
101///   - `Module { alias: None }`: bare form `include path(args);` — sugar for
102///     `as <leaf>`.
103///   - `Module { alias: Some(a) }`: aliased form `include path(args) as a;`.
104#[derive(Debug, Clone)]
105pub enum ImportKind {
106    /// Brace-list selector: `path.{ X, Y as Z, ... }`.
107    Selective(Vec<ImportItem>),
108    /// Bare or aliased form.
109    Module {
110        alias: Option<Spanned<ModuleAliasName>>,
111    },
112}
113
114/// A dot-separated module path: `nasa.rocket.dynamics`.
115///
116/// Always absolute from a package root. The first segment is the package name
117/// (real or virtual); subsequent segments walk the package's module tree
118/// (directories under `source_dir`, files inside the package, and inline `dag`
119/// declarations). There are no file-path strings, no `..` parent navigation,
120/// and no `/` separators in the source language — only `.`.
121#[derive(Debug, Clone)]
122pub struct ModulePath {
123    pub segments: NonEmpty<Ident>,
124    pub span: Span,
125}
126
127impl ModulePath {
128    #[must_use]
129    pub const fn span(&self) -> Span {
130        self.span
131    }
132
133    /// Borrow all path segments in source order.
134    #[must_use]
135    pub fn segments(&self) -> &[Ident] {
136        self.segments.as_slice()
137    }
138
139    /// Number of path segments. Always at least 1.
140    #[must_use]
141    pub const fn len(&self) -> usize {
142        self.segments.len()
143    }
144
145    /// Returns `false`; provided for API compatibility with sequence-like code.
146    #[must_use]
147    pub const fn is_empty(&self) -> bool {
148        false
149    }
150
151    /// Returns whether this is a one-segment module path.
152    #[must_use]
153    pub const fn is_bare(&self) -> bool {
154        self.segments.len() == 1
155    }
156
157    /// Human-readable path string for diagnostics: `"nasa.rocket.dynamics"`.
158    #[must_use]
159    pub fn display_path(&self) -> String {
160        self.segments
161            .iter()
162            .map(|s| s.name.as_str())
163            .collect::<Vec<_>>()
164            .join(".")
165    }
166
167    /// Returns the leaf segment of the path.
168    #[must_use]
169    pub fn leaf(&self) -> &Ident {
170        self.segments.last()
171    }
172
173    /// Split the path into qualifier segments and the leaf segment.
174    ///
175    /// The qualifier slice is empty for one-segment paths.
176    #[must_use]
177    pub fn split_last(&self) -> (&[Ident], &Ident) {
178        let (leaf, qualifier) = self.segments.split_last();
179        (qualifier, leaf)
180    }
181
182    /// Returns the qualifier segments before the leaf. Empty for bare paths.
183    #[must_use]
184    pub fn qualifier_segments(&self) -> &[Ident] {
185        self.split_last().0
186    }
187
188    /// Returns qualifier segments and leaf only when this path is qualified.
189    #[must_use]
190    pub fn qualifier_and_leaf(&self) -> Option<(&[Ident], &Ident)> {
191        let (qualifier, leaf) = self.split_last();
192        (!qualifier.is_empty()).then_some((qualifier, leaf))
193    }
194}
195/// A single item in an `import` declaration, optionally aliased.
196///
197/// Example: `name1 as local_name` → `ImportItem { name: "name1", alias: Some("local_name") }`
198/// Example: `name1` → `ImportItem { name: "name1", alias: None }`
199/// Example: `type name1` → imports from the type namespace.
200/// Example: `pub name1` → re-exported at the importer (selective form).
201#[derive(Debug, Clone)]
202pub struct ImportItem {
203    /// Attributes on this import item (e.g., `#[expected_fail(...)]`).
204    pub attributes: Vec<Attribute>,
205    /// Whether this item is re-exported (`pub` prefix) from the importer.
206    pub is_pub: bool,
207    /// Which namespace this selective import targets.
208    pub namespace: ImportItemNamespace,
209    /// The name requested from the imported module.
210    ///
211    /// Its span is the identifier's use-site span in this `import`/`include`
212    /// statement, not the definition-site span in the imported module. The AST
213    /// is produced before external module resolution.
214    pub name: Ident,
215    /// Optional local alias (introduced by `as`).
216    pub alias: Option<Ident>,
217}
218
219/// Namespace targeted by a single selective import item.
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub enum ImportItemNamespace {
222    /// Default compile-time namespace: consts, dimensions, units, indexes,
223    /// DAGs, assertions, and other non-type importable items.
224    Default,
225    /// Type namespace, written with the `type` marker.
226    Type,
227}
228
229impl ImportItem {
230    /// The name that this import introduces into the local scope.
231    /// Returns the alias if present, otherwise the original name.
232    #[must_use]
233    pub fn local_name(&self) -> &str {
234        self.alias
235            .as_ref()
236            .map_or(self.name.name.as_str(), |a| a.name.as_str())
237    }
238
239    /// The span of the local name (alias span if aliased, otherwise original name span).
240    #[must_use]
241    pub fn local_span(&self) -> Span {
242        self.alias.as_ref().map_or(self.name.span, |a| a.span)
243    }
244}
245/// An identifier with its source span.
246#[derive(Debug, Clone, PartialEq, Eq, Hash)]
247pub struct Ident {
248    pub name: NameAtom,
249    pub span: Span,
250}
251
252impl Ident {
253    /// Convert this identifier into a `Spanned<T>`, consuming the name and span.
254    #[must_use]
255    pub fn into_spanned<T: From<NameAtom>>(self) -> Spanned<T> {
256        Spanned::new(T::from(self.name), self.span)
257    }
258
259    /// Interpret this identifier as a generic parameter name.
260    #[must_use]
261    pub fn as_generic_param_name(&self) -> GenericParamName {
262        GenericParamName::new(&self.name)
263    }
264}