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(); 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")); }
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> {
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(); if juno_root.join("juno.toml").exists() {
let mut components = p.components(); components.next(); 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()
}