Skip to main content

sql_dialect_fmt_parser/
lib.rs

1//! Error-resilient, event-based recursive-descent parser for Snowflake SQL.
2//!
3//! [`parse`] turns source text into a lossless `rowan` CST plus a list of [`ParseError`]s.
4//! Parsing never fails: malformed input is wrapped in `ERROR` nodes and recovery continues, so
5//! the tree always round-trips byte-for-byte (the basis for formatting and highlighting).
6//!
7//! ## Pipeline
8//! `tokenize` → [`input::Input`] → [`parser::Parser`] (emits events) → [`builder::build_tree`].
9//!
10//! ## Modules
11//! * `event` / `input` / `parser` / `grammar` / `builder` — the parsing pipeline.
12//! * `ast` — typed accessors over the untyped tree.
13
14mod ast;
15mod builder;
16mod event;
17mod grammar;
18mod input;
19mod parser;
20
21pub use ast::*;
22pub use sql_dialect_fmt_syntax::{Dialect, SyntaxKind, SyntaxNode};
23
24/// A diagnostic produced while parsing, located at a byte span into the source.
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct ParseError {
27    pub message: String,
28    /// Byte offset into the source where the offending token begins.
29    pub offset: usize,
30    /// Byte length of the offending token's text, so a diagnostic underlines the whole token
31    /// rather than a single character. `0` for a zero-width point (e.g. an error at end of input,
32    /// where there is no token to point at).
33    pub len: usize,
34}
35
36impl ParseError {
37    /// The byte range `offset..offset + len` this error covers in the source.
38    pub fn range(&self) -> std::ops::Range<usize> {
39        self.offset..self.offset + self.len
40    }
41}
42
43impl std::fmt::Display for ParseError {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        write!(f, "{} at byte {}", self.message, self.offset)
46    }
47}
48
49impl std::error::Error for ParseError {}
50
51/// The result of parsing: a lossless green tree plus any diagnostics.
52#[derive(Clone)]
53pub struct Parse {
54    green: rowan::GreenNode,
55    errors: Vec<ParseError>,
56}
57
58impl Parse {
59    /// The typed root of the syntax tree.
60    pub fn syntax(&self) -> SyntaxNode {
61        SyntaxNode::new_root(self.green.clone())
62    }
63
64    /// Diagnostics gathered during parsing (empty for fully valid input).
65    pub fn errors(&self) -> &[ParseError] {
66        &self.errors
67    }
68}
69
70/// Parse Snowflake SQL source into a lossless CST. Never panics; never loses input.
71///
72/// Equivalent to [`parse_with_dialect`] with [`Dialect::Snowflake`].
73pub fn parse(text: &str) -> Parse {
74    parse_with_dialect(text, Dialect::Snowflake)
75}
76
77/// Parse `text` as `dialect` into a lossless CST. Never panics; never loses input.
78///
79/// The dialect drives tokenization (so the lexer's quoting/special-token rules match) and gates
80/// which dialect-specific statements and operators the grammar accepts.
81pub fn parse_with_dialect(text: &str, dialect: Dialect) -> Parse {
82    let lexed = sql_dialect_fmt_lexer::tokenize_for_dialect(text, dialect);
83    let input = input::Input::new(lexed);
84    let (events, errors) = parser::Parser::new(&input, dialect).parse();
85    let green = builder::build_tree(input.all(), events);
86    Parse { green, errors }
87}