use std::error::Error;
use std::fmt::Display;
use std::io::Read;
use std::rc::Rc;
use pest::iterators::Pairs;
use crate::environment::Environment;
use crate::rc_world;
use crate::utils::QuotedStr;
use super::value::Value;
use super::Context;
use super::ErrorLogger;
use super::Expression;
use super::Rule;
use super::State;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Format {
Text,
Ryan,
}
impl Format {
pub(crate) fn load(
self,
env: Environment,
mut reader: Box<dyn Read>,
) -> Result<Value, Box<dyn Error + 'static>> {
let mut text = String::new();
reader.read_to_string(&mut text)?;
match self {
Self::Text => Ok(Value::Text(rc_world::string_to_rc(text))),
Self::Ryan => {
let parsed = crate::parser::parse(&text).map_err(Box::new)?;
let value = crate::parser::eval(env.clone(), &parsed).map_err(Box::new)?;
Ok(value)
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Import {
pub path: Rc<str>,
pub format: Format,
pub default: Option<Box<Expression>>,
}
impl Display for Import {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.format {
Format::Ryan => write!(f, "import {}", QuotedStr(&self.path))?,
Format::Text => write!(f, "import {} as text", QuotedStr(&self.path))?,
}
if let Some(default) = &self.default {
write!(f, " or {default}")?;
}
Ok(())
}
}
impl Import {
pub(super) fn parse(logger: &mut ErrorLogger, pairs: Pairs<'_, Rule>) -> Self {
let mut path = None;
let mut format = None;
let mut default = None;
for pair in pairs {
match pair.as_rule() {
Rule::text => {
path = Some(rc_world::string_to_rc(
logger.absorb(&pair, crate::utils::unescape(pair.as_str())),
))
}
Rule::importFormatText => format = Some(Format::Text),
Rule::expression => default = Some(Expression::parse(logger, pair.into_inner())),
_ => unreachable!(),
}
}
Import {
path: path.expect("there is always a path in an import"),
format: format.unwrap_or(Format::Ryan),
default: default.map(Box::new),
}
}
pub(super) fn eval(&self, state: &mut State) -> Option<Value> {
state.push_ctx(Context::LoadingImport(self.path.clone()));
let value = match state.environment.load(self.format, &self.path) {
Ok(value) => value,
Err(err) => {
if let Some(default) = &self.default {
default.eval(state)?
} else {
state.absorb(Err(err))?
}
}
};
state.pop_ctx();
Some(value)
}
}