asciidork_parser/tasks/
parse_section.rs1use crate::internal::*;
2
3impl<'arena> Parser<'arena> {
4 pub(crate) fn parse_section(&mut self) -> Result<Option<Section<'arena>>> {
5 let Some(peeked) = self.peek_section()? else {
6 return Ok(None);
7 };
8
9 if peeked.semantic_level == 0 && self.document.meta.get_doctype() != DocType::Book {
10 self.err_line(
11 "Level 0 section allowed only in doctype=book",
12 peeked.lines.current().unwrap(),
13 )?;
14 } else if peeked.semantic_level == 0 {
15 self.restore_peeked_section(peeked);
16 return Ok(None);
17 }
18 Ok(Some(self.parse_peeked_section(peeked)?))
19 }
20
21 pub(crate) fn peek_section(&mut self) -> Result<Option<PeekedSection<'arena>>> {
22 let Some(mut lines) = self.read_lines()? else {
23 return Ok(None);
24 };
25
26 lines.discard_leading_comment_lines();
27 if lines.is_empty() {
28 return self.peek_section();
29 }
30
31 let meta = self.parse_chunk_meta(&mut lines)?;
32
33 let Some(line) = lines.current() else {
34 if !meta.is_empty() {
35 self.err_line_starting("Unattached block metadata", meta.start_loc)?;
36 }
37 self.restore_peeked_meta(meta);
38 return Ok(None);
39 };
40
41 let Some(level) = self.line_heading_level(line) else {
42 self.restore_peeked(lines, meta);
43 return Ok(None);
44 };
45
46 Ok(Some(PeekedSection {
47 meta,
48 lines,
49 semantic_level: level,
50 authored_level: level,
51 }))
52 }
53
54 pub(crate) fn parse_peeked_section(
55 &mut self,
56 peeked: PeekedSection<'arena>,
57 ) -> Result<Section<'arena>> {
58 let special_sect = peeked.special_sect();
59 let PeekedSection {
60 meta,
61 mut lines,
62 semantic_level,
63 authored_level,
64 } = peeked;
65 let last_level = self.ctx.section_level;
66 self.ctx.section_level = semantic_level;
67 let mut heading_line = lines.consume_current().unwrap();
68 let mut loc: MultiSourceLocation = heading_line.loc().unwrap().into();
69 let (err_loc, ch) = {
70 let first_token = heading_line.consume_current().unwrap();
71 if first_token.kind == TokenKind::EqualSigns {
72 (first_token.loc, "=")
73 } else {
74 let mut loc = first_token.loc;
75 while heading_line.current_is(TokenKind::Hash) {
76 loc.extend(heading_line.consume_current().unwrap().loc);
77 }
78 (loc, "#")
79 }
80 };
81 heading_line.discard_assert(TokenKind::Whitespace);
82 let id = self.section_id(&heading_line, &meta.attrs);
83
84 let out_of_sequence = semantic_level > last_level && semantic_level - last_level > 1;
85 if out_of_sequence {
86 self.err_at(
87 format!(
88 "Section title out of sequence: expected level {} `{}`",
89 last_level + 1,
90 ch.repeat((last_level + 2) as usize)
91 ),
92 err_loc,
93 )?;
94 }
95
96 let heading = self.parse_inlines(&mut heading_line.into_lines())?;
97 if !out_of_sequence {
98 self.push_toc_node(
99 authored_level,
100 semantic_level,
101 &heading,
102 id.as_ref(),
103 meta.attrs.special_sect(),
104 );
105 }
106
107 if let Some(id) = &id {
108 let reftext = meta
109 .attrs
110 .iter()
111 .find_map(|a| a.named.get("reftext"))
112 .cloned();
113 self.document.anchors.borrow_mut().insert(
114 id.clone(),
115 Anchor {
116 reftext,
117 title: heading.clone(),
118 source_loc: None,
119 source_idx: self.lexer.source_idx(),
120 is_biblio: false,
121 },
122 );
123 }
124
125 if meta.attrs.str_positional_at(0) == Some("bibliography") {
126 self.ctx.bibliography_ctx = BiblioContext::Section;
127 }
128
129 self.restore_lines(lines);
130 let mut blocks = BumpVec::new_in(self.bump);
131 while let Some(inner) = self.parse_block()? {
132 loc.extend_end(&inner.loc);
133 blocks.push(inner);
134 }
135
136 if let Some(special_sect) = special_sect {
137 if !special_sect.supports_subsections() {
138 for block in &blocks {
139 if let BlockContent::Section(subsection) = &block.content {
140 self.err_line_starting(
141 format!(
142 "{} sections do not support nested sections",
143 special_sect.to_str()
144 ),
145 subsection.heading.first_loc().unwrap(),
146 )?;
147 }
148 }
149 }
150 }
151
152 self.ctx.bibliography_ctx = BiblioContext::None;
153 self.ctx.section_level = last_level;
154 Ok(Section {
155 meta,
156 level: semantic_level,
157 id,
158 heading,
159 blocks,
160 loc,
161 })
162 }
163
164 pub(crate) fn restore_peeked_section(&mut self, peeked: PeekedSection<'arena>) {
165 self.restore_peeked(peeked.lines, peeked.meta);
166 }
167
168 pub fn push_toc_node(
169 &mut self,
170 authored_level: u8,
171 semantic_level: u8,
172 heading: &InlineNodes<'arena>,
173 as_ref: Option<&BumpString<'arena>>,
174 special_sect: Option<SpecialSection>,
175 ) {
176 let Some(toc) = self.document.toc.as_mut() else {
177 return;
178 };
179 if authored_level > self.document.meta.u8_or("toclevels", 2) {
180 return;
181 }
182 let node = TocNode {
183 level: authored_level,
184 title: heading.clone(),
185 id: as_ref.cloned(),
186 special_sect,
187 children: BumpVec::new_in(self.bump),
188 };
189 let mut nodes: &mut BumpVec<'_, TocNode<'_>> = toc.nodes.as_mut();
190 let Some(last_level) = nodes.last().map(|n| n.level) else {
191 nodes.push(node);
192 return;
193 };
194 if authored_level < last_level || authored_level == 0 {
195 nodes.push(node);
196 return;
197 }
198
199 let mut depth = semantic_level;
200
201 if last_level == 0
203 && semantic_level == 2
204 && !nodes.last().unwrap().children.iter().any(|n| n.level == 1)
205 {
206 depth = 1;
207 }
208
209 while depth > last_level {
210 nodes = nodes.last_mut().unwrap().children.as_mut();
212 depth -= 1;
213 }
214 nodes.push(node);
215 }
216}
217
218#[derive(Debug)]
219pub struct PeekedSection<'arena> {
220 pub meta: ChunkMeta<'arena>,
221 pub lines: ContiguousLines<'arena>,
222 pub semantic_level: u8,
223 pub authored_level: u8,
224}
225
226impl PeekedSection<'_> {
227 pub fn is_special_sect(&self) -> bool {
228 self.meta.attrs.special_sect().is_some()
229 }
230
231 pub fn special_sect(&self) -> Option<SpecialSection> {
232 self.meta.attrs.special_sect()
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use pretty_assertions::assert_eq;
240 use test_utils::*;
241
242 #[test]
243 fn test_parse_2_sections() {
244 let input = adoc! {"
245 == one
246
247 foo
248
249 == two
250
251 bar
252 "};
253 let mut parser = test_parser!(input);
254 let section = parser.parse_section().unwrap().unwrap();
255 assert_eq!(
256 section,
257 Section {
258 meta: chunk_meta!(0),
259 level: 1,
260 id: Some(bstr!("_one")),
261 heading: nodes![node!("one"; 3..6)],
262 blocks: vecb![Block {
263 context: BlockContext::Paragraph,
264 content: BlockContent::Simple(nodes![node!("foo"; 8..11)]),
265 loc: (8..11).into(),
266 ..empty_block!(8)
267 }],
268 loc: (0..11).into()
269 }
270 );
271 let section = parser.parse_section().unwrap().unwrap();
272 assert_eq!(
273 section,
274 Section {
275 meta: chunk_meta!(13),
276 level: 1,
277 id: Some(bstr!("_two")),
278 heading: nodes![node!("two"; 16..19)],
279 blocks: vecb![Block {
280 context: BlockContext::Paragraph,
281 content: BlockContent::Simple(nodes![node!("bar"; 21..24)]),
282 loc: (21..24).into(),
283 ..empty_block!(21)
284 }],
285 loc: (13..24).into()
286 }
287 );
288 }
289}