Skip to main content

cmake_tidy_parser/
parser.rs

1use 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}