Skip to content

Commit 151429e

Browse files
authored
Merge pull request #15 from mkpro118/implement-primitive-and-component-parsers
Implement primitive and component parsers
2 parents f15a2f5 + cf196fe commit 151429e

File tree

8 files changed

+6821
-0
lines changed

8 files changed

+6821
-0
lines changed

src/core/parser/components/attributes.rs

Lines changed: 1652 additions & 0 deletions
Large diffs are not rendered by default.

src/core/parser/components/expressions.rs

Lines changed: 1781 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//! Helper functions for parsing common patterns.
2+
//!
3+
//! Utilities shared across parser components, primarily for associating
4+
//! documentation comments with subsequent declarations. Spans are preserved
5+
//! and comments are treated according to the scanner’s guarantees.
6+
7+
use crate::core::parser::ast::Docs;
8+
use crate::core::parser::traits::TokenStream;
9+
use crate::core::scanner::tokens::{
10+
SymbolLocation, SymbolSpan, Token, TokenType,
11+
};
12+
13+
/// Build a span that goes from the start of `a` to the end of `b`.
14+
pub(crate) fn span_from_to(a: &SymbolSpan, b: &SymbolSpan) -> SymbolSpan {
15+
SymbolSpan {
16+
start: SymbolLocation {
17+
line: a.start.line,
18+
column: a.start.column,
19+
},
20+
end: SymbolLocation {
21+
line: b.end.line,
22+
column: b.end.column,
23+
},
24+
}
25+
}
26+
27+
/// Extract documentation text from a `DocComment` token.
28+
///
29+
/// Normalizes the raw doc comment text by stripping an optional leading
30+
/// `///` prefix and trimming surrounding whitespace. Works for inputs with
31+
/// or without the `///` prefix.
32+
#[must_use]
33+
pub fn extract_doc_text(token: &Token) -> Option<String> {
34+
if let TokenType::DocComment(text) = token.r#type() {
35+
let s = text.strip_prefix("///").unwrap_or(text).trim();
36+
Some(s.to_string())
37+
} else {
38+
None
39+
}
40+
}
41+
42+
/// Parse leading documentation comments from the stream.
43+
///
44+
/// Greedily consumes consecutive `DocComment` tokens at the current position
45+
/// and returns a single `Docs` node with the collected lines and combined
46+
/// span. If no doc comments are present, returns `None` and does not
47+
/// advance the stream.
48+
///
49+
/// ## Examples
50+
/// ```
51+
/// # use prisma_rs::core::parser::components::helpers::parse_leading_docs;
52+
/// # use prisma_rs::core::parser::stream::VectorTokenStream;
53+
/// # use prisma_rs::core::parser::traits::TokenStream;
54+
/// # use prisma_rs::core::scanner::tokens::{Token, TokenType};
55+
/// let mut s = VectorTokenStream::new(vec![
56+
/// Token::new(TokenType::DocComment("User model".into()), (1,1), (1,1)),
57+
/// Token::new(TokenType::DocComment("for auth".into()), (1,2), (1,2)),
58+
/// Token::new(TokenType::Model, (1,3), (1,7)),
59+
/// ]);
60+
/// let docs = parse_leading_docs(&mut s).expect("should collect docs");
61+
/// assert_eq!(docs.lines, vec!["User model", "for auth"]);
62+
/// assert!(matches!(s.peek().unwrap().r#type(), TokenType::Model));
63+
/// ```
64+
pub fn parse_leading_docs(stream: &mut dyn TokenStream) -> Option<Docs> {
65+
let mut doc_lines = Vec::new();
66+
let mut first_span: Option<SymbolSpan> = None;
67+
let mut last_span: Option<SymbolSpan> = None;
68+
69+
// Greedily consume all consecutive DocComment tokens
70+
while let Some(token) = stream.peek() {
71+
if matches!(token.r#type(), TokenType::DocComment(_)) {
72+
// Safe to consume since we peeked and confirmed it's a DocComment
73+
// This should never panic since we just checked with peek()
74+
if let Some(doc_token) = stream.next() {
75+
// Track spans for the overall Docs span
76+
if first_span.is_none() {
77+
first_span = Some(doc_token.span().clone());
78+
}
79+
last_span = Some(doc_token.span().clone());
80+
81+
// Extract and store the documentation text
82+
if let Some(doc_text) = extract_doc_text(&doc_token) {
83+
doc_lines.push(doc_text);
84+
}
85+
}
86+
} else {
87+
// Hit a non-DocComment token, stop consuming
88+
break;
89+
}
90+
}
91+
92+
// If we found any documentation comments, create a Docs node
93+
if doc_lines.is_empty() {
94+
None
95+
} else {
96+
let span = match (first_span, last_span) {
97+
(Some(first), Some(last)) => span_from_to(&first, &last),
98+
(Some(single), None) => single, // Should not happen, but handle gracefully
99+
_ => {
100+
// This should never happen if doc_lines is not empty
101+
SymbolSpan {
102+
start: SymbolLocation { line: 0, column: 0 },
103+
end: SymbolLocation { line: 0, column: 0 },
104+
}
105+
}
106+
};
107+
108+
Some(Docs {
109+
lines: doc_lines,
110+
span,
111+
})
112+
}
113+
}
114+
115+
#[cfg(test)]
116+
mod tests {
117+
#![expect(clippy::unwrap_used)]
118+
119+
use super::*;
120+
use crate::core::parser::stream::VectorTokenStream;
121+
122+
fn tok(t: TokenType) -> Token {
123+
Token::new(t, (1, 1), (1, 1))
124+
}
125+
126+
#[test]
127+
fn extract_doc_text_variants() {
128+
let t = tok(TokenType::DocComment("/// hello".into()));
129+
assert_eq!(extract_doc_text(&t).unwrap(), "hello");
130+
let t = tok(TokenType::DocComment("plain".into()));
131+
assert_eq!(extract_doc_text(&t).unwrap(), "plain");
132+
let t = tok(TokenType::Comment(" not-doc".into()));
133+
assert!(extract_doc_text(&t).is_none());
134+
}
135+
136+
#[test]
137+
fn parse_leading_docs_none_and_some() {
138+
// None path (no docs)
139+
let mut s = VectorTokenStream::new(vec![tok(TokenType::Model)]);
140+
assert!(parse_leading_docs(&mut s).is_none());
141+
142+
// Some path with multiple lines
143+
let mut s = VectorTokenStream::new(vec![
144+
tok(TokenType::DocComment(" line1".into())),
145+
tok(TokenType::DocComment(" line2".into())),
146+
tok(TokenType::Enum),
147+
]);
148+
let d = parse_leading_docs(&mut s).unwrap();
149+
assert_eq!(d.lines, vec!["line1", "line2"]);
150+
assert!(matches!(s.peek().unwrap().r#type(), TokenType::Enum));
151+
}
152+
}

0 commit comments

Comments
 (0)