commit 37c978c740aa2c23c2be7f6165ee14b888c150b5 from: Murilo Ijanc date: Tue Jan 20 16:45:00 2026 UTC Add variable support with let/let mut New opcodes GetLocal and SetLocal for stack-slot based variables. Compiler resolves variable names to slot indices and checks mutability at compile time. Immutable variables (let) cannot be reassigned. commit - e415d90b662b2024a6f38196ded11f3881cbf636 commit + 37c978c740aa2c23c2be7f6165ee14b888c150b5 blob - fb580a3dd5681dd53cdf648d3238b0d3ff185147 blob + 4b49dea9826b681b7046ab844cfe218f7c151aec --- src/compiler/mod.rs +++ src/compiler/mod.rs @@ -23,15 +23,25 @@ use crate::span::Span; use crate::vm::value::Value; use opcode::{Chunk, OpCode}; +struct Local { + name: String, + mutable: bool, +} + pub struct Compiler { chunk: Chunk, + locals: Vec, } impl Compiler { pub fn new() -> Self { - Self { chunk: Chunk::new() } + Self { chunk: Chunk::new(), locals: Vec::new() } } + fn resolve_local(&self, name: &str) -> Option { + self.locals.iter().rposition(|l| l.name == name) + } + pub fn compile( mut self, program: &Program, ) -> Result { @@ -40,17 +50,37 @@ impl Compiler { .ok_or_else(|| OlangError::new( "no main() function found", Span::new(0, 0), ))?; - for stmt in &main_fn.body { self.compile_stmt(stmt)?; } - self.chunk.emit_op(OpCode::Return, 0); Ok(self.chunk) } fn compile_stmt(&mut self, stmt: &Stmt) -> Result<(), OlangError> { match stmt { + Stmt::Let { name, mutable, value, .. } => { + self.compile_expr(value)?; + self.locals.push(Local { + name: name.clone(), mutable: *mutable, + }); + } + Stmt::Assign { name, value, span } => { + let slot = self.resolve_local(name).ok_or_else(|| { + OlangError::new( + format!("undefined variable '{name}'"), *span, + ) + })?; + if !self.locals[slot].mutable { + return Err(OlangError::new( + format!("cannot assign to immutable variable '{name}'"), + *span, + )); + } + self.compile_expr(value)?; + self.chunk.emit_op(OpCode::SetLocal, 0); + self.chunk.emit_byte(slot as u8, 0); + } Stmt::Print { args, .. } => { for arg in args { self.compile_expr(arg)?; @@ -74,18 +104,17 @@ impl Compiler { fn compile_expr(&mut self, expr: &Expr) -> Result<(), OlangError> { match expr { - Expr::IntLit(v, _) => { - self.chunk.emit_constant(Value::Int(*v), 0); + Expr::IntLit(v, _) => self.chunk.emit_constant(Value::Int(*v), 0), + Expr::FloatLit(v, _) => self.chunk.emit_constant(Value::Float(*v), 0), + Expr::StrLit(s, _) => self.chunk.emit_constant(Value::Str(s.clone()), 0), + Expr::BoolLit(v, _) => self.chunk.emit_constant(Value::Bool(*v), 0), + Expr::Ident(name, span) => { + let slot = self.resolve_local(name).ok_or_else(|| { + OlangError::new(format!("undefined variable '{name}'"), *span) + })?; + self.chunk.emit_op(OpCode::GetLocal, 0); + self.chunk.emit_byte(slot as u8, 0); } - Expr::FloatLit(v, _) => { - self.chunk.emit_constant(Value::Float(*v), 0); - } - Expr::StrLit(s, _) => { - self.chunk.emit_constant(Value::Str(s.clone()), 0); - } - Expr::BoolLit(v, _) => { - self.chunk.emit_constant(Value::Bool(*v), 0); - } Expr::Unary { op, expr, .. } => { self.compile_expr(expr)?; match op { @@ -106,19 +135,17 @@ impl Compiler { BinOp::Mul => OpCode::Mul, BinOp::Div => OpCode::Div, BinOp::Mod => OpCode::Mod, - _ => { - return Err(OlangError::new( - format!("unsupported binary op: {:?}", op), - Span::new(0, 0), - )); - } + _ => return Err(OlangError::new( + format!("unsupported binary op: {:?}", op), + Span::new(0, 0), + )), }; self.chunk.emit_op(opcode, 0); } - _ => { + Expr::Call { name, args: _, span } => { return Err(OlangError::new( - format!("unsupported expression: {:?}", expr), - Span::new(0, 0), + format!("function calls not yet supported: {name}"), + *span, )); } } blob - 9c9cbdf349de944ccdf23437fd67f1d7b8e00860 blob + fca54dff3512cbf3bac7237083caa50df0523e85 --- src/compiler/opcode.rs +++ src/compiler/opcode.rs @@ -28,6 +28,8 @@ pub enum OpCode { Div, Mod, Negate, + GetLocal, + SetLocal, Print, Return, } @@ -43,8 +45,10 @@ impl OpCode { 5 => Some(OpCode::Div), 6 => Some(OpCode::Mod), 7 => Some(OpCode::Negate), - 8 => Some(OpCode::Print), - 9 => Some(OpCode::Return), + 8 => Some(OpCode::GetLocal), + 9 => Some(OpCode::SetLocal), + 10 => Some(OpCode::Print), + 11 => Some(OpCode::Return), _ => None, } } @@ -58,11 +62,7 @@ pub struct Chunk { impl Chunk { pub fn new() -> Self { - Self { - code: Vec::new(), - constants: Vec::new(), - lines: Vec::new(), - } + Self { code: Vec::new(), constants: Vec::new(), lines: Vec::new() } } pub fn emit_byte(&mut self, byte: u8, line: usize) { @@ -107,6 +107,11 @@ impl Chunk { println!("{:<12} {:4} ({})", "Constant", index, value); offset + 2 } + OpCode::GetLocal | OpCode::SetLocal => { + let slot = self.code[offset + 1] as usize; + println!("{:<12} {:4}", format!("{:?}", op), slot); + offset + 2 + } OpCode::Print => { let count = self.code[offset + 1] as usize; println!("{:<12} {:4}", "Print", count); blob - 6e43e1f2d1aa8021436c58f5f3813ae58beb6812 blob + 4309c4ed0f97962469ee0a7463c9f86ce883004c --- src/vm/mod.rs +++ src/vm/mod.rs @@ -154,6 +154,18 @@ impl VM { }; self.push(result); } + OpCode::GetLocal => { + let slot = self.read_byte() as usize; + let value = self.stack[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; + self.pop(); + } OpCode::Print => { let count = self.read_byte() as usize; let start = self.stack.len() - count;