1use crate::bytecode::{BytecodeModule, Value, module_to_text, text_to_module};
2use crate::parser_registry::{self, ParserCli};
3use crate::sil_to_bytecode;
4use crate::vm::BytecodeVM;
5use std::fs;
6use std::path::Path;
7
8#[derive(Debug, Clone)]
9pub struct BytecodeCompileOutput {
10 pub module: BytecodeModule,
11 pub sil: String,
12 pub identity: crate::core_ir::ModuleIdentityReport,
13}
14
15pub fn compile_source_path(
16 path: &Path,
17 module_id: &str,
18 parser: ParserCli,
19) -> Result<BytecodeCompileOutput, String> {
20 let resolved = parser_registry::resolve_parser_id(path, parser);
21 let Some(mut module) =
22 parser_registry::parse_with_resolved(resolved, path).map_err(|err| err.to_string())?
23 else {
24 return Err("bytecode compiler requires a Core IR frontend; Swift SIL emit is not supported by this path".to_string());
25 };
26 crate::lower_core::desugar_module(&mut module);
28 crate::family_typecheck::typecheck_resolved(&resolved, &module)?;
29
30 if std::env::var("IN_TYPECHECK").is_ok() {
31 let strict = std::env::var("IN_TYPECHECK").as_deref() == Ok("strict");
32 if let Err(errors) = crate::typecheck::TypeChecker::new().check_module(&module) {
33 for err in &errors {
34 eprintln!("[typecheck] {:?}", err);
35 }
36 if strict {
37 return Err("typecheck-failed".to_string());
38 }
39 }
40 }
41
42 let identity = module.identity_report(module_id);
43 let sil = crate::compiler::driver::lower_unified_module(&module, &identity.effective_module_id);
44 let artifact = crate::hybrid_sil::parse_textual_sil(&sil);
45 let mut module = sil_to_bytecode::lower_sil_to_bytecode(&artifact)?;
46 module.identity = Some(identity.clone());
47 module.package_exports = crate::package_runtime::collect_package_exports_for_source(path);
48 Ok(BytecodeCompileOutput {
49 module,
50 sil,
51 identity,
52 })
53}
54
55pub fn write_bytecode_module(module: &BytecodeModule, out_path: &Path) -> Result<(), String> {
56 if let Some(parent) = out_path.parent()
57 && !parent.as_os_str().is_empty()
58 {
59 fs::create_dir_all(parent).map_err(|err| format!("create output dir: {err}"))?;
60 }
61 fs::write(out_path, module_to_text(module)).map_err(|err| format!("write bytecode: {err}"))
62}
63
64pub fn read_bytecode_module(path: &Path) -> Result<BytecodeModule, String> {
65 let text = fs::read_to_string(path).map_err(|err| format!("read bytecode: {err}"))?;
66 text_to_module(&text).map_err(|err| format!("parse bytecode: {err}"))
67}
68
69pub fn run_bytecode_module(module: BytecodeModule) -> Result<Value, String> {
70 let mut vm = BytecodeVM::new(module);
71 vm.run()
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use std::time::{SystemTime, UNIX_EPOCH};
78
79 fn temp_file(name: &str) -> std::path::PathBuf {
80 std::env::temp_dir().join(format!(
81 "inauguration-bytecode-{}-{}-{name}",
82 std::process::id(),
83 SystemTime::now()
84 .duration_since(UNIX_EPOCH)
85 .unwrap()
86 .as_nanos()
87 ))
88 }
89
90 #[test]
91 fn compiles_in_source_to_runnable_bytecode() {
92 let path = temp_file("main.in");
93 fs::write(
94 &path,
95 "fn helper(value: Int) -> Int { return value; }\nfn main() -> Int { return helper(7); }\n",
96 )
97 .unwrap();
98
99 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
100 assert!(
101 output.sil.contains("function_ref @helper"),
102 "{}",
103 output.sil
104 );
105 assert!(
106 output
107 .module
108 .functions
109 .iter()
110 .any(|function| function.name == "main")
111 );
112 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(7));
113
114 fs::remove_file(path).unwrap();
115 }
116
117 #[test]
118 fn compile_output_and_artifact_carry_core_identity_metadata() {
119 let source_path = temp_file("identity.in");
120 let bytecode_path = temp_file("identity.bca");
121 fs::write(
122 &source_path,
123 "package agents.video;\nmodule agents.video.main;\nfn helper(value: Int) -> Int { return value; }\nfn main() -> Int { return helper(9); }\n",
124 )
125 .unwrap();
126
127 let output = compile_source_path(&source_path, "App", ParserCli::Auto).unwrap();
128 assert_eq!(output.identity.package.as_deref(), Some("agents.video"));
129 assert_eq!(output.identity.module.as_deref(), Some("agents.video.main"));
130 assert_eq!(output.identity.requested_module_id, "App");
131 assert_eq!(output.identity.effective_module_id, "agents.video.main");
132 let module_identity = output.module.identity.as_ref().expect("module identity");
133 assert_eq!(module_identity.package.as_deref(), Some("agents.video"));
134 assert_eq!(module_identity.module.as_deref(), Some("agents.video.main"));
135 assert_eq!(module_identity.requested_module_id, "App");
136 assert_eq!(module_identity.effective_module_id, "agents.video.main");
137 assert!(output.sil.contains("sil @helper"), "{}", output.sil);
138 assert!(
139 !output.sil.contains("sil @agents.video.main.helper"),
140 "{}",
141 output.sil
142 );
143 assert_eq!(
144 run_bytecode_module(output.module.clone()).unwrap(),
145 Value::Int(9)
146 );
147
148 write_bytecode_module(&output.module, &bytecode_path).unwrap();
149 let roundtrip = read_bytecode_module(&bytecode_path).unwrap();
150 assert_eq!(roundtrip.entry_point, "main");
151 let roundtrip_identity = roundtrip.identity.as_ref().expect("roundtrip identity");
152 assert_eq!(roundtrip_identity.package.as_deref(), Some("agents.video"));
153 assert_eq!(
154 roundtrip_identity.module.as_deref(),
155 Some("agents.video.main")
156 );
157 assert_eq!(roundtrip_identity.requested_module_id.as_str(), "App");
158 assert_eq!(
159 roundtrip_identity.effective_module_id.as_str(),
160 "agents.video.main"
161 );
162 assert_eq!(run_bytecode_module(roundtrip).unwrap(), Value::Int(9));
163
164 fs::remove_file(source_path).unwrap();
165 fs::remove_file(bytecode_path).unwrap();
166 }
167
168 #[test]
169 fn compiles_in_modulo_to_runnable_bytecode() {
170 let path = temp_file("modulo.in");
171 fs::write(&path, "fn main() -> Int { return 7 % 4; }\n").unwrap();
172
173 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
174 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(3));
175
176 fs::remove_file(path).unwrap();
177 }
178
179 #[test]
180 fn compiles_javascript_class_method_to_runnable_bytecode() {
181 let path = temp_file("counter.js");
182 fs::write(
183 &path,
184 r#"
185class Counter {
186 value = 0;
187 constructor(start) {
188 this.value = start;
189 }
190 inc() {
191 return this.value + 1;
192 }
193}
194function answer() {
195 const c = new Counter(41);
196 return c.inc();
197}
198function main() {
199 return answer();
200}
201"#,
202 )
203 .unwrap();
204
205 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
206 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(42));
207
208 fs::remove_file(path).unwrap();
209 }
210
211 #[test]
212 fn compiles_typescript_class_method_to_runnable_bytecode() {
213 let path = temp_file("counter.ts");
214 fs::write(
215 &path,
216 r#"
217class Counter {
218 value: number;
219 constructor(start: number) {
220 this.value = start;
221 }
222 inc(): number {
223 return this.value + 1;
224 }
225}
226function answer(): number {
227 const c = new Counter(41);
228 return c.inc();
229}
230function main(): number {
231 return answer();
232}
233"#,
234 )
235 .unwrap();
236
237 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
238 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(42));
239
240 fs::remove_file(path).unwrap();
241 }
242
243 #[test]
244 fn rejects_unresolved_identifier_before_lowering() {
245 let path = temp_file("unresolved-ident.in");
246 fs::write(&path, "fn main() -> Int { return missing; }\n").unwrap();
247
248 let err = compile_source_path(&path, "App", ParserCli::Auto)
249 .expect_err("unresolved identifiers should fail before bytecode lowering");
250 assert!(
251 err.contains("unresolved identifier `missing` in `main`"),
252 "unexpected error: {err}"
253 );
254
255 fs::remove_file(path).unwrap();
256 }
257
258 #[test]
259 fn writes_and_reads_bytecode_artifact() {
260 let source_path = temp_file("artifact.in");
261 let bytecode_path = temp_file("artifact.bca");
262 fs::write(&source_path, "fn main() -> void {}\n").unwrap();
263
264 let output = compile_source_path(&source_path, "App", ParserCli::Auto).unwrap();
265 write_bytecode_module(&output.module, &bytecode_path).unwrap();
266 let roundtrip = read_bytecode_module(&bytecode_path).unwrap();
267 assert_eq!(roundtrip.entry_point, output.module.entry_point);
268 run_bytecode_module(roundtrip).unwrap();
269
270 fs::remove_file(source_path).unwrap();
271 fs::remove_file(bytecode_path).unwrap();
272 }
273
274 #[test]
275 fn bytecode_artifact_preserves_string_literals_with_spaces() {
276 let source_path = temp_file("string-space.in");
277 let bytecode_path = temp_file("string-space.bca");
278 fs::write(
279 &source_path,
280 "fn main() -> String { return \"hello world\"; }\n",
281 )
282 .unwrap();
283
284 let output = compile_source_path(&source_path, "App", ParserCli::Auto).unwrap();
285 assert_eq!(
286 run_bytecode_module(output.module.clone()).unwrap(),
287 Value::String("hello world".to_string())
288 );
289 write_bytecode_module(&output.module, &bytecode_path).unwrap();
290 let roundtrip = read_bytecode_module(&bytecode_path).unwrap();
291 assert_eq!(
292 run_bytecode_module(roundtrip).unwrap(),
293 Value::String("hello world".to_string())
294 );
295
296 fs::remove_file(source_path).unwrap();
297 fs::remove_file(bytecode_path).unwrap();
298 }
299
300 #[test]
301 fn compiles_array_literal_and_index_access() {
302 let path = temp_file("array-index.in");
303 fs::write(
304 &path,
305 "fn main() -> Int { let xs: [Int] = [2, 5, 8]; return xs[1]; }\n",
306 )
307 .unwrap();
308
309 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
310 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(5));
311
312 fs::remove_file(path).unwrap();
313 }
314
315 #[test]
316 fn compiles_array_index_assignment() {
317 let path = temp_file("array-index-assign.in");
318 fs::write(
319 &path,
320 "fn main() -> Int { let xs: [Int] = [2, 5, 8]; xs[1] = 9; return xs[1]; }\n",
321 )
322 .unwrap();
323
324 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
325 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(9));
326
327 fs::remove_file(path).unwrap();
328 }
329
330 #[test]
331 fn compiles_struct_method_call() {
332 let path = temp_file("method-call.in");
333 fs::write(
334 &path,
335 r#"
336struct Point {
337 Int x
338 Int y
339
340 fn sum() -> Int {
341 return self.x + self.y;
342 }
343}
344
345fn main() -> Int {
346 let p: Point = Point { x: 2, y: 5 };
347 return p.sum();
348}
349"#,
350 )
351 .unwrap();
352
353 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
354 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(7));
355
356 fs::remove_file(path).unwrap();
357 }
358
359 #[test]
360 fn branch_assignment_updates_variable_value() {
361 let path = temp_file("branch-assign.in");
362 fs::write(
363 &path,
364 "fn main() -> Int { let x: Int = 0; if true { x = 9; } return x; }\n",
365 )
366 .unwrap();
367
368 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
369 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(9));
370
371 fs::remove_file(path).unwrap();
372 }
373
374 #[test]
375 fn while_loop_updates_variable_until_condition_is_false() {
376 let path = temp_file("while-loop.in");
377 fs::write(
378 &path,
379 "fn main() -> Int { let x: Int = 0; while x < 3 { x = x + 1; } return x; }\n",
380 )
381 .unwrap();
382
383 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
384 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(3));
385
386 fs::remove_file(path).unwrap();
387 }
388
389 #[test]
390 fn while_loop_skips_body_when_condition_is_false() {
391 let path = temp_file("while-skip.in");
392 fs::write(
393 &path,
394 "fn main() -> Int { let x: Int = 5; while x < 3 { x = x + 1; } return x; }\n",
395 )
396 .unwrap();
397
398 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
399 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(5));
400
401 fs::remove_file(path).unwrap();
402 }
403
404 #[test]
405 fn match_statement_selects_single_arm() {
406 let path = temp_file("match.in");
407 fs::write(
408 &path,
409 r#"
410fn choose(tag: Int) -> Int {
411 let out: Int = 0;
412 match tag {
413 1 { out = 10; }
414 _ { out = 20; }
415 }
416 return out;
417}
418fn main() -> Int { return choose(1); }
419"#,
420 )
421 .unwrap();
422
423 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
424 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(10));
425
426 fs::remove_file(path).unwrap();
427 }
428
429 #[test]
430 fn string_equality_compares_string_values() {
431 let path = temp_file("string-eq.in");
432 fs::write(
433 &path,
434 "fn main() -> Bool { return \"left\" == \"right\"; }\n",
435 )
436 .unwrap();
437
438 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
439 assert_eq!(
440 run_bytecode_module(output.module).unwrap(),
441 Value::Bool(false)
442 );
443
444 fs::remove_file(path).unwrap();
445 }
446
447 #[test]
448 fn struct_field_access_returns_scalar() {
449 let path = temp_file("struct-field.in");
450 fs::write(
451 &path,
452 "struct Point { Int x; Int y }\nfn main() -> Int { let p: Point = Point { x: 2, y: 5 }; return p.y; }\n",
453 )
454 .unwrap();
455
456 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
457 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(5));
458
459 fs::remove_file(path).unwrap();
460 }
461
462 #[test]
463 fn direct_struct_field_access_returns_scalar() {
464 let path = temp_file("direct-struct-field.in");
465 fs::write(
466 &path,
467 "struct Point { Int x; Int y }\nfn main() -> Int { return Point { x: 2, y: 5 }.y; }\n",
468 )
469 .unwrap();
470
471 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
472 assert_eq!(run_bytecode_module(output.module).unwrap(), Value::Int(5));
473
474 fs::remove_file(path).unwrap();
475 }
476
477 #[test]
478 fn runs_string_bool_and_if_return_bytecode() {
479 let path = temp_file("agent.in");
480 fs::write(
481 &path,
482 "fn ready(flag: Bool) -> String { if flag { return \"ready\"; } return \"no\"; }\nfn main() -> String { return ready(true); }\n",
483 )
484 .unwrap();
485
486 let output = compile_source_path(&path, "App", ParserCli::Auto).unwrap();
487 assert_eq!(
488 run_bytecode_module(output.module).unwrap(),
489 Value::String("ready".to_string())
490 );
491
492 fs::remove_file(path).unwrap();
493 }
494}