1#![doc(html_root_url = "https://docs.rs/rucc-session/0.1.0")]
21
22use std::fmt;
23use std::str::FromStr;
24
25use rucc_base::Interner;
26use rucc_diag::{Diagnostic, Severity};
27use rucc_target::{TargetInfo, Triple};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
36pub enum OptLevel {
37 #[default]
39 O0,
40 O1,
42 O2,
44 O3,
46 Os,
48 Oz,
50}
51
52impl OptLevel {
53 pub const fn as_flag(self) -> &'static str {
55 match self {
56 OptLevel::O0 => "-O0",
57 OptLevel::O1 => "-O1",
58 OptLevel::O2 => "-O2",
59 OptLevel::O3 => "-O3",
60 OptLevel::Os => "-Os",
61 OptLevel::Oz => "-Oz",
62 }
63 }
64
65 pub const fn is_size(self) -> bool {
67 matches!(self, OptLevel::Os | OptLevel::Oz)
68 }
69
70 pub const fn runs_optimizer(self) -> bool {
72 !matches!(self, OptLevel::O0)
73 }
74}
75
76impl fmt::Display for OptLevel {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 f.write_str(self.as_flag())
79 }
80}
81
82impl FromStr for OptLevel {
83 type Err = ();
84
85 fn from_str(s: &str) -> Result<Self, ()> {
87 Ok(match s {
88 "0" => OptLevel::O0,
89 "" | "1" => OptLevel::O1,
90 "2" => OptLevel::O2,
91 "3" | "4" | "5" | "6" | "7" | "8" | "9" => OptLevel::O3,
94 "s" => OptLevel::Os,
95 "z" => OptLevel::Oz,
96 _ => return Err(()),
97 })
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
107pub enum EmitKind {
112 #[default]
114 Executable,
115 Object,
117 Asm,
119 Preprocessed,
121 Tast,
123 Ir,
125 MirFinal,
127}
128
129impl EmitKind {
130 pub const fn as_str(self) -> &'static str {
132 match self {
133 EmitKind::Executable => "exe",
134 EmitKind::Object => "obj",
135 EmitKind::Asm => "asm",
136 EmitKind::Preprocessed => "preprocessed",
137 EmitKind::Tast => "tast",
138 EmitKind::Ir => "ir",
139 EmitKind::MirFinal => "mir-final",
140 }
141 }
142}
143
144impl FromStr for EmitKind {
145 type Err = ();
146
147 fn from_str(s: &str) -> Result<Self, ()> {
148 Ok(match s {
149 "exe" => EmitKind::Executable,
150 "obj" => EmitKind::Object,
151 "asm" => EmitKind::Asm,
152 "preprocessed" => EmitKind::Preprocessed,
153 "tast" => EmitKind::Tast,
154 "ir" => EmitKind::Ir,
155 "mir-final" => EmitKind::MirFinal,
156 _ => return Err(()),
157 })
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq)]
167#[non_exhaustive]
168pub struct Options {
169 pub target: Triple,
171 pub opt_level: OptLevel,
173 pub emit: EmitKind,
175 pub debug_info: bool,
177 pub warnings_are_errors: bool,
179 pub error_limit: u32,
182}
183
184impl Options {
185 pub fn new(target: Triple) -> Self {
187 Self {
188 target,
189 opt_level: OptLevel::default(),
190 emit: EmitKind::default(),
191 debug_info: false,
192 warnings_are_errors: false,
193 error_limit: 20,
194 }
195 }
196}
197
198#[derive(Debug)]
205pub struct Session {
206 pub opts: Options,
208 pub target: TargetInfo,
210 pub interner: Interner,
212 diagnostics: Vec<Diagnostic>,
213 error_count: u32,
214 warning_count: u32,
215}
216
217impl Session {
218 pub fn new(opts: Options) -> Self {
220 let target = TargetInfo::new(opts.target);
221 Self {
222 opts,
223 target,
224 interner: Interner::with_capacity(1024),
225 diagnostics: Vec::new(),
226 error_count: 0,
227 warning_count: 0,
228 }
229 }
230
231 pub fn emit(&mut self, mut diag: Diagnostic) {
236 if self.opts.warnings_are_errors && diag.severity == Severity::Warning {
237 diag.severity = Severity::Error;
238 }
239 match diag.severity {
240 Severity::Error | Severity::Ice => self.error_count += 1,
241 Severity::Warning => self.warning_count += 1,
242 Severity::Note | Severity::Help => {}
243 }
244 self.diagnostics.push(diag);
245 }
246
247 pub fn diagnostics(&self) -> &[Diagnostic] {
249 &self.diagnostics
250 }
251
252 pub fn has_errors(&self) -> bool {
254 self.error_count > 0
255 }
256
257 pub fn error_count(&self) -> u32 {
259 self.error_count
260 }
261
262 pub fn warning_count(&self) -> u32 {
264 self.warning_count
265 }
266
267 pub fn error_limit_reached(&self) -> bool {
269 self.opts.error_limit != 0 && self.error_count >= self.opts.error_limit
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 fn session() -> Session {
278 Session::new(Options::new("x86_64-unknown-linux-gnu".parse().unwrap()))
279 }
280
281 #[test]
282 fn optimisation_levels_parse_the_way_gcc_spells_them() {
283 assert_eq!("".parse::<OptLevel>().unwrap(), OptLevel::O1);
284 assert_eq!("0".parse::<OptLevel>().unwrap(), OptLevel::O0);
285 assert_eq!("2".parse::<OptLevel>().unwrap(), OptLevel::O2);
286 assert_eq!("9".parse::<OptLevel>().unwrap(), OptLevel::O3);
287 assert_eq!("s".parse::<OptLevel>().unwrap(), OptLevel::Os);
288 assert!("q".parse::<OptLevel>().is_err());
289 }
290
291 #[test]
292 fn only_o0_skips_the_optimizer() {
293 assert!(!OptLevel::O0.runs_optimizer());
294 assert!(OptLevel::O1.runs_optimizer());
295 assert!(OptLevel::Oz.runs_optimizer());
296 }
297
298 #[test]
299 fn emit_kinds_round_trip_through_their_names() {
300 for k in [
301 EmitKind::Executable,
302 EmitKind::Object,
303 EmitKind::Asm,
304 EmitKind::Preprocessed,
305 EmitKind::Tast,
306 EmitKind::Ir,
307 EmitKind::MirFinal,
308 ] {
309 assert_eq!(k.as_str().parse::<EmitKind>().unwrap(), k);
310 }
311 }
312
313 #[test]
314 fn errors_are_counted_and_warnings_are_not() {
315 let mut s = session();
316 s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
317 s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
318 assert_eq!(s.error_count(), 1);
319 assert_eq!(s.warning_count(), 1);
320 assert!(s.has_errors());
321 assert_eq!(s.diagnostics().len(), 2);
322 }
323
324 #[test]
325 fn werror_promotes_once_at_the_sink() {
326 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
327 opts.warnings_are_errors = true;
328 let mut s = Session::new(opts);
329 s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
330 assert_eq!(s.error_count(), 1);
331 assert_eq!(s.warning_count(), 0);
332 assert_eq!(s.diagnostics()[0].severity, Severity::Error);
333 }
334
335 #[test]
336 fn the_error_limit_can_be_switched_off() {
337 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
338 opts.error_limit = 0;
339 let mut s = Session::new(opts);
340 for _ in 0..100 {
341 s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
342 }
343 assert!(!s.error_limit_reached());
344 }
345
346 #[test]
347 fn the_session_carries_the_resolved_target() {
348 let s = session();
349 assert_eq!(s.target.pointer_width, 64);
350 assert!(s.target.char_is_signed);
351 }
352}