use rowan::TextRange;
use rowan::ast::AstNode;
use smol_str::SmolStr;
use crate::dcf::syntax::{DcfLanguage, SyntaxKind, SyntaxNode, SyntaxToken};
macro_rules! ast_node {
($name:ident, $kind:expr) => {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name(SyntaxNode);
impl AstNode for $name {
type Language = DcfLanguage;
fn can_cast(kind: SyntaxKind) -> bool {
kind == $kind
}
fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| Self(syntax))
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
};
}
ast_node!(Document, SyntaxKind::ROOT);
ast_node!(Record, SyntaxKind::RECORD);
ast_node!(Field, SyntaxKind::FIELD);
ast_node!(ValueLine, SyntaxKind::VALUE_LINE);
ast_node!(CommentLine, SyntaxKind::COMMENT_LINE);
ast_node!(MalformedLine, SyntaxKind::MALFORMED_LINE);
fn token(node: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken> {
node.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|tok| tok.kind() == kind)
}
impl Document {
pub fn records(&self) -> impl Iterator<Item = Record> + use<> {
self.0.children().filter_map(Record::cast)
}
pub fn first_record(&self) -> Option<Record> {
self.records().next()
}
pub fn fields(&self) -> impl Iterator<Item = Field> + use<> {
self.records().flat_map(|record| record.fields())
}
pub fn field(&self, name: &str) -> Option<Field> {
self.fields().find(|field| field.name() == name)
}
}
impl Record {
pub fn fields(&self) -> impl Iterator<Item = Field> + use<> {
self.0.children().filter_map(Field::cast)
}
pub fn field(&self, name: &str) -> Option<Field> {
self.fields().find(|field| field.name() == name)
}
}
impl Field {
pub fn name(&self) -> SmolStr {
self.name_token()
.map(|tok| SmolStr::new(tok.text()))
.unwrap_or_default()
}
pub fn name_token(&self) -> Option<SyntaxToken> {
token(&self.0, SyntaxKind::FIELD_NAME)
}
pub fn name_range(&self) -> TextRange {
match self.name_token() {
Some(tok) => tok.text_range(),
None => {
let start = self.0.text_range().start();
TextRange::empty(start)
}
}
}
pub fn value_range(&self) -> TextRange {
let mut lines = self.value_lines();
let Some(first) = lines.next() else {
return TextRange::empty(self.0.text_range().end());
};
let last = lines.last().unwrap_or_else(|| first.clone());
TextRange::new(
first.syntax().text_range().start(),
last.syntax().text_range().end(),
)
}
pub fn value_lines(&self) -> impl Iterator<Item = ValueLine> + use<> {
self.0.children().filter_map(ValueLine::cast)
}
pub fn comment_lines(&self) -> impl Iterator<Item = CommentLine> + use<> {
self.0.children().filter_map(CommentLine::cast)
}
pub fn folded_value(&self) -> String {
self.value_lines()
.map(|line| line.trimmed_text())
.collect::<Vec<_>>()
.join("\n")
}
pub fn raw_value_text(&self) -> String {
let mut out = String::new();
let mut past_colon = false;
for tok in self
.0
.descendants_with_tokens()
.filter_map(|el| el.into_token())
{
if past_colon {
out.push_str(tok.text());
} else if tok.kind() == SyntaxKind::COLON {
past_colon = true;
}
}
out
}
}
impl ValueLine {
pub fn indent(&self) -> Option<SyntaxToken> {
self.0
.children_with_tokens()
.filter_map(|el| el.into_token())
.next()
.filter(|tok| tok.kind() == SyntaxKind::WHITESPACE)
}
pub fn content(&self) -> Option<SyntaxToken> {
token(&self.0, SyntaxKind::VALUE_TEXT)
}
pub fn trimmed_text(&self) -> String {
self.content()
.map(|tok| tok.text().trim().to_string())
.unwrap_or_default()
}
pub fn content_range(&self) -> TextRange {
match self.content() {
Some(tok) => tok.text_range(),
None => TextRange::empty(self.0.text_range().start()),
}
}
}
impl CommentLine {
pub fn text(&self) -> Option<SyntaxToken> {
token(&self.0, SyntaxKind::COMMENT)
}
}
impl MalformedLine {
pub fn text(&self) -> Option<SyntaxToken> {
token(&self.0, SyntaxKind::TEXT)
}
}