1use std::path::Path;
14
15use rucc_diag::{Diagnostic, Severity};
16use rucc_lex::{Convert, Keywords, PpToken, convert};
17use rucc_sema::{Checker, Context as CheckContext};
18use rucc_session::{EmitKind, FileSystem, Options, Session};
19
20use crate::preprocess::render;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Compiled {
25 pub text: String,
27 pub messages: Vec<String>,
29 pub errors: u32,
31}
32
33impl Compiled {
34 #[must_use]
36 pub fn failed(&self) -> bool {
37 self.errors > 0
38 }
39}
40
41#[must_use]
54pub fn compile(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
55 let mut sess = Session::new(opts.clone());
56 let keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
60 let mut diagnostics: Vec<Diagnostic> = Vec::new();
61
62 let bytes = match fs.read(Path::new(name)) {
63 Ok(bytes) => bytes,
64 Err(e) => return failure(format!("{name}: {e}")),
65 };
66 let Ok(file) = sess.sources.add_shared(name, bytes, None) else {
67 return failure(format!("{name}: the source map has no room left for this file"));
68 };
69
70 let mut pp = rucc_pp::Preprocessor::new();
74 let predef = rucc_pp::Predef::for_options(opts);
75 let expanded: Vec<PpToken> = {
76 let mut cx = rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
77 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
78 return failure(format!("{name}: the source map has no room for the built in macros"));
79 }
80 pp.run(file, &mut cx).iter().map(|token| token.to_pp()).collect()
81 };
82 diagnostics.extend(pp.take_diagnostics());
83
84 let cx = Convert {
87 keywords: &keywords,
88 interner: &sess.interner,
89 target: &sess.target,
90 std: opts.std,
91 pedantic: opts.pedantic,
92 };
93 let (tokens, complaints) = convert(&expanded, &cx);
94 diagnostics.extend(complaints);
95
96 let parsed = rucc_parse::parse(
97 &tokens,
98 rucc_parse::Context {
99 interner: &sess.interner,
100 std: opts.std,
101 gnu: opts.gnu_extensions,
102 pedantic: opts.pedantic,
103 error_limit: opts.error_limit as usize,
104 },
105 );
106 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
107 diagnostics.extend(parsed.diagnostics);
108
109 let mut text = String::new();
110 if !parse_failed {
111 let mut checker = Checker::new(
112 &parsed.ast,
113 CheckContext {
114 names: &sess.interner,
115 target: &sess.target,
116 std: opts.std,
117 gnu: opts.gnu_extensions,
118 pedantic: opts.pedantic,
119 error_limit: opts.error_limit as usize,
120 },
121 );
122 checker.check_unit();
123 let checked = checker.finish();
124 if !checked.failed() && opts.emit == EmitKind::Tast {
125 text = rucc_sema::print(&checked.tast, &checked.types, &sess.interner);
126 }
127 diagnostics.extend(checked.diagnostics);
128 }
129
130 let mut messages = Vec::with_capacity(diagnostics.len());
131 let mut errors = 0;
132 for diag in &diagnostics {
133 if diag.severity.is_fatal()
134 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
135 {
136 errors += 1;
137 }
138 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
139 }
140 if errors > 0 {
141 text.clear();
143 }
144 Compiled { text, messages, errors }
145}
146
147fn failure(message: String) -> Compiled {
150 Compiled { text: String::new(), messages: vec![format!("rucc: error: {message}")], errors: 1 }
151}
152
153#[cfg(test)]
154mod tests {
155 use rucc_session::{MemoryFileSystem, Std};
156 use rucc_target::Triple;
157
158 use super::*;
159
160 fn options() -> Options {
161 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
162 opts.emit = EmitKind::Tast;
163 opts
164 }
165
166 fn run(opts: &Options, source: &str) -> Compiled {
167 let mut fs = MemoryFileSystem::new();
168 fs.insert("/main.c", source.to_owned().into_bytes());
169 compile(opts, "/main.c", &fs)
170 }
171
172 fn tast(source: &str) -> String {
174 let result = run(&options(), source);
175 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
176 result.text
177 }
178
179 #[test]
180 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
181 let fs = MemoryFileSystem::new();
182 let result = compile(&options(), "/nope.c", &fs);
183 assert!(result.failed());
184 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
185 assert!(result.text.is_empty());
186 }
187
188 #[test]
189 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
190 let text = tast("int x = 1;\n");
191 let expected = "\
192decl #0 x : int object external static defined
193 init
194 +0
195 const 1 : int
196";
197 assert_eq!(text, expected);
198 }
199
200 #[test]
201 fn the_macros_are_expanded_before_anything_is_parsed() {
202 let text = tast("#define N 2\nint a[N];\n");
206 assert!(text.starts_with("decl #0 a : int [2] object external static tentative"), "{text}");
207 }
208
209 #[test]
210 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
211 let text = tast("long f(int a, long b) { return a + b; }\n");
215 assert!(text.contains("convert arithmetic"), "{text}");
216 }
217
218 #[test]
219 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
220 for source in [
221 "#error stop\n",
222 "int f(void) { return 1 + ; }\n",
223 "int f(void) { return undeclared; }\n",
224 ] {
225 let result = run(&options(), source);
226 assert!(result.failed(), "expected this to fail:\n{source}");
227 assert!(result.text.is_empty(), "a file that did not compile wrote a tree:\n{source}");
228 }
229 }
230
231 #[test]
232 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
233 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
237 assert_eq!(result.errors, 1, "{:?}", result.messages);
238 }
239
240 #[test]
241 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
242 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
246 assert_eq!(result.errors, 1, "{:?}", result.messages);
247 }
248
249 #[test]
250 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
251 let source = "int f(void) { char c = 300; return c; }\n";
252 let plain = run(&options(), source);
253 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
254 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
255 assert!(!plain.text.is_empty(), "a warning is not a reason to write nothing");
256
257 let mut opts = options();
258 opts.warnings_are_errors = true;
259 let strict = run(&opts, source);
260 assert!(strict.failed());
261 assert!(strict.text.is_empty(), "and under -Werror it is a reason to write nothing");
262 for message in &strict.messages {
263 assert!(!message.contains("warning:"), "{message}");
264 }
265 }
266
267 #[test]
268 fn the_dialect_reaches_the_keywords_and_the_checking() {
269 let source = "typeof(1) x;\n";
272 let mut opts = options();
273 opts.std = Std::C23;
274 opts.gnu_extensions = false;
275 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
276
277 opts.std = Std::C17;
278 assert!(run(&opts, source).failed());
279 }
280
281 #[test]
282 fn asking_for_a_later_kind_runs_the_same_front_end_and_writes_nothing_yet() {
283 let mut opts = options();
284 opts.emit = EmitKind::Ir;
285 let result = run(&opts, "int x = 1;\n");
286 assert!(!result.failed(), "{:?}", result.messages);
287 assert!(result.text.is_empty());
288 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
291 }
292}