mercur 0.1.5

mercur - junolang package manager
use anyhow::anyhow;
use libjuno::inkwell::OptimizationLevel;
use libjuno::inkwell::targets::{
    CodeModel, InitializationConfig, RelocMode, Target, TargetMachine,
};
use libjuno::phf::ordered_set::Iter;
use rayon::prelude::*;

use std::collections::HashSet;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::path::{Component, Path, PathBuf};
use std::process::{Command, exit};

use libjuno::ast::Item;
use libjuno::inkwell::module::Module;
use libjuno::pest::Parser;
use libjuno::{JunoParser, LLVMBackend, MetaIRGen, Rule, inkwell, parse_program};

use crate::build::manifest::{JunoPackageBin, JunoToml};

pub fn build_bins(proj_dir: &Path, juno_toml: JunoToml) -> anyhow::Result<()> {
    for i in &juno_toml.bins {
        build_bin(Path::new(&i.entry), &juno_toml, i.clone())?;
    }
    Ok(())
}

fn build_bin(path: &Path, juno_toml: &JunoToml, bin: JunoPackageBin) -> anyhow::Result<()> {
    let linker = std::env::var("JUNO_LD").unwrap_or("clang".to_string());
    let imports = get_imports(path.parent().ok_or(anyhow!(""))?, PathBuf::from(path), Some(&juno_toml.project.name))?;

    let mut outs : Vec<String> = imports.par_iter().map(|(path, namespace)| {

        let juno_toml = juno_toml.clone(); // FIXME
        let target_machine = get_target_machine(
            OptimizationLevel::Aggressive,
            RelocMode::Default,
            CodeModel::Small,
        );
        let compiled = compile_file(path, Some(&juno_toml.project.name));

        let mut s = DefaultHasher::new();

        compiled.to_string().hash(&mut s);
        let hash = s.finish();
        let path = &format!("./{}-{:x}.o", path.to_str().unwrap(), hash).to_string();
        let _ = target_machine.write_to_file(
            &compiled,
            libjuno::inkwell::targets::FileType::Object,
            Path::new(path),
        );
        path.clone()
    }).collect();
    let linker_args: Vec<String> = vec!["-o".to_string(), bin.output, "-no-pie".to_string()];
    outs.extend(linker_args);
    let _status = Command::new(&linker).args(&outs).status().unwrap();
    Ok(())
}

pub fn get_imports<'a>(dir: &Path, p: PathBuf, pkg_name: Option<&str>) -> anyhow::Result<HashSet<(PathBuf, String)>> {
    if !dir.is_dir() {
        return Err(anyhow!("Dir is not a dir")); // TODO better msg
    }
    let mut out: HashSet<(PathBuf, String)> = HashSet::new();
    let _ = &out.insert((p.clone(), path_to_namespace(p.as_path(), pkg_name)));
    let namespace = path_to_namespace(p.as_path(), pkg_name);
    let input = match std::fs::read_to_string(&p) {
        Err(e) => panic!(
            "Error while reading file {} (ERR: {})",
            p.clone().to_str().unwrap(),
            e
        ),
        Ok(s) => s,
    };

    let pairs = match JunoParser::parse(Rule::program, &input.as_str()) {
        Ok(pairs) => pairs,
        Err(e) => {
            panic!("{e}");
        }
    };
    let expr_owned = parse_program(pairs.into_iter().next().unwrap(), namespace).unwrap();

    let imports: Vec<&Path> = expr_owned.items.par_iter().filter_map(|x| match x {
        Item::Import(i) => Some(Path::new(&i.path)),
        _ => None
    }).collect();
    let pkg_name = pkg_name;
    for i in imports {

        out.extend(get_imports(i.parent().ok_or(anyhow!(""))?, dir.join(i), Some(&path_to_namespace(i, pkg_name)))?);

    }

    Ok(out)
}

pub fn compile_file(p: &Path, pkg_name: Option<&str>) -> Module<'static> {
    //println!("COMPINLING {}...", p.to_str().unwrap_or("<unknown>"));

    let namespace = path_to_namespace(p, pkg_name);
    let input = match std::fs::read_to_string(p) {
        Err(e) => panic!(
            "Error while reading file {} (ERR: {})",
            p.to_str().unwrap(),
            e
        ),
        Ok(s) => s,
    };

    let pairs = match JunoParser::parse(Rule::program, &input.as_str()) {
        Ok(pairs) => pairs,
        Err(e) => {
            panic!("{e}");
        }
    };
    let expr_owned = parse_program(pairs.into_iter().next().unwrap(), namespace).unwrap();
    let expr = Box::leak(Box::new(expr_owned));

    let metairgen = Box::leak(Box::new(MetaIRGen::new(expr)));
    let metair = Box::leak(Box::new(metairgen.lower_program(expr)));
    let context = Box::leak(Box::new(inkwell::context::Context::create()));
    let mut irgen = LLVMBackend::new(context, metair, "main");

    if let Err(e) = irgen.compile() {

        eprintln!("{:#?}", e);
        exit(1);
    }
    return irgen.module;
}

pub fn path_to_namespace(p: &Path, pkg_name: Option<&str>) -> String {
    let pkg_name = match pkg_name {
        None => "__main",
        Some(n) => n,
    };
    if p.is_relative() {
        let juno_root = p.parent().unwrap().parent().unwrap(); // ./src/path/to/file.juno -> .
        if juno_root.join("juno.toml").exists() {
            // Juno Package exists

            let mut components = p.components(); // ./src/path/to/file.juno -> src, path, to, file.juno
            components.next(); // src, path, to, file.juno -> path, to, file.juno
            let namespace = format!(
                "{}::{}",
                pkg_name,
                components
                    .collect::<Vec<Component>>()
                    .iter()
                    .map(|s: &Component| s.as_os_str().to_str().unwrap().to_string())
                    .collect::<Vec<String>>()
                    .join("::")
            );
            return namespace.strip_suffix(".juno").unwrap_or(&namespace).to_string();
        } else {
            println!("Warning: juno modules does not work without a juno package")
        }
    } else {
        println!(
            "Warning: juno modules does not work with absolute path, juno need a package for working with multiple files"
        );
    }
    return p.file_prefix().unwrap().to_str().unwrap().to_string();
}

pub fn get_target_machine(
    opt_level: OptimizationLevel,
    reloc_mode: RelocMode,
    code_model: CodeModel,
) -> TargetMachine {
    Target::initialize_native(&InitializationConfig::default()).unwrap();

    let triple = TargetMachine::get_default_triple();

    let target = Target::from_triple(&triple).unwrap();

    target
        .create_target_machine(&triple, "generic", "", opt_level, reloc_mode, code_model)
        .unwrap()
}