#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
#[cfg(feature = "in-rust-tree")]
extern crate rustc_driver as _;
mod parsing;
mod ptr;
mod syntax_error;
mod syntax_node;
#[cfg(test)]
mod tests;
mod token_text;
mod validation;
pub mod algo;
pub mod ast;
#[doc(hidden)]
pub mod fuzz;
pub mod hacks;
pub mod syntax_editor;
pub mod ted;
pub mod utils;
use std::{marker::PhantomData, ops::Range};
use stdx::format_to;
use triomphe::Arc;
pub use crate::{
ast::{AstNode, AstToken},
ptr::{AstPtr, SyntaxNodePtr},
syntax_error::SyntaxError,
syntax_node::{
PreorderWithTokens, RustLanguage, SyntaxElement, SyntaxElementChildren, SyntaxNode,
SyntaxNodeChildren, SyntaxToken, SyntaxTreeBuilder,
},
token_text::TokenText,
};
pub use parser::{Edition, SyntaxKind, T};
pub use rowan::{
Direction, GreenNode, NodeOrToken, SyntaxText, TextRange, TextSize, TokenAtOffset, WalkEvent,
api::Preorder,
};
pub use rustc_literal_escaper as unescape;
pub use smol_str::{SmolStr, SmolStrBuilder, ToSmolStr, format_smolstr};
#[derive(Debug, PartialEq, Eq)]
pub struct Parse<T> {
green: Option<GreenNode>,
errors: Option<Arc<[SyntaxError]>>,
_ty: PhantomData<fn() -> T>,
}
impl<T> Clone for Parse<T> {
fn clone(&self) -> Parse<T> {
Parse { green: self.green.clone(), errors: self.errors.clone(), _ty: PhantomData }
}
}
impl<T> Parse<T> {
fn new(green: GreenNode, errors: Vec<SyntaxError>) -> Parse<T> {
Parse {
green: Some(green),
errors: if errors.is_empty() { None } else { Some(errors.into()) },
_ty: PhantomData,
}
}
pub fn syntax_node(&self) -> SyntaxNode {
SyntaxNode::new_root(self.green.as_ref().unwrap().clone())
}
pub fn errors(&self) -> Vec<SyntaxError> {
let mut errors = if let Some(e) = self.errors.as_deref() { e.to_vec() } else { vec![] };
validation::validate(&self.syntax_node(), &mut errors);
errors
}
}
impl<T: AstNode> Parse<T> {
pub fn to_syntax(mut self) -> Parse<SyntaxNode> {
let green = self.green.take();
let errors = self.errors.take();
Parse { green, errors, _ty: PhantomData }
}
pub fn tree(&self) -> T {
T::cast(self.syntax_node()).unwrap()
}
pub fn ok(self) -> Result<T, Vec<SyntaxError>> {
match self.errors() {
errors if !errors.is_empty() => Err(errors),
_ => Ok(self.tree()),
}
}
}
impl Parse<SyntaxNode> {
pub fn cast<N: AstNode>(mut self) -> Option<Parse<N>> {
if N::cast(self.syntax_node()).is_some() {
Some(Parse { green: self.green.take(), errors: self.errors.take(), _ty: PhantomData })
} else {
None
}
}
}
impl Parse<SourceFile> {
pub fn debug_dump(&self) -> String {
let mut buf = format!("{:#?}", self.tree().syntax());
for err in self.errors() {
format_to!(buf, "error {:?}: {}\n", err.range(), err);
}
buf
}
pub fn reparse(&self, delete: TextRange, insert: &str, edition: Edition) -> Parse<SourceFile> {
self.incremental_reparse(delete, insert, edition)
.unwrap_or_else(|| self.full_reparse(delete, insert, edition))
}
fn incremental_reparse(
&self,
delete: TextRange,
insert: &str,
edition: Edition,
) -> Option<Parse<SourceFile>> {
parsing::incremental_reparse(
self.tree().syntax(),
delete,
insert,
self.errors.as_deref().unwrap_or_default().iter().cloned(),
edition,
)
.map(|(green_node, errors, _reparsed_range)| Parse {
green: Some(green_node),
errors: if errors.is_empty() { None } else { Some(errors.into()) },
_ty: PhantomData,
})
}
fn full_reparse(&self, delete: TextRange, insert: &str, edition: Edition) -> Parse<SourceFile> {
let mut text = self.tree().syntax().text().to_string();
text.replace_range(Range::<usize>::from(delete), insert);
SourceFile::parse(&text, edition)
}
}
impl ast::Expr {
pub fn parse(text: &str, edition: Edition) -> Parse<ast::Expr> {
let _p = tracing::info_span!("Expr::parse").entered();
let (green, errors) = parsing::parse_text_at(text, parser::TopEntryPoint::Expr, edition);
let root = SyntaxNode::new_root(green.clone());
assert!(
ast::Expr::can_cast(root.kind()) || root.kind() == SyntaxKind::ERROR,
"{:?} isn't an expression",
root.kind()
);
Parse::new(green, errors)
}
}
#[cfg(not(no_salsa_async_drops))]
impl<T> Drop for Parse<T> {
fn drop(&mut self) {
let Some(green) = self.green.take() else {
return;
};
static PARSE_DROP_THREAD: std::sync::OnceLock<std::sync::mpsc::Sender<GreenNode>> =
std::sync::OnceLock::new();
PARSE_DROP_THREAD
.get_or_init(|| {
let (sender, receiver) = std::sync::mpsc::channel::<GreenNode>();
std::thread::Builder::new()
.name("ParseNodeDropper".to_owned())
.spawn(move || {
loop {
_ = receiver.recv();
while receiver.try_recv().is_ok() {}
std::thread::sleep(std::time::Duration::from_millis(100));
}
})
.unwrap();
sender
})
.send(green)
.unwrap();
}
}
pub use crate::ast::SourceFile;
impl SourceFile {
pub fn parse(text: &str, edition: Edition) -> Parse<SourceFile> {
let _p = tracing::info_span!("SourceFile::parse").entered();
let (green, errors) = parsing::parse_text(text, edition);
let root = SyntaxNode::new_root(green.clone());
assert_eq!(root.kind(), SyntaxKind::SOURCE_FILE);
Parse::new(green, errors)
}
}
#[macro_export]
macro_rules! match_ast {
(match $node:ident { $($tt:tt)* }) => { $crate::match_ast!(match ($node) { $($tt)* }) };
(match ($node:expr) {
$( $( $path:ident )::+ ($it:pat) => $res:expr, )*
_ => $catch_all:expr $(,)?
}) => {{
$( if let Some($it) = $($path::)+cast($node.clone()) { $res } else )*
{ $catch_all }
}};
}
#[test]
fn api_walkthrough() {
use ast::{HasModuleItem, HasName};
let source_code = "
fn foo() {
1 + 1
}
";
let parse = SourceFile::parse(source_code, parser::Edition::CURRENT);
assert!(parse.errors().is_empty());
let file: SourceFile = parse.tree();
let mut func = None;
for item in file.items() {
match item {
ast::Item::Fn(f) => func = Some(f),
_ => unreachable!(),
}
}
let func: ast::Fn = func.unwrap();
let name: Option<ast::Name> = func.name();
let name = name.unwrap();
assert_eq!(name.text(), "foo");
let body: ast::BlockExpr = func.body().unwrap();
let stmt_list: ast::StmtList = body.stmt_list().unwrap();
let expr: ast::Expr = stmt_list.tail_expr().unwrap();
let bin_expr: &ast::BinExpr = match &expr {
ast::Expr::BinExpr(e) => e,
_ => unreachable!(),
};
let expr_syntax: &SyntaxNode = expr.syntax();
assert!(expr_syntax == bin_expr.syntax());
let _expr: ast::Expr = match ast::Expr::cast(expr_syntax.clone()) {
Some(e) => e,
None => unreachable!(),
};
assert_eq!(expr_syntax.kind(), SyntaxKind::BIN_EXPR);
assert_eq!(expr_syntax.text_range(), TextRange::new(32.into(), 37.into()));
let text: SyntaxText = expr_syntax.text();
assert_eq!(text.to_string(), "1 + 1");
assert_eq!(expr_syntax.parent().as_ref(), Some(stmt_list.syntax()));
assert_eq!(stmt_list.syntax().first_child_or_token().map(|it| it.kind()), Some(T!['{']));
assert_eq!(
expr_syntax.next_sibling_or_token().map(|it| it.kind()),
Some(SyntaxKind::WHITESPACE)
);
let f = expr_syntax.ancestors().find_map(ast::Fn::cast);
assert_eq!(f, Some(func));
assert!(expr_syntax.siblings_with_tokens(Direction::Next).any(|it| it.kind() == T!['}']));
assert_eq!(
expr_syntax.descendants_with_tokens().count(),
8, );
let mut buf = String::new();
let mut indent = 0;
for event in expr_syntax.preorder_with_tokens() {
match event {
WalkEvent::Enter(node) => {
let text = match &node {
NodeOrToken::Node(it) => it.text().to_string(),
NodeOrToken::Token(it) => it.text().to_owned(),
};
format_to!(buf, "{:indent$}{:?} {:?}\n", " ", text, node.kind(), indent = indent);
indent += 2;
}
WalkEvent::Leave(_) => indent -= 2,
}
}
assert_eq!(indent, 0);
assert_eq!(
buf.trim(),
r#"
"1 + 1" BIN_EXPR
"1" LITERAL
"1" INT_NUMBER
" " WHITESPACE
"+" PLUS
" " WHITESPACE
"1" LITERAL
"1" INT_NUMBER
"#
.trim()
);
let exprs_cast: Vec<String> = file
.syntax()
.descendants()
.filter_map(ast::Expr::cast)
.map(|expr| expr.syntax().text().to_string())
.collect();
let mut exprs_visit = Vec::new();
for node in file.syntax().descendants() {
match_ast! {
match node {
ast::Expr(it) => {
let res = it.syntax().text().to_string();
exprs_visit.push(res);
},
_ => (),
}
}
}
assert_eq!(exprs_cast, exprs_visit);
}