mod storage;
use crate::interned_stdlib::INTERNED_STL;
use crate::no_path;
use crate::optimizations::run_optimizations;
use crate::parsing::{build_program, tokenize};
use crate::prelude::*;
use std::io::{BufRead, BufReader, Read, Write, stderr, stdin, stdout};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::{env, fs, io};
pub use storage::Storage;
#[derive(Clone)]
pub(crate) enum Directory {
Regular(PathBuf),
FromEval,
InternedSTL,
}
pub struct State {
pub storage: Storage,
pub stdin: Box<dyn BufRead>,
pub stdout: WriteHandle,
pub stderr: WriteHandle,
pub(crate) file_directory: Directory,
pub(crate) current_file_path: Option<PathBuf>,
pub(crate) exit_unwind_value: Option<Atom>,
pub(crate) backtrace: Vec<Span>,
pub(crate) current_doc_comment: Option<String>,
pub(crate) current_fn_name: Option<String>,
pub(crate) import_stack: Vec<PathBuf>,
code: Option<String>,
next_type_id: i64,
pub(crate) optimizations_enabled: bool,
__private: (),
}
impl Default for State {
fn default() -> Self {
Self::new()
}
}
impl State {
pub fn new() -> Self {
Self {
storage: Storage::initial(),
stdin: Box::new(BufReader::new(stdin())),
stdout: WriteHandle::new_write(stdout()),
stderr: WriteHandle::new_write(stderr()),
file_directory: Directory::InternedSTL,
current_file_path: None,
exit_unwind_value: None,
backtrace: Vec::new(),
current_doc_comment: None,
current_fn_name: None,
import_stack: Vec::new(),
code: None,
next_type_id: Atom::MIN_OBJECT_TY_ID,
optimizations_enabled: false,
__private: (),
}
}
#[must_use = "this returns the new state without modifying the original"]
pub fn with_code(mut self, code: impl AsRef<str>) -> Self {
self.code = Some(code.as_ref().to_owned());
self
}
#[must_use = "this returns the new state without modifying the original"]
pub fn with_source_file(mut self, path: impl AsRef<Path>) -> io::Result<Self> {
self.code = Some(fs::read_to_string(&path)?);
self.current_file_path = Some(path.as_ref().to_owned());
let mut current_dir = path
.as_ref()
.parent()
.ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidFilename, "file path has no parent")
})?
.to_path_buf();
if current_dir == PathBuf::new() {
current_dir = PathBuf::from(".");
}
self.file_directory = Directory::Regular(current_dir);
Ok(self)
}
#[must_use = "this returns the new state without modifying the original"]
pub fn with_source_directory(mut self, dir_path: impl AsRef<Path>) -> Self {
self.file_directory = Directory::Regular(dir_path.as_ref().to_path_buf());
self
}
#[must_use = "this returns the new state without modifying the original"]
pub fn enable_optimizations(mut self) -> Self {
self.optimizations_enabled = true;
self
}
#[must_use = "this returns the new state without modifying the original"]
pub fn with_cwd(self) -> Self {
self.with_source_directory(env::current_dir().unwrap())
}
pub fn run(&mut self) -> Result<Atom> {
self.exit_unwind_value = None;
let code = format!(
"_(\n{}\n)",
self.code
.as_ref()
.expect("setting the source code is required")
);
let file_path = if let Some(path) = &self.current_file_path {
Rc::new(path.clone())
} else {
no_path()
};
let tokens = tokenize(&code, file_path)?;
let mut program = build_program(tokens)?;
if self.optimizations_enabled {
run_optimizations(&mut program);
}
self.import_prelude();
let result = program.eval(self)?;
if let Some(exit_unwind_value) = &self.exit_unwind_value {
return Ok(exit_unwind_value.clone());
}
Ok(result)
}
fn import_prelude(&mut self) {
if matches!(self.file_directory, Directory::InternedSTL) {
return;
}
let mut import_state = Self::new();
let code = INTERNED_STL
.get("prelude")
.expect("`prelude.re` missing from STL");
import_state = import_state.with_code(code);
import_state.set_current_file_path("<stl:prelude>");
import_state.optimizations_enabled = self.optimizations_enabled;
import_state.run().expect("prelude import failed");
self.storage.extend_from(import_state.storage, None);
}
pub(crate) fn write_to_stdout(&mut self, msg: &str) {
self.stdout.as_write().write_all(msg.as_bytes()).unwrap();
}
pub(crate) fn write_to_stderr(&mut self, msg: &str) {
self.stderr.as_write().write_all(msg.as_bytes()).unwrap();
}
pub(crate) fn set_current_file_path(&mut self, path: impl AsRef<Path>) {
self.current_file_path = Some(path.as_ref().to_owned());
}
pub fn make_type_id(&mut self) -> i64 {
let old = self.next_type_id;
self.next_type_id += 1;
old
}
pub fn raise(&self, error: impl Into<String>, msg: impl Into<String>) -> Exception {
Exception::with_trace(error, msg, &self.backtrace)
}
pub(crate) fn get_function(&self, name: &str) -> Result<Function> {
match self.storage.get(name) {
Some(atom) => {
if let Atom::Function(func) = atom {
Ok(func.clone())
} else {
raise!(self, "Name", "`{name}` is not a function")
}
}
None => raise!(self, "Name", "no function `{name}` found"),
}
}
}
pub trait ReadAndWrite: Read + Write {}
impl<T> ReadAndWrite for T where T: Read + Write {}
pub enum WriteHandle {
ReadWrite(Box<dyn ReadAndWrite>),
Write(Box<dyn Write>),
}
impl WriteHandle {
pub fn as_write(&mut self) -> &mut dyn Write {
match self {
Self::Write(w) => w,
Self::ReadWrite(w) => w,
}
}
pub fn new_write(write: impl Write + 'static) -> Self {
Self::Write(Box::new(write))
}
pub fn as_read(&mut self) -> Option<&mut dyn Read> {
match self {
Self::ReadWrite(w) => Some(w),
Self::Write(_) => None,
}
}
pub fn new_read_write(read_write: impl ReadAndWrite + 'static) -> Self {
Self::ReadWrite(Box::new(read_write))
}
pub fn read_to_string(&mut self) -> String {
let Self::ReadWrite(buf) = self else {
panic!("read_to_string(): cannot read from write only handle")
};
let mut vec = vec![0; 1024];
let mut n = 0;
let mut last_was_zero = false;
loop {
let amount = buf.read(&mut vec[n..]).unwrap();
n += amount;
if amount == 0 {
if last_was_zero {
break;
}
vec.extend([0; 1024]);
last_was_zero = true;
} else {
last_was_zero = false;
}
}
vec.retain(|&x| x != 0);
String::from_utf8(vec).unwrap()
}
}