1use crate::{
7 error::{BudgetTracker, ParseBudget},
8 error_recovery::ParseError,
9 position::{Position, Range},
10 token_wrapper::TokenWithPosition,
11};
12use perl_ast_v2::NodeIdGenerator;
13use perl_lexer::TokenType;
14use perl_position_tracking::LineStartsCache;
15use std::collections::VecDeque;
16
17pub struct ParserContext {
19 tokens: VecDeque<TokenWithPosition>,
21 current: usize,
23 pub id_generator: NodeIdGenerator,
25 errors: Vec<ParseError>,
27 source: String,
29 _position_tracker: PositionTracker,
31 budget: ParseBudget,
33 budget_tracker: BudgetTracker,
35}
36
37struct PositionTracker {
43 line_cache: LineStartsCache,
45 source: String,
47}
48
49impl PositionTracker {
50 fn new(source: String) -> Self {
51 let line_cache = LineStartsCache::new(&source);
52 PositionTracker { line_cache, source }
53 }
54
55 fn byte_to_position(&self, byte_offset: usize) -> Position {
57 let (line, character) = self.line_cache.offset_to_position(&self.source, byte_offset);
58 let (line, column) = zero_based_to_one_based(line, character);
60 Position::new(byte_offset, line, column)
61 }
62}
63
64fn zero_based_to_one_based(line: u32, column: u32) -> (u32, u32) {
69 (line.saturating_add(1), column.saturating_add(1))
70}
71
72impl ParserContext {
73 pub fn new(source: String) -> Self {
75 let mut tokens = VecDeque::new();
76 let position_tracker = PositionTracker::new(source.clone());
77
78 let symbol_table = perl_lexer::LocalSymbolTable::scan_subs(&source);
81 let lexer_config = perl_lexer::LexerConfig {
82 symbol_table: Some(symbol_table),
83 ..perl_lexer::LexerConfig::default()
84 };
85
86 let mut lexer = perl_lexer::PerlLexer::with_config(&source, lexer_config);
88 loop {
89 match lexer.next_token() {
90 Some(token) => {
91 if matches!(token.token_type, TokenType::EOF) {
93 break;
94 }
95
96 let start = token.start;
97 let end = token.end;
98
99 let start_pos = position_tracker.byte_to_position(start);
101 let end_pos = position_tracker.byte_to_position(end);
102
103 tokens.push_back(TokenWithPosition::new(token, start_pos, end_pos));
104 }
105 None => break,
106 }
107 }
108
109 ParserContext {
110 tokens,
111 current: 0,
112 id_generator: NodeIdGenerator::new(),
113 errors: Vec::new(),
114 source,
115 _position_tracker: position_tracker,
116 budget: ParseBudget::default(),
117 budget_tracker: BudgetTracker::new(),
118 }
119 }
120
121 pub fn with_budget(source: String, budget: ParseBudget) -> Self {
123 let mut ctx = Self::new(source);
124 ctx.budget = budget;
125 ctx
126 }
127
128 pub fn budget(&self) -> &ParseBudget {
130 &self.budget
131 }
132
133 pub fn budget_tracker(&self) -> &BudgetTracker {
135 &self.budget_tracker
136 }
137
138 pub fn budget_tracker_mut(&mut self) -> &mut BudgetTracker {
140 &mut self.budget_tracker
141 }
142
143 pub fn errors_exhausted(&self) -> bool {
145 self.budget_tracker.errors_exhausted(&self.budget)
146 }
147
148 pub fn depth_would_exceed(&self) -> bool {
150 self.budget_tracker.depth_would_exceed(&self.budget)
151 }
152
153 pub fn enter_depth(&mut self) -> bool {
155 if self.depth_would_exceed() {
156 return false;
157 }
158 self.budget_tracker.enter_depth();
159 true
160 }
161
162 pub fn exit_depth(&mut self) {
164 self.budget_tracker.exit_depth();
165 }
166
167 pub fn current_token(&self) -> Option<&TokenWithPosition> {
169 self.tokens.get(self.current)
170 }
171
172 pub fn peek_token(&self, offset: usize) -> Option<&TokenWithPosition> {
174 self.tokens.get(self.current + offset)
175 }
176
177 pub fn advance(&mut self) -> Option<&TokenWithPosition> {
179 if self.current < self.tokens.len() {
180 self.current += 1;
181 }
182 self.current_token()
183 }
184
185 pub fn is_eof(&self) -> bool {
187 self.current >= self.tokens.len()
188 }
189
190 pub fn current_position(&self) -> Position {
192 if let Some(token) = self.current_token() {
193 token.range().start
194 } else if let Some(last_token) = self.tokens.back() {
195 last_token.range().end
197 } else {
198 Position::new(0, 1, 1)
199 }
200 }
201
202 pub fn current_position_range(&self) -> Range {
204 if let Some(token) = self.current_token() {
205 token.range()
206 } else {
207 let pos = self.current_position();
208 Range::new(pos, pos)
209 }
210 }
211
212 pub fn add_error(&mut self, error: ParseError) -> bool {
216 if self.errors_exhausted() {
217 return false;
218 }
219 self.errors.push(error);
220 self.budget_tracker.record_error();
221 true
222 }
223
224 pub fn add_error_unchecked(&mut self, error: ParseError) {
226 self.errors.push(error);
227 self.budget_tracker.record_error();
228 }
229
230 pub fn take_errors(&mut self) -> Vec<ParseError> {
232 std::mem::take(&mut self.errors)
233 }
234
235 pub fn current_index(&self) -> usize {
237 self.current
238 }
239
240 pub fn set_index(&mut self, index: usize) {
242 self.current = index.min(self.tokens.len());
243 }
244
245 pub fn expect(&mut self, expected: TokenType) -> Result<&TokenWithPosition, ParseError> {
247 match self.current_token() {
248 Some(token) if token.token.token_type == expected => {
249 self.advance();
250 Ok(&self.tokens[self.current - 1])
251 }
252 Some(token) => Err(ParseError::new(
253 format!("Expected {:?}, found {:?}", expected, token.token.token_type),
254 token.range(),
255 )
256 .with_expected(vec![format!("{:?}", expected)])
257 .with_found(format!("{:?}", token.token.token_type))),
258 None => Err(ParseError::new(
259 format!("Expected {:?}, found end of file", expected),
260 self.current_position_range(),
261 )
262 .with_expected(vec![format!("{:?}", expected)])
263 .with_found("EOF".to_string())),
264 }
265 }
266
267 pub fn check(&self, token_type: &TokenType) -> bool {
269 self.current_token().map(|t| &t.token.token_type == token_type).unwrap_or(false)
270 }
271
272 pub fn consume(&mut self, token_type: &TokenType) -> bool {
274 if self.check(token_type) {
275 self.advance();
276 true
277 } else {
278 false
279 }
280 }
281
282 pub fn source_slice(&self, range: &Range) -> &str {
284 &self.source[range.start.byte..range.end.byte]
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use perl_tdd_support::must_some;
292
293 #[test]
294 fn test_parser_context_creation() {
295 let source = "my $x = 42;".to_string();
296 let ctx = ParserContext::new(source);
297
298 assert!(!ctx.is_eof());
299 assert!(!ctx.tokens.is_empty());
300 }
301
302 #[test]
303 fn test_token_advancement() {
304 let source = "my $x".to_string();
305 let mut ctx = ParserContext::new(source);
306
307 assert!(matches!(
309 ctx.current_token().map(|t| &t.token.token_type),
310 Some(TokenType::Keyword(k)) if k.as_ref() == "my"
311 ));
312
313 ctx.advance();
315 assert!(ctx.current_token().is_some());
316 }
317
318 #[test]
319 fn test_error_accumulation() {
320 let mut ctx = ParserContext::new("test".to_string());
321
322 let error1 = ParseError::new("Error 1".to_string(), ctx.current_position_range());
323 let error2 = ParseError::new("Error 2".to_string(), ctx.current_position_range());
324
325 ctx.add_error(error1);
326 ctx.add_error(error2);
327
328 let errors = ctx.take_errors();
329 assert_eq!(errors.len(), 2);
330 assert_eq!(errors[0].message, "Error 1");
331 assert_eq!(errors[1].message, "Error 2");
332 }
333
334 #[test]
335 fn test_multiline_positions() {
336 let source = "my $x = 42;\nmy $y = 43;".to_string();
337 let ctx = ParserContext::new(source.clone());
338
339 let first_offset = must_some(source.find("my"));
340 let second_offset = must_some(source.rfind("my"));
341
342 let first = must_some(ctx.tokens.iter().find(|t| t.range().start.byte == first_offset));
343 assert_eq!(first.range().start.line, 1);
344 assert_eq!(first.range().start.column, 1);
345 assert_eq!(first.range().end.line, 1);
346 assert_eq!(first.range().end.column, 3);
347
348 let second = must_some(ctx.tokens.iter().find(|t| t.range().start.byte == second_offset));
349 assert_eq!(second.range().start.line, 2);
350 assert_eq!(second.range().start.column, 1);
351 assert_eq!(second.range().end.line, 2);
352 assert_eq!(second.range().end.column, 3);
353 }
354
355 #[test]
356 fn test_multiline_string_token_positions() {
357 let source = "my $s = \"a\nb\";".to_string();
358 let ctx = ParserContext::new(source.clone());
359
360 let string_offset = must_some(source.find('"'));
361 let token = must_some(ctx.tokens.iter().find(|t| t.range().start.byte == string_offset));
362
363 assert_eq!(token.range().start.line, 1);
364 assert_eq!(token.range().start.column, 9);
365 assert_eq!(token.range().end.line, 2);
366 assert_eq!(token.range().end.column, 3);
367 }
368
369 #[test]
370 fn test_utf16_position_mapping() {
371 let source = "my $emoji = 😀;".to_string();
373 let ctx = ParserContext::new(source.clone());
374
375 let equals_offset = must_some(source.find('='));
378 let equals_token =
379 must_some(ctx.tokens.iter().find(|t| t.range().start.byte == equals_offset));
380
381 assert_eq!(equals_token.range().start.line, 1);
384 assert!(equals_token.range().start.column > 0);
386 }
387
388 #[test]
389 fn test_crlf_line_endings() {
390 let source = "my $x = 42;\r\nmy $y = 43;".to_string();
391 let ctx = ParserContext::new(source.clone());
392
393 let first_offset = must_some(source.find("my"));
394 let second_offset = must_some(source.rfind("my"));
395
396 let first = must_some(ctx.tokens.iter().find(|t| t.range().start.byte == first_offset));
397 assert_eq!(first.range().start.line, 1);
398 assert_eq!(first.range().start.column, 1);
399
400 let second = must_some(ctx.tokens.iter().find(|t| t.range().start.byte == second_offset));
401 assert_eq!(second.range().start.line, 2);
402 assert_eq!(second.range().start.column, 1);
403 }
404
405 #[test]
406 fn test_empty_source() {
407 let source = "".to_string();
408 let ctx = ParserContext::new(source);
409
410 assert!(ctx.tokens.is_empty());
411 assert!(ctx.is_eof());
412 }
413
414 #[test]
415 fn test_zero_based_to_one_based_saturates_at_u32_max() {
416 assert_eq!(zero_based_to_one_based(u32::MAX, u32::MAX), (u32::MAX, u32::MAX));
420 assert_eq!(zero_based_to_one_based(u32::MAX - 1, u32::MAX - 1), (u32::MAX, u32::MAX));
421 assert_eq!(
423 zero_based_to_one_based(u32::MAX - 2, u32::MAX - 2),
424 (u32::MAX - 1, u32::MAX - 1)
425 );
426
427 assert_eq!(zero_based_to_one_based(0, 0), (1, 1));
429 assert_eq!(zero_based_to_one_based(41, 7), (42, 8));
430 }
431
432 #[test]
433 fn test_single_token() {
434 let source = "42".to_string();
435 let ctx = ParserContext::new(source);
436
437 assert_eq!(ctx.tokens.len(), 1);
438
439 let token = &ctx.tokens[0];
440 assert_eq!(token.range().start.byte, 0);
441 assert_eq!(token.range().start.line, 1);
442 assert_eq!(token.range().start.column, 1);
443 assert_eq!(token.range().end.byte, 2);
444 assert_eq!(token.range().end.line, 1);
445 assert_eq!(token.range().end.column, 3);
446 }
447}