asciidork_parser/tasks/
parse_section.rs

1use 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    // special case: book special sections can go from 0 to 2
202    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      // we don't push out of sequence sections, shouldn't panic
211      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}