sqruff_lib_core/
parser.rs

1pub mod context;
2pub mod grammar;
3pub mod lexer;
4pub mod lookahead;
5pub mod markers;
6pub mod match_algorithms;
7pub mod match_result;
8pub mod matchable;
9pub mod node_matcher;
10pub mod parsers;
11pub mod segments;
12pub mod types;
13
14use ahash::AHashMap;
15
16use crate::dialects::Dialect;
17use crate::errors::SQLParseError;
18use crate::parser::segments::file::FileSegment;
19use context::ParseContext;
20use segments::{ErasedSegment, Tables};
21
22#[derive(Clone)]
23pub struct Parser<'a> {
24    dialect: &'a Dialect,
25    pub(crate) indentation_config: AHashMap<String, bool>,
26}
27
28impl<'a> From<&'a Dialect> for Parser<'a> {
29    fn from(value: &'a Dialect) -> Self {
30        Self {
31            dialect: value,
32            indentation_config: AHashMap::new(),
33        }
34    }
35}
36
37impl<'a> Parser<'a> {
38    pub fn new(dialect: &'a Dialect, indentation_config: AHashMap<String, bool>) -> Self {
39        Self {
40            dialect,
41            indentation_config,
42        }
43    }
44
45    pub fn dialect(&self) -> &Dialect {
46        self.dialect
47    }
48
49    pub fn indentation_config(&self) -> &AHashMap<String, bool> {
50        &self.indentation_config
51    }
52
53    pub fn parse(
54        &self,
55        tables: &Tables,
56        segments: &[ErasedSegment],
57        filename: Option<String>,
58    ) -> Result<Option<ErasedSegment>, SQLParseError> {
59        if segments.is_empty() {
60            // This should normally never happen because there will usually
61            // be an end_of_file segment. It would probably only happen in
62            // api use cases.
63            return Ok(None);
64        }
65
66        // NOTE: This is the only time we use the parse context not in the
67        // context of a context manager. That's because it's the initial
68        // instantiation.
69        let mut parse_cx: ParseContext = self.into();
70        // Kick off parsing with the root segment. The BaseFileSegment has
71        // a unique entry point to facilitate exaclty this. All other segments
72        // will use the standard .match()/.parse() route.
73        let root = FileSegment.root_parse(
74            tables,
75            parse_cx.dialect().name,
76            segments,
77            &mut parse_cx,
78            filename,
79        )?;
80
81        #[cfg(debug_assertions)]
82        {
83            // Basic Validation, that we haven't dropped anything.
84            let join_segments_raw = |segments: &[ErasedSegment]| {
85                smol_str::SmolStr::from_iter(segments.iter().map(|s| s.raw().as_str()))
86            };
87
88            pretty_assertions::assert_eq!(&join_segments_raw(segments), root.raw());
89        }
90
91        Ok(root.into())
92    }
93}