cmake_tidy_parser/
parser.rs1use cmake_tidy_ast::{
2 Argument, BracketArgument, CommandInvocation, File, Identifier, ParenGroup, QuotedArgument,
3 Statement, TextRange, UnquotedArgument,
4};
5use cmake_tidy_lexer::{Token, TokenKind, tokenize};
6
7use crate::parsed::{ParseError, Parsed};
8use crate::token_source::TokenSource;
9
10#[must_use]
11pub fn parse_file(source: &str) -> Parsed<File> {
12 let tokens = tokenize(source);
13 let (syntax, errors) = {
14 let mut parser = Parser::new(&tokens, source.len());
15 let syntax = parser.parse_file();
16 (syntax, parser.errors)
17 };
18
19 Parsed {
20 syntax,
21 tokens,
22 errors,
23 }
24}
25
26struct Parser<'a> {
27 tokens: TokenSource<'a>,
28 errors: Vec<ParseError>,
29 source_len: usize,
30}
31
32impl<'a> Parser<'a> {
33 fn new(tokens: &'a [Token], source_len: usize) -> Self {
34 Self {
35 tokens: TokenSource::new(tokens),
36 errors: Vec::new(),
37 source_len,
38 }
39 }
40
41 fn parse_file(&mut self) -> File {
42 let mut items = Vec::new();
43
44 while self.tokens.current().is_some() {
45 if let Some(statement) = self.parse_statement() {
46 items.push(statement);
47 }
48 }
49
50 File {
51 items,
52 range: TextRange::new(0, self.source_len),
53 }
54 }
55
56 fn parse_statement(&mut self) -> Option<Statement> {
57 let token = self.tokens.current()?;
58
59 if let TokenKind::Identifier(_) = &token.kind {
60 self.parse_command_invocation().map(Statement::Command)
61 } else {
62 self.error_here("expected a command name");
63 self.tokens.bump();
64 None
65 }
66 }
67
68 fn parse_command_invocation(&mut self) -> Option<CommandInvocation> {
69 let name_token = self.tokens.bump()?;
70 let TokenKind::Identifier(text) = &name_token.kind else {
71 self.error_at(name_token.range, "expected a command name");
72 return None;
73 };
74
75 let name = Identifier {
76 text: text.clone(),
77 range: name_token.range,
78 };
79
80 let Some(next_token) = self.tokens.current() else {
81 self.error_at(name.range, "expected `(` after command name");
82 return Some(CommandInvocation {
83 name,
84 arguments: Vec::new(),
85 range: name_token.range,
86 });
87 };
88
89 if !matches!(next_token.kind, TokenKind::LeftParen) {
90 self.error_at(next_token.range, "expected `(` after command name");
91 return Some(CommandInvocation {
92 name,
93 arguments: Vec::new(),
94 range: TextRange::new(name_token.range.start, name_token.range.end),
95 });
96 }
97
98 let left_paren = self.tokens.bump().expect("left paren must exist");
99 let (arguments, end) = self.parse_argument_list(left_paren.range.end);
100
101 Some(CommandInvocation {
102 name,
103 arguments,
104 range: TextRange::new(name_token.range.start, end),
105 })
106 }
107
108 fn parse_argument_list(&mut self, fallback_end: usize) -> (Vec<Argument>, usize) {
109 let mut arguments = Vec::new();
110 let mut end = fallback_end;
111
112 loop {
113 let Some(token) = self.tokens.current() else {
114 self.errors.push(ParseError {
115 message: "expected `)` to close command invocation".to_owned(),
116 range: TextRange::new(end, end),
117 });
118 break;
119 };
120
121 if matches!(token.kind, TokenKind::RightParen) {
122 end = token.range.end;
123 self.tokens.bump();
124 break;
125 }
126
127 let Some(argument) = self.parse_argument() else {
128 self.error_here("expected a command argument");
129 self.tokens.bump();
130 continue;
131 };
132
133 end = argument.range().end;
134 arguments.push(argument);
135 }
136
137 (arguments, end)
138 }
139
140 fn parse_argument(&mut self) -> Option<Argument> {
141 let token = self.tokens.current()?;
142
143 match &token.kind {
144 TokenKind::Identifier(text) | TokenKind::UnquotedArgument(text) => {
145 let range = token.range;
146 let text = text.clone();
147 self.tokens.bump();
148 Some(Argument::Unquoted(UnquotedArgument { text, range }))
149 }
150 TokenKind::QuotedArgument(text) => {
151 let range = token.range;
152 let text = text.clone();
153 self.tokens.bump();
154 Some(Argument::Quoted(QuotedArgument { text, range }))
155 }
156 TokenKind::BracketArgument(text) => {
157 let range = token.range;
158 let text = text.clone();
159 self.tokens.bump();
160 Some(Argument::Bracket(BracketArgument { text, range }))
161 }
162 TokenKind::LeftParen => self.parse_paren_group().map(Argument::ParenGroup),
163 _ => None,
164 }
165 }
166
167 fn parse_paren_group(&mut self) -> Option<ParenGroup> {
168 let left_paren = self.tokens.bump()?;
169 let start = left_paren.range.start;
170 let (items, end) = self.parse_argument_list(left_paren.range.end);
171
172 Some(ParenGroup {
173 items,
174 range: TextRange::new(start, end),
175 })
176 }
177
178 fn error_here(&mut self, message: &str) {
179 let range = self.tokens.current().map_or_else(
180 || TextRange::new(self.source_len, self.source_len),
181 |token| token.range,
182 );
183 self.error_at(range, message);
184 }
185
186 fn error_at(&mut self, range: TextRange, message: &str) {
187 self.errors.push(ParseError {
188 message: message.to_owned(),
189 range,
190 });
191 }
192}