Skip to main content

mago_syntax/ast/
sequence.rs

1//! PHP-specific view over the shared sequence primitives.
2//!
3//! Every syntax crate uses the same two container types, defined in
4//! [`mago_syntax_core::ast`]. PHP fixes the [`TokenSeparatedSequence`]
5//! separator to its own [`Token<'arena>`] via a type alias so that the
6//! rest of the PHP AST can continue to write
7//! `TokenSeparatedSequence<'arena, T>` (one generic) and pick up PHP
8//! extension methods through [`TokenSeparatedSequenceExt`].
9
10use mago_database::file::FileId;
11use mago_span::HasSpan;
12use mago_span::Position;
13use mago_span::Span;
14use mago_syntax_core::ast::TokenSeparatedSequence as CoreTokenSeparatedSequence;
15
16use crate::token::Token;
17
18pub use mago_syntax_core::ast::Sequence;
19
20/// A comma-/semicolon-separated sequence of PHP AST nodes.
21pub type TokenSeparatedSequence<'arena, T> = CoreTokenSeparatedSequence<'arena, T, Token<'arena>>;
22
23/// PHP-specific helpers on a [`TokenSeparatedSequence`]. Supplies the
24/// span-reconstruction methods that need to combine a file id with a
25/// token's start and value length.
26pub trait TokenSeparatedSequenceExt<'arena, T: HasSpan> {
27    fn first_span(&self, file_id: FileId) -> Option<Span>;
28    fn last_span(&self, file_id: FileId) -> Option<Span>;
29    fn span(&self, file_id: FileId, from: Position) -> Span;
30}
31
32impl<'arena, T: HasSpan> TokenSeparatedSequenceExt<'arena, T> for TokenSeparatedSequence<'arena, T> {
33    #[inline]
34    fn first_span(&self, file_id: FileId) -> Option<Span> {
35        match (self.tokens.first(), self.nodes.first()) {
36            (Some(token), Some(node)) => {
37                let token_end = token.start.offset + token.value.len() as u32;
38                if token_end <= node.span().start.offset { Some(token.span_for(file_id)) } else { Some(node.span()) }
39            }
40            (Some(token), None) => Some(token.span_for(file_id)),
41            (None, Some(node)) => Some(node.span()),
42            (None, None) => None,
43        }
44    }
45
46    #[inline]
47    fn last_span(&self, file_id: FileId) -> Option<Span> {
48        match (self.tokens.last(), self.nodes.last()) {
49            (Some(token), Some(node)) => {
50                if token.start.offset >= node.span().end.offset {
51                    Some(token.span_for(file_id))
52                } else {
53                    Some(node.span())
54                }
55            }
56            (Some(token), None) => Some(token.span_for(file_id)),
57            (None, Some(node)) => Some(node.span()),
58            (None, None) => None,
59        }
60    }
61
62    #[inline]
63    fn span(&self, file_id: FileId, from: Position) -> Span {
64        match (self.first_span(file_id), self.last_span(file_id)) {
65            (Some(first), Some(last)) => Span::new(file_id, first.start, last.end),
66            _ => Span::new(file_id, from, from),
67        }
68    }
69}