244 lines
6.6 KiB
Rust
244 lines
6.6 KiB
Rust
use lazy_static::lazy_static;
|
|
use ordered_float::OrderedFloat;
|
|
use std::{
|
|
collections::HashMap,
|
|
fs::File,
|
|
io::{stdin, stdout, BufReader, ErrorKind, Read, Seek, SeekFrom, Write},
|
|
rc::Rc,
|
|
sync::RwLock,
|
|
time::{SystemTime, UNIX_EPOCH},
|
|
};
|
|
|
|
use crate::{
|
|
callable::{Callable, CallingError},
|
|
interpreter::Interpreter,
|
|
value::{CallableType, Value},
|
|
};
|
|
|
|
/// 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()
|
|
}
|
|
|
|
fn arity(&self) -> usize {
|
|
0
|
|
}
|
|
|
|
/// Return the current system time in seconds since the Unix epoch
|
|
fn call(
|
|
&self,
|
|
_interpreter: &mut Interpreter,
|
|
_args: Vec<Value>,
|
|
) -> Result<Value, CallingError> {
|
|
let start = SystemTime::now();
|
|
let since_the_epoch = start
|
|
.duration_since(UNIX_EPOCH)
|
|
.map_err(|e| CallingError::CallFailed(e.to_string()))?;
|
|
|
|
Ok(Value::Number(OrderedFloat(since_the_epoch.as_secs_f64())))
|
|
}
|
|
}
|
|
|
|
lazy_static! {
|
|
static ref FILE_HANDLES: RwLock<HashMap<String, BufReader<File>>> = 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 {
|
|
2
|
|
}
|
|
|
|
fn call(
|
|
&self,
|
|
_interpreter: &mut Interpreter,
|
|
args: Vec<Value>,
|
|
) -> Result<Value, CallingError> {
|
|
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;
|
|
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<Value>,
|
|
) -> Result<Value, CallingError> {
|
|
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(),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
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<Value>) -> Result<Value, CallingError> {
|
|
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<Value>,
|
|
) -> Result<Value, CallingError> {
|
|
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)),
|
|
),
|
|
(
|
|
"out".into(),
|
|
Value::Callable((Rc::new(Out {}), CallableType::Function)),
|
|
),
|
|
(
|
|
"read".into(),
|
|
Value::Callable((Rc::new(ReadFile {}), CallableType::Function)),
|
|
),
|
|
(
|
|
"prompt".into(),
|
|
Value::Callable((Rc::new(PromptAscii {}), CallableType::Function)),
|
|
),
|
|
]
|
|
}
|