1use pulldown_cmark::{BrokenLinkCallback, CowStr, Event, Options, Parser, Tag, TagEnd};
4use std::{iter, ops::Range};
5pub mod visit_mut;
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub struct Spanned<T> {
9 pub item: T,
10 pub span: Span,
11}
12impl<T> Spanned<T> {
13 pub fn into_inner(self) -> T {
14 self.item
15 }
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub struct Span(pub Range<usize>);
20
21#[derive(Default, Debug, Clone, PartialEq)]
22pub struct Ast<'a>(pub Vec<Tree<'a>>);
23
24impl<'a> IntoIterator for Ast<'a> {
25 type Item = Spanned<Event<'a>>;
26
27 type IntoIter = Box<dyn Iterator<Item = Spanned<Event<'a>>> + 'a>;
28
29 fn into_iter(self) -> Self::IntoIter {
30 let Self { 0: trees } = self;
31 Box::new(trees.into_iter().flatten())
32 }
33}
34
35#[derive(Debug, Clone, PartialEq)]
36pub enum Tree<'a> {
37 Group(Group<'a>),
38 Text(Spanned<CowStr<'a>>),
39 Code(Spanned<CowStr<'a>>),
41 Html(Spanned<CowStr<'a>>),
43 InlineHtml(Spanned<CowStr<'a>>),
45 FootnoteReference(Spanned<CowStr<'a>>),
49 SoftBreak(Span),
51 HardBreak(Span),
53 Rule(Span),
55 TaskListMarker(Spanned<bool>),
57 InlineMath(Spanned<CowStr<'a>>),
59 DisplayMath(Spanned<CowStr<'a>>),
61}
62
63impl<'a> IntoIterator for Tree<'a> {
64 type Item = Spanned<Event<'a>>;
65
66 type IntoIter = Box<dyn Iterator<Item = Spanned<Event<'a>>> + 'a>;
67
68 fn into_iter(self) -> Self::IntoIter {
69 fn once<'a, T: 'a>(item: T, span: Span) -> Box<dyn Iterator<Item = Spanned<T>> + 'a> {
71 Box::new(iter::once(Spanned { item, span }))
72 }
73 match self {
74 Tree::Group(it) => it.into_iter(),
75 Tree::Text(Spanned { item, span }) => once(Event::Text(item), span),
76 Tree::Code(Spanned { item, span }) => once(Event::Code(item), span),
77 Tree::Html(Spanned { item, span }) => once(Event::Html(item), span),
78 Tree::InlineHtml(Spanned { item, span }) => once(Event::InlineHtml(item), span),
79 Tree::FootnoteReference(Spanned { item, span }) => {
80 once(Event::FootnoteReference(item), span)
81 }
82 Tree::SoftBreak(span) => once(Event::SoftBreak, span),
83 Tree::HardBreak(span) => once(Event::HardBreak, span),
84 Tree::Rule(span) => once(Event::Rule, span),
85 Tree::TaskListMarker(Spanned { item, span }) => once(Event::TaskListMarker(item), span),
86 Tree::InlineMath(Spanned { item, span }) => once(Event::InlineMath(item), span),
87 Tree::DisplayMath(Spanned { item, span }) => once(Event::DisplayMath(item), span),
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq)]
93pub struct Group<'a> {
94 pub tag: Spanned<Tag<'a>>,
95 pub stream: Ast<'a>,
96 pub end_span: Span,
97}
98
99impl<'a> IntoIterator for Group<'a> {
100 type Item = Spanned<Event<'a>>;
101
102 type IntoIter = Box<dyn Iterator<Item = Spanned<Event<'a>>> + 'a>;
103
104 fn into_iter(self) -> Self::IntoIter {
105 let Group {
106 tag: Spanned { item: tag, span },
107 stream,
108 end_span,
109 } = self;
110 let end = Spanned {
111 item: Event::End(tag.to_end()),
112 span: end_span,
113 };
114 Box::new(
115 iter::once(Spanned {
116 item: Event::Start(tag),
117 span,
118 })
119 .chain(stream)
120 .chain(iter::once(end)),
121 )
122 }
123}
124
125impl<'a> Ast<'a> {
126 pub fn new(text: &'a str) -> Self {
127 Self::new_ext(text, Options::empty())
128 }
129 pub fn new_ext(text: &'a str, options: Options) -> Self {
130 Self::new_with_broken_link_callback(text, options, Some(|_| None))
131 }
132 pub fn new_with_broken_link_callback<C: BrokenLinkCallback<'a>>(
133 text: &'a str,
134 options: Options,
135 broken_link_callback: Option<C>,
136 ) -> Self {
137 match Self::from_events(
138 &mut Parser::new_with_broken_link_callback(text, options, broken_link_callback)
139 .into_offset_iter()
140 .map(|(item, range)| Spanned {
141 item,
142 span: Span(range),
143 }),
144 ) {
145 Ok((this, None)) => this,
146 Ok((_, Some(_))) | Err(_) => {
147 unreachable!("pulldown_cmark guarantees delimters are matched")
148 }
149 }
150 }
151 fn from_events(
152 evts: &mut dyn Iterator<Item = Spanned<Event<'a>>>,
153 ) -> Result<(Self, Option<Spanned<TagEnd>>), Mismatched> {
154 let mut this = Self::default();
155 while let Some(Spanned { item, span }) = evts.next() {
156 match item {
157 Event::Start(tag) => match Self::from_events(evts)? {
158 (
159 stream,
160 Some(Spanned {
161 item,
162 span: end_span,
163 }),
164 ) if tag.to_end() == item => this.0.push(Tree::Group(Group {
165 tag: Spanned { item: tag, span },
166 stream,
167 end_span,
168 })),
169 _ => return Err(Mismatched),
170 },
171 Event::End(item) => return Ok((this, Some(Spanned { item, span }))),
172 Event::Text(item) => this.0.push(Tree::Text(Spanned { item, span })),
173 Event::Code(item) => this.0.push(Tree::Code(Spanned { item, span })),
174 Event::Html(item) => this.0.push(Tree::Html(Spanned { item, span })),
175 Event::InlineHtml(item) => this.0.push(Tree::InlineHtml(Spanned { item, span })),
176 Event::FootnoteReference(item) => {
177 this.0.push(Tree::FootnoteReference(Spanned { item, span }))
178 }
179 Event::SoftBreak => this.0.push(Tree::SoftBreak(span)),
180 Event::HardBreak => this.0.push(Tree::HardBreak(span)),
181 Event::Rule => this.0.push(Tree::Rule(span)),
182 Event::TaskListMarker(item) => {
183 this.0.push(Tree::TaskListMarker(Spanned { item, span }))
184 }
185 Event::InlineMath(item) => this.0.push(Tree::InlineMath(Spanned { item, span })),
186 Event::DisplayMath(item) => this.0.push(Tree::DisplayMath(Spanned { item, span })),
187 }
188 }
189 Ok((this, None))
190 }
191}
192
193struct Mismatched;