ziyy_core/resolver/
mod.rs

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        // ensures we are working with the root node if not we could get some weird bugs
79        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        // self.tables.clear();
231    }
232
233    /// Resolve all declared bindings: <let />
234    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    /// Optimizes Excess Whitespace
293    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    /// Optimize styles
407    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                        //
417                        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                        //
433                        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}