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