Commit Diff


commit - 37c978c740aa2c23c2be7f6165ee14b888c150b5
commit + 862cec691012ceb800f90f956da2b62c8811e4f6
blob - fca54dff3512cbf3bac7237083caa50df0523e85
blob + 880b6fa2587ba1fb9699f81e65fb64eab69bf956
--- src/compiler/opcode.rs
+++ src/compiler/opcode.rs
@@ -28,8 +28,19 @@ pub enum OpCode {
     Div,
     Mod,
     Negate,
+    Not,
+    Equal,
+    NotEqual,
+    Less,
+    LessEqual,
+    Greater,
+    GreaterEqual,
     GetLocal,
     SetLocal,
+    JumpIfFalse,
+    Jump,
+    Loop,
+    Call,
     Print,
     Return,
 }
@@ -45,76 +56,210 @@ impl OpCode {
             5 => Some(OpCode::Div),
             6 => Some(OpCode::Mod),
             7 => Some(OpCode::Negate),
-            8 => Some(OpCode::GetLocal),
-            9 => Some(OpCode::SetLocal),
-            10 => Some(OpCode::Print),
-            11 => Some(OpCode::Return),
+            8 => Some(OpCode::Not),
+            9 => Some(OpCode::Equal),
+            10 => Some(OpCode::NotEqual),
+            11 => Some(OpCode::Less),
+            12 => Some(OpCode::LessEqual),
+            13 => Some(OpCode::Greater),
+            14 => Some(OpCode::GreaterEqual),
+            15 => Some(OpCode::GetLocal),
+            16 => Some(OpCode::SetLocal),
+            17 => Some(OpCode::JumpIfFalse),
+            18 => Some(OpCode::Jump),
+            19 => Some(OpCode::Loop),
+            20 => Some(OpCode::Call),
+            21 => Some(OpCode::Print),
+            22 => Some(OpCode::Return),
             _ => None,
         }
     }
 }
 
 pub struct Chunk {
+    pub name: String,
     pub code: Vec<u8>,
     pub constants: Vec<Value>,
     pub lines: Vec<usize>,
+    pub arity: u8,
 }
 
 impl Chunk {
-    pub fn new() -> Self {
-        Self { code: Vec::new(), constants: Vec::new(), lines: Vec::new() }
+    pub fn new(
+        name: impl Into<String>,
+        arity: u8,
+    ) -> Self {
+        Self {
+            name: name.into(),
+            code: Vec::new(),
+            constants: Vec::new(),
+            lines: Vec::new(),
+            arity,
+        }
     }
 
-    pub fn emit_byte(&mut self, byte: u8, line: usize) {
+    pub fn emit_byte(
+        &mut self,
+        byte: u8,
+        line: usize,
+    ) {
         self.code.push(byte);
         self.lines.push(line);
     }
 
-    pub fn emit_op(&mut self, op: OpCode, line: usize) {
+    pub fn emit_op(
+        &mut self,
+        op: OpCode,
+        line: usize,
+    ) {
         self.emit_byte(op as u8, line);
     }
 
-    pub fn add_constant(&mut self, value: Value) -> u8 {
+    pub fn add_constant(
+        &mut self,
+        value: Value,
+    ) -> u8 {
         self.constants.push(value);
         (self.constants.len() - 1) as u8
     }
 
-    pub fn emit_constant(&mut self, value: Value, line: usize) {
+    pub fn emit_constant(
+        &mut self,
+        value: Value,
+        line: usize,
+    ) {
         let index = self.add_constant(value);
         self.emit_op(OpCode::Constant, line);
         self.emit_byte(index, line);
     }
 
+    /// Emit a jump instruction with a 2-byte
+    /// placeholder. Returns the offset of the
+    /// placeholder so it can be patched later.
+    pub fn emit_jump(
+        &mut self,
+        op: OpCode,
+        line: usize,
+    ) -> usize {
+        self.emit_op(op, line);
+        self.emit_byte(0xFF, line);
+        self.emit_byte(0xFF, line);
+        self.code.len() - 2
+    }
+
+    /// Patch a previously emitted jump placeholder
+    /// with the actual offset.
+    pub fn patch_jump(&mut self, offset: usize) {
+        let jump =
+            self.code.len() - offset - 2;
+        self.code[offset] = (jump >> 8) as u8;
+        self.code[offset + 1] = jump as u8;
+    }
+
+    /// Emit a loop instruction that jumps back
+    /// to `loop_start`.
+    pub fn emit_loop(
+        &mut self,
+        loop_start: usize,
+        line: usize,
+    ) {
+        self.emit_op(OpCode::Loop, line);
+        let offset = self.code.len() - loop_start + 2;
+        self.emit_byte((offset >> 8) as u8, line);
+        self.emit_byte(offset as u8, line);
+    }
+
     pub fn disassemble(&self, name: &str) {
         println!("== {name} ==");
         let mut offset = 0;
+
         while offset < self.code.len() {
-            offset = self.disassemble_instruction(offset);
+            offset =
+                self.disassemble_instruction(offset);
         }
     }
 
-    fn disassemble_instruction(&self, offset: usize) -> usize {
+    fn disassemble_instruction(
+        &self,
+        offset: usize,
+    ) -> usize {
         print!("{:04}  ", offset);
+
         let byte = self.code[offset];
         let Some(op) = OpCode::from_byte(byte) else {
             println!("unknown opcode {byte}");
             return offset + 1;
         };
+
         match op {
             OpCode::Constant => {
-                let index = self.code[offset + 1] as usize;
+                let index =
+                    self.code[offset + 1] as usize;
                 let value = &self.constants[index];
-                println!("{:<12} {:4} ({})", "Constant", index, value);
+                println!(
+                    "{:<14} {:4} ({})",
+                    "Constant", index, value
+                );
                 offset + 2
             }
             OpCode::GetLocal | OpCode::SetLocal => {
-                let slot = self.code[offset + 1] as usize;
-                println!("{:<12} {:4}", format!("{:?}", op), slot);
+                let slot =
+                    self.code[offset + 1] as usize;
+                println!(
+                    "{:<14} {:4}",
+                    format!("{:?}", op),
+                    slot
+                );
                 offset + 2
             }
+            OpCode::JumpIfFalse | OpCode::Jump => {
+                let hi =
+                    self.code[offset + 1] as u16;
+                let lo =
+                    self.code[offset + 2] as u16;
+                let jump = (hi << 8) | lo;
+                let target = offset + 3 + jump as usize;
+                println!(
+                    "{:<14} {:4} -> {:04}",
+                    format!("{:?}", op),
+                    jump,
+                    target
+                );
+                offset + 3
+            }
+            OpCode::Loop => {
+                let hi =
+                    self.code[offset + 1] as u16;
+                let lo =
+                    self.code[offset + 2] as u16;
+                let jump = (hi << 8) | lo;
+                let target = offset + 3 - jump as usize;
+                println!(
+                    "{:<14} {:4} -> {:04}",
+                    format!("{:?}", op),
+                    jump,
+                    target
+                );
+                offset + 3
+            }
+            OpCode::Call => {
+                let fn_index =
+                    self.code[offset + 1] as usize;
+                let arg_count =
+                    self.code[offset + 2] as usize;
+                println!(
+                    "{:<14} {:4} args={}",
+                    "Call", fn_index, arg_count
+                );
+                offset + 3
+            }
             OpCode::Print => {
-                let count = self.code[offset + 1] as usize;
-                println!("{:<12} {:4}", "Print", count);
+                let count =
+                    self.code[offset + 1] as usize;
+                println!(
+                    "{:<14} {:4}",
+                    "Print", count
+                );
                 offset + 2
             }
             _ => {
blob - 4309c4ed0f97962469ee0a7463c9f86ce883004c
blob + 803a9813685a32571358dab9e713eeab70ddf457
--- src/vm/mod.rs
+++ src/vm/mod.rs
@@ -20,27 +20,64 @@ pub mod value;
 use crate::compiler::opcode::{Chunk, OpCode};
 use value::Value;
 
-pub struct VM {
-    chunk: Chunk,
+struct CallFrame {
+    fn_index: usize,
     ip: usize,
+    slot_offset: usize,
+}
+
+pub struct VM {
+    functions: Vec<Chunk>,
+    frames: Vec<CallFrame>,
     stack: Vec<Value>,
 }
 
 impl VM {
-    pub fn new(chunk: Chunk) -> Self {
-        Self {
-            chunk,
+    pub fn new(
+        functions: Vec<Chunk>,
+        main_index: usize,
+    ) -> Self {
+        let frames = vec![CallFrame {
+            fn_index: main_index,
             ip: 0,
+            slot_offset: 0,
+        }];
+        Self {
+            functions,
+            frames,
             stack: Vec::new(),
         }
     }
 
+    fn frame(&self) -> &CallFrame {
+        self.frames.last().expect("no call frame")
+    }
+
+    fn frame_mut(&mut self) -> &mut CallFrame {
+        self.frames.last_mut().expect("no call frame")
+    }
+
     fn read_byte(&mut self) -> u8 {
-        let byte = self.chunk.code[self.ip];
-        self.ip += 1;
+        let frame = self.frame();
+        let byte = self.functions[frame.fn_index]
+            .code[frame.ip];
+        self.frame_mut().ip += 1;
         byte
     }
 
+    fn read_u16(&mut self) -> u16 {
+        let hi = self.read_byte() as u16;
+        let lo = self.read_byte() as u16;
+        (hi << 8) | lo
+    }
+
+    fn read_constant(&mut self) -> Value {
+        let index = self.read_byte() as usize;
+        let fn_index = self.frame().fn_index;
+        self.functions[fn_index].constants[index]
+            .clone()
+    }
+
     fn push(&mut self, value: Value) {
         self.stack.push(value);
     }
@@ -61,11 +98,8 @@ impl VM {
 
             match op {
                 OpCode::Constant => {
-                    let index =
-                        self.read_byte() as usize;
                     let value =
-                        self.chunk.constants[index]
-                            .clone();
+                        self.read_constant();
                     self.push(value);
                 }
                 OpCode::Pop => {
@@ -75,15 +109,29 @@ impl VM {
                     let b = self.pop();
                     let a = self.pop();
                     let result = match (&a, &b) {
-                        (Value::Int(a), Value::Int(b)) =>
-                            Value::Int(a + b),
-                        (Value::Float(a), Value::Float(b)) =>
-                            Value::Float(a + b),
-                        (Value::Str(a), Value::Str(b)) =>
-                            Value::Str(format!("{a}{b}")),
-                        _ => return Err(format!(
-                            "cannot add {:?} and {:?}", a, b
-                        )),
+                        (
+                            Value::Int(a),
+                            Value::Int(b),
+                        ) => Value::Int(a + b),
+                        (
+                            Value::Float(a),
+                            Value::Float(b),
+                        ) => Value::Float(a + b),
+                        (
+                            Value::Str(a),
+                            Value::Str(b),
+                        ) => {
+                            Value::Str(format!(
+                                "{a}{b}"
+                            ))
+                        }
+                        _ => {
+                            return Err(format!(
+                                "cannot add {:?} \
+                                 and {:?}",
+                                a, b
+                            ))
+                        }
                     };
                     self.push(result);
                 }
@@ -91,13 +139,21 @@ impl VM {
                     let b = self.pop();
                     let a = self.pop();
                     let result = match (&a, &b) {
-                        (Value::Int(a), Value::Int(b)) =>
-                            Value::Int(a - b),
-                        (Value::Float(a), Value::Float(b)) =>
-                            Value::Float(a - b),
-                        _ => return Err(format!(
-                            "cannot subtract {:?} and {:?}", a, b
-                        )),
+                        (
+                            Value::Int(a),
+                            Value::Int(b),
+                        ) => Value::Int(a - b),
+                        (
+                            Value::Float(a),
+                            Value::Float(b),
+                        ) => Value::Float(a - b),
+                        _ => {
+                            return Err(format!(
+                                "cannot subtract \
+                                 {:?} and {:?}",
+                                a, b
+                            ))
+                        }
                     };
                     self.push(result);
                 }
@@ -105,13 +161,21 @@ impl VM {
                     let b = self.pop();
                     let a = self.pop();
                     let result = match (&a, &b) {
-                        (Value::Int(a), Value::Int(b)) =>
-                            Value::Int(a * b),
-                        (Value::Float(a), Value::Float(b)) =>
-                            Value::Float(a * b),
-                        _ => return Err(format!(
-                            "cannot multiply {:?} and {:?}", a, b
-                        )),
+                        (
+                            Value::Int(a),
+                            Value::Int(b),
+                        ) => Value::Int(a * b),
+                        (
+                            Value::Float(a),
+                            Value::Float(b),
+                        ) => Value::Float(a * b),
+                        _ => {
+                            return Err(format!(
+                                "cannot multiply \
+                                 {:?} and {:?}",
+                                a, b
+                            ))
+                        }
                     };
                     self.push(result);
                 }
@@ -119,15 +183,29 @@ impl VM {
                     let b = self.pop();
                     let a = self.pop();
                     let result = match (&a, &b) {
-                        (Value::Int(a), Value::Int(b)) => {
-                            if *b == 0 { return Err("division by zero".to_string()); }
+                        (
+                            Value::Int(a),
+                            Value::Int(b),
+                        ) => {
+                            if *b == 0 {
+                                return Err(
+                                    "division by zero"
+                                        .to_string(),
+                                );
+                            }
                             Value::Int(a / b)
                         }
-                        (Value::Float(a), Value::Float(b)) =>
-                            Value::Float(a / b),
-                        _ => return Err(format!(
-                            "cannot divide {:?} and {:?}", a, b
-                        )),
+                        (
+                            Value::Float(a),
+                            Value::Float(b),
+                        ) => Value::Float(a / b),
+                        _ => {
+                            return Err(format!(
+                                "cannot divide \
+                                 {:?} and {:?}",
+                                a, b
+                            ))
+                        }
                     };
                     self.push(result);
                 }
@@ -135,49 +213,318 @@ impl VM {
                     let b = self.pop();
                     let a = self.pop();
                     let result = match (&a, &b) {
-                        (Value::Int(a), Value::Int(b)) => {
-                            if *b == 0 { return Err("modulo by zero".to_string()); }
+                        (
+                            Value::Int(a),
+                            Value::Int(b),
+                        ) => {
+                            if *b == 0 {
+                                return Err(
+                                    "modulo by zero"
+                                        .to_string(),
+                                );
+                            }
                             Value::Int(a % b)
                         }
-                        _ => return Err(format!(
-                            "cannot modulo {:?} and {:?}", a, b
-                        )),
+                        _ => {
+                            return Err(format!(
+                                "cannot modulo \
+                                 {:?} and {:?}",
+                                a, b
+                            ))
+                        }
                     };
                     self.push(result);
                 }
                 OpCode::Negate => {
                     let v = self.pop();
                     let result = match v {
-                        Value::Int(n) => Value::Int(-n),
-                        Value::Float(n) => Value::Float(-n),
-                        _ => return Err(format!("cannot negate {:?}", v)),
+                        Value::Int(n) => {
+                            Value::Int(-n)
+                        }
+                        Value::Float(n) => {
+                            Value::Float(-n)
+                        }
+                        _ => {
+                            return Err(format!(
+                                "cannot negate {:?}",
+                                v
+                            ))
+                        }
                     };
                     self.push(result);
                 }
+                OpCode::Not => {
+                    let v = self.pop();
+                    let result = match v {
+                        Value::Bool(b) => {
+                            Value::Bool(!b)
+                        }
+                        _ => {
+                            return Err(format!(
+                                "cannot not {:?}",
+                                v
+                            ))
+                        }
+                    };
+                    self.push(result);
+                }
+                OpCode::Equal => {
+                    let b = self.pop();
+                    let a = self.pop();
+                    let result = match (&a, &b) {
+                        (
+                            Value::Int(a),
+                            Value::Int(b),
+                        ) => a == b,
+                        (
+                            Value::Float(a),
+                            Value::Float(b),
+                        ) => a == b,
+                        (
+                            Value::Str(a),
+                            Value::Str(b),
+                        ) => a == b,
+                        (
+                            Value::Bool(a),
+                            Value::Bool(b),
+                        ) => a == b,
+                        _ => {
+                            return Err(format!(
+                                "cannot compare \
+                                 {:?} and {:?}",
+                                a, b
+                            ))
+                        }
+                    };
+                    self.push(Value::Bool(result));
+                }
+                OpCode::NotEqual => {
+                    let b = self.pop();
+                    let a = self.pop();
+                    let result = match (&a, &b) {
+                        (
+                            Value::Int(a),
+                            Value::Int(b),
+                        ) => a != b,
+                        (
+                            Value::Float(a),
+                            Value::Float(b),
+                        ) => a != b,
+                        (
+                            Value::Str(a),
+                            Value::Str(b),
+                        ) => a != b,
+                        (
+                            Value::Bool(a),
+                            Value::Bool(b),
+                        ) => a != b,
+                        _ => {
+                            return Err(format!(
+                                "cannot compare \
+                                 {:?} and {:?}",
+                                a, b
+                            ))
+                        }
+                    };
+                    self.push(Value::Bool(result));
+                }
+                OpCode::Less => {
+                    let b = self.pop();
+                    let a = self.pop();
+                    let result = match (&a, &b) {
+                        (
+                            Value::Int(a),
+                            Value::Int(b),
+                        ) => a < b,
+                        (
+                            Value::Float(a),
+                            Value::Float(b),
+                        ) => a < b,
+                        _ => {
+                            return Err(format!(
+                                "cannot compare \
+                                 {:?} and {:?}",
+                                a, b
+                            ))
+                        }
+                    };
+                    self.push(Value::Bool(result));
+                }
+                OpCode::LessEqual => {
+                    let b = self.pop();
+                    let a = self.pop();
+                    let result = match (&a, &b) {
+                        (
+                            Value::Int(a),
+                            Value::Int(b),
+                        ) => a <= b,
+                        (
+                            Value::Float(a),
+                            Value::Float(b),
+                        ) => a <= b,
+                        _ => {
+                            return Err(format!(
+                                "cannot compare \
+                                 {:?} and {:?}",
+                                a, b
+                            ))
+                        }
+                    };
+                    self.push(Value::Bool(result));
+                }
+                OpCode::Greater => {
+                    let b = self.pop();
+                    let a = self.pop();
+                    let result = match (&a, &b) {
+                        (
+                            Value::Int(a),
+                            Value::Int(b),
+                        ) => a > b,
+                        (
+                            Value::Float(a),
+                            Value::Float(b),
+                        ) => a > b,
+                        _ => {
+                            return Err(format!(
+                                "cannot compare \
+                                 {:?} and {:?}",
+                                a, b
+                            ))
+                        }
+                    };
+                    self.push(Value::Bool(result));
+                }
+                OpCode::GreaterEqual => {
+                    let b = self.pop();
+                    let a = self.pop();
+                    let result = match (&a, &b) {
+                        (
+                            Value::Int(a),
+                            Value::Int(b),
+                        ) => a >= b,
+                        (
+                            Value::Float(a),
+                            Value::Float(b),
+                        ) => a >= b,
+                        _ => {
+                            return Err(format!(
+                                "cannot compare \
+                                 {:?} and {:?}",
+                                a, b
+                            ))
+                        }
+                    };
+                    self.push(Value::Bool(result));
+                }
                 OpCode::GetLocal => {
-                    let slot = self.read_byte() as usize;
-                    let value = self.stack[slot].clone();
+                    let slot =
+                        self.read_byte() as usize;
+                    let offset =
+                        self.frame().slot_offset;
+                    let value =
+                        self.stack[offset + slot]
+                            .clone();
                     self.push(value);
                 }
                 OpCode::SetLocal => {
-                    let slot = self.read_byte() as usize;
-                    let value = self.stack.last()
-                        .expect("stack underflow").clone();
-                    self.stack[slot] = value;
+                    let slot =
+                        self.read_byte() as usize;
+                    let offset =
+                        self.frame().slot_offset;
+                    let value = self
+                        .stack
+                        .last()
+                        .expect("stack underflow")
+                        .clone();
+                    self.stack[offset + slot] = value;
                     self.pop();
                 }
+                OpCode::JumpIfFalse => {
+                    let jump =
+                        self.read_u16() as usize;
+                    if let Value::Bool(false) =
+                        self.stack.last().expect(
+                            "stack underflow",
+                        )
+                    {
+                        self.frame_mut().ip += jump;
+                    }
+                }
+                OpCode::Jump => {
+                    let jump =
+                        self.read_u16() as usize;
+                    self.frame_mut().ip += jump;
+                }
+                OpCode::Loop => {
+                    let jump =
+                        self.read_u16() as usize;
+                    self.frame_mut().ip -= jump;
+                }
+                OpCode::Call => {
+                    let fn_index =
+                        self.read_byte() as usize;
+                    let arg_count =
+                        self.read_byte() as usize;
+
+                    let arity = self.functions
+                        [fn_index]
+                        .arity
+                        as usize;
+                    if arg_count != arity {
+                        return Err(format!(
+                            "function '{}' expects \
+                             {} args, got {}",
+                            self.functions[fn_index]
+                                .name,
+                            arity,
+                            arg_count
+                        ));
+                    }
+
+                    let slot_offset =
+                        self.stack.len() - arg_count;
+
+                    self.frames.push(CallFrame {
+                        fn_index,
+                        ip: 0,
+                        slot_offset,
+                    });
+                }
+                OpCode::Return => {
+                    let result = self.pop();
+
+                    let frame = self
+                        .frames
+                        .pop()
+                        .expect("no call frame");
+
+                    if self.frames.is_empty() {
+                        // Returning from main
+                        return Ok(());
+                    }
+
+                    // Pop locals + args from stack
+                    self.stack
+                        .truncate(frame.slot_offset);
+
+                    // Push return value
+                    self.push(result);
+                }
                 OpCode::Print => {
-                    let count = self.read_byte() as usize;
-                    let start = self.stack.len() - count;
-                    let args: Vec<Value> =
-                        self.stack.drain(start..).collect();
-                    let output: Vec<String> =
-                        args.iter().map(|v| format!("{v}")).collect();
+                    let count =
+                        self.read_byte() as usize;
+                    let start =
+                        self.stack.len() - count;
+                    let args: Vec<Value> = self
+                        .stack
+                        .drain(start..)
+                        .collect();
+                    let output: Vec<String> = args
+                        .iter()
+                        .map(|v| format!("{v}"))
+                        .collect();
                     println!("{}", output.join(" "));
                 }
-                OpCode::Return => {
-                    return Ok(());
-                }
             }
         }
     }