1use std::{borrow::Cow, collections::HashMap, rc::Rc};
2
3use crate::{
4 Error, ErrorType, Fragment, FragmentType, Result,
5 builtin::{BUILTIN_STYLES, BUILTIN_TAGS},
6 common::Span,
7 parser::{
8 ansi::Ansi,
9 chunk::{Chunk, ChunkData},
10 tag_parser::tag::{Tag, TagType},
11 word_parser::WORD_PARSER,
12 },
13 splitter::is_whitespace,
14};
15use document::{Document, Node};
16
17pub mod document;
18
19pub struct Resolver<'a> {
20 ansi_only: bool,
21 tables: Vec<Rc<Node<'a>>>,
22 tree: Rc<Document<'a>>,
23}
24
25impl<'a> Resolver<'a> {
26 pub fn new(ansi_only: bool) -> Self {
27 Self {
28 ansi_only,
29 tables: Vec::with_capacity(16),
30 tree: Document::new(),
31 }
32 }
33
34 pub fn resolve(&mut self, chunks: Vec<Result<Chunk<'a>>>) -> Result<Rc<Document<'a>>> {
35 let node = self.tree.root();
36
37 chunks.into_iter().try_fold(node, |mut node, chunk| {
38 let chunk = chunk?;
39 match &chunk.data {
40 ChunkData::Tag(tag) => match tag.r#type {
41 TagType::Open => {
42 node = node.append(chunk);
43 }
44
45 TagType::Close => {
46 {
47 let this = node.chunk().borrow();
48 let open_name = this.tag().unwrap().name();
49 if open_name != tag.name() {
50 return Err(Error::new(
51 ErrorType::InvalidTag,
52 format!("Mismatched tags: <{}>...</{}>", open_name, tag.name()),
53 chunk.span,
54 ));
55 }
56 }
57
58 node.append(chunk);
59 node = node.parent().unwrap();
60 }
61
62 TagType::SelfClose => {
63 node.append(chunk);
64 }
65 },
66 ChunkData::WhiteSpace(_) => {
67 node.append(chunk);
68 }
69 ChunkData::Word(_) => {
70 node.append(chunk);
71 }
72 }
73 Ok(node)
74 })?;
75
76 let node = self.tree.root();
77
78 assert!(node.id() == 0);
80
81 if self.ansi_only {
82 Resolver::optimize_ansi(&node);
83 return Ok(self.tree.clone());
84 }
85
86 {
87 let mut resolved = Vec::with_capacity(128);
88 self.parse_words(&node, &mut resolved)?;
89 for (node, chunks) in resolved {
90 for chunk in chunks {
91 node.insert_before(chunk?);
92 }
93 node.detach(true);
94 }
95 }
96
97 let mut detachables = Vec::with_capacity(128);
98 {
99 let mut bindings: HashMap<String, Tag> = HashMap::new();
100 Resolver::resolve_bindings(&mut bindings, &node, &mut detachables);
101 for node in detachables.drain(..) {
102 node.detach(true);
103 }
104 }
105
106 {
107 Resolver::optimize_ws(&node, &mut detachables);
108 for node in &detachables {
109 node.detach(true);
110 }
111 }
112
113 Resolver::_resolve(&node, "$root");
114 Resolver::optimize_styles(&node);
115 Resolver::optimize_ansi(&node);
116 self.set_tables();
117
118 Ok(self.tree.clone())
119 }
120
121 fn parse_words(
122 &mut self,
123 node: &Rc<Node<'a>>,
124 resolved: &mut Vec<(Rc<Node<'a>>, Vec<Result<Chunk<'a>>>)>,
125 ) -> crate::Result<()> {
126 for child in node.children() {
127 let child_chunk = child.chunk().borrow_mut();
128 if child_chunk.is_word() {
129 let word = child_chunk.word().unwrap();
130 let chs = WORD_PARSER.parse(Fragment {
131 r#type: FragmentType::Word,
132 lexeme: word.clone(),
133 span: child_chunk.span,
134 });
135 resolved.push((child.clone(), chs));
136 } else if child_chunk.is_tag() {
137 let tag = child_chunk.tag().unwrap();
138 if tag.r#type == TagType::Open {
139 let name = tag.name();
140 if matches!(name.as_str(), "pre" | "a" | "script" | "style") {
141 continue;
142 } else if name == "table" {
143 self.tables.push(child.clone());
144 } else if name == "td" {
145 child.insert_before(Chunk {
146 data: ChunkData::WhiteSpace(Cow::Borrowed("")),
147 span: Span::inserted(),
148 });
149 }
150 }
151
152 self.parse_words(&child, resolved)?;
153 continue;
154 }
155 }
156
157 Ok(())
158 }
159
160 fn set_tables(&self) {
161 for table in &self.tables {
162 let indent = table
163 .chunk()
164 .borrow_mut()
165 .tag_mut()
166 .unwrap()
167 .custom()
168 .parse()
169 .unwrap_or(0);
170 let indent = " ".repeat(indent);
171
172 let mut widths = Vec::with_capacity(16);
173 let mut _table: Vec<Vec<(Rc<Node>, usize)>> = Vec::with_capacity(16);
174
175 let mut x = 0;
176 for tr in table.children() {
177 if tr.chunk().borrow().is_ws() {
178 continue;
179 }
180
181 _table.push(Vec::with_capacity(16));
182
183 let mut y = 0;
184 for td in tr.children() {
185 if td.chunk().borrow().is_ws() {
186 td.chunk().borrow_mut().data = ChunkData::WhiteSpace(Cow::Borrowed(" "));
187 continue;
188 }
189 let mut len = 0;
190 td.word_len(&mut len);
191 if let Some(prev_len) = widths.get(y) {
192 if len > *prev_len {
193 widths[y] = len;
194 }
195 } else {
196 widths.push(len);
197 }
198 _table[x].push((td.clone(), len));
199
200 y += 1
201 }
202
203 if let Some(first) = tr.first_child() {
204 first.insert_before(Chunk {
205 data: ChunkData::WhiteSpace(Cow::Owned(indent.clone())),
206 span: Span::inserted(),
207 });
208 }
209
210 x += 1
211 }
212
213 for row in _table {
214 for (i, (col, width)) in row.iter().enumerate() {
215 let lwidth = &widths[i];
216 if lwidth == width {
217 continue;
218 }
219
220 let indent = " ".repeat(lwidth - width);
221 if let Some(last) = col.last_child() {
222 last.insert_after(Chunk {
223 data: ChunkData::WhiteSpace(Cow::Owned(indent.clone())),
224 span: Span::inserted(),
225 });
226 }
227 }
228 }
229 }
230 }
232
233 fn resolve_bindings(
235 bindings: &mut HashMap<String, Tag>,
236 node: &Rc<Node<'a>>,
237 detachables: &mut Vec<Rc<Node<'a>>>,
238 ) {
239 for child in node.children() {
240 let mut child_chunk = child.chunk().borrow_mut();
241 if child_chunk.is_tag() {
242 let tag = child_chunk.tag_mut().unwrap();
243 let name = tag.name().clone();
244
245 if !BUILTIN_TAGS.contains(&name.as_str()) {
246 for ansector in child.ancestors() {
247 if let Some(binding) = bindings.get(&format!("{}/{}", ansector.id(), name))
248 {
249 tag.inherit(binding);
250 break;
251 }
252 }
253 }
254
255 if !tag.class().is_empty() {
256 for class in tag
257 .class()
258 .clone()
259 .split(|x| is_whitespace(x as u8))
260 .filter(|s| !s.is_empty())
261 .rev()
262 {
263 for ansector in child.ancestors() {
264 if let Some(binding) = BUILTIN_STYLES.get(class) {
265 tag.inherit(binding);
266 break;
267 }
268 if let Some(binding) =
269 bindings.get(&format!("{}/{}", ansector.id(), class))
270 {
271 tag.inherit(binding);
272 break;
273 }
274 }
275 }
276 }
277
278 if name == "let" {
279 let tag = tag.clone();
280 let name = tag.custom();
281 if !BUILTIN_TAGS.contains(&name.as_str()) {
282 let id = node.id();
283 bindings.insert(format!("{id}/{name}"), tag);
284 detachables.push(child.clone());
285 }
286 }
287 }
288 Resolver::resolve_bindings(bindings, &child, detachables);
289 }
290 }
291
292 fn optimize_ws(node: &Rc<Node<'a>>, detachables: &mut Vec<Rc<Node<'a>>>) {
294 for child in node.children() {
295 let mut child_chunk = child.chunk().borrow_mut();
296 if child_chunk.is_ws() {
297 if child.id() == 1 {
298 detachables.push(child.clone());
299 } else if child.id() as usize == child.doc().len() - 1
300 && child_chunk.ws().is_some_and(|s| s.contains("\n"))
301 {
302 child_chunk.data = ChunkData::WhiteSpace(Cow::Borrowed("\n"));
303 } else {
304 child_chunk.data = ChunkData::WhiteSpace(Cow::Borrowed(" "));
305 }
306
307 if let Some(first) = child.next_sibling().and_then(|next| next.first_child()) {
308 if first.chunk().borrow().is_ws() {
309 detachables.push(first);
310 }
311 } else if child.next_sibling().is_some_and(|node| {
312 node.chunk()
313 .borrow()
314 .is_tag_and(|tag| tag.r#type == TagType::Close)
315 }) {
316 if let Some(next) = node.next_sibling() {
317 if next.chunk().borrow().is_ws() {
318 if child.next_sibling().is_some_and(|node| {
319 node.chunk().borrow().is_tag_and(|tag| tag.name() == "td")
320 }) {
321 detachables.push(child.clone());
322 } else {
323 detachables.push(next);
324 }
325 }
326 }
327 } else if let Some(next) = child.next_sibling() {
328 if next.chunk().borrow().is_ws() {
329 detachables.push(next);
330 }
331 }
332 } else if child_chunk.is_tag() {
333 let name = child_chunk.tag().unwrap().name();
334 if matches!(name.as_str(), "p" | "ziyy" | "$root" | "div") {
335 if let Some(first) = child.first_child() {
336 if first.chunk().borrow().is_ws() {
337 detachables.push(first);
338 }
339 }
340 } else if name == "br" {
341 if let Some(prev) = child.prev_sibling() {
342 if prev.chunk().borrow().is_ws() {
343 detachables.push(prev);
344 }
345 }
346
347 if let Some(next) = child.next_sibling() {
348 if next.chunk().borrow().is_ws() {
349 detachables.push(next);
350 }
351 }
352 } else if matches!(name.as_str(), "pre") {
353 continue;
354 }
355 }
356
357 Resolver::optimize_ws(&child, detachables);
358 }
359 }
360
361 fn _resolve(node: &Rc<Node>, node_name: &str) {
362 for child in node.children() {
363 let mut child_chunk = child.chunk().borrow_mut();
364 if child_chunk.is_tag() {
365 let tag = child_chunk.tag_mut().unwrap();
366 if tag.r#type == TagType::Open {
367 let name = tag.name();
368 if matches!(name.as_str(), "ziyy" | "p" | "div" | "pre" | "table" | "tr") {
369 if matches!(
370 node_name,
371 "ziyy" | "$root" | "p" | "div" | "pre" | "table" | "tr"
372 ) && node
373 .first_child()
374 .is_some_and(|first| first.id() == child.id())
375 {
376 } else {
377 child.insert_before(Chunk {
378 data: ChunkData::WhiteSpace(Cow::Borrowed("\n")),
379 span: Span::inserted(),
380 });
381 }
382 } else if name == "a" {
383 for grand_child in child.children() {
384 grand_child.strip_styles();
385 }
386 }
387
388 if let Some(last) = child.last_child() {
389 let last_chunk = last.chunk().borrow_mut();
390 if !last_chunk.is_tag_and(|tag| tag.r#type == TagType::Close) {
391 last.insert_after(Chunk {
392 data: ChunkData::Tag(tag.close()),
393 span: Span::inserted(),
394 });
395 }
396 }
397 }
398
399 Resolver::_resolve(&child, tag.name());
400 } else {
401 Resolver::_resolve(&child, node_name);
402 }
403 }
404 }
405
406 pub fn optimize_styles(node: &Rc<Node>) {
408 let mut stack: Vec<(String, Ansi, Ansi)> = Vec::with_capacity(1024);
409 for child in node.descendants() {
410 let mut child_chunk = child.chunk().borrow_mut();
411 if child_chunk.is_tag() {
412 let tag = child_chunk.tag_mut().unwrap();
413 match tag.r#type {
414 TagType::Open => {
415 let (_, prev_style, _) = stack.last().cloned().unwrap_or_default();
416 let new_style = prev_style.clone() + tag.clone().ansi;
418 let new_delta = tag.clone().ansi - prev_style.clone();
419
420 tag.ansi = new_delta.clone();
421
422 stack.push((tag.name().clone(), new_style, new_delta));
423 }
424
425 TagType::Close => {
426 let mut current_tag = stack.pop().unwrap_or_default();
427 let new_tag = current_tag.clone();
428 while current_tag.0 == "$ansi" {
429 current_tag = stack.pop().unwrap_or_default();
430 }
431
432 tag.ansi = !new_tag.2.clone();
434
435 if let Some((_, prev, prev_delta)) = stack.last() {
436 tag.ansi = tag.ansi.clone() + prev_delta.clone();
437 if !prev.fg_color().is_empty() && !tag.fg_color().is_empty() {
438 tag.ansi.set_fg_color(prev.fg_color().clone());
439 }
440 if !prev.bg_color().is_empty() && !tag.bg_color().is_empty() {
441 tag.ansi.set_bg_color(prev.bg_color().clone());
442 }
443 }
444 }
445
446 TagType::SelfClose => {}
447 }
448 }
449 }
450 }
451
452 fn optimize_ansi(node: &Rc<Node>) {
453 let decendants: Vec<_> = node.descendants().collect();
454
455 let mut i = 0;
456 while i < decendants.len() {
457 let first = &decendants[i];
458 let mut first_chunk = first.chunk().borrow_mut();
459 if first_chunk.is_tag() {
460 if let Some(second) = decendants.get(i + 1) {
461 let mut second_chunk = second.chunk().borrow_mut();
462 if second_chunk.is_tag() {
463 let first_tag = first_chunk.tag_mut().unwrap();
464 let second_tag = second_chunk.tag_mut().unwrap();
465
466 *second_tag = first_tag.clone() + second_tag.clone();
467 first_tag.reset_styles();
468 }
469 }
470 }
471 i += 1;
472 }
473 }
474}