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}