1#![feature(debug_closure_helpers)]
2#![no_std]
3
4extern crate alloc;
5#[cfg(feature = "std")]
6extern crate std;
7use alloc::{
8 borrow::ToOwned,
9 string::{String, ToString},
10 vec::Vec,
11};
12
13mod color;
14pub mod diagnostics;
15#[cfg(feature = "std")]
16mod duration;
17mod emit;
18mod emitter;
19pub mod flags;
20mod inputs;
21mod libs;
22mod options;
23mod outputs;
24#[cfg(feature = "std")]
25mod statistics;
26
27use alloc::{fmt, sync::Arc};
28use std::path::{Path, PathBuf};
29
30pub const MIDENC_BUILD_VERSION: &str = env!("MIDENC_BUILD_VERSION");
32
33pub const MIDENC_BUILD_REV: &str = env!("MIDENC_BUILD_REV");
35
36use clap::ValueEnum;
37use midenc_hir_symbol::Symbol;
38
39pub use self::{
40 color::ColorChoice,
41 diagnostics::{DiagnosticsHandler, Emitter, SourceManager},
42 duration::HumanDuration,
43 emit::Emit,
44 flags::{CompileFlag, CompileFlags, FlagAction},
45 inputs::{FileType, InputFile, InputType, InvalidInputError},
46 libs::{LibraryKind, LinkLibrary},
47 options::*,
48 outputs::{OutputFile, OutputFiles, OutputMode, OutputType, OutputTypeSpec, OutputTypes},
49 statistics::Statistics,
50};
51
52#[derive(Debug, Copy, Clone, Default)]
54pub enum ProjectType {
55 #[default]
57 Program,
58 Library,
60}
61impl ProjectType {
62 pub fn default_for_target(target: TargetEnv) -> Self {
63 match target {
64 TargetEnv::Base | TargetEnv::Rollup => Self::Program,
67 TargetEnv::Emu => Self::Library,
71 }
72 }
73}
74
75pub struct Session {
78 pub name: String,
80 pub options: Options,
82 pub source_manager: Arc<dyn SourceManager>,
84 pub diagnostics: Arc<DiagnosticsHandler>,
86 pub inputs: Vec<InputFile>,
88 pub output_files: OutputFiles,
90 #[cfg(feature = "std")]
92 pub statistics: Statistics,
93}
94
95impl fmt::Debug for Session {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 let inputs = self.inputs.iter().map(|input| input.file_name()).collect::<Vec<_>>();
98 f.debug_struct("Session")
99 .field("name", &self.name)
100 .field("options", &self.options)
101 .field("inputs", &inputs)
102 .field("output_files", &self.output_files)
103 .finish_non_exhaustive()
104 }
105}
106
107impl Session {
108 pub fn new<I>(
109 inputs: I,
110 output_dir: Option<PathBuf>,
111 output_file: Option<OutputFile>,
112 target_dir: PathBuf,
113 options: Options,
114 emitter: Option<Arc<dyn Emitter>>,
115 source_manager: Arc<dyn SourceManager>,
116 ) -> Self
117 where
118 I: IntoIterator<Item = InputFile>,
119 {
120 let inputs = inputs.into_iter().collect::<Vec<_>>();
121
122 Self::make(inputs, output_dir, output_file, target_dir, options, emitter, source_manager)
123 }
124
125 fn make(
126 inputs: Vec<InputFile>,
127 output_dir: Option<PathBuf>,
128 output_file: Option<OutputFile>,
129 target_dir: PathBuf,
130 options: Options,
131 emitter: Option<Arc<dyn Emitter>>,
132 source_manager: Arc<dyn SourceManager>,
133 ) -> Self {
134 log::debug!("creating session for {} inputs:", inputs.len());
135 if log::log_enabled!(log::Level::Debug) {
136 for input in inputs.iter() {
137 log::debug!(" - {} ({})", input.file_name(), input.file_type());
138 }
139 log::debug!(
140 " | outputs_dir = {}",
141 output_dir
142 .as_ref()
143 .map(|p| p.display().to_string())
144 .unwrap_or("<unset>".to_string())
145 );
146 log::debug!(
147 " | output_file = {}",
148 output_file.as_ref().map(|of| of.to_string()).unwrap_or("<unset>".to_string())
149 );
150 log::debug!(" | target_dir = {}", target_dir.display());
151 }
152 let diagnostics = Arc::new(DiagnosticsHandler::new(
153 options.diagnostics,
154 source_manager.clone(),
155 emitter.unwrap_or_else(|| options.default_emitter()),
156 ));
157
158 let output_dir = output_dir
159 .as_deref()
160 .or_else(|| output_file.as_ref().and_then(|of| of.parent()))
161 .map(|path| path.to_path_buf());
162
163 let name = options
164 .name
165 .clone()
166 .or_else(|| {
167 output_file
168 .as_ref()
169 .and_then(|of| of.filestem().map(|stem| stem.to_string_lossy().into_owned()))
170 })
171 .unwrap_or_else(|| match inputs.first() {
172 Some(InputFile {
173 file: InputType::Real(ref path),
174 ..
175 }) => path
176 .file_stem()
177 .and_then(|stem| stem.to_str())
178 .or_else(|| path.extension().and_then(|stem| stem.to_str()))
179 .unwrap_or_else(|| {
180 panic!(
181 "invalid input path: '{}' has no file stem or extension",
182 path.display()
183 )
184 })
185 .to_string(),
186 Some(
187 input @ InputFile {
188 file: InputType::Stdin { ref name, .. },
189 ..
190 },
191 ) => {
192 let name = name.as_str();
193 if matches!(name, "empty" | "stdin") {
194 options
195 .current_dir
196 .file_stem()
197 .and_then(|stem| stem.to_str())
198 .unwrap_or(name)
199 .to_string()
200 } else {
201 input.filestem().to_owned()
202 }
203 }
204 None => "out".to_owned(),
205 });
206
207 let output_files = OutputFiles::new(
208 name.clone(),
209 options.current_dir.clone(),
210 output_dir.unwrap_or_else(|| options.current_dir.clone()),
211 output_file,
212 target_dir,
213 options.output_types.clone(),
214 );
215
216 Self {
217 name,
218 options,
219 source_manager,
220 diagnostics,
221 inputs,
222 output_files,
223 statistics: Default::default(),
224 }
225 }
226
227 pub fn with_project_type(mut self, ty: ProjectType) -> Self {
228 self.options.project_type = ty;
229 self
230 }
231
232 #[doc(hidden)]
233 pub fn with_output_type(mut self, ty: OutputType, path: Option<OutputFile>) -> Self {
234 self.output_files.outputs.insert(ty, path.clone());
235 self.options.output_types.insert(ty, path.clone());
236 self
237 }
238
239 #[doc(hidden)]
240 pub fn with_extra_flags(mut self, flags: CompileFlags) -> Self {
241 self.options.set_extra_flags(flags);
242 self
243 }
244
245 #[inline]
247 pub fn get_flag(&self, name: &str) -> bool {
248 self.options.flags.get_flag(name)
249 }
250
251 #[inline]
253 pub fn get_flag_count(&self, name: &str) -> usize {
254 self.options.flags.get_flag_count(name)
255 }
256
257 #[inline]
259 pub fn get_flag_value<T>(&self, name: &str) -> Option<&T>
260 where
261 T: core::any::Any + Clone + Send + Sync + 'static,
262 {
263 self.options.flags.get_flag_value(name)
264 }
265
266 #[inline]
268 #[cfg(feature = "std")]
269 pub fn get_flag_values<T>(&self, name: &str) -> Option<clap::parser::ValuesRef<'_, T>>
270 where
271 T: core::any::Any + Clone + Send + Sync + 'static,
272 {
273 self.options.flags.get_flag_values(name)
274 }
275
276 #[inline]
278 #[cfg(feature = "std")]
279 pub fn matches(&self) -> &clap::ArgMatches {
280 self.options.flags.matches()
281 }
282
283 pub fn name(&self) -> &str {
285 &self.name
286 }
287
288 pub fn out_file(&self) -> OutputFile {
290 let out_file = self.output_files.output_file(OutputType::Masl, None);
291
292 if let OutputFile::Real(ref path) = out_file {
293 self.check_file_is_writeable(path);
294 }
295
296 out_file
297 }
298
299 fn check_file_is_writeable(&self, file: &Path) {
300 if let Ok(m) = file.metadata() {
301 if m.permissions().readonly() {
302 panic!(
303 "Compiler exited with a fatal error: file is not writeable: {}",
304 file.display()
305 );
306 }
307 }
308 }
309
310 pub fn parse_only(&self) -> bool {
312 self.options.parse_only
313 }
314
315 pub fn analyze_only(&self) -> bool {
317 self.options.analyze_only
318 }
319
320 pub fn rewrite_only(&self) -> bool {
322 let link_or_masm_requested = self.should_link() || self.should_codegen();
323 !self.options.parse_only && !self.options.analyze_only && !link_or_masm_requested
324 }
325
326 pub fn should_link(&self) -> bool {
328 self.options.output_types.should_link() && !self.options.no_link
329 }
330
331 pub fn should_codegen(&self) -> bool {
333 self.options.output_types.should_codegen() && !self.options.link_only
334 }
335
336 pub fn should_assemble(&self) -> bool {
338 self.options.output_types.should_assemble() && !self.options.link_only
339 }
340
341 pub fn should_emit(&self, ty: OutputType) -> bool {
343 self.options.output_types.contains_key(&ty)
344 }
345
346 pub fn should_print_ir(&self, pass: &str) -> bool {
348 self.options.print_ir_after_all
349 || self.options.print_ir_after_pass.iter().any(|p| p == pass)
350 }
351
352 pub fn should_print_cfg(&self, pass: &str) -> bool {
354 self.options.print_cfg_after_all
355 || self.options.print_cfg_after_pass.iter().any(|p| p == pass)
356 }
357
358 pub fn print(&self, ir: impl Emit, pass: &str) -> std::io::Result<()> {
360 if self.should_print_ir(pass) {
361 ir.write_to_stdout(self)?;
362 }
363 Ok(())
364 }
365
366 pub fn emit_to(&self, ty: OutputType, name: Option<Symbol>) -> Option<PathBuf> {
368 if self.should_emit(ty) {
369 match self.output_files.output_file(ty, name.map(|n| n.as_str())) {
370 OutputFile::Real(path) => Some(path),
371 OutputFile::Stdout => None,
372 }
373 } else {
374 None
375 }
376 }
377
378 pub fn emit<E: Emit>(&self, mode: OutputMode, item: &E) -> std::io::Result<()> {
380 let output_type = item.output_type(mode);
381 if self.should_emit(output_type) {
382 let name = item.name().map(|n| n.as_str());
383 match self.output_files.output_file(output_type, name) {
384 OutputFile::Real(path) => {
385 item.write_to_file(&path, mode, self)?;
386 }
387 OutputFile::Stdout => {
388 let stdout = std::io::stdout().lock();
389 item.write_to(stdout, mode, self)?;
390 }
391 }
392 }
393
394 Ok(())
395 }
396}
397
398#[derive(Debug, Copy, Clone, Default)]
400#[cfg_attr(feature = "std", derive(ValueEnum))]
401pub enum TargetEnv {
402 Emu,
404 #[default]
406 Base,
407 Rollup,
409}
410impl fmt::Display for TargetEnv {
411 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
412 match self {
413 Self::Emu => f.write_str("emu"),
414 Self::Base => f.write_str("base"),
415 Self::Rollup => f.write_str("rollup"),
416 }
417 }
418}