1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
use crate::{error::*, ConstantPool, Node};
use koto_lexer::Span;

/// The index type used by nodes in the [Ast]
pub type AstIndex = u32;

/// A [Node] in the [Ast], along with its corresponding [Span]
#[derive(Clone, Debug, Default)]
pub struct AstNode {
    /// The node itself
    pub node: Node,
    /// The index of the node's corresponding [Span]
    ///
    /// The span is stored in the [Ast], and retrieved via [Ast::span].
    pub span: AstIndex,
}

/// A Koto program represented as an Abstract Syntax Tree
///
/// This is produced by the parser, and consumed by the compiler.
#[derive(Debug, Default)]
pub struct Ast {
    nodes: Vec<AstNode>,
    spans: Vec<Span>,
    constants: ConstantPool,
}

impl Ast {
    /// Initializes an Ast with the given capacity
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            nodes: Vec::with_capacity(capacity),
            spans: Vec::with_capacity(capacity),
            constants: ConstantPool::default(),
        }
    }

    /// Pushes a node and corresponding span onto the tree
    pub fn push(&mut self, node: Node, span: Span) -> Result<AstIndex> {
        // We could potentially achieve some compression by
        // using a set for the spans, for now a Vec will do.
        self.spans.push(span);
        let span_index = AstIndex::try_from(self.spans.len() - 1)
            .map_err(|_| Error::new(InternalError::AstCapacityOverflow.into(), span))?;

        self.nodes.push(AstNode {
            node,
            span: span_index,
        });
        AstIndex::try_from(self.nodes.len() - 1)
            .map_err(|_| Error::new(InternalError::AstCapacityOverflow.into(), span))
    }

    /// Returns a node for a given node index
    pub fn node(&self, index: AstIndex) -> &AstNode {
        &self.nodes[index as usize]
    }

    /// Returns a span for a given span index
    pub fn span(&self, index: AstIndex) -> &Span {
        &self.spans[index as usize]
    }

    /// Returns the constant pool referred to by the AST
    pub fn constants(&self) -> &ConstantPool {
        &self.constants
    }

    /// Moves the constants out of the AST
    ///
    /// This is used when building a `Chunk` after compilation.
    /// The constants get transferred to the `Chunk` once the AST has been converted into bytecode.
    pub fn consume_constants(self) -> ConstantPool {
        self.constants
    }

    pub(crate) fn set_constants(&mut self, constants: ConstantPool) {
        self.constants = constants
    }

    /// Returns the root node in the tree
    pub fn entry_point(&self) -> Option<AstIndex> {
        if self.nodes.is_empty() {
            None
        } else {
            Some((self.nodes.len() - 1) as AstIndex)
        }
    }

    /// Used in testing to validate the tree's contents
    pub fn nodes(&self) -> &[AstNode] {
        &self.nodes
    }
}