Skip to main content

ktrs_parser/builder/
semantic.rs

1//! Kotlin's builder wrappers as one struct: `SemanticWhitespaceAwarePsiBuilderImpl` (newline and
2//! complex-token-joining stacks) here, the Truncated/ForByClause adapters in `layers.rs`.
3//!
4//! Java dispatch is reproduced exactly: the Impl's own methods (`advanceLexer`,
5//! `newlineBeforeCurrentToken`, joined lookups) call the Impl's `getTokenType`/`eof`, never the
6//! truncated overrides; only calls made by parser code go through the layers.
7
8use ktrs_syntax::SyntaxKind::{self, *};
9
10use super::layers::Layer;
11use super::marker::{Marker, MarkerHost};
12use super::psi_builder::PsiBuilder;
13
14pub struct SemanticWhitespaceAwarePsiBuilder {
15    pub psi: PsiBuilder,
16    join_complex_tokens: BoolStack,
17    newlines_enabled: BoolStack,
18    pub(super) layers: Vec<Layer>,
19    /// `impl_get_token_type` memo: its result at post-whitespace lexeme `tt_lexeme` (`usize::MAX`
20    /// = none). It depends only on that lexeme's kind and the next one's (changed only by remaps of
21    /// the current token) and the joining flag, so those clear it; any cursor move changes the key.
22    tt_lexeme: usize,
23    tt: Option<SyntaxKind>,
24}
25
26/// A `Stack<Boolean>` whose top, read on every token query, lives inline.
27struct BoolStack {
28    top: Option<bool>,
29    below: Vec<bool>,
30}
31
32impl BoolStack {
33    fn new(initial: bool) -> BoolStack {
34        BoolStack { top: Some(initial), below: Vec::new() }
35    }
36
37    fn push(&mut self, value: bool) {
38        if let Some(top) = self.top.replace(value) {
39            self.below.push(top);
40        }
41    }
42
43    fn pop(&mut self) {
44        self.top = self.below.pop();
45    }
46
47    fn peek(&self) -> bool {
48        self.top.expect("empty stack")
49    }
50
51    fn len(&self) -> usize {
52        self.below.len() + usize::from(self.top.is_some())
53    }
54}
55
56impl MarkerHost for SemanticWhitespaceAwarePsiBuilder {
57    fn psi_builder(&mut self) -> &mut PsiBuilder {
58        &mut self.psi
59    }
60}
61
62/// `complexTokens`.
63fn is_complex_token(kind: Option<SyntaxKind>) -> bool {
64    matches!(kind, Some(SAFE_ACCESS | ELVIS | EXCLEXCL))
65}
66
67impl SemanticWhitespaceAwarePsiBuilder {
68    pub fn new(psi: PsiBuilder) -> Self {
69        SemanticWhitespaceAwarePsiBuilder {
70            psi,
71            join_complex_tokens: BoolStack::new(true),
72            newlines_enabled: BoolStack::new(true),
73            layers: Vec::new(),
74            tt_lexeme: usize::MAX,
75            tt: None,
76        }
77    }
78
79    pub fn is_whitespace_or_comment(&self, kind: SyntaxKind) -> bool {
80        self.psi.is_whitespace_or_comment(kind)
81    }
82
83    pub fn newline_before_current_token(&mut self) -> bool {
84        if !self.newlines_enabled.peek() {
85            return false;
86        }
87
88        if self.psi.eof() {
89            return true;
90        }
91
92        // Upstream walks back `i` lexemes while `i <= offset`; trivia is never empty, so only the
93        // start of the input can stop it first. `prev` is the lexeme `i` back.
94        let psi = &self.psi;
95        let mut prev = psi.current_lexeme;
96        while prev > 0 {
97            prev -= 1;
98            match psi.lex_types[prev] {
99                BLOCK_COMMENT | DOC_COMMENT | EOL_COMMENT | SHEBANG_COMMENT => continue,
100                WHITE_SPACE => {
101                    if psi.token_text(prev).as_bytes().contains(&b'\n') {
102                        return true;
103                    }
104                }
105                _ => break,
106            }
107        }
108
109        false
110    }
111
112    pub fn disable_newlines(&mut self) {
113        self.newlines_enabled.push(false);
114        self.count_newlines_call(1);
115    }
116
117    pub fn enable_newlines(&mut self) {
118        self.newlines_enabled.push(true);
119        self.count_newlines_call(1);
120    }
121
122    pub fn restore_newlines_state(&mut self) {
123        assert!(self.newlines_enabled.len() > 1);
124        self.newlines_enabled.pop();
125        self.count_newlines_call(-1);
126    }
127
128    fn join_complex_tokens(&self) -> bool {
129        self.join_complex_tokens.peek()
130    }
131
132    pub fn restore_joining_complex_tokens_state(&mut self) {
133        self.join_complex_tokens.pop();
134        self.tt_lexeme = usize::MAX;
135    }
136
137    pub fn enable_joining_complex_tokens(&mut self) {
138        self.join_complex_tokens.push(true);
139        self.tt_lexeme = usize::MAX;
140    }
141
142    pub fn disable_joining_complex_tokens(&mut self) {
143        self.join_complex_tokens.push(false);
144        self.tt_lexeme = usize::MAX;
145    }
146
147    /// The Impl's `getTokenType` (no truncation), memoized (see `tt_lexeme`). Returning the memo
148    /// without `eof()`'s skip is fine: the key is a lexeme that skip leaves in place.
149    #[inline]
150    pub(super) fn impl_get_token_type(&mut self) -> Option<SyntaxKind> {
151        if self.tt_lexeme == self.psi.current_lexeme {
152            return self.tt;
153        }
154        self.impl_get_token_type_uncached()
155    }
156
157    #[inline(never)]
158    fn impl_get_token_type_uncached(&mut self) -> Option<SyntaxKind> {
159        let raw = self.psi.get_token_type();
160        let tt = if self.join_complex_tokens() { self.get_joined_token_type(raw, 1) } else { raw };
161        (self.tt_lexeme, self.tt) = (self.psi.current_lexeme, tt);
162        tt
163    }
164
165    #[inline]
166    fn get_joined_token_type(&self, raw_token_type: Option<SyntaxKind>, raw_lookup_steps: i32) -> Option<SyntaxKind> {
167        match raw_token_type {
168            Some(QUEST) => match self.psi.raw_lookup(raw_lookup_steps) {
169                Some(DOT) => return Some(SAFE_ACCESS),
170                Some(COLON) => return Some(ELVIS),
171                _ => {}
172            },
173            Some(EXCL) if self.psi.raw_lookup(raw_lookup_steps) == Some(EXCL) => return Some(EXCLEXCL),
174            _ => {}
175        }
176        raw_token_type
177    }
178
179    pub fn advance_lexer(&mut self) {
180        if !self.join_complex_tokens() {
181            self.psi.advance_lexer();
182            return;
183        }
184        let token_type = self.impl_get_token_type();
185        if is_complex_token(token_type) {
186            let mark = self.psi.mark();
187            self.psi.advance_lexer();
188            self.psi.advance_lexer();
189            mark.collapse(&mut self.psi, token_type.unwrap());
190        } else {
191            self.psi.advance_lexer();
192        }
193    }
194
195    /// The Impl's `getTokenText` (no truncation).
196    pub(super) fn impl_get_token_text(&mut self) -> Option<&str> {
197        if self.join_complex_tokens() {
198            match self.impl_get_token_type() {
199                Some(ELVIS) => return Some("?:"),
200                Some(SAFE_ACCESS) => return Some("?."),
201                _ => {}
202            }
203        }
204        self.psi.get_token_text()
205    }
206
207    /// The Impl's `lookAhead` (no truncation).
208    pub(super) fn impl_look_ahead(&mut self, steps: i32) -> Option<SyntaxKind> {
209        if !self.join_complex_tokens() {
210            return self.psi.look_ahead(steps);
211        }
212
213        if is_complex_token(self.impl_get_token_type()) {
214            return self.psi.look_ahead(steps + 1);
215        }
216        let raw = self.psi.look_ahead(steps);
217        // Upstream checks the raw token 2 after the *current* one, whatever `steps` is.
218        self.get_joined_token_type(raw, 2)
219    }
220
221    // ---- Plain delegation to PsiBuilderImpl ----
222
223    pub fn mark(&mut self) -> Marker {
224        self.psi.mark()
225    }
226
227    pub fn error(&mut self, message: &str) {
228        self.psi.error(message);
229    }
230
231    pub fn raw_lookup(&self, steps: i32) -> Option<SyntaxKind> {
232        self.psi.raw_lookup(steps)
233    }
234
235    pub fn raw_token_type_start(&self, steps: i32) -> i32 {
236        self.psi.raw_token_type_start(steps)
237    }
238
239    pub fn raw_token_index(&self) -> i32 {
240        self.psi.raw_token_index()
241    }
242
243    pub fn get_current_offset(&mut self) -> i32 {
244        self.psi.get_current_offset()
245    }
246
247    pub fn remap_current_token(&mut self, kind: SyntaxKind) {
248        self.psi.remap_current_token(kind);
249        self.tt_lexeme = usize::MAX;
250    }
251
252    pub fn get_original_text(&self) -> &str {
253        self.psi.get_original_text()
254    }
255}