diff --git a/lox/read_file.lox b/lox/read_file.lox new file mode 100644 index 0000000..28c5d9a --- /dev/null +++ b/lox/read_file.lox @@ -0,0 +1,6 @@ +var filepath = "Cargo.toml"; +var c = read(filepath); +while (c != nil) { + out(c); + c = read(filepath); +} diff --git a/src/native_functions.rs b/src/native_functions.rs index 73438cf..c02c6ca 100644 --- a/src/native_functions.rs +++ b/src/native_functions.rs @@ -1,10 +1,14 @@ +use lazy_static::lazy_static; +use ordered_float::OrderedFloat; use std::{ + collections::HashMap, + fs::File, + io::{BufReader, ErrorKind, Read}, rc::Rc, + sync::RwLock, time::{SystemTime, UNIX_EPOCH}, }; -use ordered_float::OrderedFloat; - use crate::{ callable::{Callable, CallingError}, interpreter::Interpreter, @@ -13,7 +17,6 @@ use crate::{ /// Native clock function that returns the current time in seconds since the Unix epoch struct Clock; - impl Callable for Clock { fn name(&self) -> String { "clock".into() @@ -27,7 +30,7 @@ impl Callable for Clock { fn call( &self, _interpreter: &mut Interpreter, - _args: Vec, + _args: Vec, ) -> Result { let start = SystemTime::now(); let since_the_epoch = start @@ -38,10 +41,122 @@ impl Callable for Clock { } } +lazy_static! { + static ref FILE_HANDLES: RwLock>> = RwLock::new(HashMap::new()); +} + +/// Read function that returns each byte of a file. +struct ReadFile; +impl Callable for ReadFile { + fn name(&self) -> String { + "read".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 file_path = args + .first() + .ok_or(CallingError::CallFailed("arg not readable".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 character = match reader.read_exact(&mut buffer) { + Ok(_) => { + let character = buffer[0] as char; + Value::String(character.to_string()) + } + Err(e) if e.kind() == ErrorKind::UnexpectedEof => Value::Nil, + Err(e) => return Err(CallingError::CallFailed(e.to_string())), + }; + + Ok(character) + } else { + Err(CallingError::CallFailed( + "file_path arg must be a string".into(), + )) + } + } +} + +impl ReadFile { + fn is_fresh(&self, file_path: &str) -> bool { + let file_handles = FILE_HANDLES.read().unwrap(); + !file_handles.contains_key(file_path) + } + + fn ensure_file_handle(&self, file_path: &str) -> Result<(), CallingError> { + if self.is_fresh(file_path) { + let file = + File::open(file_path).map_err(|e| CallingError::CallFailed(e.to_string()))?; + let reader = BufReader::new(file); + let mut file_handles = FILE_HANDLES.write().unwrap(); + file_handles.insert(file_path.to_string(), reader); + } + + Ok(()) + } +} + +struct Out; +impl Callable for Out { + fn name(&self) -> String { + "out".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::String(value) = value { + print!("{value}"); + Ok(Value::Nil) + } else { + Err(CallingError::CallFailed( + "file_path arg must be a string".into(), + )) + } + } +} + /// Return all native functions available to the Lox interpreter pub fn all() -> Vec<(String, Value)> { - vec![( - "clock".into(), - Value::Callable((Rc::new(Clock {}), CallableType::Function)), - )] + vec![ + ( + "clock".into(), + Value::Callable((Rc::new(Clock {}), CallableType::Function)), + ), + ( + "out".into(), + Value::Callable((Rc::new(Out {}), CallableType::Function)), + ), + ( + "read".into(), + Value::Callable((Rc::new(ReadFile {}), CallableType::Function)), + ), + ] }