Skip to main content

sqruff_lib_core/parser/
lexer.rs

1use std::borrow::Cow;
2use std::fmt::Debug;
3use std::ops::Range;
4use std::str::Chars;
5
6use std::collections::HashMap;
7
8use smol_str::SmolStr;
9
10use super::markers::PositionMarker;
11use super::segments::{BlockType, ErasedSegment, SegmentBuilder, Tables, TemplateInfo};
12use crate::dialects::Dialect;
13use crate::dialects::syntax::SyntaxKind;
14use crate::errors::SQLLexError;
15use crate::slice_helpers::{is_zero_slice, offset_slice};
16use crate::templaters::{TemplateSliceKind, TemplatedFile, TemplatedFileSlice};
17
18/// An element matched during lexing.
19#[derive(Debug, Clone)]
20pub struct Element<'a> {
21    name: &'static str,
22    text: Cow<'a, str>,
23    syntax_kind: SyntaxKind,
24}
25
26impl<'a> Element<'a> {
27    fn new(name: &'static str, syntax_kind: SyntaxKind, text: impl Into<Cow<'a, str>>) -> Self {
28        Self {
29            name,
30            syntax_kind,
31            text: text.into(),
32        }
33    }
34}
35
36/// A LexedElement, bundled with it's position in the templated file.
37#[derive(Debug)]
38pub struct TemplateElement<'a> {
39    raw: Cow<'a, str>,
40    template_slice: Range<usize>,
41    matcher: Info,
42}
43
44#[derive(Debug)]
45struct Info {
46    name: &'static str,
47    syntax_kind: SyntaxKind,
48}
49
50impl<'a> TemplateElement<'a> {
51    /// Make a TemplateElement from a LexedElement.
52    pub fn from_element(element: Element<'a>, template_slice: Range<usize>) -> Self {
53        TemplateElement {
54            raw: element.text,
55            template_slice,
56            matcher: Info {
57                name: element.name,
58                syntax_kind: element.syntax_kind,
59            },
60        }
61    }
62
63    pub fn to_segment(
64        &self,
65        pos_marker: PositionMarker,
66        subslice: Option<Range<usize>>,
67    ) -> ErasedSegment {
68        let slice = subslice.map_or_else(|| self.raw.as_ref(), |slice| &self.raw[slice]);
69        SegmentBuilder::token(0, slice, self.matcher.syntax_kind)
70            .with_position(pos_marker)
71            .finish()
72    }
73}
74
75/// A class to hold matches from the lexer.
76#[derive(Debug)]
77pub struct Match<'a> {
78    pub forward_string: &'a str,
79    pub elements: Vec<Element<'a>>,
80}
81
82#[derive(Debug, Clone)]
83pub struct Matcher {
84    pattern: Pattern,
85    subdivider: Option<Pattern>,
86    trim_post_subdivide: Option<Pattern>,
87}
88
89impl Matcher {
90    pub const fn new(pattern: Pattern) -> Self {
91        Self {
92            pattern,
93            subdivider: None,
94            trim_post_subdivide: None,
95        }
96    }
97
98    pub const fn string(
99        name: &'static str,
100        pattern: &'static str,
101        syntax_kind: SyntaxKind,
102    ) -> Self {
103        Self::new(Pattern::string(name, pattern, syntax_kind))
104    }
105
106    #[track_caller]
107    pub fn regex(name: &'static str, pattern: &'static str, syntax_kind: SyntaxKind) -> Self {
108        Self::new(Pattern::regex(name, pattern, syntax_kind))
109    }
110
111    pub fn native(name: &'static str, f: fn(&mut Cursor) -> bool, syntax_kind: SyntaxKind) -> Self {
112        Self::new(Pattern::native(name, f, syntax_kind))
113    }
114
115    #[track_caller]
116    pub fn legacy(
117        name: &'static str,
118        starts_with: fn(&str) -> bool,
119        pattern: &'static str,
120        syntax_kind: SyntaxKind,
121    ) -> Self {
122        Self::new(Pattern::legacy(name, starts_with, pattern, syntax_kind))
123    }
124
125    pub fn subdivider(mut self, subdivider: Pattern) -> Self {
126        assert!(matches!(
127            self.pattern.kind,
128            SearchPatternKind::Legacy(_, _) | SearchPatternKind::Native(_)
129        ));
130        self.subdivider = Some(subdivider);
131        self
132    }
133
134    pub fn post_subdivide(mut self, trim_post_subdivide: Pattern) -> Self {
135        assert!(matches!(
136            self.pattern.kind,
137            SearchPatternKind::Legacy(_, _) | SearchPatternKind::Native(_)
138        ));
139        self.trim_post_subdivide = Some(trim_post_subdivide);
140        self
141    }
142
143    pub fn name(&self) -> &'static str {
144        self.pattern.name
145    }
146
147    #[track_caller]
148    pub fn matches<'a>(&self, forward_string: &'a str) -> Match<'a> {
149        match self.pattern.matches(forward_string) {
150            Some(matched) => {
151                let new_elements = self.subdivide(matched, self.pattern.syntax_kind);
152
153                Match {
154                    forward_string: &forward_string[matched.len()..],
155                    elements: new_elements,
156                }
157            }
158            None => Match {
159                forward_string,
160                elements: Vec::new(),
161            },
162        }
163    }
164
165    fn subdivide<'a>(&self, matched: &'a str, matched_kind: SyntaxKind) -> Vec<Element<'a>> {
166        match &self.subdivider {
167            Some(subdivider) => {
168                let mut elem_buff = Vec::new();
169                let mut str_buff = matched;
170
171                while !str_buff.is_empty() {
172                    let Some(div_pos) = subdivider.search(str_buff) else {
173                        let mut trimmed_elems = self.trim_match(str_buff);
174                        elem_buff.append(&mut trimmed_elems);
175                        break;
176                    };
177
178                    let mut trimmed_elems = self.trim_match(&str_buff[..div_pos.start]);
179                    let div_elem = Element::new(
180                        subdivider.name,
181                        subdivider.syntax_kind,
182                        &str_buff[div_pos.start..div_pos.end],
183                    );
184
185                    elem_buff.append(&mut trimmed_elems);
186                    elem_buff.push(div_elem);
187
188                    str_buff = &str_buff[div_pos.end..];
189                }
190
191                elem_buff
192            }
193            None => {
194                vec![Element::new(self.name(), matched_kind, matched)]
195            }
196        }
197    }
198
199    fn trim_match<'a>(&self, matched_str: &'a str) -> Vec<Element<'a>> {
200        let Some(trim_post_subdivide) = &self.trim_post_subdivide else {
201            return Vec::new();
202        };
203
204        let mk_element = |text| {
205            Element::new(
206                trim_post_subdivide.name,
207                trim_post_subdivide.syntax_kind,
208                text,
209            )
210        };
211
212        let mut elem_buff = Vec::new();
213        let mut content_buff = String::new();
214        let mut str_buff = matched_str;
215
216        while !str_buff.is_empty() {
217            let Some(trim_pos) = trim_post_subdivide.search(str_buff) else {
218                break;
219            };
220
221            let start = trim_pos.start;
222            let end = trim_pos.end;
223
224            if start == 0 {
225                elem_buff.push(mk_element(&str_buff[..end]));
226                str_buff = str_buff[end..].into();
227            } else if end == str_buff.len() {
228                let raw = format!("{}{}", content_buff, &str_buff[..start]);
229
230                elem_buff.push(Element::new(
231                    trim_post_subdivide.name,
232                    trim_post_subdivide.syntax_kind,
233                    raw,
234                ));
235                elem_buff.push(mk_element(&str_buff[start..end]));
236
237                content_buff.clear();
238                str_buff = "";
239            } else {
240                content_buff.push_str(&str_buff[..end]);
241                str_buff = &str_buff[end..];
242            }
243        }
244
245        if !content_buff.is_empty() || !str_buff.is_empty() {
246            let raw = format!("{content_buff}{str_buff}");
247            elem_buff.push(Element::new(
248                self.pattern.name,
249                self.pattern.syntax_kind,
250                raw,
251            ));
252        }
253
254        elem_buff
255    }
256}
257
258#[derive(Debug, Clone)]
259pub struct Pattern {
260    name: &'static str,
261    syntax_kind: SyntaxKind,
262    kind: SearchPatternKind,
263}
264
265#[derive(Debug, Clone)]
266pub enum SearchPatternKind {
267    String(&'static str),
268    Regex(&'static str),
269    Native(fn(&mut Cursor) -> bool),
270    Legacy(fn(&str) -> bool, fancy_regex::Regex),
271}
272
273impl Pattern {
274    pub const fn string(
275        name: &'static str,
276        template: &'static str,
277        syntax_kind: SyntaxKind,
278    ) -> Self {
279        Self {
280            name,
281            syntax_kind,
282            kind: SearchPatternKind::String(template),
283        }
284    }
285
286    #[track_caller]
287    pub fn regex(name: &'static str, regex: &'static str, syntax_kind: SyntaxKind) -> Self {
288        #[cfg(debug_assertions)]
289        if regex_automata::dfa::regex::Regex::new(regex).is_err() {
290            panic!("Invalid regex pattern: {}", std::panic::Location::caller());
291        }
292
293        Self {
294            name,
295            syntax_kind,
296            kind: SearchPatternKind::Regex(regex),
297        }
298    }
299
300    pub fn native(name: &'static str, f: fn(&mut Cursor) -> bool, syntax_kind: SyntaxKind) -> Self {
301        Self {
302            name,
303            syntax_kind,
304            kind: SearchPatternKind::Native(f),
305        }
306    }
307
308    #[track_caller]
309    pub fn legacy(
310        name: &'static str,
311        starts_with: fn(&str) -> bool,
312        regex: &'static str,
313        syntax_kind: SyntaxKind,
314    ) -> Self {
315        let regex = format!("^{regex}");
316        Self {
317            name,
318            syntax_kind,
319            kind: SearchPatternKind::Legacy(starts_with, fancy_regex::Regex::new(&regex).unwrap()),
320        }
321    }
322
323    fn matches<'a>(&self, forward_string: &'a str) -> Option<&'a str> {
324        match self.kind {
325            SearchPatternKind::String(template) => {
326                if forward_string.starts_with(template) {
327                    return Some(template);
328                }
329            }
330            SearchPatternKind::Legacy(f, ref template) => {
331                if !f(forward_string) {
332                    return None;
333                }
334
335                if let Ok(Some(matched)) = template.find(forward_string)
336                    && matched.start() == 0
337                {
338                    return Some(matched.as_str());
339                }
340            }
341            SearchPatternKind::Native(f) => {
342                let mut cursor = Cursor::new(forward_string);
343                return f(&mut cursor).then(|| cursor.lexed());
344            }
345            _ => unreachable!(),
346        };
347
348        None
349    }
350
351    fn search(&self, forward_string: &str) -> Option<Range<usize>> {
352        match &self.kind {
353            SearchPatternKind::String(template) => forward_string
354                .find(template)
355                .map(|start| start..start + template.len()),
356            SearchPatternKind::Legacy(_, template) => {
357                if let Ok(Some(matched)) = template.find(forward_string) {
358                    return Some(matched.range());
359                }
360                None
361            }
362            _ => unreachable!("{:?}", self.kind),
363        }
364    }
365}
366
367pub struct Cursor<'text> {
368    text: &'text str,
369    chars: Chars<'text>,
370}
371
372impl<'text> Cursor<'text> {
373    const EOF: char = '\0';
374
375    fn new(text: &'text str) -> Self {
376        Self {
377            text,
378            chars: text.chars(),
379        }
380    }
381
382    pub fn peek(&self) -> char {
383        self.chars.clone().next().unwrap_or(Self::EOF)
384    }
385
386    pub fn peek_next(&self) -> char {
387        self.chars.clone().nth(1).unwrap_or(Self::EOF)
388    }
389
390    pub fn shift(&mut self) -> char {
391        self.chars.next().unwrap_or(Self::EOF)
392    }
393
394    pub fn shift_while(&mut self, f: impl Fn(char) -> bool + Copy) {
395        while self.peek() != Self::EOF && f(self.peek()) {
396            self.shift();
397        }
398    }
399
400    fn lexed(&self) -> &'text str {
401        let len = self.text.len() - self.chars.as_str().len();
402        &self.text[..len]
403    }
404}
405
406pub fn nested_block_comment(cursor: &mut Cursor) -> bool {
407    if cursor.peek() != '/' || cursor.peek_next() != '*' {
408        return false;
409    }
410    cursor.shift();
411    cursor.shift();
412    let mut depth = 1;
413    loop {
414        let ch = cursor.peek();
415        if ch == Cursor::EOF {
416            return false;
417        }
418        if ch == '/' && cursor.peek_next() == '*' {
419            cursor.shift();
420            cursor.shift();
421            depth += 1;
422            continue;
423        }
424        if ch == '*' && cursor.peek_next() == '/' {
425            cursor.shift();
426            cursor.shift();
427            depth -= 1;
428            if depth == 0 {
429                return true;
430            }
431            continue;
432        }
433        cursor.shift();
434    }
435}
436
437/// The Lexer class actually does the lexing step.
438#[derive(Debug, Clone)]
439pub struct Lexer {
440    syntax_map: Vec<(&'static str, SyntaxKind)>,
441    regex: regex_automata::meta::Regex,
442    matchers: Vec<Matcher>,
443    last_resort_lexer: Matcher,
444}
445
446impl<'a> From<&'a Dialect> for Lexer {
447    fn from(dialect: &'a Dialect) -> Self {
448        Lexer::new(dialect.lexer_matchers())
449    }
450}
451
452impl Lexer {
453    /// Create a new lexer.
454    pub(crate) fn new(lexer_matchers: &[Matcher]) -> Self {
455        let mut patterns = Vec::new();
456        let mut syntax_map = Vec::new();
457        let mut matchers = Vec::new();
458
459        for matcher in lexer_matchers {
460            match matcher.pattern.kind {
461                SearchPatternKind::String(pattern) | SearchPatternKind::Regex(pattern) => {
462                    let pattern = if matches!(matcher.pattern.kind, SearchPatternKind::String(_)) {
463                        fancy_regex::escape(pattern)
464                    } else {
465                        pattern.into()
466                    };
467
468                    patterns.push(pattern);
469                    syntax_map.push((matcher.pattern.name, matcher.pattern.syntax_kind));
470                }
471                SearchPatternKind::Legacy(_, _) | SearchPatternKind::Native(_) => {
472                    matchers.push(matcher.clone());
473                }
474            }
475        }
476
477        Lexer {
478            syntax_map,
479            matchers,
480            regex: regex_automata::meta::Regex::new_many(&patterns).unwrap(),
481            last_resort_lexer: Matcher::legacy(
482                "<unlexable>",
483                |_| true,
484                r"[^\t\n.]*",
485                SyntaxKind::Unlexable,
486            ),
487        }
488    }
489
490    pub fn lex(
491        &self,
492        tables: &Tables,
493        template: impl Into<TemplatedFile>,
494    ) -> (Vec<ErasedSegment>, Vec<SQLLexError>) {
495        let template = template.into();
496        let mut str_buff = template.templated_str.as_deref().unwrap();
497
498        // Lex the string to get a tuple of LexedElement
499        let mut element_buffer: Vec<Element> = Vec::new();
500
501        loop {
502            let mut res = self.lex_match(str_buff);
503            element_buffer.append(&mut res.elements);
504
505            if res.forward_string.is_empty() {
506                break;
507            }
508
509            // If we STILL can't match, then just panic out.
510            let mut resort_res = self.last_resort_lexer.matches(str_buff);
511            if !resort_res.elements.is_empty() {
512                break;
513            }
514
515            str_buff = resort_res.forward_string;
516            element_buffer.append(&mut resort_res.elements);
517        }
518
519        // Map tuple LexedElement to list of TemplateElement.
520        // This adds the template_slice to the object.
521        let templated_buffer = Lexer::map_template_slices(element_buffer, &template);
522        // Turn lexed elements into segments.
523        let mut segments = self.elements_to_segments(templated_buffer, &template);
524
525        for seg in &mut segments {
526            seg.get_mut().set_id(tables.next_id())
527        }
528
529        (segments, Vec::new())
530    }
531
532    /// Generate any lexing errors for any un-lex-ables.
533    ///
534    /// TODO: Taking in an iterator, also can make the typing better than use
535    /// unwrap.
536    #[allow(dead_code)]
537    fn violations_from_segments(segments: Vec<ErasedSegment>) -> Vec<SQLLexError> {
538        segments
539            .into_iter()
540            .filter(|s| s.is_type(SyntaxKind::Unlexable))
541            .map(|s| {
542                SQLLexError::new(
543                    format!(
544                        "Unable to lex characters: {}",
545                        s.raw().chars().take(10).collect::<String>()
546                    ),
547                    s.get_position_marker().unwrap().clone(),
548                )
549            })
550            .collect()
551    }
552
553    /// Iteratively match strings using the selection of sub-matchers.
554    fn lex_match<'b>(&self, mut forward_string: &'b str) -> Match<'b> {
555        let mut elem_buff = Vec::new();
556
557        'main: loop {
558            if forward_string.is_empty() {
559                return Match {
560                    forward_string,
561                    elements: elem_buff,
562                };
563            }
564
565            for matcher in &self.matchers {
566                let mut match_result = matcher.matches(forward_string);
567
568                if !match_result.elements.is_empty() {
569                    elem_buff.append(&mut match_result.elements);
570                    forward_string = match_result.forward_string;
571                    continue 'main;
572                }
573            }
574
575            let input =
576                regex_automata::Input::new(forward_string).anchored(regex_automata::Anchored::Yes);
577
578            if let Some(match_) = self.regex.find(input) {
579                let (name, kind) = self.syntax_map[match_.pattern().as_usize()];
580
581                elem_buff.push(Element::new(
582                    name,
583                    kind,
584                    &forward_string[match_.start()..match_.end()],
585                ));
586                forward_string = &forward_string[match_.end()..];
587
588                continue 'main;
589            }
590
591            return Match {
592                forward_string,
593                elements: elem_buff,
594            };
595        }
596    }
597
598    /// Create a tuple of TemplateElement from a tuple of LexedElement.
599    ///
600    /// This adds slices in the templated file to the original lexed
601    /// elements. We'll need this to work out the position in the source
602    /// file.
603    /// TODO Can this vec be turned into an iterator and return iterator to make
604    /// lazy?
605    fn map_template_slices<'b>(
606        elements: Vec<Element<'b>>,
607        template: &TemplatedFile,
608    ) -> Vec<TemplateElement<'b>> {
609        let mut idx = 0;
610        let mut templated_buff: Vec<TemplateElement> = Vec::with_capacity(elements.len());
611
612        for element in elements {
613            let template_slice = offset_slice(idx, element.text.len());
614            idx += element.text.len();
615
616            let templated_string = template.templated();
617            if templated_string[template_slice.clone()] != element.text {
618                panic!(
619                    "Template and lexed elements do not match. This should never happen {:?} != \
620                     {:?}",
621                    element.text, &templated_string[template_slice]
622                );
623            }
624
625            templated_buff.push(TemplateElement::from_element(element, template_slice));
626        }
627
628        templated_buff
629    }
630
631    /// Convert a tuple of lexed elements into a tuple of segments.
632    fn elements_to_segments(
633        &self,
634        elements: Vec<TemplateElement>,
635        templated_file: &TemplatedFile,
636    ) -> Vec<ErasedSegment> {
637        let mut segments = iter_segments(elements, templated_file);
638
639        // Add an end of file marker
640        let position_maker = match segments.last() {
641            Some(segment) => segment.get_position_marker().unwrap().end_point_marker(),
642            None => PositionMarker::from_point(0, 0, templated_file.clone(), None, None),
643        };
644
645        segments.push(
646            SegmentBuilder::token(0, "", SyntaxKind::EndOfFile)
647                .with_position(position_maker)
648                .finish(),
649        );
650
651        segments
652    }
653}
654
655/// Tracks template block nesting, pairing block tags with a shared uuid so the
656/// linter can treat matching `{% .. %}` / `{% end.. %}` tags consistently.
657#[derive(Default)]
658struct BlockTracker {
659    stack: Vec<u32>,
660    map: HashMap<(usize, usize), u32>,
661    next: u32,
662}
663
664impl BlockTracker {
665    fn enter(&mut self, src: Range<usize>) {
666        let key = (src.start, src.end);
667        let uuid = match self.map.get(&key) {
668            Some(&u) => u,
669            None => {
670                let id = self.next;
671                self.next += 1;
672                self.map.insert(key, id);
673                id
674            }
675        };
676        self.stack.push(uuid);
677    }
678
679    fn exit(&mut self) {
680        self.stack.pop();
681    }
682
683    fn top(&self) -> Option<u32> {
684        self.stack.last().copied()
685    }
686}
687
688fn block_type_from(kind: TemplateSliceKind) -> BlockType {
689    match kind {
690        TemplateSliceKind::Literal => BlockType::Literal,
691        TemplateSliceKind::Templated => BlockType::Templated,
692        TemplateSliceKind::Comment => BlockType::Comment,
693        TemplateSliceKind::BlockStart => BlockType::BlockStart,
694        TemplateSliceKind::BlockMid => BlockType::BlockMid,
695        TemplateSliceKind::BlockEnd => BlockType::BlockEnd,
696    }
697}
698
699fn make_template_segment(
700    kind: SyntaxKind,
701    block_type: BlockType,
702    block_uuid: Option<u32>,
703    source_str: SmolStr,
704    is_template: bool,
705    position: PositionMarker,
706) -> ErasedSegment {
707    SegmentBuilder::token(0, "", kind)
708        .with_template_info(TemplateInfo {
709            block_type,
710            block_uuid,
711            source_str,
712            is_template,
713        })
714        .with_position(position)
715        .finish()
716}
717
718fn make_placeholder(
719    block_type: BlockType,
720    block_uuid: Option<u32>,
721    source_str: SmolStr,
722    source_slice: Range<usize>,
723    templated_slice: Range<usize>,
724    templated_file: &TemplatedFile,
725) -> ErasedSegment {
726    let position = PositionMarker::new(
727        source_slice,
728        templated_slice,
729        templated_file.clone(),
730        None,
731        None,
732    );
733    make_template_segment(
734        SyntaxKind::Placeholder,
735        block_type,
736        block_uuid,
737        source_str,
738        false,
739        position,
740    )
741}
742
743/// A template-introduced indent/dedent meta.
744fn make_template_meta(
745    kind: SyntaxKind,
746    block_uuid: Option<u32>,
747    source_point: usize,
748    templated_point: usize,
749    templated_file: &TemplatedFile,
750) -> ErasedSegment {
751    let position = PositionMarker::from_point(
752        source_point,
753        templated_point,
754        templated_file.clone(),
755        None,
756        None,
757    );
758    make_template_segment(
759        kind,
760        BlockType::Templated,
761        block_uuid,
762        "".into(),
763        true,
764        position,
765    )
766}
767
768/// Generate placeholder and loop segments for a zero-length template slice:
769/// backward jumps (`TemplateLoop`), blocks, forward jumps and other unrendered
770/// template elements. Mirrors SQLFluff's `_handle_zero_length_slice`.
771fn handle_zero_length_slice(
772    tfs: &TemplatedFileSlice,
773    next_tfs: Option<&TemplatedFileSlice>,
774    block_stack: &mut BlockTracker,
775    templated_file: &TemplatedFile,
776) -> Vec<ErasedSegment> {
777    let mut out = Vec::new();
778    let is_block = matches!(
779        tfs.slice_type,
780        TemplateSliceKind::BlockStart | TemplateSliceKind::BlockMid | TemplateSliceKind::BlockEnd
781    );
782
783    if is_block {
784        // Backward jump -> loop marker, wrapped in template dedent/indent.
785        if let Some(next) = next_tfs
786            && next.source_slice.start < tfs.source_slice.start
787        {
788            let (sp, tp) = (tfs.source_slice.start, tfs.templated_slice.start);
789            out.push(make_template_meta(
790                SyntaxKind::Dedent,
791                None,
792                sp,
793                tp,
794                templated_file,
795            ));
796            out.push(make_template_segment(
797                SyntaxKind::TemplateLoop,
798                BlockType::Templated,
799                block_stack.top(),
800                "".into(),
801                false,
802                PositionMarker::from_point(sp, tp, templated_file.clone(), None, None),
803            ));
804            out.push(make_template_meta(
805                SyntaxKind::Indent,
806                None,
807                sp,
808                tp,
809                templated_file,
810            ));
811            return out;
812        }
813
814        if tfs.slice_type == TemplateSliceKind::BlockStart {
815            block_stack.enter(tfs.source_slice.clone());
816        } else {
817            // block_mid / block_end: template dedent before the tag.
818            out.push(make_template_meta(
819                SyntaxKind::Dedent,
820                block_stack.top(),
821                tfs.source_slice.start,
822                tfs.templated_slice.start,
823                templated_file,
824            ));
825        }
826
827        out.push(make_placeholder(
828            block_type_from(tfs.slice_type),
829            block_stack.top(),
830            templated_file.source_str[tfs.source_slice.clone()].into(),
831            tfs.source_slice.clone(),
832            tfs.templated_slice.clone(),
833            templated_file,
834        ));
835
836        if tfs.slice_type == TemplateSliceKind::BlockEnd {
837            block_stack.exit();
838        } else {
839            // block_start / block_mid: template indent after the tag.
840            out.push(make_template_meta(
841                SyntaxKind::Indent,
842                block_stack.top(),
843                tfs.source_slice.end,
844                tfs.templated_slice.end,
845                templated_file,
846            ));
847        }
848
849        // Forward jump -> skipped source placeholder.
850        if let Some(next) = next_tfs
851            && next.source_slice.start > tfs.source_slice.end
852        {
853            let gap = tfs.source_slice.end..next.source_slice.start;
854            let mut src = templated_file.source_str[gap.clone()].to_string();
855            if src.len() >= 20 {
856                src = format!("... [{} unused template characters] ...", src.len());
857            }
858            out.push(make_placeholder(
859                BlockType::SkippedSource,
860                None,
861                src.into(),
862                gap,
863                tfs.templated_slice.clone(),
864                templated_file,
865            ));
866        }
867
868        return out;
869    }
870
871    out.push(make_placeholder(
872        block_type_from(tfs.slice_type),
873        None,
874        templated_file.source_str[tfs.source_slice.clone()].into(),
875        tfs.source_slice.clone(),
876        tfs.templated_slice.clone(),
877        templated_file,
878    ));
879    out
880}
881
882fn iter_segments(
883    lexed_elements: Vec<TemplateElement>,
884    templated_file: &TemplatedFile,
885) -> Vec<ErasedSegment> {
886    let mut result: Vec<ErasedSegment> = Vec::with_capacity(lexed_elements.len());
887    // An index to track where we've got to in the templated file.
888    let mut tfs_idx = 0;
889    let mut block_stack = BlockTracker::default();
890    // Highest zero-slice index already emitted, to avoid duplicates on re-scan.
891    let mut handled_zero_until = 0;
892    let templated_file_slices = &templated_file.sliced_file;
893
894    // Now work out source slices, and add in template placeholders.
895    for element in lexed_elements {
896        let consumed_element_length = 0;
897        let mut stashed_source_idx = None;
898
899        for (idx, tfs) in templated_file_slices
900            .iter()
901            .skip(tfs_idx)
902            .enumerate()
903            .map(|(i, tfs)| (i + tfs_idx, tfs))
904        {
905            // Is it a zero slice?
906            if is_zero_slice(&tfs.templated_slice) {
907                if idx >= handled_zero_until {
908                    let next_tfs = templated_file_slices.get(idx + 1);
909                    result.extend(handle_zero_length_slice(
910                        tfs,
911                        next_tfs,
912                        &mut block_stack,
913                        templated_file,
914                    ));
915                    handled_zero_until = idx + 1;
916                }
917                continue;
918            }
919
920            if tfs.has_slice_kind(TemplateSliceKind::Literal) {
921                let tfs_offset =
922                    (tfs.source_slice.start as isize) - (tfs.templated_slice.start as isize);
923
924                // NOTE: Greater than OR EQUAL, to include the case of it matching
925                // length exactly.
926                if element.template_slice.end <= tfs.templated_slice.end {
927                    let slice_start = stashed_source_idx.unwrap_or_else(|| {
928                        let sum = element.template_slice.start as isize
929                            + consumed_element_length as isize
930                            + tfs_offset;
931                        if sum < 0 {
932                            panic!("Slice start is negative: {sum}");
933                        }
934                        sum.try_into()
935                            .unwrap_or_else(|_| panic!("Cannot convert {sum} to usize"))
936                    });
937
938                    let source_slice_end =
939                        (element.template_slice.end as isize + tfs_offset) as usize;
940                    result.push(element.to_segment(
941                        PositionMarker::new(
942                            slice_start..source_slice_end,
943                            element.template_slice.clone(),
944                            templated_file.clone(),
945                            None,
946                            None,
947                        ),
948                        Some(consumed_element_length..element.raw.len()),
949                    ));
950
951                    // If it was an exact match, consume the templated element too.
952                    if element.template_slice.end == tfs.templated_slice.end {
953                        tfs_idx += 1
954                    }
955                    // In any case, we're done with this element. Move on
956                    break;
957                } else if element.template_slice.start >= tfs.templated_slice.end {
958                    // Element starts at or after this slice ends - skip to next slice.
959                    // This can happen when zero-length slices exist (e.g., stripped
960                    // whitespace from Jinja comments like {#- ... #}).
961                    log::debug!("Element starts at or after slice end, skipping");
962                    continue;
963                } else {
964                    // This means that the current lexed element spans across
965                    // multiple templated file slices.
966
967                    log::debug!("Consuming whole spanning literal",);
968
969                    // This almost certainly means there's a templated element
970                    // in the middle of a whole lexed element.
971
972                    // What we do here depends on whether we're allowed to split
973                    // lexed elements. This is basically only true if it's whitespace.
974                    // NOTE: We should probably make this configurable on the
975                    // matcher object, but for now we're going to look for the
976                    // name of the lexer.
977                    if element.matcher.name == "whitespace" {
978                        if stashed_source_idx.is_some() {
979                            panic!("Found literal whitespace with stashed idx!")
980                        }
981
982                        let incremental_length =
983                            tfs.templated_slice.end - element.template_slice.start;
984
985                        let source_slice_start = element.template_slice.start as isize
986                            + consumed_element_length as isize
987                            + tfs_offset;
988                        let source_slice_start =
989                            source_slice_start.try_into().unwrap_or_else(|_| {
990                                panic!("Cannot convert {source_slice_start} to usize")
991                            });
992                        let source_slice_end =
993                            source_slice_start as isize + incremental_length as isize;
994                        let source_slice_end = source_slice_end.try_into().unwrap_or_else(|_| {
995                            panic!("Cannot convert {source_slice_end} to usize")
996                        });
997
998                        result.push(element.to_segment(
999                            PositionMarker::new(
1000                                source_slice_start..source_slice_end,
1001                                element.template_slice.clone(),
1002                                templated_file.clone(),
1003                                None,
1004                                None,
1005                            ),
1006                            offset_slice(consumed_element_length, incremental_length).into(),
1007                        ));
1008                        // Continue to the next slice to process remaining whitespace
1009                        continue;
1010                    } else {
1011                        // We can't split it. We're going to end up yielding a segment
1012                        // which spans multiple slices. Stash the type, and if we haven't
1013                        // set the start yet, stash it too.
1014                        log::debug!("Spilling over literal slice.");
1015                        if stashed_source_idx.is_none() {
1016                            stashed_source_idx = (element.template_slice.start + idx).into();
1017                            log::debug!("Stashing a source start. {stashed_source_idx:?}");
1018                        }
1019                        // Continue to next slice regardless of whether we stashed
1020                        continue;
1021                    }
1022                }
1023            } else if matches!(
1024                tfs.slice_kind(),
1025                TemplateSliceKind::Templated | TemplateSliceKind::BlockStart
1026            ) {
1027                // Found a templated slice. Does it have length in the templated file?
1028                // If it doesn't, then we'll pick it up next.
1029                if !is_zero_slice(&tfs.templated_slice) {
1030                    // If it's a block_start. Append to the block stack.
1031                    // NOTE: This is rare, but call blocks do occasionally
1032                    // have length (and so don't get picked up by
1033                    // _handle_zero_length_slice)
1034                    if tfs.has_slice_kind(TemplateSliceKind::BlockStart) {
1035                        block_stack.enter(tfs.source_slice.clone());
1036                    }
1037
1038                    // Is our current element totally contained in this slice?
1039                    if element.template_slice.end <= tfs.templated_slice.end {
1040                        log::debug!("Contained templated slice.");
1041                        // Yes it is. Add lexed element with source slices as the whole
1042                        // span of the source slice for the file slice.
1043                        // If we've got an existing stashed source start, use that
1044                        // as the start of the source slice.
1045                        let slice_start = if let Some(stashed_source_idx) = stashed_source_idx {
1046                            stashed_source_idx
1047                        } else {
1048                            tfs.source_slice.start + consumed_element_length
1049                        };
1050
1051                        result.push(element.to_segment(
1052                            PositionMarker::new(
1053                                slice_start..tfs.source_slice.end,
1054                                element.template_slice.clone(),
1055                                templated_file.clone(),
1056                                None,
1057                                None,
1058                            ),
1059                            Some(consumed_element_length..element.raw.len()),
1060                        ));
1061
1062                        // If it was an exact match, consume the templated element too.
1063                        if element.template_slice.end == tfs.templated_slice.end {
1064                            tfs_idx += 1
1065                        }
1066                        // Carry on to the next lexed element
1067                        break;
1068                    } else {
1069                        // We've got an element which extends beyond this templated slice.
1070                        // This means that a _single_ lexed element claims both some
1071                        // templated elements and some non-templated elements. That could
1072                        // include all kinds of things (and from here we don't know what
1073                        // else is yet to come, comments, blocks, literals etc...).
1074
1075                        // In the `literal` version of this code we would consider
1076                        // splitting the literal element here, but in the templated
1077                        // side we don't. That's because the way that templated tokens
1078                        // are lexed, means that they should arrive "pre-split".
1079
1080                        // Stash the source idx for later when we do make a segment.
1081                        if stashed_source_idx.is_none() {
1082                            stashed_source_idx = Some(tfs.source_slice.start);
1083                            continue;
1084                        }
1085                        // Move on to the next template slice
1086                        continue;
1087                    }
1088                }
1089            }
1090            panic!("Unable to process slice: {tfs:?}");
1091        }
1092    }
1093
1094    // Drain any trailing zero-length slices (e.g. a file ending on a block
1095    // tag), which the element loop never reaches.
1096    for idx in handled_zero_until..templated_file_slices.len() {
1097        let tfs = &templated_file_slices[idx];
1098        if is_zero_slice(&tfs.templated_slice) {
1099            let next_tfs = templated_file_slices.get(idx + 1);
1100            result.extend(handle_zero_length_slice(
1101                tfs,
1102                next_tfs,
1103                &mut block_stack,
1104                templated_file,
1105            ));
1106        }
1107    }
1108
1109    result
1110}
1111
1112#[cfg(test)]
1113mod tests {
1114    use super::*;
1115
1116    /// Assert that a matcher does or doesn't work on a string.
1117    ///
1118    /// The optional `matchstring` argument, which can optionally
1119    /// be None, allows to either test positive matching of a
1120    /// particular string or negative matching (that it explicitly)
1121    /// doesn't match.
1122    fn assert_matches(in_string: &str, matcher: &Matcher, match_string: Option<&str>) {
1123        let res = matcher.matches(in_string);
1124        if let Some(match_string) = match_string {
1125            assert_eq!(res.forward_string, &in_string[match_string.len()..]);
1126            assert_eq!(res.elements.len(), 1);
1127            assert_eq!(res.elements[0].text, match_string);
1128        } else {
1129            assert_eq!(res.forward_string, in_string);
1130            assert_eq!(res.elements.len(), 0);
1131        }
1132    }
1133
1134    #[test]
1135    fn test_parser_lexer_trim_post_subdivide() {
1136        let matcher: Vec<Matcher> = vec![
1137            Matcher::legacy(
1138                "function_script_terminator",
1139                |_| true,
1140                r";\s+(?!\*)\/(?!\*)|\s+(?!\*)\/(?!\*)",
1141                SyntaxKind::StatementTerminator,
1142            )
1143            .subdivider(Pattern::string("semicolon", ";", SyntaxKind::Semicolon))
1144            .post_subdivide(Pattern::legacy(
1145                "newline",
1146                |_| true,
1147                r"(\n|\r\n)+",
1148                SyntaxKind::Newline,
1149            )),
1150        ];
1151
1152        let res = Lexer::new(&matcher).lex_match(";\n/\n");
1153        assert_eq!(res.elements[0].text, ";");
1154        assert_eq!(res.elements[1].text, "\n");
1155        assert_eq!(res.elements[2].text, "/");
1156        assert_eq!(res.elements.len(), 3);
1157    }
1158
1159    /// Test the RegexLexer.
1160    #[test]
1161    fn test_parser_lexer_regex() {
1162        let tests = &[
1163            ("fsaljk", "f", "f"),
1164            ("fsaljk", r"f", "f"),
1165            ("fsaljk", r"[fas]*", "fsa"),
1166            // Matching whitespace segments
1167            ("   \t   fsaljk", r"[^\S\r\n]*", "   \t   "),
1168            // Matching whitespace segments (with a newline)
1169            ("   \t \n  fsaljk", r"[^\S\r\n]*", "   \t "),
1170            // Matching quotes containing stuff
1171            (
1172                "'something boring'   \t \n  fsaljk",
1173                r"'[^']*'",
1174                "'something boring'",
1175            ),
1176            (
1177                "' something exciting \t\n '   \t \n  fsaljk",
1178                r"'[^']*'",
1179                "' something exciting \t\n '",
1180            ),
1181        ];
1182
1183        for (raw, reg, res) in tests {
1184            let matcher = Matcher::legacy("test", |_| true, reg, SyntaxKind::Word);
1185
1186            assert_matches(raw, &matcher, Some(res));
1187        }
1188    }
1189
1190    /// Test the lexer string
1191    #[test]
1192    fn test_parser_lexer_string() {
1193        let matcher = Matcher::string("dot", ".", SyntaxKind::Dot);
1194
1195        assert_matches(".fsaljk", &matcher, Some("."));
1196        assert_matches("fsaljk", &matcher, None);
1197    }
1198
1199    /// Test the RepeatedMultiMatcher
1200    #[test]
1201    fn test_parser_lexer_lex_match() {
1202        let matchers: Vec<Matcher> = vec![
1203            Matcher::string("dot", ".", SyntaxKind::Dot),
1204            Matcher::regex("test", "#[^#]*#", SyntaxKind::Dash),
1205        ];
1206
1207        let res = Lexer::new(&matchers).lex_match("..#..#..#");
1208
1209        assert_eq!(res.forward_string, "#");
1210        assert_eq!(res.elements.len(), 5);
1211        assert_eq!(res.elements[2].text, "#..#");
1212    }
1213}