1use crate::error::CompileError;
8use crate::intern::Symbol;
9use crate::opcode::OpCode;
10use crate::value::VMValue;
11
12#[derive(Clone, Default)]
18pub struct Chunk {
19 pub code: Vec<u8>,
21 pub constants: Vec<VMValue>,
23 pub lines: Vec<u32>,
26 pub key_symbols: Vec<Option<Symbol>>,
32 pub source_file: Option<String>,
35}
36
37impl Chunk {
38 #[must_use]
40 pub fn new() -> Self {
41 Self {
42 code: Vec::new(),
43 constants: Vec::new(),
44 lines: Vec::new(),
45 key_symbols: Vec::new(),
46 source_file: None,
47 }
48 }
49
50 pub fn write_byte(&mut self, byte: u8, line: u32) {
52 self.code.push(byte);
53 self.lines.push(line);
54 }
55
56 pub fn write_op(&mut self, op: OpCode, line: u32) {
58 self.write_byte(op as u8, line);
59 }
60
61 pub fn write_u16(&mut self, value: u16, line: u32) {
63 let bytes = value.to_le_bytes();
64 self.write_byte(bytes[0], line);
65 self.write_byte(bytes[1], line);
66 }
67
68 pub fn write_u32(&mut self, value: u32, line: u32) {
70 let bytes = value.to_le_bytes();
71 for &b in &bytes {
72 self.write_byte(b, line);
73 }
74 }
75
76 pub fn add_constant(&mut self, value: VMValue) -> Result<u16, CompileError> {
80 if self.constants.len() >= u16::MAX as usize {
81 return Err(CompileError::ConstantPoolOverflow);
82 }
83 let idx = self.constants.len() as u16;
84 self.constants.push(value);
85 self.key_symbols.push(None);
86 Ok(idx)
87 }
88
89 pub fn add_key_constant(&mut self, value: VMValue, sym: Symbol) -> Result<u16, CompileError> {
94 if self.constants.len() >= u16::MAX as usize {
95 return Err(CompileError::ConstantPoolOverflow);
96 }
97 let idx = self.constants.len() as u16;
98 self.constants.push(value);
99 self.key_symbols.push(Some(sym));
100 Ok(idx)
101 }
102
103 #[must_use]
107 pub fn read_u16(&self, offset: usize) -> u16 {
108 u16::from_le_bytes([self.code[offset], self.code[offset + 1]])
109 }
110
111 #[must_use]
113 pub fn read_u32(&self, offset: usize) -> u32 {
114 u32::from_le_bytes([
115 self.code[offset],
116 self.code[offset + 1],
117 self.code[offset + 2],
118 self.code[offset + 3],
119 ])
120 }
121
122 #[must_use]
124 pub fn len(&self) -> usize {
125 self.code.len()
126 }
127
128 #[must_use]
130 pub fn is_empty(&self) -> bool {
131 self.code.is_empty()
132 }
133
134 pub fn patch_u16(&mut self, offset: usize, value: u16) {
138 let bytes = value.to_le_bytes();
139 self.code[offset] = bytes[0];
140 self.code[offset + 1] = bytes[1];
141 }
142}
143
144impl std::fmt::Debug for Chunk {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 writeln!(f, "Chunk ({} bytes, {} constants)", self.code.len(), self.constants.len())?;
147 let mut offset = 0;
148 while offset < self.code.len() {
149 let byte = self.code[offset];
150 let line = self.lines.get(offset).copied().unwrap_or(0);
151 if let Some(op) = OpCode::from_byte(byte) {
152 write!(f, " {offset:04} L{line:<4} {op:?}")?;
153 offset += 1;
154 match op.disasm_operands() {
161 1 => {
162 if offset + 1 < self.code.len() {
163 let operand = self.read_u16(offset);
164 write!(f, " {operand}")?;
165 offset += 2;
166 }
167 }
168 2 => {
170 if offset + 3 < self.code.len() {
171 let slot = self.read_u16(offset);
172 let key = self.read_u16(offset + 2);
173 write!(f, " slot={slot} key={key}")?;
174 offset += 4;
175 }
176 }
177 _ => {}
178 }
179 writeln!(f)?;
180 } else {
181 writeln!(f, " {offset:04} L{line:<4} <unknown {byte}>")?;
182 offset += 1;
183 }
184 }
185 Ok(())
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn write_and_read_constant() {
195 let mut chunk = Chunk::new();
196 let idx = chunk.add_constant(VMValue::Int(42)).unwrap();
197 assert_eq!(idx, 0);
198 assert_eq!(chunk.constants[0], VMValue::Int(42));
199 }
200
201 #[test]
202 fn write_and_read_u16() {
203 let mut chunk = Chunk::new();
204 chunk.write_u16(0x1234, 1);
205 assert_eq!(chunk.read_u16(0), 0x1234);
206 }
207
208 #[test]
209 fn patch_u16() {
210 let mut chunk = Chunk::new();
211 chunk.write_u16(0xFFFF, 1);
213 chunk.patch_u16(0, 0x0042);
215 assert_eq!(chunk.read_u16(0), 0x0042);
216 }
217
218 #[test]
219 fn write_op_and_line_tracking() {
220 let mut chunk = Chunk::new();
221 chunk.write_op(OpCode::Null, 5);
222 chunk.write_op(OpCode::Return, 5);
223 assert_eq!(chunk.code.len(), 2);
224 assert_eq!(chunk.lines.len(), 2);
225 assert_eq!(chunk.lines[0], 5);
226 assert_eq!(chunk.lines[1], 5);
227 }
228
229 #[test]
230 fn debug_format() {
231 let mut chunk = Chunk::new();
232 chunk.write_op(OpCode::Null, 1);
233 chunk.write_op(OpCode::Return, 1);
234 let debug = format!("{chunk:?}");
235 assert!(debug.contains("Null"));
236 assert!(debug.contains("Return"));
237 }
238
239 #[test]
245 fn disassembly_output_is_byte_identical() {
246 let mut chunk = Chunk::new();
247 chunk.write_op(OpCode::Constant, 1);
249 chunk.write_u16(7, 1);
250 chunk.write_op(OpCode::GetLocalAttr, 1);
252 chunk.write_u16(3, 1);
253 chunk.write_u16(9, 1);
254 chunk.write_op(OpCode::Return, 2);
256
257 let expected = "\
258Chunk (9 bytes, 0 constants)
259 0000 L1 Constant 7
260 0003 L1 GetLocalAttr slot=3 key=9
261 0008 L2 Return
262";
263 assert_eq!(format!("{chunk:?}"), expected);
264 }
265}