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 equals = heading_line.consume_current().unwrap();
70 heading_line.discard_assert(TokenKind::Whitespace);
71 let id = self.section_id(&heading_line, &meta.attrs);
72
73 let out_of_sequence = semantic_level > last_level && semantic_level - last_level > 1;
74 if out_of_sequence {
75 self.err_token_full(
76 format!(
77 "Section title out of sequence: expected level {} `{}`",
78 last_level + 1,
79 "=".repeat((last_level + 2) as usize)
80 ),
81 &equals,
82 )?;
83 }
84
85 let heading = self.parse_inlines(&mut heading_line.into_lines())?;
86 if !out_of_sequence {
87 self.push_toc_node(
88 authored_level,
89 semantic_level,
90 &heading,
91 id.as_ref(),
92 meta.attrs.special_sect(),
93 );
94 }
95
96 if let Some(id) = &id {
97 let reftext = meta
98 .attrs
99 .iter()
100 .find_map(|a| a.named.get("reftext"))
101 .cloned();
102 self.document.anchors.borrow_mut().insert(
103 id.clone(),
104 Anchor {
105 reftext,
106 title: heading.clone(),
107 source_loc: None,
108 source_idx: self.lexer.source_idx(),
109 is_biblio: false,
110 },
111 );
112 }
113
114 if meta.attrs.str_positional_at(0) == Some("bibliography") {
115 self.ctx.bibliography_ctx = BiblioContext::Section;
116 }
117
118 self.restore_lines(lines);
119 let mut blocks = BumpVec::new_in(self.bump);
120 while let Some(inner) = self.parse_block()? {
121 loc.extend_end(&inner.loc);
122 blocks.push(inner);
123 }
124
125 if let Some(special_sect) = special_sect {
126 if !special_sect.supports_subsections() {
127 for block in &blocks {
128 if let BlockContent::Section(subsection) = &block.content {
129 self.err_line_starting(
130 format!(
131 "{} sections do not support nested sections",
132 special_sect.to_str()
133 ),
134 subsection.heading.first_loc().unwrap(),
135 )?;
136 }
137 }
138 }
139 }
140
141 self.ctx.bibliography_ctx = BiblioContext::None;
142 self.ctx.section_level = last_level;
143 Ok(Section {
144 meta,
145 level: semantic_level,
146 id,
147 heading,
148 blocks,
149 loc,
150 })
151 }
152
153 pub(crate) fn restore_peeked_section(&mut self, peeked: PeekedSection<'arena>) {
154 self.restore_peeked(peeked.lines, peeked.meta);
155 }
156
157 pub fn push_toc_node(
158 &mut self,
159 authored_level: u8,
160 semantic_level: u8,
161 heading: &InlineNodes<'arena>,
162 as_ref: Option<&BumpString<'arena>>,
163 special_sect: Option<SpecialSection>,
164 ) {
165 let Some(toc) = self.document.toc.as_mut() else {
166 return;
167 };
168 if authored_level > self.document.meta.u8_or("toclevels", 2) {
169 return;
170 }
171 let node = TocNode {
172 level: authored_level,
173 title: heading.clone(),
174 id: as_ref.cloned(),
175 special_sect,
176 children: BumpVec::new_in(self.bump),
177 };
178 let mut nodes: &mut BumpVec<'_, TocNode<'_>> = toc.nodes.as_mut();
179 let Some(last_level) = nodes.last().map(|n| n.level) else {
180 nodes.push(node);
181 return;
182 };
183 if authored_level < last_level || authored_level == 0 {
184 nodes.push(node);
185 return;
186 }
187
188 let mut depth = semantic_level;
189
190 if last_level == 0
192 && semantic_level == 2
193 && !nodes.last().unwrap().children.iter().any(|n| n.level == 1)
194 {
195 depth = 1;
196 }
197
198 while depth > last_level {
199 nodes = nodes.last_mut().unwrap().children.as_mut();
201 depth -= 1;
202 }
203 nodes.push(node);
204 }
205}
206
207#[derive(Debug)]
208pub struct PeekedSection<'arena> {
209 pub meta: ChunkMeta<'arena>,
210 pub lines: ContiguousLines<'arena>,
211 pub semantic_level: u8,
212 pub authored_level: u8,
213}
214
215impl PeekedSection<'_> {
216 pub fn is_special_sect(&self) -> bool {
217 self.meta.attrs.special_sect().is_some()
218 }
219
220 pub fn special_sect(&self) -> Option<SpecialSection> {
221 self.meta.attrs.special_sect()
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use pretty_assertions::assert_eq;
229 use test_utils::*;
230
231 #[test]
232 fn test_parse_2_sections() {
233 let input = adoc! {"
234 == one
235
236 foo
237
238 == two
239
240 bar
241 "};
242 let mut parser = test_parser!(input);
243 let section = parser.parse_section().unwrap().unwrap();
244 assert_eq!(
245 section,
246 Section {
247 meta: chunk_meta!(0),
248 level: 1,
249 id: Some(bstr!("_one")),
250 heading: nodes![node!("one"; 3..6)],
251 blocks: vecb![Block {
252 context: BlockContext::Paragraph,
253 content: BlockContent::Simple(nodes![node!("foo"; 8..11)]),
254 loc: (8..11).into(),
255 ..empty_block!(8)
256 }],
257 loc: (0..11).into()
258 }
259 );
260 let section = parser.parse_section().unwrap().unwrap();
261 assert_eq!(
262 section,
263 Section {
264 meta: chunk_meta!(13),
265 level: 1,
266 id: Some(bstr!("_two")),
267 heading: nodes![node!("two"; 16..19)],
268 blocks: vecb![Block {
269 context: BlockContext::Paragraph,
270 content: BlockContent::Simple(nodes![node!("bar"; 21..24)]),
271 loc: (21..24).into(),
272 ..empty_block!(21)
273 }],
274 loc: (13..24).into()
275 }
276 );
277 }
278}