mod expr;
mod statement;
pub use expr::{lower_expr, lower_type_expr};
pub(crate) use statement::lower_statement;
pub use statement::lower_statements;
use crate::ast::{Expr, PartialNode, Script, Spanned, Statement};
use crate::parse::ParsedSource;
use crate::span::ByteRange;
use tree_sitter::Node;
pub fn lower_first_expr(parsed: &ParsedSource, cst_kind: &str) -> Option<Spanned<Expr>> {
let node = find_first(parsed.tree().root_node(), cst_kind)?;
Some(lower_expr(node, parsed.text()))
}
pub fn lower_first_statement(parsed: &ParsedSource, cst_kind: &str) -> Option<Spanned<Statement>> {
let node = find_first(parsed.tree().root_node(), cst_kind)?;
Some(lower_statement(node, parsed.text()))
}
fn find_first<'tree>(node: Node<'tree>, cst_kind: &str) -> Option<Node<'tree>> {
if node.kind() == cst_kind {
return Some(node);
}
let mut cursor = node.walk();
let children: Vec<Node<'tree>> = node.children(&mut cursor).collect();
children
.into_iter()
.find_map(|child| find_first(child, cst_kind))
}
pub fn lower(parsed: &ParsedSource) -> Script {
let root = parsed.tree().root_node();
let mut statements = Vec::new();
let mut cursor = root.walk();
for child in root.children(&mut cursor) {
if !child.is_named() || matches!(child.kind(), "Comment" | "BlockComment") {
continue;
}
statements.push(statement::lower_statement(child, parsed.text()));
}
Script { statements }
}
pub(crate) fn node_range(node: Node<'_>) -> ByteRange {
let start = u32::try_from(node.start_byte()).unwrap_or(u32::MAX);
let end = u32::try_from(node.end_byte()).unwrap_or(u32::MAX);
ByteRange::new(start, end).expect("tree-sitter nodes have ordered byte ranges")
}
pub(crate) fn partial(node: Node<'_>) -> PartialNode {
let cst_kind = if node.is_missing() {
format!("MISSING {}", node.kind())
} else {
node.kind().to_string()
};
PartialNode {
span: node_range(node),
cst_kind,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Statement;
use crate::parse::parse_source;
use crate::source::SourceId;
#[test]
fn script_lowering_preserves_statement_order() {
let parsed = parse_source(
SourceId::new("lower:script"),
"DEFINE TABLE person;\nSELECT * FROM person;\nRETURN 1;",
)
.expect("parses");
let script = lower(&parsed);
assert!(matches!(&script.statements[0].node, Statement::Define(_)));
assert!(matches!(&script.statements[1].node, Statement::Select(_)));
assert!(matches!(&script.statements[2].node, Statement::Return(_)));
let spans: Vec<_> = script.statements.iter().map(|s| s.span).collect();
assert!(spans.windows(2).all(|w| w[0].end() <= w[1].start()));
}
}