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 && semantic_level == 2 && !nodes.iter().any(|n| n.level == 1) {
192 depth = 1;
193 }
194
195 while depth > last_level {
196 nodes = nodes.last_mut().unwrap().children.as_mut();
198 depth -= 1;
199 }
200 nodes.push(node);
201 }
202}
203
204#[derive(Debug)]
205pub struct PeekedSection<'arena> {
206 pub meta: ChunkMeta<'arena>,
207 pub lines: ContiguousLines<'arena>,
208 pub semantic_level: u8,
209 pub authored_level: u8,
210}
211
212impl PeekedSection<'_> {
213 pub fn is_special_sect(&self) -> bool {
214 self.meta.attrs.special_sect().is_some()
215 }
216
217 pub fn special_sect(&self) -> Option<SpecialSection> {
218 self.meta.attrs.special_sect()
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use pretty_assertions::assert_eq;
226 use test_utils::*;
227
228 #[test]
229 fn test_parse_2_sections() {
230 let input = adoc! {"
231 == one
232
233 foo
234
235 == two
236
237 bar
238 "};
239 let mut parser = test_parser!(input);
240 let section = parser.parse_section().unwrap().unwrap();
241 assert_eq!(
242 section,
243 Section {
244 meta: chunk_meta!(0),
245 level: 1,
246 id: Some(bstr!("_one")),
247 heading: nodes![node!("one"; 3..6)],
248 blocks: vecb![Block {
249 context: BlockContext::Paragraph,
250 content: BlockContent::Simple(nodes![node!("foo"; 8..11)]),
251 loc: (8..11).into(),
252 ..empty_block!(8)
253 }],
254 loc: (0..11).into()
255 }
256 );
257 let section = parser.parse_section().unwrap().unwrap();
258 assert_eq!(
259 section,
260 Section {
261 meta: chunk_meta!(13),
262 level: 1,
263 id: Some(bstr!("_two")),
264 heading: nodes![node!("two"; 16..19)],
265 blocks: vecb![Block {
266 context: BlockContext::Paragraph,
267 content: BlockContent::Simple(nodes![node!("bar"; 21..24)]),
268 loc: (21..24).into(),
269 ..empty_block!(21)
270 }],
271 loc: (13..24).into()
272 }
273 );
274 }
275}