kind-driver 0.1.3

Driver for the kind compiler
Documentation
use checker::eval;
use diagnostic::{DriverDiagnostic, GenericDriverError};
use kind_pass::{desugar, erasure, inline::inline_book};
use kind_report::{
    data::{FileCache, Log, Severity},
    report::Report,
    RenderConfig,
};
use kind_span::SyntaxCtxIndex;

use hvm::language::syntax as backend;
use kind_tree::{concrete, desugared, untyped};
use resolution::ResolutionError;
use session::Session;
use std::{path::PathBuf, time::Instant};

use kind_checker as checker;

pub mod diagnostic;
pub mod resolution;
pub mod session;

pub use resolution::get_unbound_variables;

impl FileCache for Session {
    fn fetch(&self, ctx: SyntaxCtxIndex) -> Option<(PathBuf, &String)> {
        let path = self.loaded_paths[ctx.0].as_ref().to_owned();
        Some((path, &self.loaded_sources[ctx.0]))
    }
}

pub fn type_check_book(
    session: &mut Session,
    path: &PathBuf,
    entrypoints: Vec<String>,
    tids: Option<usize>,
    check_coverage: bool,
) -> anyhow::Result<(untyped::Book, u64)> {
    let concrete_book = to_book(session, path)?;
    let desugared_book = desugar::desugar_book(session.diagnostic_sender.clone(), &concrete_book)?;

    let all = desugared_book.entrs.iter().map(|x| x.0).cloned().collect();

    let result = checker::type_check(
        &desugared_book,
        session.diagnostic_sender.clone(),
        all,
        check_coverage,
        tids,
    );

    if result.is_none() {
        return Err(ResolutionError.into());
    }

    let mut book = erasure::erase_book(
        &desugared_book,
        session.diagnostic_sender.clone(),
        entrypoints,
    )?;
    inline_book(&mut book);

    Ok((book, result.unwrap()))
}

pub fn to_book(session: &mut Session, path: &PathBuf) -> anyhow::Result<concrete::Book> {
    let mut concrete_book = resolution::parse_and_store_book(session, path)?;

    resolution::check_unbound_top_level(session, &mut concrete_book)?;

    Ok(concrete_book)
}

pub fn erase_book(
    session: &mut Session,
    path: &PathBuf,
    entrypoints: Vec<String>,
) -> anyhow::Result<untyped::Book> {
    let concrete_book = to_book(session, path)?;
    let desugared_book = desugar::desugar_book(session.diagnostic_sender.clone(), &concrete_book)?;

    let mut book = erasure::erase_book(
        &desugared_book,
        session.diagnostic_sender.clone(),
        entrypoints,
    )?;

    inline_book(&mut book);
    Ok(book)
}

pub fn desugar_book(session: &mut Session, path: &PathBuf) -> anyhow::Result<desugared::Book> {
    let concrete_book = to_book(session, path)?;
    desugar::desugar_book(session.diagnostic_sender.clone(), &concrete_book)
}

pub fn check_erasure_book(
    session: &mut Session,
    path: &PathBuf,
) -> anyhow::Result<desugared::Book> {
    let concrete_book = to_book(session, path)?;
    desugar::desugar_book(session.diagnostic_sender.clone(), &concrete_book)
}

pub fn compile_book_to_hvm(book: untyped::Book, trace: bool) -> backend::File {
    kind_target_hvm::compile_book(book, trace)
}

pub fn compile_book_to_kdl(
    path: &PathBuf,
    session: &mut Session,
    namespace: &str,
    entrypoints: Vec<String>,
) -> anyhow::Result<kind_target_kdl::File> {
    let concrete_book = to_book(session, path)?;
    let desugared_book = desugar::desugar_book(session.diagnostic_sender.clone(), &concrete_book)?;

    let mut book = erasure::erase_book(
        &desugared_book,
        session.diagnostic_sender.clone(),
        entrypoints,
    )?;

    inline_book(&mut book);

    let res = kind_target_kdl::compile_book(book, session.diagnostic_sender.clone(), namespace)?;

    Ok(res)
}

pub fn check_main_entry(session: &mut Session, book: &untyped::Book) -> anyhow::Result<()> {
    if !book.entrs.contains_key("Main") {
        let err = Box::new(DriverDiagnostic::ThereIsntAMain);
        session.diagnostic_sender.send(err).unwrap();
        Err(ResolutionError.into())
    } else {
        Ok(())
    }
}

pub fn check_main_desugared_entry(
    session: &mut Session,
    book: &desugared::Book,
) -> anyhow::Result<()> {
    if !book.entrs.contains_key("Main") {
        let err = Box::new(DriverDiagnostic::ThereIsntAMain);
        session.diagnostic_sender.send(err).unwrap();
        Err(ResolutionError.into())
    } else {
        Ok(())
    }
}

pub fn execute_file(file: &str, tids: Option<usize>) -> anyhow::Result<(String, u64)> {
    match eval(file, "Main", false, tids) {
        Ok((res, rewrites)) => Ok((res.to_string(), rewrites)),
        Err(_) => anyhow::Result::Err(GenericDriverError.into()),
    }
}

pub fn eval_in_checker(book: &desugared::Book) -> (String, u64) {
    checker::eval_api(book)
}

pub fn generate_checker(book: &desugared::Book, check_coverage: bool) -> String {
    checker::gen_checker(book, check_coverage, book.entrs.keys().cloned().collect())
}

pub fn run_in_session<T>(
    render_config: &RenderConfig,
    root: PathBuf,
    file: String,
    compiled: bool,
    action: &mut dyn FnMut(&mut Session) -> anyhow::Result<T>,
    log: &dyn Fn(&Session, &dyn Report),
) -> anyhow::Result<T> {
    let (rx, tx) = std::sync::mpsc::channel();

    let mut session = Session::new(root, rx, render_config.show_immediate_deps);

    log(&session, &Log::Empty);
    log(&session, &Log::Checking(format!("The file '{}'", file)));

    let start = Instant::now();

    let res = action(&mut session);

    let diagnostics = tx.try_iter().collect::<Vec<_>>();

    let mut contains_error = false;

    let mut hidden = 0;
    let total = diagnostics.len() as u64;

    for diagnostic in diagnostics {
        if diagnostic.get_severity() == Severity::Error {
            contains_error = true;
        }

        let is_root = diagnostic
            .get_syntax_ctx()
            .map(|x| x.is_root())
            .unwrap_or_default();

        if render_config.only_main && !is_root {
            hidden += 1;
            continue;
        }
        log(&session, &diagnostic);
    }

    if !contains_error {
        log(
            &session,
            &if compiled {
                Log::Compiled(start.elapsed())
            } else {
                Log::Checked(start.elapsed())
            },
        );
        log(&session, &Log::Empty);

        res
    } else {
        log(&session, &Log::Failed(start.elapsed(), total, hidden));
        log(&session, &Log::Empty);

        match res {
            Ok(_) => Err(ResolutionError.into()),
            Err(res) => Err(res),
        }
    }
}