Skip to main content

squawk_syntax/
lib.rs

1// via https://github.com/rust-lang/rust-analyzer/blob/d8887c0758bbd2d5f752d5bd405d4491e90e7ed6/crates/syntax/src/lib.rs
2//
3// Permission is hereby granted, free of charge, to any
4// person obtaining a copy of this software and associated
5// documentation files (the "Software"), to deal in the
6// Software without restriction, including without
7// limitation the rights to use, copy, modify, merge,
8// publish, distribute, sublicense, and/or sell copies of
9// the Software, and to permit persons to whom the Software
10// is furnished to do so, subject to the following
11// conditions:
12//
13// The above copyright notice and this permission notice
14// shall be included in all copies or substantial portions
15// of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25// DEALINGS IN THE SOFTWARE.
26
27pub mod ast;
28pub mod column_name;
29mod generated;
30mod parsing;
31mod ptr;
32pub mod quote;
33pub mod syntax_error;
34mod syntax_node;
35mod token_text;
36pub mod unescape;
37mod validation;
38
39#[cfg(test)]
40mod test;
41
42use std::{marker::PhantomData, sync::Arc};
43
44pub use squawk_parser::SyntaxKind;
45
46use ast::AstNode;
47pub use ptr::{AstPtr, SyntaxNodePtr};
48use rowan::GreenNode;
49use syntax_error::SyntaxError;
50pub use syntax_node::{SyntaxElement, SyntaxNode, SyntaxToken};
51pub use token_text::TokenText;
52
53/// `Parse` is the result of the parsing: a syntax tree and a collection of
54/// errors.
55///
56/// Note that we always produce a syntax tree, even for completely invalid
57/// files.
58#[derive(Debug, PartialEq, Eq)]
59pub struct Parse<T> {
60    green: GreenNode,
61    errors: Option<Arc<[SyntaxError]>>,
62    _ty: PhantomData<fn() -> T>,
63}
64
65impl<T> Clone for Parse<T> {
66    fn clone(&self) -> Parse<T> {
67        Parse {
68            green: self.green.clone(),
69            errors: self.errors.clone(),
70            _ty: PhantomData,
71        }
72    }
73}
74
75impl<T> Parse<T> {
76    fn new(green: GreenNode, errors: Vec<SyntaxError>) -> Parse<T> {
77        Parse {
78            green,
79            errors: if errors.is_empty() {
80                None
81            } else {
82                Some(errors.into())
83            },
84            _ty: PhantomData,
85        }
86    }
87
88    pub fn syntax_node(&self) -> SyntaxNode {
89        SyntaxNode::new_root(self.green.clone())
90    }
91
92    pub fn errors(&self) -> Vec<SyntaxError> {
93        let mut errors = if let Some(e) = self.errors.as_deref() {
94            e.to_vec()
95        } else {
96            vec![]
97        };
98        validation::validate(&self.syntax_node(), &mut errors);
99        errors.sort_by_key(|error| error.range().start());
100        errors
101    }
102}
103
104impl<T: AstNode> Parse<T> {
105    /// Converts this parse result into a parse result for an untyped syntax tree.
106    pub fn to_syntax(self) -> Parse<SyntaxNode> {
107        Parse {
108            green: self.green,
109            errors: self.errors,
110            _ty: PhantomData,
111        }
112    }
113
114    /// Gets the parsed syntax tree as a typed ast node.
115    ///
116    /// # Panics
117    ///
118    /// Panics if the root node cannot be casted into the typed ast node
119    /// (e.g. if it's an `ERROR` node).
120    pub fn tree(&self) -> T {
121        T::cast(self.syntax_node()).unwrap()
122    }
123
124    /// Converts from `Parse<T>` to [`Result<T, Vec<SyntaxError>>`].
125    pub fn ok(self) -> Result<T, Vec<SyntaxError>> {
126        match self.errors() {
127            errors if !errors.is_empty() => Err(errors),
128            _ => Ok(self.tree()),
129        }
130    }
131}
132
133impl Parse<SyntaxNode> {
134    pub fn cast<N: AstNode>(self) -> Option<Parse<N>> {
135        if N::cast(self.syntax_node()).is_some() {
136            Some(Parse {
137                green: self.green,
138                errors: self.errors,
139                _ty: PhantomData,
140            })
141        } else {
142            None
143        }
144    }
145}
146
147/// `SourceFile` represents a parse tree for a single SQL file.
148pub use crate::ast::SourceFile;
149
150impl SourceFile {
151    pub fn parse(text: &str) -> Parse<SourceFile> {
152        let (green, errors) = parsing::parse_text(text);
153        let root = SyntaxNode::new_root(green.clone());
154
155        assert_eq!(root.kind(), SyntaxKind::SOURCE_FILE);
156        Parse::new(green, errors)
157    }
158}
159
160/// Matches a `SyntaxNode` against an `ast` type.
161///
162/// # Example:
163///
164/// ```ignore
165/// match_ast! {
166///     match node {
167///         ast::CallExpr(it) => { ... },
168///         ast::MethodCallExpr(it) => { ... },
169///         ast::MacroCall(it) => { ... },
170///         _ => None,
171///     }
172/// }
173/// ```
174#[macro_export]
175macro_rules! match_ast {
176    (match $node:ident { $($tt:tt)* }) => { $crate::match_ast!(match ($node) { $($tt)* }) };
177
178    (match ($node:expr) {
179        $( $( $path:ident )::+ ($it:pat) => $res:expr, )*
180        _ => $catch_all:expr $(,)?
181    }) => {{
182        $( if let Some($it) = $($path::)+cast($node.clone()) { $res } else )*
183        { $catch_all }
184    }};
185}
186
187/// This test does not assert anything and instead just shows off the crate's
188/// API.
189#[test]
190fn api_walkthrough() {
191    use ast::SourceFile;
192    use rowan::{Direction, NodeOrToken, SyntaxText, TextRange, WalkEvent};
193    use std::fmt::Write;
194
195    let source_code = "
196        create function foo(p int8)
197        returns int
198        as 'select 1 + 1'
199        language sql;
200    ";
201    // `SourceFile` is the main entry point.
202    //
203    // The `parse` method returns a `Parse` -- a pair of syntax tree and a list
204    // of errors. That is, syntax tree is constructed even in presence of errors.
205    let parse = SourceFile::parse(source_code);
206    assert!(parse.errors().is_empty());
207
208    // The `tree` method returns an owned syntax node of type `SourceFile`.
209    // Owned nodes are cheap: inside, they are `Rc` handles to the underling data.
210    let file: SourceFile = parse.tree();
211
212    // `SourceFile` is the root of the syntax tree. We can iterate file's items.
213    // Let's fetch the `foo` function.
214    let mut func = None;
215    for stmt in file.stmts() {
216        match stmt {
217            ast::Stmt::CreateFunction(f) => func = Some(f),
218            _ => unreachable!(),
219        }
220    }
221    let func: ast::CreateFunction = func.unwrap();
222
223    // Each AST node has a bunch of getters for children. All getters return
224    // `Option`s though, to account for incomplete code. Some getters are common
225    // for several kinds of node. In this case, a trait like `ast::NameOwner`
226    // usually exists. By convention, all ast types should be used with `ast::`
227    // qualifier.
228    let path: Option<ast::Path> = func.name().and_then(|name| name.path());
229    let name: ast::PathSegment = path.unwrap().segment().unwrap();
230    assert_eq!(name.text(), "foo");
231
232    // return
233    let ret_type: Option<ast::RetType> = func.ret_type();
234    let r_ty = &ret_type.unwrap().ty().unwrap();
235    let type_: &ast::PathType = match &r_ty {
236        ast::Type::PathType(r) => r,
237        _ => unreachable!(),
238    };
239    let type_path: ast::PathRef = type_.path_ref().unwrap();
240    assert_eq!(type_path.syntax().to_string(), "int");
241
242    // params
243    let param_list: ast::ParamList = func.param_list().unwrap();
244    let param: ast::Param = param_list.params().next().unwrap();
245
246    let param_name: ast::ParamName = param.name().unwrap();
247    assert_eq!(param_name.syntax().to_string(), "p");
248
249    let param_ty: ast::Type = param.ty().unwrap();
250    assert_eq!(param_ty.syntax().to_string(), "int8");
251
252    let func_option_list: ast::FuncOptionList = func.option_list().unwrap();
253
254    // Enums are used to group related ast nodes together, and can be used for
255    // matching. However, because there are no public fields, it's possible to
256    // match only the top level enum: that is the price we pay for increased API
257    // flexibility
258    let func_option = func_option_list.options().next().unwrap();
259    let option: &ast::AsFuncOption = match &func_option {
260        ast::FuncOption::AsFuncOption(o) => o,
261        _ => unreachable!(),
262    };
263    let as_definition: ast::AsDefinition = match option.as_func_target().unwrap() {
264        ast::AsFuncTarget::AsDefinition(d) => d,
265        _ => unreachable!(),
266    };
267    let definition: ast::Literal = as_definition.literal().unwrap();
268    assert_eq!(definition.syntax().to_string(), "'select 1 + 1'");
269
270    // Besides the "typed" AST API, there's an untyped CST one as well.
271    // To switch from AST to CST, call `.syntax()` method:
272    let func_option_syntax = func_option.syntax();
273
274    // Note how `func_option_syntax` and `option` are in fact the same node underneath:
275    assert!(func_option_syntax == option.syntax());
276
277    // To go from CST to AST, `AstNode::cast` function is used:
278    let _expr: ast::FuncOption = match ast::FuncOption::cast(func_option_syntax.clone()) {
279        Some(e) => e,
280        None => unreachable!(),
281    };
282
283    // The two properties each syntax node has is a `SyntaxKind`:
284    assert_eq!(func_option_syntax.kind(), SyntaxKind::AS_FUNC_OPTION);
285
286    // And text range:
287    assert_eq!(
288        func_option_syntax.text_range(),
289        TextRange::new(65.into(), 82.into())
290    );
291
292    // You can get node's text as a `SyntaxText` object, which will traverse the
293    // tree collecting token's text:
294    let text: SyntaxText = func_option_syntax.text();
295    assert_eq!(text.to_string(), "as 'select 1 + 1'");
296
297    // There's a bunch of traversal methods on `SyntaxNode`:
298    assert_eq!(
299        func_option_syntax.parent().as_ref(),
300        Some(func_option_list.syntax())
301    );
302    assert_eq!(
303        param_list
304            .syntax()
305            .first_child_or_token()
306            .map(|it| it.kind()),
307        Some(SyntaxKind::L_PAREN)
308    );
309    assert_eq!(
310        func_option_syntax
311            .next_sibling_or_token()
312            .map(|it| it.kind()),
313        Some(SyntaxKind::WHITESPACE)
314    );
315
316    // As well as some iterator helpers:
317    let f = func_option_syntax
318        .ancestors()
319        .find_map(ast::CreateFunction::cast);
320    assert_eq!(f, Some(func));
321    assert!(
322        param
323            .syntax()
324            .siblings_with_tokens(Direction::Next)
325            .any(|it| it.kind() == SyntaxKind::R_PAREN)
326    );
327    assert_eq!(
328        func_option_syntax.descendants_with_tokens().count(),
329        6, // 1 the node itself: `as 'select 1 + 1'`
330           // 2 tokens: `as`, ` `
331           // 2 child nodes: `AsDefinition`, `Literal`
332           // 1 token: `'select 1 + 1'`
333    );
334
335    // There's also a `preorder` method with a more fine-grained iteration control:
336    let mut buf = String::new();
337    let mut indent = 0;
338    for event in func_option_syntax.preorder_with_tokens() {
339        match event {
340            WalkEvent::Enter(node) => {
341                let text = match &node {
342                    NodeOrToken::Node(it) => it.text().to_string(),
343                    NodeOrToken::Token(it) => it.text().to_owned(),
344                };
345                buf.write_fmt(format_args!(
346                    "{:indent$}{:?} {:?}\n",
347                    " ",
348                    text,
349                    node.kind(),
350                    indent = indent
351                ))
352                .unwrap();
353                indent += 2;
354            }
355            WalkEvent::Leave(_) => indent -= 2,
356        }
357    }
358    assert_eq!(indent, 0);
359    assert_eq!(
360        buf.trim(),
361        r#"
362"as 'select 1 + 1'" AS_FUNC_OPTION
363  "as" AS_KW
364  " " WHITESPACE
365  "'select 1 + 1'" AS_DEFINITION
366    "'select 1 + 1'" LITERAL
367      "'select 1 + 1'" STRING
368    "#
369        .trim()
370    );
371
372    // To recursively process the tree, there are three approaches:
373    // 1. explicitly call getter methods on AST nodes.
374    // 2. use descendants and `AstNode::cast`.
375    // 3. use descendants and `match_ast!`.
376    //
377    // Here's how the first one looks like:
378    let exprs_cast: Vec<String> = file
379        .syntax()
380        .descendants()
381        .filter_map(ast::FuncOption::cast)
382        .map(|expr| expr.syntax().text().to_string())
383        .collect();
384
385    // An alternative is to use a macro.
386    let mut exprs_visit = Vec::new();
387    for node in file.syntax().descendants() {
388        match_ast! {
389            match node {
390                ast::FuncOption(it) => {
391                    let res = it.syntax().text().to_string();
392                    exprs_visit.push(res);
393                },
394                _ => (),
395            }
396        }
397    }
398    assert_eq!(exprs_cast, exprs_visit);
399}
400
401#[test]
402fn create_table() {
403    use insta::assert_debug_snapshot;
404
405    let source_code = "
406        create table users (
407            id int8 primary key,
408            name varchar(255) not null,
409            email text,
410            created_at timestamp default now()
411        );
412        
413        create table posts (
414            id serial primary key,
415            title varchar(500),
416            content text,
417            user_id int8 references users(id)
418        );
419    ";
420
421    let parse = SourceFile::parse(source_code);
422    assert!(parse.errors().is_empty());
423    let file: SourceFile = parse.tree();
424
425    let mut tables: Vec<(String, Vec<(String, String)>)> = vec![];
426
427    for stmt in file.stmts() {
428        if let ast::Stmt::CreateTable(create_table) = stmt {
429            let table_name = create_table
430                .table_name()
431                .and_then(|table| table.path())
432                .unwrap()
433                .syntax()
434                .to_string();
435            let mut columns = vec![];
436            for arg in create_table.table_arg_list().unwrap().args() {
437                match arg {
438                    ast::TableArg::Column(column) => {
439                        let column_name = column.name().unwrap();
440                        let column_type = column.ty().unwrap();
441                        columns.push((
442                            column_name.syntax().to_string(),
443                            column_type.syntax().to_string(),
444                        ));
445                    }
446                    ast::TableArg::TableConstraint(_) | ast::TableArg::LikeClause(_) => (),
447                }
448            }
449            tables.push((table_name, columns));
450        }
451    }
452
453    assert_debug_snapshot!(tables, @r#"
454    [
455        (
456            "users",
457            [
458                (
459                    "id",
460                    "int8",
461                ),
462                (
463                    "name",
464                    "varchar(255)",
465                ),
466                (
467                    "email",
468                    "text",
469                ),
470                (
471                    "created_at",
472                    "timestamp",
473                ),
474            ],
475        ),
476        (
477            "posts",
478            [
479                (
480                    "id",
481                    "serial",
482                ),
483                (
484                    "title",
485                    "varchar(500)",
486                ),
487                (
488                    "content",
489                    "text",
490                ),
491                (
492                    "user_id",
493                    "int8",
494                ),
495            ],
496        ),
497    ]
498    "#)
499}
500
501#[test]
502fn bin_expr() {
503    use insta::assert_debug_snapshot;
504
505    let source_code = "select 1 is not null;";
506    let parse = SourceFile::parse(source_code);
507    assert!(parse.errors().is_empty());
508    let file: SourceFile = parse.tree();
509
510    let ast::Stmt::Select(select) = file.stmts().next().unwrap() else {
511        unreachable!()
512    };
513
514    let target_list = select.select_clause().unwrap().target_list().unwrap();
515    let target = target_list.targets().next().unwrap();
516    let ast::Expr::BinExpr(bin_expr) = target.expr().unwrap() else {
517        unreachable!()
518    };
519
520    let lhs = bin_expr.lhs();
521    let op = bin_expr.op();
522    let rhs = bin_expr.rhs();
523
524    assert_debug_snapshot!(lhs, @r#"
525    Some(
526        Literal(
527            Literal {
528                syntax: LITERAL@7..8
529                  INT_NUMBER@7..8 "1"
530                ,
531            },
532        ),
533    )
534    "#);
535    assert_debug_snapshot!(op, @r#"
536    Some(
537        IsNot(
538            IsNot {
539                syntax: IS_NOT@9..15
540                  IS_KW@9..11 "is"
541                  WHITESPACE@11..12 " "
542                  NOT_KW@12..15 "not"
543                ,
544            },
545        ),
546    )
547    "#);
548    assert_debug_snapshot!(rhs, @r#"
549    Some(
550        Literal(
551            Literal {
552                syntax: LITERAL@16..20
553                  NULL_KW@16..20 "null"
554                ,
555            },
556        ),
557    )
558    "#);
559}