use std::{
io::stdin,
path::{Path, PathBuf},
};
use clap::{Parser, ValueEnum};
use mimium_audiodriver::{
backends::{csv::csv_driver, local_buffer::LocalBufferDriver},
driver::{Driver, SampleRate},
load_default_runtime,
};
use mimium_lang::{
compiler::{bytecodegen::SelfEvalMode, emit_ast}, interner::{Symbol, ToSymbol}, log, plugin::Plugin, utils::{error::{report, ReportableError}, fileloader, miniprint::MiniPrint}, Config, ExecContext
};
use mimium_symphonia::SamplerPlugin;
#[derive(clap::Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[command(flatten)]
pub mode: Mode,
#[clap(value_parser)]
pub file: Option<String>,
#[arg(long, short)]
pub output: Option<PathBuf>,
#[arg(long, default_value_t = 10)]
pub times: usize,
#[arg(long, value_enum)]
pub output_format: Option<OutputFileFormat>,
#[arg(long, default_value_t = false)]
pub no_gui: bool,
#[arg(long, default_value_t = false)]
pub self_init_0: bool,
}
impl Args {
pub fn to_execctx_config(self) -> mimium_lang::Config {
mimium_lang::Config {
compiler: mimium_lang::compiler::Config {
self_eval_mode: if self.self_init_0 {
SelfEvalMode::ZeroAtInit
} else {
SelfEvalMode::SimpleState
},
},
}
}
}
#[derive(Clone, Debug, ValueEnum)]
pub enum OutputFileFormat {
Csv,
}
#[derive(clap::Args, Debug, Clone, Copy)]
#[group(required = false, multiple = false)]
pub struct Mode {
#[arg(long, default_value_t = false)]
pub emit_ast: bool,
#[arg(long, default_value_t = false)]
pub emit_mir: bool,
#[arg(long, default_value_t = false)]
pub emit_bytecode: bool,
}
pub enum RunMode {
EmitAst,
EmitMir,
EmitByteCode,
NativeAudio,
WriteCsv {
times: usize,
output: Option<PathBuf>,
},
}
pub struct RunOptions {
mode: RunMode,
with_gui: bool,
config: Config,
}
impl RunOptions {
pub fn from_args(args: &Args) -> Self {
let config = args.clone().to_execctx_config();
if args.mode.emit_ast {
return Self {
mode: RunMode::EmitAst,
with_gui: true,
config,
};
}
if args.mode.emit_mir {
return Self {
mode: RunMode::EmitMir,
with_gui: false,
config,
};
}
if args.mode.emit_bytecode {
return Self {
mode: RunMode::EmitByteCode,
with_gui: true,
config,
};
}
let mode = match (&args.output_format, args.output.as_ref()) {
(None, None) => RunMode::NativeAudio,
(Some(OutputFileFormat::Csv), path) => RunMode::WriteCsv {
times: args.times,
output: path.cloned(),
},
(None, Some(output)) => match output.extension() {
Some(x) if &x.to_os_string() == "csv" => RunMode::WriteCsv {
times: args.times,
output: Some(output.clone()),
},
_ => panic!("cannot determine the output file format"),
},
};
let with_gui = match &mode {
RunMode::NativeAudio => !args.no_gui,
_ => false,
};
Self {
mode,
with_gui,
config,
}
}
fn get_driver(&self) -> Box<dyn Driver<Sample = f64>> {
match &self.mode {
RunMode::NativeAudio => load_default_runtime(),
RunMode::WriteCsv { times, output } => csv_driver(*times, output),
_ => unreachable!(),
}
}
}
pub fn get_default_context(path: Option<Symbol>, with_gui: bool, config: Config) -> ExecContext {
let plugins: Vec<Box<dyn Plugin>> = vec![Box::new(SamplerPlugin)];
let mut ctx = ExecContext::new(plugins.into_iter(), path, config);
ctx.add_system_plugin(mimium_scheduler::get_default_scheduler_plugin());
if let Some(midi_plug) = mimium_midi::MidiPlugin::try_new() {
ctx.add_system_plugin(midi_plug);
} else {
log::warn!("Midi is not supported on this platform.")
}
if with_gui {
#[cfg(not(target_arch = "wasm32"))]
ctx.add_system_plugin(mimium_guitools::GuiToolPlugin::default());
}
ctx
}
pub fn run_file(
options: RunOptions,
content: &str,
fullpath: &Path,
) -> Result<(), Vec<Box<dyn ReportableError>>> {
log::debug!("Filename: {}", fullpath.display());
let path_sym = fullpath.to_string_lossy().to_symbol();
let mut ctx = get_default_context(Some(path_sym), options.with_gui, options.config);
match options.mode {
RunMode::EmitAst => {
let ast = emit_ast(content, Some(path_sym))?;
println!("{}", ast.pretty_print());
Ok(())
}
RunMode::EmitMir => {
ctx.prepare_compiler();
let res = ctx.get_compiler().unwrap().emit_mir(content);
res.map(|r| {
println!("{r}");
})
}
RunMode::EmitByteCode => {
let localdriver = LocalBufferDriver::new(0);
let plug = localdriver.get_as_plugin();
ctx.add_plugin(plug);
ctx.prepare_machine(content)?;
Ok(println!("{}", ctx.get_vm().unwrap().prog))
}
_ => {
let mut driver = options.get_driver();
let audiodriver_plug = driver.get_as_plugin();
ctx.add_plugin(audiodriver_plug);
ctx.prepare_machine(content)?;
let _res = ctx.run_main();
let mainloop = ctx.try_get_main_loop().unwrap_or(Box::new(|| {
let mut dummy = String::new();
eprintln!("Press Enter to exit");
let _size = stdin().read_line(&mut dummy).expect("stdin read error.");
}));
driver.init(ctx, Some(SampleRate::from(48000)));
driver.play();
mainloop();
Ok(())
}
}
}
pub fn lib_main() -> Result<(), Box<dyn std::error::Error>> {
if cfg!(debug_assertions) | cfg!(test) {
colog::default_builder()
.filter_level(log::LevelFilter::Trace)
.init();
} else {
colog::default_builder().init();
}
let args = Args::parse();
match &args.file {
Some(file) => {
let fullpath = fileloader::get_canonical_path(".", file)?;
let content = fileloader::load(fullpath.to_str().unwrap())?;
let options = RunOptions::from_args(&args);
match run_file(options, &content, &fullpath) {
Ok(_) => {}
Err(e) => {
report(&content, fullpath.to_string_lossy().to_symbol(), &e);
return Err(format!("Failed to process {file}").into());
}
}
}
None => {
}
}
Ok(())
}