use std::path::{Path, PathBuf};
use nom_locate::LocatedSpan;
use crate::context::Context;
use crate::error::{ErrKind, Error};
use crate::generics::GenericUser;
use crate::instance::ObjectInstance;
use crate::instruction::{InstrKind, Instruction};
use crate::io_trait::JkReader;
use crate::location::SpanTuple;
use crate::parser::constructs;
use crate::typechecker::{CheckedType, TypeCheck, TypeCtx};
#[derive(Clone)]
pub struct Incl {
path: String,
alias: Option<String>,
base: Option<PathBuf>,
typechecked: bool,
instructions: Vec<Box<dyn Instruction>>,
location: Option<SpanTuple>,
}
const DEFAULT_INCL: &str = "lib.jk";
impl Incl {
pub fn new(path: String, alias: Option<String>) -> Incl {
Incl {
path,
alias,
base: None,
typechecked: false,
instructions: vec![],
location: None,
}
}
fn fetch_instructions(
&self,
formatted: &Path,
reader: &dyn JkReader,
) -> Result<Vec<Box<dyn Instruction>>, Error> {
let input = reader.read_to_string(
formatted
.to_str()
.ok_or_else(|| Error::new(ErrKind::UTF8))?,
)?;
let (remaining_input, instructions) =
constructs::many_expr(LocatedSpan::new_extra(input.as_str(), Some(formatted)))?;
match remaining_input.len() {
0 => Ok(instructions),
_ => Err(Error::new(ErrKind::Parsing)
.with_msg(format!(
"error when parsing included file: {:?},\non the following input:\n{}",
formatted, remaining_input
))
.with_loc(self.location.clone())),
}
}
pub fn set_base(&mut self, path: PathBuf) {
self.base = Some(path);
}
pub fn set_location(&mut self, location: SpanTuple) {
self.location = Some(location)
}
fn check_base(&self, base: &Path) -> Result<PathBuf, Error> {
let (mut dir_candidate, mut file_candidate) = (
PathBuf::from(base)
.join(self.path.clone())
.join(DEFAULT_INCL),
PathBuf::from(base).join(self.path.clone()),
);
dir_candidate.set_extension("jk");
file_candidate.set_extension("jk");
match (dir_candidate.is_file(), file_candidate.is_file()) {
(true, true) => Err(Error::new(ErrKind::Context)
.with_msg(format!(
"invalid include: {:?} and {:?} are both valid candidates",
dir_candidate, file_candidate
))
.with_loc(self.location.clone())),
(false, false) => Err(Error::new(ErrKind::Context)
.with_msg(format!(
"no candidate for include: {:?} and {:?} do not exist",
dir_candidate, file_candidate
))
.with_loc(self.location.clone())),
(false, true) => Ok(file_candidate),
(true, false) => Ok(dir_candidate),
}
}
fn load_home_library(&self) -> Result<PathBuf, Error> {
let home = std::env::var("HOME")?;
let home_base = PathBuf::from(format!("{}/.jinko/libs/", home));
self.check_base(&home_base)
}
fn load_local_library(&self, base: &Path) -> Result<PathBuf, Error> {
self.check_base(base)
}
pub fn get_final_path(&self, base: &Path) -> Result<PathBuf, (Error, Error)> {
let local_err = match self.load_local_library(base) {
Ok(path) => return Ok(path),
Err(e) => e,
};
let home_err = match self.load_home_library() {
Ok(path) => return Ok(path),
Err(e) => e,
};
Err((local_err, home_err))
}
}
impl Instruction for Incl {
fn kind(&self) -> InstrKind {
InstrKind::Statement
}
fn print(&self) -> String {
use std::ffi::OsStr;
let path: &OsStr = self.path.as_ref();
let mut base = format!("incl {}", path.to_str().unwrap());
base = match &self.alias {
Some(alias) => format!("{} as {}", base, alias),
None => base,
};
base
}
fn execute(&self, ctx: &mut Context) -> Option<ObjectInstance> {
self.instructions.iter().for_each(|instr| {
instr.execute(ctx);
});
None
}
fn location(&self) -> Option<&SpanTuple> {
self.location.as_ref()
}
}
impl TypeCheck for Incl {
fn resolve_type(&mut self, ctx: &mut TypeCtx) -> CheckedType {
let base = match &self.base {
Some(b) => b.clone(),
None => match ctx.path() {
Some(path) => path.parent().unwrap().to_owned(),
None => PathBuf::new(),
},
};
let final_path = match self.get_final_path(&base) {
Ok(path) => path,
Err((e1, e2)) => {
ctx.error(e1);
ctx.error(e2);
return CheckedType::Error;
}
};
if ctx.is_included(&final_path) {
return CheckedType::Void;
}
let instructions = match self.fetch_instructions(&final_path, ctx.reader()) {
Ok(instructions) => instructions,
Err(e) => {
ctx.error(e);
return CheckedType::Error;
}
};
self.instructions = instructions;
let old_path = ctx.path().cloned();
ctx.include(final_path.clone());
ctx.set_path(Some(final_path));
self.instructions.iter_mut().for_each(|instr| {
instr.type_of(ctx);
});
ctx.set_path(old_path);
CheckedType::Void
}
fn set_cached_type(&mut self, _ty: CheckedType) {
self.typechecked = true
}
fn cached_type(&self) -> Option<&CheckedType> {
match self.typechecked {
true => Some(&CheckedType::Void),
false => None,
}
}
}
impl GenericUser for Incl {}
#[cfg(test)]
mod tests {
use super::*;
use crate::{jinko, jinko_fail};
#[test]
fn tc_typecheck_stdlib() {
let mut ctx = Context::new(Box::new(crate::io_trait::JkStdReader));
ctx.execute().unwrap();
}
#[test]
fn include_stdlib() {
jinko! {};
}
#[test]
fn include_non_existant() {
jinko_fail! {
incl does_not_exist_at_all;
};
}
#[test]
fn include_already_included() {
jinko! {
incl stdlib;
incl stdlib as std;
};
}
}