1use std::{
2 io::stdin,
3 path::{Path, PathBuf},
4};
5
6use clap::{Parser, ValueEnum};
7use mimium_audiodriver::{
8 backends::{csv::csv_driver, local_buffer::LocalBufferDriver},
9 driver::{Driver, SampleRate},
10 load_default_runtime,
11};
12use mimium_lang::{
13 compiler::{bytecodegen::SelfEvalMode, emit_ast}, interner::{Symbol, ToSymbol}, log, plugin::Plugin, utils::{error::{report, ReportableError}, fileloader, miniprint::MiniPrint}, Config, ExecContext
14};
15use mimium_symphonia::SamplerPlugin;
16
17#[derive(clap::Parser, Debug, Clone)]
18#[command(author, version, about, long_about = None)]
19pub struct Args {
20 #[command(flatten)]
21 pub mode: Mode,
22
23 #[clap(value_parser)]
25 pub file: Option<String>,
26
27 #[arg(long, short)]
29 pub output: Option<PathBuf>,
30
31 #[arg(long, default_value_t = 10)]
34 pub times: usize,
35
36 #[arg(long, value_enum)]
38 pub output_format: Option<OutputFileFormat>,
39
40 #[arg(long, default_value_t = false)]
42 pub no_gui: bool,
43
44 #[arg(long, default_value_t = false)]
46 pub self_init_0: bool,
47}
48
49impl Args {
50 pub fn to_execctx_config(self) -> mimium_lang::Config {
51 mimium_lang::Config {
52 compiler: mimium_lang::compiler::Config {
53 self_eval_mode: if self.self_init_0 {
54 SelfEvalMode::ZeroAtInit
55 } else {
56 SelfEvalMode::SimpleState
57 },
58 },
59 }
60 }
61}
62
63#[derive(Clone, Debug, ValueEnum)]
64pub enum OutputFileFormat {
65 Csv,
66}
67
68#[derive(clap::Args, Debug, Clone, Copy)]
69#[group(required = false, multiple = false)]
70pub struct Mode {
71 #[arg(long, default_value_t = false)]
73 pub emit_ast: bool,
74
75 #[arg(long, default_value_t = false)]
77 pub emit_mir: bool,
78
79 #[arg(long, default_value_t = false)]
81 pub emit_bytecode: bool,
82}
83
84pub enum RunMode {
85 EmitAst,
86 EmitMir,
87 EmitByteCode,
88 NativeAudio,
89 WriteCsv {
90 times: usize,
91 output: Option<PathBuf>,
92 },
93}
94
95pub struct RunOptions {
97 mode: RunMode,
98 with_gui: bool,
99 config: Config,
100}
101
102impl RunOptions {
103 pub fn from_args(args: &Args) -> Self {
105 let config = args.clone().to_execctx_config();
106 if args.mode.emit_ast {
107 return Self {
108 mode: RunMode::EmitAst,
109 with_gui: true,
110 config,
111 };
112 }
113
114 if args.mode.emit_mir {
115 return Self {
116 mode: RunMode::EmitMir,
117 with_gui: false,
118 config,
119 };
120 }
121
122 if args.mode.emit_bytecode {
123 return Self {
124 mode: RunMode::EmitByteCode,
125 with_gui: true,
126 config,
127 };
128 }
129
130 let mode = match (&args.output_format, args.output.as_ref()) {
131 (None, None) => RunMode::NativeAudio,
133 (Some(OutputFileFormat::Csv), path) => RunMode::WriteCsv {
135 times: args.times,
136 output: path.cloned(),
137 },
138 (None, Some(output)) => match output.extension() {
140 Some(x) if &x.to_os_string() == "csv" => RunMode::WriteCsv {
141 times: args.times,
142 output: Some(output.clone()),
143 },
144 _ => panic!("cannot determine the output file format"),
145 },
146 };
147
148 let with_gui = match &mode {
149 RunMode::NativeAudio => !args.no_gui,
151 _ => false,
153 };
154
155 Self {
156 mode,
157 with_gui,
158 config,
159 }
160 }
161
162 fn get_driver(&self) -> Box<dyn Driver<Sample = f64>> {
163 match &self.mode {
164 RunMode::NativeAudio => load_default_runtime(),
165 RunMode::WriteCsv { times, output } => csv_driver(*times, output),
166 _ => unreachable!(),
167 }
168 }
169}
170
171pub fn get_default_context(path: Option<Symbol>, with_gui: bool, config: Config) -> ExecContext {
173 let plugins: Vec<Box<dyn Plugin>> = vec![Box::new(SamplerPlugin)];
174 let mut ctx = ExecContext::new(plugins.into_iter(), path, config);
175 ctx.add_system_plugin(mimium_scheduler::get_default_scheduler_plugin());
176 if let Some(midi_plug) = mimium_midi::MidiPlugin::try_new() {
177 ctx.add_system_plugin(midi_plug);
178 } else {
179 log::warn!("Midi is not supported on this platform.")
180 }
181
182 if with_gui {
183 #[cfg(not(target_arch = "wasm32"))]
184 ctx.add_system_plugin(mimium_guitools::GuiToolPlugin::default());
185 }
186
187 ctx
188}
189
190pub fn run_file(
192 options: RunOptions,
193 content: &str,
194 fullpath: &Path,
195) -> Result<(), Vec<Box<dyn ReportableError>>> {
196 log::debug!("Filename: {}", fullpath.display());
197 let path_sym = fullpath.to_string_lossy().to_symbol();
198 let mut ctx = get_default_context(Some(path_sym), options.with_gui, options.config);
199
200 match options.mode {
201 RunMode::EmitAst => {
202 let ast = emit_ast(content, Some(path_sym))?;
203 println!("{}", ast.pretty_print());
204 Ok(())
205 }
206 RunMode::EmitMir => {
207 ctx.prepare_compiler();
208 let res = ctx.get_compiler().unwrap().emit_mir(content);
209 res.map(|r| {
210 println!("{r}");
211 })
212 }
213 RunMode::EmitByteCode => {
214 let localdriver = LocalBufferDriver::new(0);
216 let plug = localdriver.get_as_plugin();
217 ctx.add_plugin(plug);
218 ctx.prepare_machine(content)?;
219 Ok(println!("{}", ctx.get_vm().unwrap().prog))
220 }
221 _ => {
222 let mut driver = options.get_driver();
223 let audiodriver_plug = driver.get_as_plugin();
224 ctx.add_plugin(audiodriver_plug);
225 ctx.prepare_machine(content)?;
226 let _res = ctx.run_main();
227 let mainloop = ctx.try_get_main_loop().unwrap_or(Box::new(|| {
228 let mut dummy = String::new();
230 eprintln!("Press Enter to exit");
231 let _size = stdin().read_line(&mut dummy).expect("stdin read error.");
232 }));
233 driver.init(ctx, Some(SampleRate::from(48000)));
234 driver.play();
235 mainloop();
236 Ok(())
237 }
238 }
239}
240
241
242pub fn lib_main() -> Result<(), Box<dyn std::error::Error>> {
243 if cfg!(debug_assertions) | cfg!(test) {
244 colog::default_builder()
245 .filter_level(log::LevelFilter::Trace)
246 .init();
247 } else {
248 colog::default_builder().init();
249 }
250
251 let args = Args::parse();
252 match &args.file {
253 Some(file) => {
254 let fullpath = fileloader::get_canonical_path(".", file)?;
255 let content = fileloader::load(fullpath.to_str().unwrap())?;
256 let options = RunOptions::from_args(&args);
257 match run_file(options, &content, &fullpath) {
258 Ok(_) => {}
259 Err(e) => {
260 report(&content, fullpath.to_string_lossy().to_symbol(), &e);
265 return Err(format!("Failed to process {file}").into());
266 }
267 }
268 }
269 None => {
270 }
272 }
273 Ok(())
274}