167 lines
4.5 KiB
Rust
167 lines
4.5 KiB
Rust
use std::{
|
|
fs,
|
|
path::{Path, PathBuf},
|
|
process,
|
|
time::Instant,
|
|
};
|
|
|
|
use mist_parser::error::ParseError;
|
|
|
|
pub fn build() -> PathBuf {
|
|
let start = Instant::now();
|
|
|
|
let root = std::env::current_dir()
|
|
.ok()
|
|
.expect("Unable to find project root");
|
|
|
|
let src_dir = root.join("src");
|
|
let out_dir = root.join(".mist/src");
|
|
|
|
build_dir(&root, &src_dir, &src_dir, &out_dir);
|
|
|
|
let elapsed = start.elapsed();
|
|
|
|
println!(
|
|
"\x1b[32m\nTranspile successful\x1b[0m in \x1b[34m{:.2?}\x1b[0m",
|
|
elapsed
|
|
);
|
|
|
|
root
|
|
}
|
|
|
|
fn build_dir(root: &Path, base_src: &Path, current_dir: &Path, out_dir: &Path) {
|
|
let entries = match fs::read_dir(current_dir) {
|
|
Ok(entries) => entries,
|
|
Err(e) => {
|
|
eprintln!(
|
|
"error: failed to read directory {}\n {}",
|
|
current_dir.display(),
|
|
e
|
|
);
|
|
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
for entry in entries {
|
|
let entry = match entry {
|
|
Ok(entry) => entry,
|
|
Err(e) => {
|
|
eprintln!("error: failed to read directory entry\n {}", e);
|
|
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
let path = entry.path();
|
|
|
|
// recurse into nested directories
|
|
if path.is_dir() {
|
|
build_dir(root, base_src, &path, out_dir);
|
|
continue;
|
|
}
|
|
|
|
let relative = path.strip_prefix(base_src).unwrap();
|
|
|
|
// Handle non-mist files with a cache check
|
|
if path.extension().and_then(|e| e.to_str()) != Some("mist") {
|
|
let dest_path = out_dir.join(relative);
|
|
|
|
// Create parent directories for static assets if needed
|
|
if let Some(parent) = dest_path.parent() {
|
|
let _ = fs::create_dir_all(parent);
|
|
}
|
|
|
|
if should_skip(&path, &dest_path) {
|
|
continue;
|
|
}
|
|
|
|
fs::copy(&path, dest_path).expect("Failed to copy non-mist file");
|
|
continue;
|
|
}
|
|
|
|
let output_path = out_dir.join(relative).with_extension("rs");
|
|
|
|
// Cache layer: Skip if the generated .rs file is newer than the .mist source
|
|
if should_skip(&path, &output_path) {
|
|
continue;
|
|
}
|
|
|
|
// create parent directories
|
|
if let Some(parent) = output_path.parent() {
|
|
if let Err(e) = fs::create_dir_all(parent) {
|
|
eprintln!(
|
|
"error: failed to create output directory {}\n {}",
|
|
parent.display(),
|
|
e
|
|
);
|
|
|
|
process::exit(1);
|
|
}
|
|
}
|
|
|
|
// read source
|
|
let source = match fs::read_to_string(&path) {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
eprintln!("error: failed to read file {}\n {}", path.display(), e);
|
|
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
let parser_result = mist_parser::parse(&source).map_err(|e| match e {
|
|
ParseError::Ast(e) => {
|
|
let start_pos = e.span.start_pos().line_col();
|
|
|
|
let span = e.span.as_str();
|
|
|
|
format!(
|
|
"\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}\n\t{}{}\t{}",
|
|
path.as_os_str().display(),
|
|
start_pos.0,
|
|
start_pos.1,
|
|
e.error_message,
|
|
span,
|
|
if span.ends_with("\n") { "" } else { "\n" },
|
|
"^".repeat(span.trim().len()),
|
|
)
|
|
}
|
|
ParseError::PreAst(e) => format!("{e}"),
|
|
});
|
|
|
|
let ast = match parser_result {
|
|
Ok(ast) => ast,
|
|
Err(e) => {
|
|
eprintln!("error: parse failed in {}\n{}", path.display(), e);
|
|
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
// semantic::walk_ast(semantic::scope::Scope::from_top(root, &ast), &mut ast);
|
|
|
|
let mut gc = crate::codegen::RustCodegen::new();
|
|
let output = gc.generate(ast);
|
|
|
|
if let Err(e) = fs::write(&output_path, output) {
|
|
eprintln!(
|
|
"error: failed to write output {}\n {}",
|
|
output_path.display(),
|
|
e
|
|
);
|
|
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn should_skip(source: &Path, output: &Path) -> bool {
|
|
if let (Ok(src_meta), Ok(out_meta)) = (fs::metadata(source), fs::metadata(output)) {
|
|
if let (Ok(src_time), Ok(out_time)) = (src_meta.modified(), out_meta.modified()) {
|
|
return out_time >= src_time;
|
|
}
|
|
}
|
|
false
|
|
}
|