use crate::syntax::ast::{
Attribute, AttributeArg, BindableVisibility, DeclKind, Declaration, Visibility,
};
use crate::syntax::non_empty::NonEmpty;
use crate::syntax::span::Span;
use crate::syntax::token::Token;
use super::{ParseError, Parser};
use multi::SlotKind;
mod dag;
mod dim_unit;
mod figure;
mod import;
mod index;
mod layer;
mod multi;
mod plot;
#[cfg(test)]
mod tests;
mod type_decl;
mod value;
const fn visibility_without_bindability(visibility: BindableVisibility) -> Visibility {
match visibility {
BindableVisibility::Private => Visibility::Private,
BindableVisibility::Public | BindableVisibility::PublicBind => Visibility::Public,
}
}
const fn decl_accepts_bindable_visibility(decl: &Declaration) -> bool {
matches!(
decl.kind,
DeclKind::Dimension(_) | DeclKind::Type(_) | DeclKind::Index(_)
)
}
const fn set_decl_visibility(decl: &mut Declaration, visibility: BindableVisibility) {
match &mut decl.kind {
DeclKind::Param(_) | DeclKind::Sugar(_) => {}
DeclKind::Node(d) => d.visibility = visibility_without_bindability(visibility),
DeclKind::ConstNode(d) => d.visibility = visibility_without_bindability(visibility),
DeclKind::BaseDimension(d) => d.visibility = visibility_without_bindability(visibility),
DeclKind::Dimension(d) => d.visibility = visibility,
DeclKind::Unit(d) => d.visibility = visibility_without_bindability(visibility),
DeclKind::Type(d) => d.visibility = visibility,
DeclKind::Index(d) => d.visibility = visibility,
DeclKind::Import(d) => d.visibility = visibility_without_bindability(visibility),
DeclKind::Include(d) => d.visibility = visibility_without_bindability(visibility),
DeclKind::Dag(d) => d.visibility = visibility_without_bindability(visibility),
DeclKind::Assert(d) => d.visibility = visibility_without_bindability(visibility),
DeclKind::Plot(d) => d.visibility = visibility_without_bindability(visibility),
DeclKind::Figure(d) => d.visibility = visibility_without_bindability(visibility),
DeclKind::Layer(d) => d.visibility = visibility_without_bindability(visibility),
}
}
impl Parser<'_> {
#[expect(
clippy::too_many_lines,
reason = "single entry point dispatches across every declaration kind"
)]
pub(super) fn parse_declaration(&mut self) -> Result<Declaration, ParseError> {
let mut attributes = Vec::new();
while self.lexer.peek() == Some(&Token::Hash) {
attributes.push(self.parse_attribute()?);
}
let (visibility, visibility_span) = self.parse_visibility_prefix()?;
let found = match visibility {
BindableVisibility::Private => None,
BindableVisibility::Public => Some("`pub`"),
BindableVisibility::PublicBind => Some("`pub(bind)`"),
};
if let Some(found) = found
&& self.lexer.peek() == Some(&Token::Param)
&& let Some(vis_span) = visibility_span
{
return Err(self.unexpected_token(
"no visibility annotation (params are always visible and bindable)",
found,
vis_span,
));
}
if visibility == BindableVisibility::PublicBind
&& matches!(self.lexer.peek(), Some(Token::Import | Token::Include))
&& let Some(vis_span) = visibility_span
{
return Err(self.unexpected_token(
"`pub` (use-sites are not bindable — `pub(bind)` is only for declaration kinds)",
"`pub(bind)`",
vis_span,
));
}
let is_node_decl = match self.lexer.peek() {
Some(Token::Node) => true,
Some(Token::Const) => matches!(self.lexer.peek_second(), Some(Token::Node)),
_ => false,
};
if visibility == BindableVisibility::PublicBind
&& is_node_decl
&& let Some(vis_span) = visibility_span
{
return Err(self.unexpected_token(
"`pub` (nodes are computed values — `pub(bind)` is not meaningful; use `param` to declare a bindable input)",
"`pub(bind)`",
vis_span,
));
}
let expected = "`param`, `node`, `const node`, `base dim`, `dim`, `unit`, `const unit`, `type`, `dag`, `index`, `import`, `include`, `assert`, `plot`, `figure`, or `layer`";
match self.lexer.peek() {
Some(Token::Param) => {
let (_, kind_span) = self.advance()?;
return self.finish_value_decl_or_multi(
SlotKind::Param,
kind_span,
attributes,
visibility,
visibility_span,
);
}
Some(Token::Node) => {
let (_, kind_span) = self.advance()?;
return self.finish_value_decl_or_multi(
SlotKind::Node,
kind_span,
attributes,
visibility,
visibility_span,
);
}
Some(Token::Const) => {
let (_, const_span) = self.advance()?;
match self.lexer.peek() {
Some(Token::Node) => {
let (_, node_span) = self.advance()?;
return self.finish_value_decl_or_multi(
SlotKind::ConstNode,
const_span.merge(node_span),
attributes,
visibility,
visibility_span,
);
}
Some(Token::Unit) => {
let mut decl = self.parse_const_unit(const_span)?;
if visibility == BindableVisibility::PublicBind
&& let Some(vis_span) = visibility_span
{
return Err(self.unexpected_token(
"`pub` (`pub(bind)` is only valid on bindable declaration kinds: `dim`, `type`, and `index`)",
"`pub(bind)`",
vis_span,
));
}
set_decl_visibility(&mut decl, visibility);
if let Some(ps) = visibility_span {
decl.span = ps.merge(decl.span);
}
if let Some(first_attr) = attributes.first() {
decl.span = first_attr.span.merge(decl.span);
}
decl.attributes = attributes;
return Ok(decl);
}
Some(_) => {
let (tok, span) = self.advance()?;
return Err(self.unexpected_token(
"`node` or `unit` after `const`",
&tok.to_string(),
span,
));
}
None => {
return Err(self.unexpected_eof("`node` or `unit` after `const`"));
}
}
}
_ => {}
}
let mut decl = match self.lexer.peek() {
Some(Token::Base) => {
let (_, base_span) = self.advance()?;
match self.lexer.peek() {
Some(Token::Dimension) => self.parse_base_dimension_decl(base_span),
Some(Token::Unit) => self.parse_base_unit_decl(base_span),
Some(_) => {
let (tok, span) = self.advance()?;
Err(self.unexpected_token(
"`dim` or `unit` after `base`",
&tok.to_string(),
span,
))
}
None => Err(self.unexpected_eof("`dim` or `unit` after `base`")),
}
}
Some(Token::Dimension) => self.parse_dimension_decl(),
Some(Token::Unit) => self.parse_unit_decl(),
Some(Token::Type) => self.parse_type_decl(),
Some(Token::Index) => self.parse_index_decl(),
Some(Token::Import) => self.parse_import_decl(),
Some(Token::Include) => self.parse_include_decl(),
Some(Token::Dag) => self.parse_dag_decl(),
Some(Token::Assert) => self.parse_assert(),
Some(Token::Plot) => self.parse_plot(),
Some(Token::Figure) => self.parse_figure(),
Some(Token::Layer) => self.parse_layer(),
Some(_) => {
let (tok, span) = self.advance()?;
Err(self.unexpected_token(expected, &tok.to_string(), span))
}
None => Err(self.unexpected_eof(expected)),
}?;
if visibility == BindableVisibility::PublicBind
&& !decl_accepts_bindable_visibility(&decl)
&& let Some(vis_span) = visibility_span
{
return Err(self.unexpected_token(
"`pub` (`pub(bind)` is only valid on bindable declaration kinds: `dim`, `type`, and `index`)",
"`pub(bind)`",
vis_span,
));
}
set_decl_visibility(&mut decl, visibility);
if visibility == BindableVisibility::Public
&& let Some(vis_span) = visibility_span
{
let selective_items = match &decl.kind {
DeclKind::Import(d) => match &d.kind {
crate::syntax::ast::ImportKind::Selective(items) => Some(items.as_slice()),
crate::syntax::ast::ImportKind::Module { .. } => None,
},
DeclKind::Include(d) => match &d.kind {
crate::syntax::ast::ImportKind::Selective(items) => Some(items.as_slice()),
crate::syntax::ast::ImportKind::Module { .. } => None,
},
_ => None,
};
if let Some(items) = selective_items
&& items.iter().any(|it| it.is_pub)
{
return Err(self.unexpected_token(
"either `pub include/import \"X\" ...` (whole-module re-export) or `include/import \"X\" { pub items }` (selective re-export), not both",
"`pub`",
vis_span,
));
}
}
if let Some(ps) = visibility_span {
decl.span = ps.merge(decl.span);
}
if let Some(first_attr) = attributes.first() {
decl.span = first_attr.span.merge(decl.span);
}
decl.attributes = attributes;
Ok(decl)
}
fn finish_value_decl_or_multi(
&mut self,
kind: SlotKind,
kind_span: Span,
attributes: Vec<Attribute>,
visibility: BindableVisibility,
visibility_span: Option<Span>,
) -> Result<Declaration, ParseError> {
let header = self.parse_slot_header_tail(visibility, kind, kind_span)?;
if self.lexer.peek() == Some(&Token::Comma) {
if let Some(first_attr) = attributes.first() {
return Err(self.unexpected_token(
"no attributes on multi-decl (attributes are forbidden on multi-decl surface forms in v1)",
"`#[...]`",
first_attr.span,
));
}
return self.parse_multi_decl_rest(header, visibility, visibility_span);
}
let mut decl = self.finish_single_value_decl(header)?;
set_decl_visibility(&mut decl, visibility);
if let Some(ps) = visibility_span {
decl.span = ps.merge(decl.span);
}
if let Some(first_attr) = attributes.first() {
decl.span = first_attr.span.merge(decl.span);
}
decl.attributes = attributes;
Ok(decl)
}
pub(super) fn parse_visibility_prefix(
&mut self,
) -> Result<(BindableVisibility, Option<Span>), ParseError> {
if self.lexer.peek() != Some(&Token::Pub) {
return Ok((BindableVisibility::Private, None));
}
let (_, pub_span) = self.advance()?;
if self.lexer.peek() != Some(&Token::LParen) {
return Ok((BindableVisibility::Public, Some(pub_span)));
}
self.expect(Token::LParen)?;
let (bind_tok, bind_span) = self.advance()?;
if bind_tok != Token::Ident || self.lexer.slice_at(bind_span) != "bind" {
return Err(self.unexpected_token("`bind`", &bind_tok.to_string(), bind_span));
}
let (_, rparen_span) = self.expect(Token::RParen)?;
Ok((
BindableVisibility::PublicBind,
Some(pub_span.merge(rparen_span)),
))
}
fn parse_attribute(&mut self) -> Result<Attribute, ParseError> {
let (_, start_span) = self.expect(Token::Hash)?;
self.expect(Token::LBracket)?;
let name = self.parse_any_ident()?;
let mut args = Vec::new();
if self.lexer.peek() == Some(&Token::LParen) {
self.expect(Token::LParen)?;
if self.lexer.peek() != Some(&Token::RParen) {
args.push(self.parse_attribute_arg()?);
while self.lexer.peek() == Some(&Token::Comma) {
self.expect(Token::Comma)?;
if self.lexer.peek() == Some(&Token::RParen) {
break;
}
args.push(self.parse_attribute_arg()?);
}
}
self.expect(Token::RParen)?;
}
let (_, end_span) = self.expect(Token::RBracket)?;
let span = start_span.merge(end_span);
Ok(Attribute { name, args, span })
}
fn parse_attribute_arg(&mut self) -> Result<AttributeArg, ParseError> {
if self.lexer.peek() == Some(&Token::LParen) {
let (_, start_span) = self.expect(Token::LParen)?;
let mut elements = Vec::new();
if self.lexer.peek() != Some(&Token::RParen) {
elements.push(self.parse_attribute_arg()?);
while self.lexer.peek() == Some(&Token::Comma) {
self.expect(Token::Comma)?;
if self.lexer.peek() == Some(&Token::RParen) {
break;
}
elements.push(self.parse_attribute_arg()?);
}
}
let (_, end_span) = self.expect(Token::RParen)?;
Ok(AttributeArg::Group {
elements,
span: start_span.merge(end_span),
})
} else if self.lexer.peek() == Some(&Token::Hash) {
let (_, hash_span) = self.expect(Token::Hash)?;
let (_, num_span) = self.expect(Token::Number)?;
let text = self.lexer.slice_at(num_span).replace('_', "");
let step: u64 = text.parse().map_err(|_| ParseError::InvalidNumber {
reason: "expected non-negative integer after `#` in attribute argument".to_string(),
src: self.named_source(),
span: num_span.into(),
})?;
Ok(AttributeArg::RangeStep {
step,
span: hash_span.merge(num_span),
})
} else {
let first = self.parse_any_ident()?;
let start_span = first.span;
let mut end_span = start_span;
let mut rest_segments = Vec::new();
while self.lexer.peek() == Some(&Token::Dot) {
self.expect(Token::Dot)?;
let segment = self.parse_any_ident()?;
end_span = segment.span;
rest_segments.push(segment);
}
Ok(AttributeArg::Path {
segments: NonEmpty::new(first, rest_segments),
span: start_span.merge(end_span),
})
}
}
}