libmoonwave/
source_file.rs1use crate::{
2 diagnostic::Diagnostics, doc_comment::DocComment, doc_entry::DocEntry, error::Error, tags::Tag,
3};
4use full_moon::{
5 self,
6 ast::{LastStmt, Stmt},
7 node::Node,
8 tokenizer::{Token, TokenReference, TokenType},
9 visitors::Visitor,
10};
11
12#[derive(Debug)]
13pub struct SourceFile {
14 doc_comments: Vec<DocComment>,
15 file_id: usize,
16}
17
18impl<'a> SourceFile {
19 pub fn from_str(source: &'a str, file_id: usize, relative_path: String) -> Result<Self, Error> {
20 let ast = full_moon::parse(source).map_err(|e| {
21 Error::FullMoonError(
22 e.iter()
23 .map(|e| (relative_path.clone(), e.to_owned()))
24 .collect::<Vec<(String, full_moon::Error)>>(),
25 )
26 })?;
27
28 struct Collector<'b> {
29 buffer: Vec<(Token, Option<Stmt>)>,
30 last_line: usize,
31 file_id: usize,
32 relative_path: &'b str,
33 doc_comments: Vec<DocComment>,
34 }
35
36 impl<'b> Collector<'b> {
37 fn new(file_id: usize, relative_path: &'b str) -> Self {
38 Self {
39 buffer: Vec::new(),
40 file_id,
41 last_line: 0,
42 relative_path,
43 doc_comments: Vec::new(),
44 }
45 }
46
47 fn scan(&mut self, token: Token, stmt: Option<Stmt>) {
48 match token.token_type() {
49 TokenType::MultiLineComment { blocks: 1, comment } => {
50 self.last_line = token.end_position().line();
51 self.clear();
52
53 self.doc_comments.push(DocComment::new(
54 comment.to_string(),
55 token.start_position().bytes() + "--[=[".len(),
56 token.end_position().line() + 1,
57 self.file_id,
58 self.relative_path.to_owned(),
59 stmt,
60 ));
61 }
62 TokenType::SingleLineComment { comment } => {
63 self.last_line = token.start_position().line();
64
65 if let Some(comment) = comment.strip_prefix('-') {
66 if comment.trim().chars().all(|char| char == '-') {
67 return;
69 }
70
71 if comment.len() > 1 {
72 if let Some(first_non_whitespace) =
73 comment.find(|char: char| !char.is_whitespace())
74 {
75 let tag_body = &comment[first_non_whitespace..];
78
79 if tag_body.starts_with("@module") {
80 return;
81 }
82 }
83 }
84
85 self.buffer.push((token, stmt));
86 } else if let Some(doc_comment) = self.flush() {
87 self.doc_comments.push(doc_comment);
88 }
89 }
90 TokenType::Whitespace { .. } => {
91 let line = token.start_position().line();
92 let is_consecutive_newline = line > self.last_line;
93
94 self.last_line = line;
95
96 if is_consecutive_newline {
97 if let Some(doc_comment) = self.flush() {
98 self.doc_comments.push(doc_comment);
99 }
100 }
101 }
102 _ => {}
103 }
104 }
105
106 fn clear(&mut self) {
107 self.buffer.clear();
108 }
109
110 fn flush(&mut self) -> Option<DocComment> {
111 if self.buffer.is_empty() {
112 return None;
113 }
114
115 let comment = self
116 .buffer
117 .iter()
118 .map(|(token, _)| match token.token_type() {
119 TokenType::SingleLineComment { comment } => {
120 format!("--{}", comment)
121 }
122 _ => unreachable!(),
123 })
124 .collect::<Vec<_>>()
125 .join("\n");
126
127 let doc_comment = Some(DocComment::new(
128 comment,
129 self.buffer.first().unwrap().0.start_position().bytes(),
130 self.buffer.last().unwrap().0.end_position().line() + 1,
131 self.file_id,
132 self.relative_path.to_owned(),
133 self.buffer.last().unwrap().1.as_ref().cloned(),
134 ));
135
136 self.clear();
137
138 doc_comment
139 }
140
141 fn finish(mut self) -> Vec<DocComment> {
142 if let Some(doc_comment) = self.flush() {
143 self.doc_comments.push(doc_comment);
144 }
145
146 self.doc_comments
147 }
148 }
149
150 impl Visitor for Collector<'_> {
151 fn visit_stmt(&mut self, stmt: &Stmt) {
152 let surrounding_trivia = stmt.surrounding_trivia().0;
153 for trivia in surrounding_trivia {
154 self.scan(trivia.clone(), Some(stmt.clone()));
155 }
156 }
157
158 fn visit_last_stmt(&mut self, stmt: &LastStmt) {
159 let stmt = stmt.clone();
160 let surrounding_trivia = stmt.surrounding_trivia().0;
161 for trivia in surrounding_trivia {
162 self.scan(trivia.clone(), None);
163 }
164 }
165
166 fn visit_eof(&mut self, stmt: &TokenReference) {
167 let surrounding_trivia = stmt.surrounding_trivia().0;
168 for trivia in surrounding_trivia {
169 self.scan(trivia.clone(), None);
170 }
171 }
172 }
173
174 let mut collector = Collector::new(file_id, &relative_path);
175
176 collector.visit_ast(&ast);
177
178 let doc_comments = collector.finish();
179
180 Ok(Self {
181 doc_comments,
182 file_id,
183 })
184 }
185
186 pub fn parse(&'a self) -> Result<(Vec<DocEntry>, Vec<Tag>), Error> {
187 let (doc_entries, errors): (Vec<_>, Vec<_>) = self
188 .doc_comments
189 .iter()
190 .map(DocEntry::parse)
191 .partition(Result::is_ok);
192
193 let (doc_entries, tags): (Vec<_>, Vec<_>) =
194 doc_entries.into_iter().map(Result::unwrap).unzip();
195
196 let tags: Vec<Tag> = tags.into_iter().flatten().collect();
197
198 let errors: Diagnostics = errors
199 .into_iter()
200 .map(Result::unwrap_err)
201 .flat_map(Diagnostics::into_iter)
202 .collect::<Vec<_>>()
203 .into();
204
205 if errors.is_empty() {
206 Ok((doc_entries, tags))
207 } else {
208 Err(Error::ParseErrors(errors))
209 }
210 }
211}