Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/parser/ast/parse_utils/errors.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Error types and helpers for delimiter tracking during type parsing.
//!
//! The functions in `type_parsing` rely on these structures to report
//! mismatched or unclosed delimiters when reading parameter lists and type
//! expressions.
//! The functions in `params` and `type_expr` rely on these structures to
//! report mismatched or unclosed delimiters when reading parameter lists and
//! type expressions.

use rowan::TextRange;

Expand Down
8 changes: 6 additions & 2 deletions src/parser/ast/parse_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@

mod delimiter;
pub(crate) mod errors;
mod outputs;
mod params;
mod token_utils;
mod type_parsing;
mod type_expr;

pub use delimiter::extract_parenthesized;
pub(crate) use delimiter::paren_block_span;

pub(crate) use type_parsing::{parse_name_type_pairs, parse_output_list, parse_type_after_colon};
pub(crate) use outputs::parse_output_list;
pub(crate) use params::parse_name_type_pairs;
pub(crate) use type_expr::parse_type_after_colon;
88 changes: 88 additions & 0 deletions src/parser/ast/parse_utils/outputs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! Parsing helpers for transformer output lists.
//!
//! The helper functions here scan tokens following a transformer declaration
//! and collect top-level identifiers after a colon. Nested parentheses are
//! handled so only names at the outermost level are returned.

use rowan::SyntaxElement;

use crate::{DdlogLanguage, SyntaxKind};

use super::super::skip_whitespace_and_comments;

pub(super) fn skip_to_top_level_colon<I>(iter: &mut std::iter::Peekable<I>)
where
I: Iterator<Item = SyntaxElement<DdlogLanguage>>,
{
let mut depth = 0isize;
for e in iter.by_ref() {
match e.kind() {
SyntaxKind::T_LPAREN => depth += 1,
SyntaxKind::T_RPAREN => {
depth -= 1;
if depth < 0 {
log::warn!("excess closing parenthesis detected in skip_to_top_level_colon",);
depth = 0;
}
}
SyntaxKind::T_COLON if depth == 0 => break,
_ => {}
}
}
}

/// Parse comma separated output names after a `:` token.
pub(crate) fn parse_output_list<I>(iter: I) -> Vec<String>
where
I: Iterator<Item = SyntaxElement<DdlogLanguage>>,
{
use rowan::NodeOrToken;

let mut iter = iter.peekable();
skip_to_top_level_colon(&mut iter);

let mut names = Vec::new();
loop {
skip_whitespace_and_comments(&mut iter);
match iter.next() {
Some(NodeOrToken::Token(t)) if t.kind() == SyntaxKind::T_IDENT => {
names.push(t.text().to_string());
}
_ => break,
}
skip_whitespace_and_comments(&mut iter);
match iter.peek() {
Some(NodeOrToken::Token(t)) if t.kind() == SyntaxKind::T_COMMA => {
iter.next();
}
_ => break,
}
}

names
}

#[cfg(test)]
mod tests {
use super::*;
use crate::parse;
use crate::parser::ast::AstNode;
use rstest::rstest;

#[rstest]
#[case("extern transformer t(a: A, b: B): C, D", vec!["C", "D"])]
#[case("extern transformer t(): X", vec!["X"])]
fn outputs(#[case] src: &str, #[case] expected: Vec<&str>) {
let parsed = parse(src);
#[expect(clippy::expect_used, reason = "Using expect for clearer test failures")]
let transformer = parsed
.root()
.transformers()
.first()
.cloned()
.expect("transformer missing");
let names = parse_output_list(transformer.syntax().children_with_tokens());
let expected: Vec<String> = expected.into_iter().map(String::from).collect();
assert_eq!(names, expected);
}
}
Loading