1use rowan::TextRange;
12use rowan::ast::AstNode;
13use smol_str::SmolStr;
14
15use crate::dcf::syntax::{DcfLanguage, SyntaxKind, SyntaxNode, SyntaxToken};
16
17macro_rules! ast_node {
18 ($name:ident, $kind:expr) => {
19 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
20 pub struct $name(SyntaxNode);
21
22 impl AstNode for $name {
23 type Language = DcfLanguage;
24
25 fn can_cast(kind: SyntaxKind) -> bool {
26 kind == $kind
27 }
28
29 fn cast(syntax: SyntaxNode) -> Option<Self> {
30 Self::can_cast(syntax.kind()).then(|| Self(syntax))
31 }
32
33 fn syntax(&self) -> &SyntaxNode {
34 &self.0
35 }
36 }
37 };
38}
39
40ast_node!(Document, SyntaxKind::ROOT);
41ast_node!(Record, SyntaxKind::RECORD);
42ast_node!(Field, SyntaxKind::FIELD);
43ast_node!(ValueLine, SyntaxKind::VALUE_LINE);
44ast_node!(CommentLine, SyntaxKind::COMMENT_LINE);
45ast_node!(MalformedLine, SyntaxKind::MALFORMED_LINE);
46
47fn token(node: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken> {
49 node.children_with_tokens()
50 .filter_map(|el| el.into_token())
51 .find(|tok| tok.kind() == kind)
52}
53
54impl Document {
55 pub fn records(&self) -> impl Iterator<Item = Record> + use<> {
57 self.0.children().filter_map(Record::cast)
58 }
59
60 pub fn first_record(&self) -> Option<Record> {
62 self.records().next()
63 }
64
65 pub fn fields(&self) -> impl Iterator<Item = Field> + use<> {
72 self.records().flat_map(|record| record.fields())
73 }
74
75 pub fn field(&self, name: &str) -> Option<Field> {
79 self.fields().filter(|field| field.name() == name).last()
80 }
81}
82
83impl Record {
84 pub fn fields(&self) -> impl Iterator<Item = Field> + use<> {
86 self.0.children().filter_map(Field::cast)
87 }
88
89 pub fn field(&self, name: &str) -> Option<Field> {
91 self.fields().filter(|field| field.name() == name).last()
92 }
93}
94
95impl Field {
96 pub fn name(&self) -> SmolStr {
100 self.name_token()
101 .map(|tok| SmolStr::new(tok.text()))
102 .unwrap_or_default()
103 }
104
105 pub fn name_token(&self) -> Option<SyntaxToken> {
107 token(&self.0, SyntaxKind::FIELD_NAME)
108 }
109
110 pub fn name_range(&self) -> TextRange {
113 match self.name_token() {
114 Some(tok) => tok.text_range(),
115 None => {
116 let start = self.0.text_range().start();
117 TextRange::empty(start)
118 }
119 }
120 }
121
122 pub fn value_range(&self) -> TextRange {
125 let mut lines = self.value_lines();
126 let Some(first) = lines.next() else {
127 return TextRange::empty(self.0.text_range().end());
128 };
129 let last = lines.last().unwrap_or_else(|| first.clone());
130 TextRange::new(
131 first.syntax().text_range().start(),
132 last.syntax().text_range().end(),
133 )
134 }
135
136 pub fn value_lines(&self) -> impl Iterator<Item = ValueLine> + use<> {
139 self.0.children().filter_map(ValueLine::cast)
140 }
141
142 pub fn comment_lines(&self) -> impl Iterator<Item = CommentLine> + use<> {
145 self.0.children().filter_map(CommentLine::cast)
146 }
147
148 pub fn folded_value(&self) -> String {
152 self.value_lines()
153 .map(|line| line.trimmed_text())
154 .filter(|line| !line.is_empty())
155 .collect::<Vec<_>>()
156 .join("\n")
157 }
158
159 pub fn raw_value_text(&self) -> String {
163 let mut out = String::new();
164 let mut past_colon = false;
165 for tok in self
166 .0
167 .descendants_with_tokens()
168 .filter_map(|el| el.into_token())
169 {
170 if past_colon {
171 out.push_str(tok.text());
172 } else if tok.kind() == SyntaxKind::COLON {
173 past_colon = true;
174 }
175 }
176 out
177 }
178}
179
180impl ValueLine {
181 pub fn indent(&self) -> Option<SyntaxToken> {
184 self.0
185 .children_with_tokens()
186 .filter_map(|el| el.into_token())
187 .next()
188 .filter(|tok| tok.kind() == SyntaxKind::WHITESPACE)
189 }
190
191 pub fn content(&self) -> Option<SyntaxToken> {
193 token(&self.0, SyntaxKind::VALUE_TEXT)
194 }
195
196 pub fn trimmed_text(&self) -> String {
198 self.content()
199 .map(|tok| tok.text().trim().to_string())
200 .unwrap_or_default()
201 }
202
203 pub fn content_range(&self) -> TextRange {
206 match self.content() {
207 Some(tok) => tok.text_range(),
208 None => TextRange::empty(self.0.text_range().start()),
209 }
210 }
211}
212
213impl CommentLine {
214 pub fn text(&self) -> Option<SyntaxToken> {
216 token(&self.0, SyntaxKind::COMMENT)
217 }
218}
219
220impl MalformedLine {
221 pub fn text(&self) -> Option<SyntaxToken> {
223 token(&self.0, SyntaxKind::TEXT)
224 }
225}