1use rowan::{GreenNodeBuilder, ast::AstNode};
7
8use super::ast::Document;
9use super::macros::{self, Items, Mode};
10use super::syntax::{SyntaxKind as K, SyntaxNode};
11use crate::parser::ParseDiagnostic;
12
13#[derive(Debug, Clone)]
15pub struct ParseOutput {
16 pub cst: SyntaxNode,
17 pub diagnostics: Vec<ParseDiagnostic>,
18}
19
20impl ParseOutput {
21 pub fn document(&self) -> Document {
23 Document::cast(self.cst.clone()).expect("Rd parses always have a ROOT")
24 }
25}
26
27pub fn parse(text: &str) -> ParseOutput {
29 let mut parser = Parser {
30 text,
31 pos: 0,
32 builder: GreenNodeBuilder::new(),
33 diagnostics: Vec::new(),
34 };
35 parser.builder.start_node(K::ROOT.into());
36 parser.sequence(
37 Context {
38 mode: Mode::Prose,
39 items: Items::None,
40 sections: true,
41 },
42 Stop::Eof,
43 &mut State::default(),
44 0,
45 );
46 parser.builder.finish_node();
47 parser
48 .diagnostics
49 .sort_by_key(|diagnostic| (diagnostic.start, diagnostic.end));
50 ParseOutput {
51 cst: SyntaxNode::new_root(parser.builder.finish()),
52 diagnostics: parser.diagnostics,
53 }
54}
55
56pub fn reconstruct(text: &str) -> String {
58 parse(text).cst.text().to_string()
59}
60
61#[derive(Clone, Copy)]
62struct Context {
63 mode: Mode,
64 items: Items,
65 sections: bool,
66}
67
68#[derive(Clone, Copy, PartialEq, Eq)]
69enum Stop {
70 Eof,
71 Brace,
72 Bracket,
73 Endif,
74}
75
76#[derive(Default)]
77struct State {
78 quote: Option<(u8, usize)>,
79 braces: usize,
80 r_comment: bool,
81}
82
83struct Parser<'a> {
84 text: &'a str,
85 pos: usize,
86 builder: GreenNodeBuilder<'static>,
87 diagnostics: Vec<ParseDiagnostic>,
88}
89
90impl Parser<'_> {
91 fn byte(&self) -> Option<u8> {
92 self.text.as_bytes().get(self.pos).copied()
93 }
94
95 fn emit(&mut self, kind: K, end: usize) {
96 if end > self.pos {
97 self.builder.token(kind.into(), &self.text[self.pos..end]);
98 self.pos = end;
99 }
100 }
101
102 fn diagnostic(&mut self, message: &str, start: usize, end: usize) {
103 self.diagnostics.push(ParseDiagnostic {
104 message: message.to_owned(),
105 start,
106 end,
107 });
108 }
109
110 fn line_end(&self) -> usize {
111 self.text[self.pos..]
112 .find('\n')
113 .map_or(self.text.len(), |offset| self.pos + offset)
114 }
115
116 fn directive(&self) -> Option<&'static str> {
117 if self.pos != 0 && self.text.as_bytes()[self.pos - 1] != b'\n' {
118 return None;
119 }
120 ["#ifdef", "#ifndef", "#endif"].into_iter().find(|name| {
121 self.text[self.pos..].starts_with(name)
122 && self
123 .text
124 .as_bytes()
125 .get(self.pos + name.len())
126 .is_none_or(|byte| !byte.is_ascii_alphabetic())
127 })
128 }
129
130 fn leaf(mode: Mode) -> K {
131 match mode {
132 Mode::Prose => K::TEXT,
133 Mode::RLike => K::RCODE,
134 Mode::Verbatim | Mode::Equation => K::VERBATIM,
135 }
136 }
137
138 fn sequence(&mut self, context: Context, stop: Stop, state: &mut State, depth: usize) {
139 if depth >= 128 {
142 self.diagnostic("Rd nesting limit exceeded", self.pos, self.pos);
143 self.emit(K::ERROR, self.text.len());
144 return;
145 }
146 while let Some(byte) = self.byte() {
147 if let Some(name) = self.directive().filter(|_| context.mode != Mode::Equation) {
148 if name == "#endif" {
149 if stop == Stop::Endif {
150 return;
151 }
152 self.diagnostic("unmatched #endif", self.pos, self.pos + name.len());
153 self.emit(K::DIRECTIVE, self.line_end());
154 } else {
155 self.conditional(context, state, depth + 1, name);
156 }
157 continue;
158 }
159 if byte == b'%' && context.mode != Mode::Equation && !state.r_comment {
160 let end = self.line_end();
161 let end = if self.text.as_bytes()[end - 1] == b'\r' {
162 end - 1
163 } else {
164 end
165 };
166 self.emit(K::COMMENT, end);
167 continue;
168 }
169 if byte == b'}' && state.quote.is_none() && state.braces == 0 {
170 if stop == Stop::Brace {
171 return;
172 }
173 if stop == Stop::Endif {
175 return;
176 }
177 self.diagnostic("unexpected closing brace", self.pos, self.pos + 1);
178 self.emit(K::RBRACE, self.pos + 1);
179 continue;
180 }
181 if byte == b']' && stop == Stop::Bracket {
182 return;
183 }
184 if byte == b'{' && context.mode == Mode::Prose {
185 if context.sections {
186 self.diagnostic(
187 "unexpected brace group at document level",
188 self.pos,
189 self.pos + 1,
190 );
191 }
192 self.group(K::GROUP, context, depth + 1);
193 continue;
194 }
195 if byte == b'\\' {
196 let next = self.text.as_bytes().get(self.pos + 1).copied();
197 let markup = match context.mode {
198 Mode::Prose => next.is_some_and(|byte| byte.is_ascii_alphabetic()),
199 Mode::RLike if !state.r_comment => next.is_some_and(|byte| {
200 byte.is_ascii_alphabetic()
201 && (state.quote.is_none() || matches!(byte, b'l' | b'v'))
202 }),
203 _ => false,
204 };
205 if markup {
206 self.macro_call(context, depth + 1);
207 continue;
208 }
209 if self.escape(context, state) {
210 continue;
211 }
212 }
213 if context.mode == Mode::RLike
214 && state.quote.is_none()
215 && !state.r_comment
216 && matches!(byte, b'r' | b'R')
217 && self.raw_string()
218 {
219 continue;
220 }
221 self.text_run(context, stop, state);
222 }
223 if let Some((_, start)) = state.quote.take() {
224 self.diagnostic("unterminated R-like string", start, self.pos);
225 }
226 }
227
228 fn text_run(&mut self, context: Context, stop: Stop, state: &mut State) {
229 let start = self.pos;
230 let mut cursor = start;
231 let bytes = self.text.as_bytes();
232 while let Some(&byte) = bytes.get(cursor) {
233 if cursor > start
234 && (byte == b'\\'
235 || (byte == b'%' && context.mode != Mode::Equation && !state.r_comment)
236 || (byte == b'{' && context.mode == Mode::Prose)
237 || (byte == b'}' && state.quote.is_none() && state.braces == 0)
238 || (byte == b']' && stop == Stop::Bracket)
239 || (byte == b'#' && bytes[cursor - 1] == b'\n')
240 || (context.mode == Mode::RLike
241 && state.quote.is_none()
242 && !state.r_comment
243 && matches!(byte, b'r' | b'R')
244 && matches!(bytes.get(cursor + 1), Some(b'\'' | b'"'))))
245 {
246 break;
247 }
248 if context.mode == Mode::RLike {
249 if byte == b'\n' {
250 state.r_comment = false;
251 }
252 if !state.r_comment {
253 if let Some((quote, _)) = state.quote {
254 if byte == quote {
255 state.quote = None;
256 }
257 } else if matches!(byte, b'\'' | b'"' | b'`') {
258 state.quote = Some((byte, cursor));
259 } else if byte == b'#' {
260 state.r_comment = true;
261 }
262 }
263 }
264 if state.quote.is_none() && context.mode != Mode::Prose {
265 if byte == b'{' {
266 state.braces += 1;
267 }
268 if byte == b'}' {
269 state.braces = state.braces.saturating_sub(1);
270 }
271 }
272 cursor += 1;
273 if byte == b'\n' {
276 break;
277 }
278 }
279 self.emit(Self::leaf(context.mode), cursor);
280 }
281
282 fn escape(&mut self, context: Context, state: &mut State) -> bool {
283 let bytes = self.text.as_bytes();
284 let Some(&next) = bytes.get(self.pos + 1) else {
285 return false;
286 };
287 if context.mode == Mode::RLike
288 && let Some((quote, _)) = state.quote
289 {
290 let count = bytes[self.pos..]
291 .iter()
292 .take_while(|&&byte| byte == b'\\')
293 .count();
294 if bytes.get(self.pos + count) == Some("e) {
295 if count % 4 == 0 {
296 state.quote = None;
297 }
298 self.emit(K::R_STRING_ESCAPE, self.pos + count + 1);
299 return true;
300 }
301 if count > 1 {
304 for _ in 0..count / 2 {
305 self.emit(K::ESCAPED_RCODE, self.pos + 2);
306 }
307 return true;
308 }
309 }
310 let decoded = matches!(next, b'\\' | b'%')
311 || (matches!(next, b'{' | b'}')
312 && (context.mode != Mode::RLike || state.quote.is_none()));
313 if decoded {
314 let kind = match context.mode {
315 Mode::Prose => K::ESCAPED_TEXT,
316 Mode::RLike => K::ESCAPED_RCODE,
317 Mode::Verbatim => K::ESCAPED_VERBATIM,
318 Mode::Equation => K::VERBATIM,
319 };
320 self.emit(kind, self.pos + 2);
321 return true;
322 }
323 if context.mode != Mode::Prose {
326 let width = self.text[self.pos + 1..].chars().next().unwrap().len_utf8();
327 if next == b'\n' {
328 state.r_comment = false;
329 }
330 self.emit(Self::leaf(context.mode), self.pos + 1 + width);
331 return true;
332 }
333 false
334 }
335
336 fn raw_string(&mut self) -> bool {
337 let bytes = self.text.as_bytes();
338 let Some("e @ (b'\'' | b'"')) = bytes.get(self.pos + 1) else {
339 return false;
340 };
341 let mut opener = self.pos + 2;
342 while bytes.get(opener) == Some(&b'-') {
343 opener += 1;
344 }
345 let close = match bytes.get(opener) {
346 Some(b'(') => ')',
347 Some(b'[') => ']',
348 Some(b'{') => '}',
349 Some(b'|') => '|',
350 _ => return false,
351 };
352 let ending = format!(
353 "{close}{}{}",
354 "-".repeat(opener - self.pos - 2),
355 char::from(quote)
356 );
357 let end = if let Some(offset) = self.text[opener + 1..].find(&ending) {
358 opener + 1 + offset + ending.len()
359 } else {
360 self.diagnostic("unterminated R-like raw string", self.pos, self.text.len());
361 self.text.len()
362 };
363 self.emit(K::RCODE, end);
364 true
365 }
366
367 fn group(&mut self, kind: K, context: Context, depth: usize) {
368 let start = self.pos;
369 self.builder.start_node(kind.into());
370 self.emit(K::LBRACE, self.pos + 1);
371 self.sequence(context, Stop::Brace, &mut State::default(), depth);
372 if self.byte() == Some(b'}') {
373 self.emit(K::RBRACE, self.pos + 1);
374 } else {
375 self.diagnostic("unclosed brace", start, start + 1);
376 }
377 self.builder.finish_node();
378 }
379
380 fn macro_call(&mut self, context: Context, depth: usize) {
381 let start = self.pos;
382 let mut end = start + 1;
383 while self
384 .text
385 .as_bytes()
386 .get(end)
387 .is_some_and(u8::is_ascii_alphanumeric)
388 {
389 end += 1;
390 }
391 let mut spec = macros::lookup(&self.text[start + 1..end], context.items);
392 if spec.is_none() {
394 let trimmed =
395 self.text[start + 1..end].trim_end_matches(|ch: char| ch.is_ascii_digit());
396 if let Some(found) = macros::lookup(trimmed, context.items) {
397 end = start + 1 + trimmed.len();
398 spec = Some(found);
399 }
400 }
401 self.builder.start_node(K::MACRO.into());
402 self.emit(K::MACRO_NAME, end);
403 if let Some(spec) = spec {
404 if context.sections && !spec.top_level {
405 self.diagnostic("unexpected inline macro at document level", start, end);
406 }
407 if spec.section && !context.sections {
408 self.diagnostic(
409 "unexpected section header inside an Rd argument",
410 start,
411 end,
412 );
413 }
414 if spec.option && self.byte() == Some(b'[') {
415 self.option(depth);
416 }
417 for (index, &mode) in spec.modes.iter().enumerate() {
418 if self.byte() == Some(b'{') {
419 self.group(
420 K::ARGUMENT,
421 Context {
422 mode,
423 items: spec.items,
424 sections: false,
425 },
426 depth,
427 );
428 } else if index < spec.required {
429 self.diagnostic("expected a braced macro argument", self.pos, self.pos);
430 break;
431 }
432 }
433 }
434 self.builder.finish_node();
435 }
436
437 fn option(&mut self, depth: usize) {
438 let start = self.pos;
439 self.builder.start_node(K::OPTION.into());
440 self.emit(K::LBRACKET, self.pos + 1);
441 self.sequence(
442 Context {
443 mode: Mode::Prose,
444 items: Items::None,
445 sections: false,
446 },
447 Stop::Bracket,
448 &mut State::default(),
449 depth,
450 );
451 if self.byte() == Some(b']') {
452 self.emit(K::RBRACKET, self.pos + 1);
453 } else {
454 self.diagnostic("unclosed macro option", start, start + 1);
455 }
456 self.builder.finish_node();
457 }
458
459 fn conditional(&mut self, context: Context, state: &mut State, depth: usize, name: &str) {
460 let start = self.pos;
461 self.builder.start_node(K::CONDITIONAL.into());
462 self.emit(K::DIRECTIVE, self.pos + name.len());
463 let end = self.line_end();
464 if self.text[self.pos..end].trim().is_empty() {
465 self.diagnostic("expected a conditional target", self.pos, end);
466 }
467 self.emit(
468 K::TARGET,
469 (end + usize::from(end < self.text.len())).min(self.text.len()),
470 );
471 self.sequence(context, Stop::Endif, state, depth);
472 if self.directive() == Some("#endif") {
473 self.emit(K::DIRECTIVE, self.line_end());
474 if self.byte() == Some(b'\n') {
475 self.emit(K::DIRECTIVE, self.pos + 1);
476 }
477 } else {
478 self.diagnostic("unclosed Rd conditional", start, start + name.len());
479 }
480 self.builder.finish_node();
481 }
482}