outline/document/
ast.rs

1//! Internal AST representation
2
3use std::collections::HashMap;
4use std::iter::FromIterator;
5
6use super::code::CodeBlock;
7use super::text::TextBlock;
8use super::{CompileError, CompileErrorKind};
9use crate::parser::Printer;
10
11/// A `Node` in the `Ast`
12#[derive(Debug)]
13pub(crate) enum Node<'a> {
14    /// A text block
15    Text(TextBlock<'a>),
16    /// A code block
17    Code(CodeBlock<'a>),
18}
19
20/// The AST of a literate document
21#[derive(Debug)]
22pub struct Ast<'a> {
23    /// A list of the nodes in this "tree", which is actually just a sequence until the parsers get
24    /// cooler.
25    nodes: Vec<Node<'a>>,
26}
27
28impl<'a> Ast<'a> {
29    /// Create a new empty AST
30    pub(crate) fn new(nodes: Vec<Node<'a>>) -> Self {
31        Ast { nodes }
32    }
33
34    /// Gets all the code blocks of this AST, concatenating blocks of the same name
35    pub(crate) fn code_blocks(&self, language: Option<&str>) -> HashMap<Option<&str>, CodeBlock> {
36        let mut code_blocks = HashMap::new();
37        for node in &self.nodes {
38            if let Node::Code(block) = node {
39                // skip blocks in the wrong language. If either language is None, then assume it is
40                // ok
41                if let Some(language) = language {
42                    if let Some(block_language) = &block.language {
43                        if language != block_language {
44                            continue;
45                        }
46                    }
47                }
48                code_blocks
49                    .entry(block.name.as_ref().map(|x| &x[..])) // TODO: any nicer way to write this
50                    .and_modify(|existing: &mut CodeBlock<'a>| existing.append(block))
51                    .or_insert_with(|| block.clone());
52            }
53        }
54        code_blocks
55    }
56
57    /// Renders the program this AST is representing in the documentation format
58    pub(crate) fn print_docs<P: Printer>(&self, printer: &P) -> String {
59        let mut output = String::new();
60        for node in &self.nodes {
61            match node {
62                Node::Text(text_block) => output.push_str(&printer.print_text_block(text_block)),
63                Node::Code(code_block) => output.push_str(
64                    &printer
65                        .print_code_block(code_block)
66                        .split("\n")
67                        .map(|line| {
68                            if line.is_empty() {
69                                line.to_string()
70                            } else {
71                                format!("{}{}", code_block.indent, line)
72                            }
73                        })
74                        .collect::<Vec<_>>()
75                        .join("\n"),
76                ),
77            }
78        }
79        output
80    }
81
82    /// Renders the program this AST is representing in the code format
83    pub(crate) fn print_code(
84        &self,
85        entrypoint: Option<&str>,
86        language: Option<&str>,
87    ) -> Result<String, CompileError> {
88        let code_blocks = self.code_blocks(language);
89        code_blocks
90            .get(&entrypoint)
91            .map(|entrypoint| entrypoint.compile(&code_blocks))
92            .unwrap_or(Err(CompileError::Single {
93                line_number: 0,
94                kind: CompileErrorKind::MissingEntrypoint,
95            }))
96    }
97}
98
99impl<'a> FromIterator<Node<'a>> for Ast<'a> {
100    fn from_iter<I: IntoIterator<Item = Node<'a>>>(iter: I) -> Self {
101        Self::new(iter.into_iter().collect())
102    }
103}
104
105impl<'a> From<Vec<Node<'a>>> for Ast<'a> {
106    fn from(nodes: Vec<Node<'a>>) -> Self {
107        Self::new(nodes)
108    }
109}