1use crate::{
4 Diagnostic, DiagnosticCode as Code, Limits, Node, NodeKind, Span, SyntaxTree, Token, TokenKind,
5 tokenize_with_limits,
6};
7use sim_kernel::{Fixity, PrattOperator, PrattResult, PrattTable, Symbol};
8
9pub fn parse_module(source: &str) -> Result<SyntaxTree, Diagnostic> {
11 parse_module_with_limits(source, Limits::default())
12}
13
14pub fn parse_module_with_limits(source: &str, limits: Limits) -> Result<SyntaxTree, Diagnostic> {
16 let tokens = tokenize_with_limits(source, limits)?;
17 let mut parser = Parser {
18 source,
19 tokens: &tokens,
20 limits,
21 delimiters: Vec::new(),
22 };
23 let root = parser.module()?;
24 Ok(SyntaxTree::new(source, tokens, root))
25}
26
27struct Parser<'a> {
28 source: &'a str,
29 tokens: &'a [Token],
30 limits: Limits,
31 delimiters: Vec<(char, usize)>,
32}
33
34impl Parser<'_> {
35 fn module(&mut self) -> Result<Node, Diagnostic> {
36 let mut children = Vec::new();
37 let mut statement_start = 0;
38 let mut suite_stack: Vec<usize> = Vec::new();
39 let mut significant = false;
40 for (index, token) in self.tokens.iter().enumerate() {
41 match token.kind {
42 TokenKind::Trivia => {}
43 TokenKind::Indent => {
44 suite_stack.push(index);
45 if suite_stack.len() > self.limits.max_nesting {
46 return Err(self.diag(
47 Code::ResourceLimit,
48 token.span,
49 "suite nesting limit exceeded",
50 ));
51 }
52 }
53 TokenKind::Dedent => {
54 if suite_stack.pop().is_none() {
55 return Err(self.diag(
56 Code::InvalidIndentation,
57 token.span,
58 "unexpected dedent",
59 ));
60 }
61 }
62 TokenKind::Operator => {
63 significant = true;
64 self.delimiter(index)?;
65 }
66 TokenKind::Newline if self.delimiters.is_empty() => {
67 if significant {
68 children.push(self.statement(statement_start, index + 1)?);
69 }
70 statement_start = index + 1;
71 significant = false;
72 }
73 TokenKind::End => {
74 if significant {
75 children.push(self.statement(statement_start, index)?);
76 }
77 }
78 _ => significant = true,
79 }
80 }
81 if let Some((open, index)) = self.delimiters.last().copied() {
82 return Err(self.diag(
83 Code::UnmatchedDelimiter,
84 self.tokens[index].span,
85 &format!("unclosed delimiter {open}"),
86 ));
87 }
88 if !suite_stack.is_empty() {
89 return Err(self.diag(
90 Code::InvalidIndentation,
91 self.tokens.last().expect("end token").span,
92 "unterminated suite",
93 ));
94 }
95 Ok(Node {
96 kind: NodeKind::Module,
97 tokens: 0..self.tokens.len(),
98 children,
99 })
100 }
101
102 fn statement(&self, start: usize, end: usize) -> Result<Node, Diagnostic> {
103 let visible: Vec<_> = (start..end)
104 .filter(|i| {
105 !matches!(
106 self.tokens[*i].kind,
107 TokenKind::Trivia | TokenKind::Indent | TokenKind::Dedent | TokenKind::Newline
108 )
109 })
110 .collect();
111 if visible.is_empty() {
112 return Ok(Node {
113 kind: NodeKind::Statement,
114 tokens: start..end,
115 children: Vec::new(),
116 });
117 }
118 let first = self.text(visible[0]);
119 if matches!(first, "elif" | "else" | "except" | "finally" | "case")
120 && !self.line_ends_colon(&visible)
121 {
122 return Err(self.diag(
123 Code::InvalidSyntax,
124 self.tokens[visible[0]].span,
125 "compound clause requires a trailing colon",
126 ));
127 }
128 if matches!(
129 first,
130 "if" | "while" | "for" | "with" | "try" | "def" | "class" | "match"
131 ) && !self.line_ends_colon(&visible)
132 {
133 return Err(self.diag(
134 Code::InvalidSyntax,
135 self.tokens[visible[0]].span,
136 "compound statement requires a trailing colon",
137 ));
138 }
139 let children = if visible
140 .iter()
141 .any(|i| is_precedence_operator(self.text(*i)))
142 {
143 let table = python_pratt_table();
146 debug_assert!(table.require_infix(&Symbol::new("+")).is_ok());
147 vec![Node {
148 kind: NodeKind::Expression,
149 tokens: start..end,
150 children: Vec::new(),
151 }]
152 } else {
153 Vec::new()
154 };
155 Ok(Node {
156 kind: NodeKind::Statement,
157 tokens: start..end,
158 children,
159 })
160 }
161
162 fn delimiter(&mut self, index: usize) -> Result<(), Diagnostic> {
163 let text = self.text(index);
164 if let Some(open) = text.chars().next().filter(|c| matches!(c, '(' | '[' | '{')) {
165 self.delimiters.push((open, index));
166 return Ok(());
167 }
168 let Some(close) = text.chars().next().filter(|c| matches!(c, ')' | ']' | '}')) else {
169 return Ok(());
170 };
171 let expected = match close {
172 ')' => '(',
173 ']' => '[',
174 '}' => '{',
175 _ => unreachable!(),
176 };
177 match self.delimiters.pop() {
178 Some((open, _)) if open == expected => Ok(()),
179 _ => Err(self.diag(
180 Code::UnmatchedDelimiter,
181 self.tokens[index].span,
182 "unmatched or crossed closing delimiter",
183 )),
184 }
185 }
186
187 fn line_ends_colon(&self, visible: &[usize]) -> bool {
188 visible.last().is_some_and(|i| self.text(*i) == ":")
189 }
190 fn text(&self, index: usize) -> &str {
191 let span = self.tokens[index].span;
192 &self.source[span.start..span.end]
193 }
194 fn diag(&self, code: Code, span: Span, message: &str) -> Diagnostic {
195 let token = self.tokens.iter().find(|t| t.span.start == span.start);
196 Diagnostic {
197 code,
198 span,
199 line: token.map_or(1, |t| t.line),
200 column: token.map_or(0, |t| t.column),
201 message: message.to_owned(),
202 }
203 }
204}
205
206fn is_precedence_operator(text: &str) -> bool {
207 matches!(
208 text,
209 "+" | "-" | "*" | "/" | "//" | "%" | "@" | "**" | "<<" | ">>" | "&" | "^" | "|"
210 )
211}
212
213fn python_pratt_table() -> PrattTable {
214 let mut table = PrattTable::new();
215 for (symbol, power, right) in [
216 ("|", 20, false),
217 ("^", 30, false),
218 ("&", 40, false),
219 ("<<", 50, false),
220 (">>", 50, false),
221 ("+", 60, false),
222 ("-", 60, false),
223 ("*", 70, false),
224 ("@", 70, false),
225 ("/", 70, false),
226 ("//", 70, false),
227 ("%", 70, false),
228 ("**", 90, true),
229 ] {
230 table.register(PrattOperator {
231 symbol: Symbol::new(symbol),
232 fixity: if right {
233 Fixity::InfixRight
234 } else {
235 Fixity::InfixLeft
236 },
237 left_bp: power,
238 right_bp: power + u16::from(!right),
239 result: PrattResult::ExprInfix,
240 });
241 }
242 for symbol in ["+", "-", "~"] {
243 table.register(PrattOperator {
244 symbol: Symbol::new(symbol),
245 fixity: Fixity::Prefix,
246 left_bp: 0,
247 right_bp: 80,
248 result: PrattResult::ExprPrefix,
249 });
250 }
251 table
252}