koto_bytecode/
chunk.rs

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use crate::InstructionReader;
use koto_memory::Ptr;
use koto_parser::{ConstantPool, KString, Span};
use std::fmt::{self, Write};

/// Debug information for a Koto program
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DebugInfo {
    source_map: Vec<(u32, Span)>,
    /// The source of the program that the debug info was derived from
    pub source: String,
}

impl DebugInfo {
    /// Adds a span to the source map for a given ip
    ///
    /// Instructions with matching spans share the same entry, so if the span matches the
    /// previously pushed span then this is a no-op.
    pub fn push(&mut self, ip: u32, span: Span) {
        if let Some(entry) = self.source_map.last() {
            if entry.1 == span {
                // Don't add entries with matching spans, a search is performed in
                // get_source_span which will find the correct span
                // for intermediate ips.
                return;
            }
        }
        self.source_map.push((ip, span));
    }

    /// Returns a source span for a given instruction pointer
    pub fn get_source_span(&self, ip: u32) -> Option<Span> {
        // Find the last entry with an ip less than or equal to the input
        // an upper_bound would nice here, but this isn't currently a performance sensitive function
        // so a scan through the entries will do.
        let mut result = None;
        for entry in self.source_map.iter() {
            if entry.0 <= ip {
                result = Some(entry.1);
            } else {
                break;
            }
        }
        result
    }
}

/// A compiled chunk of bytecode, along with its associated constants and metadata
#[derive(Clone, Default, PartialEq)]
pub struct Chunk {
    /// The bytes representing the chunk's bytecode
    pub bytes: Vec<u8>,
    /// The constant data associated with the chunk's bytecode
    pub constants: ConstantPool,
    /// The path of the program's source file
    pub path: Option<KString>,
    /// Debug information associated with the chunk's bytecode
    pub debug_info: DebugInfo,
}

impl Chunk {
    /// Returns a [String] displaying the instructions contained in the compiled [Chunk]
    pub fn bytes_as_string(chunk: &Chunk) -> String {
        let mut iter = chunk.bytes.iter();
        let mut result = String::new();

        'outer: loop {
            for i in 1..=16 {
                match iter.next() {
                    Some(byte) => write!(result, "{byte:02x}").ok(),
                    None => break 'outer,
                };

                if i < 16 {
                    result += " ";

                    if i % 4 == 0 {
                        result += " ";
                    }
                }
            }
            result += "\n";
        }

        result
    }

    /// Returns a [String] displaying the annotated instructions contained in the compiled [Chunk]
    pub fn instructions_as_string(chunk: Ptr<Chunk>, source_lines: &[&str]) -> String {
        let mut result = String::new();
        let mut reader = InstructionReader::new(chunk);
        let mut ip = reader.ip;
        let mut span: Option<Span> = None;
        let mut first = true;

        while let Some(instruction) = reader.next() {
            let instruction_span = reader
                .chunk
                .debug_info
                .get_source_span(ip as u32)
                .expect("Missing source span");

            let print_source_lines = if let Some(span) = span {
                instruction_span.start.line != span.start.line
            } else {
                true
            };

            if print_source_lines && !source_lines.is_empty() {
                if !first {
                    result += "\n";
                }
                first = false;

                let line = instruction_span
                    .start
                    .line
                    .clamp(0, source_lines.len() as u32 - 1) as usize;
                writeln!(result, "|{}| {}", line + 1, source_lines[line]).ok();
                span = Some(instruction_span);
            }

            writeln!(result, "{ip}\t{instruction:?}").ok();
            ip = reader.ip;
        }

        result
    }
}

impl fmt::Debug for Chunk {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        write!(f, "Chunk ({self:p})")
    }
}