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 crate::dialects::Dialect;
15use crate::errors::SQLParseError;
16use crate::parser::segments::file::FileSegment;
17use context::ParseContext;
18use segments::{ErasedSegment, Tables};
19
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
21pub struct IndentationConfig {
22    flags: u16,
23}
24
25impl IndentationConfig {
26    const INDENTED_JOINS_KEY: &'static str = "indented_joins";
27    const INDENTED_USING_ON_KEY: &'static str = "indented_using_on";
28    const INDENTED_ON_CONTENTS_KEY: &'static str = "indented_on_contents";
29    const INDENTED_THEN_KEY: &'static str = "indented_then";
30    const INDENTED_THEN_CONTENTS_KEY: &'static str = "indented_then_contents";
31    const INDENTED_JOINS_ON_KEY: &'static str = "indented_joins_on";
32    const INDENTED_CTES_KEY: &'static str = "indented_ctes";
33
34    pub const INDENTED_JOINS: Self = Self { flags: 1 << 0 };
35    pub const INDENTED_USING_ON: Self = Self { flags: 1 << 1 };
36    pub const INDENTED_ON_CONTENTS: Self = Self { flags: 1 << 2 };
37    pub const INDENTED_THEN: Self = Self { flags: 1 << 3 };
38    pub const INDENTED_THEN_CONTENTS: Self = Self { flags: 1 << 4 };
39    pub const INDENTED_JOINS_ON: Self = Self { flags: 1 << 5 };
40    pub const INDENTED_CTES: Self = Self { flags: 1 << 6 };
41
42    pub fn from_bool_lookup(mut get: impl FnMut(&str) -> bool) -> Self {
43        let mut config = Self::default();
44
45        config.insert_if(Self::INDENTED_JOINS, get(Self::INDENTED_JOINS_KEY));
46        config.insert_if(Self::INDENTED_USING_ON, get(Self::INDENTED_USING_ON_KEY));
47        config.insert_if(
48            Self::INDENTED_ON_CONTENTS,
49            get(Self::INDENTED_ON_CONTENTS_KEY),
50        );
51        config.insert_if(Self::INDENTED_THEN, get(Self::INDENTED_THEN_KEY));
52        config.insert_if(
53            Self::INDENTED_THEN_CONTENTS,
54            get(Self::INDENTED_THEN_CONTENTS_KEY),
55        );
56        config.insert_if(Self::INDENTED_JOINS_ON, get(Self::INDENTED_JOINS_ON_KEY));
57        config.insert_if(Self::INDENTED_CTES, get(Self::INDENTED_CTES_KEY));
58
59        config
60    }
61
62    pub fn contains(self, required: Self) -> bool {
63        (self.flags & required.flags) == required.flags
64    }
65
66    pub fn insert(&mut self, flag: Self) {
67        self.flags |= flag.flags;
68    }
69
70    pub fn insert_if(&mut self, flag: Self, enabled: bool) {
71        if enabled {
72            self.insert(flag);
73        }
74    }
75}
76
77#[derive(Clone)]
78pub struct Parser<'a> {
79    dialect: &'a Dialect,
80    pub(crate) indentation_config: IndentationConfig,
81}
82
83impl<'a> From<&'a Dialect> for Parser<'a> {
84    fn from(value: &'a Dialect) -> Self {
85        Self {
86            dialect: value,
87            indentation_config: IndentationConfig::default(),
88        }
89    }
90}
91
92impl<'a> Parser<'a> {
93    pub fn new(dialect: &'a Dialect, indentation_config: IndentationConfig) -> Self {
94        Self {
95            dialect,
96            indentation_config,
97        }
98    }
99
100    pub fn dialect(&self) -> &Dialect {
101        self.dialect
102    }
103
104    pub fn indentation_config(&self) -> IndentationConfig {
105        self.indentation_config
106    }
107
108    pub fn parse(
109        &self,
110        tables: &Tables,
111        segments: &[ErasedSegment],
112    ) -> Result<Option<ErasedSegment>, SQLParseError> {
113        if segments.is_empty() {
114            // This should normally never happen because there will usually
115            // be an end_of_file segment. It would probably only happen in
116            // api use cases.
117            return Ok(None);
118        }
119
120        // NOTE: This is the only time we use the parse context not in the
121        // context of a context manager. That's because it's the initial
122        // instantiation.
123        let mut parse_cx: ParseContext = self.into();
124
125        // Kick off parsing with the root segment. The BaseFileSegment has
126        // a unique entry point to facilitate exaclty this. All other segments
127        // will use the standard .match()/.parse() route.
128        let root =
129            FileSegment.root_parse(tables, parse_cx.dialect().name, segments, &mut parse_cx)?;
130
131        #[cfg(debug_assertions)]
132        {
133            // Basic Validation, that we haven't dropped anything.
134            let join_segments_raw = |segments: &[ErasedSegment]| {
135                smol_str::SmolStr::from_iter(segments.iter().map(|s| s.raw().as_str()))
136            };
137
138            pretty_assertions::assert_eq!(&join_segments_raw(segments), root.raw());
139        }
140
141        Ok(root.into())
142    }
143}