Commit Diff


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<Local>,
 }
 
 impl Compiler {
     pub fn new() -> Self {
-        Self { chunk: Chunk::new() }
+        Self { chunk: Chunk::new(), locals: Vec::new() }
     }
 
+    fn resolve_local(&self, name: &str) -> Option<usize> {
+        self.locals.iter().rposition(|l| l.name == name)
+    }
+
     pub fn compile(
         mut self, program: &Program,
     ) -> Result<Chunk, OlangError> {
@@ -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;