2022-09-23 18:55:18 +00:00
|
|
|
use std::{io::{Write, Read}, fs::{OpenOptions, File}, rc::Rc, cell::RefCell, fmt};
|
2022-09-20 13:06:45 +00:00
|
|
|
|
2022-09-23 19:09:22 +00:00
|
|
|
use complexpr::{env::Environment, value::{Value, Native, TypeData}, RuntimeError};
|
|
|
|
|
|
|
|
use crate::declare_fn;
|
2022-09-20 13:06:45 +00:00
|
|
|
|
|
|
|
pub fn load(env: &mut Environment) {
|
2022-09-25 23:25:13 +00:00
|
|
|
let file_type = FILE_TYPE.with(|x| x.clone());
|
|
|
|
env.declare(file_type.name.clone(), Value::Type(file_type));
|
2022-09-20 13:06:45 +00:00
|
|
|
declare_fn!(env, print, 1);
|
|
|
|
declare_fn!(env, println, 1);
|
|
|
|
declare_fn!(env, input, 0);
|
2022-09-25 23:25:13 +00:00
|
|
|
declare_fn!(env, open, 2);
|
2022-09-23 18:55:18 +00:00
|
|
|
declare_fn!(env, close, 1);
|
|
|
|
declare_fn!(env, read, 1);
|
2022-09-25 23:25:13 +00:00
|
|
|
declare_fn!(env, write, 2);
|
|
|
|
declare_fn!(env, flush, 1);
|
2022-09-20 13:06:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn fn_print(args: Vec<Value>) -> Result<Value, RuntimeError> {
|
2022-09-23 18:55:18 +00:00
|
|
|
print!("{}", args[0]);
|
2022-09-20 13:06:45 +00:00
|
|
|
std::io::stdout().flush().map_err(|e| e.to_string())?;
|
|
|
|
Ok(Value::Nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fn_println(args: Vec<Value>) -> Result<Value, RuntimeError> {
|
2022-09-23 18:55:18 +00:00
|
|
|
println!("{}", args[0]);
|
2022-09-20 13:06:45 +00:00
|
|
|
Ok(Value::Nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fn_input(_: Vec<Value>) -> Result<Value, RuntimeError> {
|
|
|
|
let mut buffer = String::new();
|
|
|
|
let stdin = std::io::stdin();
|
|
|
|
stdin.read_line(&mut buffer).map_err(|e| e.to_string())?;
|
|
|
|
if buffer.ends_with('\n') {
|
|
|
|
buffer.pop();
|
|
|
|
}
|
|
|
|
Ok(Value::from(buffer))
|
|
|
|
}
|
2022-09-23 18:55:18 +00:00
|
|
|
|
|
|
|
struct FileBox {
|
|
|
|
f: Option<File>
|
|
|
|
}
|
|
|
|
|
|
|
|
lazy_static::lazy_static! {
|
2022-09-23 19:09:22 +00:00
|
|
|
static ref FILE_TYPE_ID: usize = complexpr::value::generate_type_id();
|
2022-09-23 18:55:18 +00:00
|
|
|
}
|
|
|
|
thread_local!(static FILE_TYPE_NAME: Rc<str> = Rc::from("File"));
|
2022-09-25 23:25:13 +00:00
|
|
|
thread_local!(static FILE_TYPE: complexpr::value::Type = complexpr::value::Type { name: FILE_TYPE_NAME.with(Rc::clone), id: *FILE_TYPE_ID, typedata: TypeData::None });
|
2022-09-23 18:55:18 +00:00
|
|
|
|
|
|
|
impl Native for FileBox {
|
|
|
|
fn as_any(&self) -> &dyn std::any::Any {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2022-09-23 19:09:22 +00:00
|
|
|
fn get_type(&self) -> complexpr::value::Type {
|
2022-09-25 23:25:13 +00:00
|
|
|
FILE_TYPE.with(|x| x.clone())
|
2022-09-23 18:55:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Debug for FileBox {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
fmt::Display::fmt(self, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for FileBox {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "<file {:?}>", self.f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fn_open(args: Vec<Value>) -> Result<Value, RuntimeError> {
|
|
|
|
let fname = match &args[0] {
|
|
|
|
Value::String(s) => s,
|
|
|
|
_ => return Err(format!("Expected filename, got {}", args[0]).into())
|
|
|
|
};
|
2022-09-25 23:25:13 +00:00
|
|
|
let opts = match &args[1] {
|
|
|
|
Value::String(s) => s,
|
|
|
|
_ => return Err(format!("Expected open options string, got {}", args[0]).into())
|
|
|
|
};
|
|
|
|
let mut options = OpenOptions::new();
|
|
|
|
options.append(false);
|
|
|
|
for c in opts.chars() {
|
|
|
|
match c {
|
|
|
|
'r' => options.read(true),
|
|
|
|
'w' => options.write(true).append(false).truncate(true),
|
|
|
|
'a' => options.write(true).append(true).truncate(false),
|
|
|
|
's' => options.write(true).append(false).truncate(false),
|
|
|
|
'x' => options.create_new(false).create(true),
|
|
|
|
'X' => options.create(false).create_new(true),
|
|
|
|
_ => return Err(format!("Character {} is not valid in an open options string", c).into())
|
|
|
|
};
|
|
|
|
}
|
|
|
|
println!("{:?}", options);
|
|
|
|
let f = match options.open(fname.as_ref()) {
|
2022-09-23 18:55:18 +00:00
|
|
|
Ok(f) => f,
|
|
|
|
Err(e) => return Err(format!("Could not open file '{}': {}", fname, e).into())
|
|
|
|
};
|
|
|
|
Ok(Value::Native(Rc::new(RefCell::new(FileBox { f: Some(f) }))))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fn_close(args: Vec<Value>) -> Result<Value, RuntimeError> {
|
|
|
|
match &args[0] {
|
|
|
|
Value::Native(s) => {
|
|
|
|
let mut bs = s.borrow_mut();
|
|
|
|
let f: &mut FileBox = match bs.as_any_mut().downcast_mut() {
|
|
|
|
Some(f) => f,
|
|
|
|
None => return Err(format!("Expected a file, got {}", args[0]).into())
|
|
|
|
};
|
2022-09-25 23:25:13 +00:00
|
|
|
match f.f.take() {
|
|
|
|
Some(x) => drop(x),
|
|
|
|
None => return Err("File already closed".into())
|
|
|
|
}
|
2022-09-23 18:55:18 +00:00
|
|
|
Ok(Value::Nil)
|
|
|
|
},
|
2022-09-25 23:25:13 +00:00
|
|
|
_ => Err(format!("Expected a file, got {}", args[0]).into())
|
2022-09-23 18:55:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fn_read(args: Vec<Value>) -> Result<Value, RuntimeError> {
|
|
|
|
match &args[0] {
|
|
|
|
Value::Native(s) => {
|
|
|
|
let mut bs = s.borrow_mut();
|
|
|
|
let f: &mut FileBox = match bs.as_any_mut().downcast_mut() {
|
|
|
|
Some(f) => f,
|
|
|
|
None => return Err(format!("Expected a file, got {}", args[0]).into())
|
|
|
|
};
|
|
|
|
if let Some(file) = &mut f.f {
|
|
|
|
let mut buf = String::new();
|
|
|
|
match file.read_to_string(&mut buf) {
|
2022-09-25 23:25:13 +00:00
|
|
|
Ok(_) => Ok(Value::from(buf)),
|
|
|
|
Err(e) => Err(format!("Error reading file {}: {}", f, e).into())
|
2022-09-23 18:55:18 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err("Attempt to read file that has been closed".into())
|
|
|
|
}
|
|
|
|
},
|
2022-09-25 23:25:13 +00:00
|
|
|
_ => Err(format!("Expected a file, got {}", args[0]).into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fn_write(args: Vec<Value>) -> Result<Value, RuntimeError> {
|
|
|
|
let contents = match &args[1] {
|
|
|
|
Value::String(s) => s,
|
|
|
|
_ => return Err(format!("Expected string to write as second argument, got {}", args[1]).into())
|
|
|
|
};
|
|
|
|
match &args[0] {
|
|
|
|
Value::Native(s) => {
|
|
|
|
let mut bs = s.borrow_mut();
|
|
|
|
let f: &mut FileBox = match bs.as_any_mut().downcast_mut() {
|
|
|
|
Some(f) => f,
|
|
|
|
None => return Err(format!("Expected a file as first argument, got {}", args[0]).into())
|
|
|
|
};
|
|
|
|
if let Some(file) = &mut f.f {
|
|
|
|
match file.write(contents.as_bytes()) {
|
|
|
|
Ok(_) => Ok(Value::Nil),
|
|
|
|
Err(e) => Err(format!("Error writing to file {}: {}", f, e).into())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err("Attempt to write to file that has been closed".into())
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => Err(format!("Expected a file as first argument, got {}", args[0]).into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fn_flush(args: Vec<Value>) -> Result<Value, RuntimeError> {
|
|
|
|
match &args[0] {
|
|
|
|
Value::Native(s) => {
|
|
|
|
let mut bs = s.borrow_mut();
|
|
|
|
let f: &mut FileBox = match bs.as_any_mut().downcast_mut() {
|
|
|
|
Some(f) => f,
|
|
|
|
None => return Err(format!("Expected a file, got {}", args[0]).into())
|
|
|
|
};
|
|
|
|
if let Some(file) = &mut f.f {
|
|
|
|
match file.flush() {
|
|
|
|
Ok(_) => Ok(Value::Nil),
|
|
|
|
Err(e) => Err(format!("Error flushing file {}: {}", f, e).into())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err("Attempt to flush file that has been closed".into())
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => Err(format!("Expected a file, got {}", args[0]).into())
|
2022-09-23 18:55:18 +00:00
|
|
|
}
|
|
|
|
}
|