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}