From 463e4abd3094bfa8549363250dcc7bc828ce96ab Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Wed, 28 May 2025 09:05:18 +0200 Subject: [PATCH 1/3] wip --- .vscode/launch.json | 30 ++++++++++++ bf/fibonacci.bf | 6 +++ bf/helloworld.bf | 1 + bf/simple_loop.bf | 1 + lox/nested_while.lox | 14 ++++++ lox/scope.lox | 8 ++++ lox/simple_for.lox | 3 ++ lox/simple_while.lox | 5 ++ src/environment.rs | 16 +++---- src/interpreter.rs | 9 +--- src/lib.rs | 3 ++ src/native_functions.rs | 100 ++++++++++++++++++++++++++++++++++++---- src/value.rs | 2 +- 13 files changed, 172 insertions(+), 26 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 bf/fibonacci.bf create mode 100644 bf/helloworld.bf create mode 100644 bf/simple_loop.bf create mode 100644 lox/nested_while.lox create mode 100644 lox/scope.lox create mode 100644 lox/simple_for.lox create mode 100644 lox/simple_while.lox diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4b8c354 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://raw.githubusercontent.com/mfussenegger/dapconfig-schema/master/dapconfig-schema.json", + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Run Lox Interpreter", + "cargo": { + "args": [ + "build", + "--package", + "rox", + "--message-format", + "json" + ] + }, + "program": "${workspaceFolder}/target/debug/rox", + "args": [ + "run", + "-s", + "./lox/simple_for.lox" + ], + "env": { + "RUST_LOG": "debug" + }, + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/bf/fibonacci.bf b/bf/fibonacci.bf new file mode 100644 index 0000000..ac429e9 --- /dev/null +++ b/bf/fibonacci.bf @@ -0,0 +1,6 @@ +>++++++++++>+>+[ + [+++++[>++++++++<-]>.<++++++[>--------<-]+<<<]>.>>[ + [-]<[>+<-]>>[<<+>+>-]<[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- + [>+<-[>+<-[>+<-[>[-]>+>+<<<-[>+<-]]]]]]]]]]]+>>> + ]<<< +] diff --git a/bf/helloworld.bf b/bf/helloworld.bf new file mode 100644 index 0000000..8fa0f72 --- /dev/null +++ b/bf/helloworld.bf @@ -0,0 +1 @@ +++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++. diff --git a/bf/simple_loop.bf b/bf/simple_loop.bf new file mode 100644 index 0000000..ffa6054 --- /dev/null +++ b/bf/simple_loop.bf @@ -0,0 +1 @@ +++++++[>++++++++<-]>. // prints '0' diff --git a/lox/nested_while.lox b/lox/nested_while.lox new file mode 100644 index 0000000..31a7e2a --- /dev/null +++ b/lox/nested_while.lox @@ -0,0 +1,14 @@ +{ + var i = 0; + var j = 0; + while (i < 10) { + print i; + + while (j < 100) { + print j; + j = j + 1; + } + + i = i + 1; + } +} diff --git a/lox/scope.lox b/lox/scope.lox new file mode 100644 index 0000000..99293e7 --- /dev/null +++ b/lox/scope.lox @@ -0,0 +1,8 @@ +var a = "outer"; + +{ + var a = "inner"; + print a; // expect: inner +} + +print a; diff --git a/lox/simple_for.lox b/lox/simple_for.lox new file mode 100644 index 0000000..1c15ec5 --- /dev/null +++ b/lox/simple_for.lox @@ -0,0 +1,3 @@ +for (var i = 0; i < 10; i = i + 1) { + print i; +} diff --git a/lox/simple_while.lox b/lox/simple_while.lox new file mode 100644 index 0000000..2acbf36 --- /dev/null +++ b/lox/simple_while.lox @@ -0,0 +1,5 @@ +var i = 0; +while (i < 10) { + print i; + i = i + 1; +} diff --git a/src/environment.rs b/src/environment.rs index edfd980..e8e4747 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -99,16 +99,14 @@ impl Environment { /// Find an environment at a specific lexical distance from the current one. fn ancestor(&self, distance: usize) -> Result { - let mut environment = self.clone(); - - for _ in 0..distance { - let enclosing = match &environment.enclosing { - Some(enc) => enc.borrow().clone(), - None => return Err(EnvironmentError::InvalidDistance), - }; - environment = enclosing; + // println!("{distance}: {self:?}"); + if distance == 0 { + return Ok(self.clone()); } - Ok(environment) + match &self.enclosing { + Some(enclosing) => enclosing.borrow().ancestor(distance - 1), + None => Err(EnvironmentError::InvalidDistance), + } } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 0388687..dabd215 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -52,9 +52,9 @@ pub struct Interpreter { /// Global environment containing built-in functions and top-level variables pub globals: Rc>, /// Current environment for variable lookups and assignments - environment: Rc>, + pub environment: Rc>, /// Map of expressions to their lexical distance for variable resolution - locals: HashMap, + pub locals: HashMap, } /// Default configuration for the interpreter, with builtin native functions. @@ -452,11 +452,6 @@ impl Interpreter { fn assign(&mut self, name: &Token, expression: &Expression) -> Result { let value = self.evaluate(expression)?; - self.environment - .borrow_mut() - .assign(name, value.clone()) - .map_err(InterpreterError::UndefinedVariable)?; - if let Some(distance) = self.locals.get(expression) { self.environment .borrow_mut() diff --git a/src/lib.rs b/src/lib.rs index d27e546..c072493 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,9 @@ fn run_rox(input: &str, interpreter: &mut Interpreter) -> Result<(), RoxError> { debug!("AST:\n{}", crate::ast_printer::print(&ast)); resolver.resolve(&ast)?; + println!("globals: {:?}", interpreter.globals); + println!("environment: {:?}", interpreter.environment); + println!("locals: {:?}", interpreter.locals); interpreter.run(ast)?; Ok(()) diff --git a/src/native_functions.rs b/src/native_functions.rs index c02c6ca..f72b221 100644 --- a/src/native_functions.rs +++ b/src/native_functions.rs @@ -3,7 +3,7 @@ use ordered_float::OrderedFloat; use std::{ collections::HashMap, fs::File, - io::{BufReader, ErrorKind, Read}, + io::{stdin, stdout, BufReader, ErrorKind, Read, Seek, SeekFrom, Write}, rc::Rc, sync::RwLock, time::{SystemTime, UNIX_EPOCH}, @@ -53,7 +53,7 @@ impl Callable for ReadFile { } fn arity(&self) -> usize { - 1 + 2 } fn call( @@ -61,18 +61,24 @@ impl Callable for ReadFile { _interpreter: &mut Interpreter, args: Vec, ) -> Result { - if args.len() != self.arity() { - return Err(CallingError::ArgumentMismatch(self.arity(), args.len())); - } - - let file_path = args - .first() - .ok_or(CallingError::CallFailed("arg not readable".into()))?; + let file_path = args.first().ok_or(CallingError::CallFailed( + "first arg must be file path".into(), + ))?; if let Value::String(file_path) = file_path { self.ensure_file_handle(file_path)?; let mut file_handles = FILE_HANDLES.write().unwrap(); let reader = file_handles.get_mut(file_path).unwrap(); let mut buffer = [0; 1]; + + let backwards = args.get(1).unwrap_or(&Value::Boolean(false)); + if let Value::Boolean(backwards) = backwards { + if *backwards { + reader + .seek(SeekFrom::Current(-2)) + .map_err(|e| CallingError::CallFailed(e.to_string()))?; + } + } + let character = match reader.read_exact(&mut buffer) { Ok(_) => { let character = buffer[0] as char; @@ -143,9 +149,81 @@ impl Callable for Out { } } +struct AsciiOut; +impl Callable for AsciiOut { + fn name(&self) -> String { + "asciiOut".into() + } + + fn arity(&self) -> usize { + 1 + } + + fn call(&self, interpreter: &mut Interpreter, args: Vec) -> Result { + if args.len() != self.arity() { + return Err(CallingError::ArgumentMismatch(self.arity(), args.len())); + } + + let value = args + .first() + .ok_or(CallingError::CallFailed("arg not readable".into()))?; + if let Value::Number(value) = value { + let ascii = value.0 as u8 as char; + print!("{ascii}"); + + Ok(Value::Nil) + } else { + Err(CallingError::CallFailed( + "file_path arg must be a number".into(), + )) + } + } +} + +struct PromptAscii; +impl Callable for PromptAscii { + fn name(&self) -> String { + "prompt_ascii".into() + } + + fn arity(&self) -> usize { + 1 + } + + fn call( + &self, + _interpreter: &mut Interpreter, + args: Vec, + ) -> Result { + if args.len() != self.arity() { + return Err(CallingError::ArgumentMismatch(self.arity(), args.len())); + } + + let prompt = args + .first() + .ok_or(CallingError::CallFailed("arg not readable".into()))?; + if let Value::String(prompt) = prompt { + print!("{prompt} "); + stdout().flush(); + + let mut buffer = [0; 1]; + stdin().read_exact(&mut buffer); + + todo!() + // Ok(Value::Number(buffer[0] as char)) + } else { + Err(CallingError::CallFailed("prompt must be a string".into())) + } + } +} + /// Return all native functions available to the Lox interpreter pub fn all() -> Vec<(String, Value)> { vec![ + ( + "asciiOut".into(), + Value::Callable((Rc::new(AsciiOut {}), CallableType::Function)), + ), ( "clock".into(), Value::Callable((Rc::new(Clock {}), CallableType::Function)), @@ -158,5 +236,9 @@ pub fn all() -> Vec<(String, Value)> { "read".into(), Value::Callable((Rc::new(ReadFile {}), CallableType::Function)), ), + ( + "prompt".into(), + Value::Callable((Rc::new(PromptAscii {}), CallableType::Function)), + ), ] } diff --git a/src/value.rs b/src/value.rs index db858a5..cc1ff27 100644 --- a/src/value.rs +++ b/src/value.rs @@ -4,7 +4,7 @@ use ordered_float::OrderedFloat; use crate::{callable::Callable, instance, token::Literal}; -/// Represents the type of a callable value in Lox. +/// Represent the type of a callable value in Lox. #[derive(Clone, Debug, PartialEq)] pub enum CallableType { /// A regular function or method From 94b57f83048ed7ccb28b9ec059aae110e9f05166 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Wed, 28 May 2025 09:21:59 +0200 Subject: [PATCH 2/3] fix resolving of vars --- lox/fibonacci.lox | 1 - src/environment.rs | 49 ++++++++++++++++++++++---------------- src/interpreter.rs | 52 ++++++++++++++++++++++------------------- src/lib.rs | 3 --- src/native_functions.rs | 6 ++++- src/resolver.rs | 8 +++---- 6 files changed, 66 insertions(+), 53 deletions(-) diff --git a/lox/fibonacci.lox b/lox/fibonacci.lox index 630e3b2..117d457 100644 --- a/lox/fibonacci.lox +++ b/lox/fibonacci.lox @@ -1,7 +1,6 @@ var a = 0; var temp; -var b = 1; for(var b = 1; a < 1000; b = temp + b) { print a; temp = a; diff --git a/src/environment.rs b/src/environment.rs index e8e4747..8966ee8 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -55,15 +55,24 @@ impl Environment { } } - /// Assign a value to a variable at a specific lexical distance (number of scopes away). - /// Used by the resolver to handle closures correctly. + /// Assign a value to a variable in an environment at a specific lexical distance. pub fn assign_at( - &mut self, + env: Rc>, distance: usize, token: &Token, value: Value, ) -> Result<(), EnvironmentError> { - self.ancestor(distance)? + let mut current_env = env; + for _ in 0..distance { + let enclosing = current_env + .borrow() + .enclosing + .clone() + .ok_or(EnvironmentError::InvalidDistance)?; + current_env = enclosing; + } + current_env + .borrow_mut() .values .insert(token.lexeme.clone(), value); Ok(()) @@ -85,9 +94,22 @@ impl Environment { } /// Get a variable's value from an environment at a specific lexical distance. - /// Used by the resolver to handle closures correctly. - pub fn get_at(&self, distance: usize, token: &Token) -> Result { - if let Some(v) = self.ancestor(distance)?.values.get(token.lexeme.as_str()) { + pub fn get_at( + env: Rc>, + distance: usize, + token: &Token, + ) -> Result { + let mut current_env_rc = env; + for _ in 0..distance { + let enclosing = current_env_rc + .borrow() + .enclosing + .clone() + .ok_or(EnvironmentError::InvalidDistance)?; + current_env_rc = enclosing; + } + let borrowed_env = current_env_rc.borrow(); + if let Some(v) = borrowed_env.values.get(token.lexeme.as_str()) { Ok(v.clone()) } else { Err(EnvironmentError::UndefinedVariable( @@ -96,17 +118,4 @@ impl Environment { )) } } - - /// Find an environment at a specific lexical distance from the current one. - fn ancestor(&self, distance: usize) -> Result { - // println!("{distance}: {self:?}"); - if distance == 0 { - return Ok(self.clone()); - } - - match &self.enclosing { - Some(enclosing) => enclosing.borrow().ancestor(distance - 1), - None => Err(EnvironmentError::InvalidDistance), - } - } } diff --git a/src/interpreter.rs b/src/interpreter.rs index dabd215..7adfef3 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -242,7 +242,7 @@ impl Interpreter { right, } => self.binary(left, operator, right), Expression::Variable { name } => self.var_expression(name, expression), - Expression::Assign { name, value } => self.assign(name, value), + Expression::Assign { .. } => self.assign(expression), Expression::Logical { left, operator, @@ -323,8 +323,9 @@ impl Interpreter { method: &Token, ) -> Result { if let Some(distance) = self.locals.get(expression) { - let superclass = self.environment.borrow().get_at(*distance, keyword)?; - let object = self.environment.borrow().get_at( + let superclass = Environment::get_at(self.environment.clone(), *distance, keyword)?; + let object = Environment::get_at( + self.environment.clone(), *distance - 1, &Token { token_type: TokenType::This, @@ -449,22 +450,27 @@ impl Interpreter { } /// Assign the value of an expression to a variable. - fn assign(&mut self, name: &Token, expression: &Expression) -> Result { - let value = self.evaluate(expression)?; + fn assign(&mut self, assign_expr: &Expression) -> Result { + if let Expression::Assign { name, value } = assign_expr { + let evaluated_value = self.evaluate(value)?; - if let Some(distance) = self.locals.get(expression) { - self.environment - .borrow_mut() - .assign_at(*distance, name, value.clone()) - .map_err(InterpreterError::UndefinedVariable)?; + if let Some(distance) = self.locals.get(assign_expr) { + Environment::assign_at( + self.environment.clone(), + *distance, + name, + evaluated_value.clone(), + )?; + } else { + self.globals + .borrow_mut() + .assign(name, evaluated_value.clone())?; + } + Ok(evaluated_value) } else { - self.globals - .borrow_mut() - .assign(name, value.clone()) - .map_err(InterpreterError::UndefinedVariable)?; + // This should ideally not be reached if called correctly from `evaluate` + unreachable!("Interpreter::assign called with a non-Assign expression"); } - - Ok(value) } /// Convert the literal value into a Value. @@ -537,15 +543,13 @@ impl Interpreter { expression: &Expression, ) -> Result { if let Some(distance) = self.locals.get(expression) { - self.environment - .borrow() - .get_at(*distance, name) - .map_err(InterpreterError::UndefinedVariable) + Ok(Environment::get_at( + self.environment.clone(), + *distance, + name, + )?) } else { - self.globals - .borrow() - .get(name) - .map_err(InterpreterError::UndefinedVariable) + Ok(self.globals.borrow().get(name)?) } } diff --git a/src/lib.rs b/src/lib.rs index c072493..d27e546 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,9 +94,6 @@ fn run_rox(input: &str, interpreter: &mut Interpreter) -> Result<(), RoxError> { debug!("AST:\n{}", crate::ast_printer::print(&ast)); resolver.resolve(&ast)?; - println!("globals: {:?}", interpreter.globals); - println!("environment: {:?}", interpreter.environment); - println!("locals: {:?}", interpreter.locals); interpreter.run(ast)?; Ok(()) diff --git a/src/native_functions.rs b/src/native_functions.rs index f72b221..2e61e13 100644 --- a/src/native_functions.rs +++ b/src/native_functions.rs @@ -159,7 +159,11 @@ impl Callable for AsciiOut { 1 } - fn call(&self, interpreter: &mut Interpreter, args: Vec) -> Result { + fn call( + &self, + _interpreter: &mut Interpreter, + args: Vec, + ) -> Result { if args.len() != self.arity() { return Err(CallingError::ArgumentMismatch(self.arity(), args.len())); } diff --git a/src/resolver.rs b/src/resolver.rs index 5e97ac6..5c39cc9 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -71,7 +71,7 @@ impl<'a> Resolver<'a> { Statement::Block(statements) => self.resolve_block(statements), Statement::Print(expression) => self.resolve_expression(expression), Statement::Expression(expression) => self.resolve_expression(expression), - Statement::Var { name, initializer } => self.resolve_var(name, *initializer.clone()), + Statement::Var { name, initializer } => self.resolve_var(name, initializer.as_ref().as_ref()), Statement::If { condition, then_branch, @@ -262,12 +262,12 @@ impl<'a> Resolver<'a> { fn resolve_var( &mut self, name: &Token, - initializer: Option, + initializer: Option<&Expression>, ) -> Result<(), ResolverError> { self.declare(name)?; - if let Some(initializer) = initializer { - self.resolve_expression(&initializer)?; + if let Some(init_expr_ref) = initializer { + self.resolve_expression(init_expr_ref)?; } self.define(name); From 9aa6bef6d92b09488826b53f9b34fe60a5a565ec Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Wed, 28 May 2025 09:40:35 +0200 Subject: [PATCH 3/3] update bf interpreter --- lox/bf.lox | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lox/bf.lox b/lox/bf.lox index cb6deab..bb9281b 100644 --- a/lox/bf.lox +++ b/lox/bf.lox @@ -71,7 +71,7 @@ class Interpreter { } - if (instruction == "[" and tape.current.data < 0.0001) { + if (instruction == "[" and tape.current.data = 0) { instruction = read(this.bfpath); var bracketCount = 1; while (bracketCount > 0 and instruction != nil) { @@ -81,7 +81,7 @@ class Interpreter { } } - if (instruction == "]" and tape.current.data >= 0.0001) { + if (instruction == "]" and tape.current.data != 0) { instruction = read(this.bfpath, true); var bracketCount = 1; while (bracketCount > 0 and instruction != nil) {