surrealguard_syntax/lower/
mod.rs1mod expr;
11mod statement;
12
13pub use expr::{lower_expr, lower_type_expr};
14pub(crate) use statement::lower_statement;
15pub use statement::lower_statements;
16
17use crate::ast::{Expr, PartialNode, Script, Spanned, Statement};
18use crate::parse::ParsedSource;
19use crate::span::ByteRange;
20use tree_sitter::Node;
21
22pub fn lower_first_expr(parsed: &ParsedSource, cst_kind: &str) -> Option<Spanned<Expr>> {
28 let node = find_first(parsed.tree().root_node(), cst_kind)?;
29 Some(lower_expr(node, parsed.text()))
30}
31
32pub fn lower_first_statement(parsed: &ParsedSource, cst_kind: &str) -> Option<Spanned<Statement>> {
34 let node = find_first(parsed.tree().root_node(), cst_kind)?;
35 Some(lower_statement(node, parsed.text()))
36}
37
38fn find_first<'tree>(node: Node<'tree>, cst_kind: &str) -> Option<Node<'tree>> {
39 if node.kind() == cst_kind {
40 return Some(node);
41 }
42 let mut cursor = node.walk();
43 let children: Vec<Node<'tree>> = node.children(&mut cursor).collect();
44 children
45 .into_iter()
46 .find_map(|child| find_first(child, cst_kind))
47}
48
49pub fn lower(parsed: &ParsedSource) -> Script {
51 let root = parsed.tree().root_node();
52 let mut statements = Vec::new();
53
54 let mut cursor = root.walk();
55 for child in root.children(&mut cursor) {
56 if !child.is_named() || matches!(child.kind(), "Comment" | "BlockComment") {
57 continue;
58 }
59 statements.push(statement::lower_statement(child, parsed.text()));
60 }
61
62 Script { statements }
63}
64
65pub(crate) fn node_range(node: Node<'_>) -> ByteRange {
66 let start = u32::try_from(node.start_byte()).unwrap_or(u32::MAX);
67 let end = u32::try_from(node.end_byte()).unwrap_or(u32::MAX);
68 ByteRange::new(start, end).expect("tree-sitter nodes have ordered byte ranges")
69}
70
71pub(crate) fn partial(node: Node<'_>) -> PartialNode {
72 let cst_kind = if node.is_missing() {
73 format!("MISSING {}", node.kind())
74 } else {
75 node.kind().to_string()
76 };
77 PartialNode {
78 span: node_range(node),
79 cst_kind,
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::ast::Statement;
87 use crate::parse::parse_source;
88 use crate::source::SourceId;
89
90 #[test]
91 fn script_lowering_preserves_statement_order() {
92 let parsed = parse_source(
93 SourceId::new("lower:script"),
94 "DEFINE TABLE person;\nSELECT * FROM person;\nRETURN 1;",
95 )
96 .expect("parses");
97
98 let script = lower(&parsed);
99
100 assert!(matches!(&script.statements[0].node, Statement::Define(_)));
101 assert!(matches!(&script.statements[1].node, Statement::Select(_)));
102 assert!(matches!(&script.statements[2].node, Statement::Return(_)));
103 let spans: Vec<_> = script.statements.iter().map(|s| s.span).collect();
106 assert!(spans.windows(2).all(|w| w[0].end() <= w[1].start()));
107 }
108}