1pub 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#[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 pub fn to_syntax(self) -> Parse<SyntaxNode> {
107 Parse {
108 green: self.green,
109 errors: self.errors,
110 _ty: PhantomData,
111 }
112 }
113
114 pub fn tree(&self) -> T {
121 T::cast(self.syntax_node()).unwrap()
122 }
123
124 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
147pub 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#[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#[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 let parse = SourceFile::parse(source_code);
206 assert!(parse.errors().is_empty());
207
208 let file: SourceFile = parse.tree();
211
212 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 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 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 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 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 let func_option_syntax = func_option.syntax();
273
274 assert!(func_option_syntax == option.syntax());
276
277 let _expr: ast::FuncOption = match ast::FuncOption::cast(func_option_syntax.clone()) {
279 Some(e) => e,
280 None => unreachable!(),
281 };
282
283 assert_eq!(func_option_syntax.kind(), SyntaxKind::AS_FUNC_OPTION);
285
286 assert_eq!(
288 func_option_syntax.text_range(),
289 TextRange::new(65.into(), 82.into())
290 );
291
292 let text: SyntaxText = func_option_syntax.text();
295 assert_eq!(text.to_string(), "as 'select 1 + 1'");
296
297 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 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, );
334
335 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 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 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}