#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![deny(warnings)]
#![deny(missing_docs)]
use std::{
fs,
path::{Path, PathBuf},
};
use anyhow::Context;
use clap::Parser as ClapParser;
use crate::{emitter::Emitter, parser::Parser};
mod emitter;
mod lexer;
mod parser;
fn main() -> anyhow::Result<()> {
let options = CompileOptions::parse();
compile_file(&options)?;
println!("wrote {}", options.output_path().to_string_lossy());
Ok(())
}
#[derive(ClapParser)]
#[command(version, about)]
struct CompileOptions {
input_path: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
}
impl CompileOptions {
fn output_path(&self) -> PathBuf {
self.output
.clone()
.unwrap_or_else(|| default_output_path(&self.input_path))
}
}
fn default_output_path(input_path: &Path) -> PathBuf {
input_path.with_extension("o")
}
fn compile_file(options: &CompileOptions) -> anyhow::Result<()> {
let source = fs::read_to_string(&options.input_path).with_context(|| {
format!(
"failed to read input file {}",
options.input_path.to_string_lossy()
)
})?;
let mut parser = Parser::from_input(&source);
let program = parser.parse_program();
let bytes = Emitter::new()?.emit_program(&program)?;
let output_path = options.output_path();
fs::write(&output_path, bytes).with_context(|| {
format!(
"failed to write output file {}",
output_path.to_string_lossy()
)
})?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use clap::Parser;
use crate::{CompileOptions, default_output_path};
#[test]
fn default_output_replaces_extension() {
assert_eq!(
default_output_path(PathBuf::from("tests/input/print.bsc").as_path()),
PathBuf::from("tests/input/print.o")
);
}
#[test]
fn compile_options_use_default_output() -> anyhow::Result<()> {
let options = CompileOptions::try_parse_from(["bcomp", "program.bsc"])?;
assert_eq!(options.input_path, PathBuf::from("program.bsc"));
assert_eq!(options.output_path(), PathBuf::from("program.o"));
Ok(())
}
#[test]
fn compile_options_accept_output_flag() -> anyhow::Result<()> {
let options =
CompileOptions::try_parse_from(["bcomp", "program.bsc", "-o", "build/program.o"])?;
assert_eq!(options.input_path, PathBuf::from("program.bsc"));
assert_eq!(options.output_path(), PathBuf::from("build/program.o"));
Ok(())
}
}