1use std::fs;
74use std::io::Write;
75use std::path::Path;
76use std::process::Command;
77
78const CRATES_DATA_PATH: &str = "crates/logicaffeine_data";
80const CRATES_SYSTEM_PATH: &str = "crates/logicaffeine_system";
81
82use std::fmt::Write as FmtWrite;
83
84use crate::analysis::{DiscoveryPass, EscapeChecker, OwnershipChecker, PolicyRegistry};
85use crate::arena::Arena;
86use crate::arena_ctx::AstContext;
87use crate::ast::{Expr, Stmt, TypeExpr};
88use crate::codegen::{codegen_program, generate_c_header, generate_python_bindings, generate_typescript_bindings};
89use crate::diagnostic::{parse_rustc_json, translate_diagnostics, LogosError};
90use crate::drs::WorldState;
91use crate::error::ParseError;
92use crate::intern::Interner;
93use crate::lexer::Lexer;
94use crate::parser::Parser;
95use crate::sourcemap::SourceMap;
96
97#[derive(Debug, Clone)]
99pub struct CrateDependency {
100 pub name: String,
101 pub version: String,
102 pub features: Vec<String>,
103}
104
105#[derive(Debug)]
107pub struct CompileOutput {
108 pub rust_code: String,
109 pub dependencies: Vec<CrateDependency>,
110 pub c_header: Option<String>,
112 pub python_bindings: Option<String>,
114 pub typescript_types: Option<String>,
116 pub typescript_bindings: Option<String>,
118}
119
120pub fn interpret_program(source: &str) -> Result<String, ParseError> {
138 use futures::executor::block_on;
139
140 let result = block_on(crate::ui_bridge::interpret_for_ui(source));
141 if let Some(err) = result.error {
142 Err(ParseError {
143 kind: crate::error::ParseErrorKind::Custom(err),
144 span: crate::token::Span::default(),
145 })
146 } else {
147 Ok(result.lines.join("\n"))
148 }
149}
150
151pub fn compile_to_rust(source: &str) -> Result<String, ParseError> {
184 compile_program_full(source).map(|o| o.rust_code)
185}
186
187pub fn compile_program_full(source: &str) -> Result<CompileOutput, ParseError> {
192 let mut interner = Interner::new();
193 let mut lexer = Lexer::new(source, &mut interner);
194 let tokens = lexer.tokenize();
195
196 let (type_registry, policy_registry) = {
198 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
199 let result = discovery.run_full();
200 (result.types, result.policies)
201 };
202 let codegen_registry = type_registry.clone();
204 let codegen_policies = policy_registry.clone();
205
206 let mut world_state = WorldState::new();
207 let expr_arena = Arena::new();
208 let term_arena = Arena::new();
209 let np_arena = Arena::new();
210 let sym_arena = Arena::new();
211 let role_arena = Arena::new();
212 let pp_arena = Arena::new();
213 let stmt_arena: Arena<Stmt> = Arena::new();
214 let imperative_expr_arena: Arena<Expr> = Arena::new();
215 let type_expr_arena: Arena<TypeExpr> = Arena::new();
216
217 let ast_ctx = AstContext::with_types(
218 &expr_arena,
219 &term_arena,
220 &np_arena,
221 &sym_arena,
222 &role_arena,
223 &pp_arena,
224 &stmt_arena,
225 &imperative_expr_arena,
226 &type_expr_arena,
227 );
228
229 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
231 let stmts = parser.parse_program()?;
234
235 let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner);
237
238 let mut dependencies = extract_dependencies(&stmts, &interner)?;
240
241 let needs_wasm_bindgen = stmts.iter().any(|stmt| {
243 if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
244 interner.resolve(*target).eq_ignore_ascii_case("wasm")
245 } else {
246 false
247 }
248 });
249 if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
250 dependencies.push(CrateDependency {
251 name: "wasm-bindgen".to_string(),
252 version: "0.2".to_string(),
253 features: vec![],
254 });
255 }
256
257 let mut escape_checker = EscapeChecker::new(&interner);
260 escape_checker.check_program(&stmts).map_err(|e| {
261 ParseError {
264 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
265 span: e.span,
266 }
267 })?;
268
269 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
273
274 let has_c = stmts.iter().any(|stmt| {
276 if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
277 match export_target {
278 None => true,
279 Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
280 }
281 } else {
282 false
283 }
284 });
285
286 let c_header = if has_c {
287 Some(generate_c_header(&stmts, "module", &interner, &codegen_registry))
288 } else {
289 None
290 };
291
292 if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
294 dependencies.push(CrateDependency {
295 name: "serde_json".to_string(),
296 version: "1".to_string(),
297 features: vec![],
298 });
299 }
300
301 let python_bindings = if has_c {
302 Some(generate_python_bindings(&stmts, "module", &interner, &codegen_registry))
303 } else {
304 None
305 };
306
307 let (typescript_bindings, typescript_types) = if has_c {
308 let (js, dts) = generate_typescript_bindings(&stmts, "module", &interner, &codegen_registry);
309 (Some(js), Some(dts))
310 } else {
311 (None, None)
312 };
313
314 Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
315}
316
317fn extract_dependencies(stmts: &[Stmt], interner: &Interner) -> Result<Vec<CrateDependency>, ParseError> {
323 use std::collections::HashMap;
324
325 let mut seen: HashMap<String, String> = HashMap::new(); let mut deps: Vec<CrateDependency> = Vec::new();
327
328 for stmt in stmts {
329 if let Stmt::Require { crate_name, version, features, span } = stmt {
330 let name = interner.resolve(*crate_name).to_string();
331 let ver = interner.resolve(*version).to_string();
332
333 if let Some(existing_ver) = seen.get(&name) {
334 if *existing_ver != ver {
335 return Err(ParseError {
336 kind: crate::error::ParseErrorKind::Custom(format!(
337 "Conflicting versions for crate \"{}\": \"{}\" and \"{}\".",
338 name, existing_ver, ver
339 )),
340 span: *span,
341 });
342 }
343 } else {
345 seen.insert(name.clone(), ver.clone());
346 deps.push(CrateDependency {
347 name,
348 version: ver,
349 features: features.iter().map(|f| interner.resolve(*f).to_string()).collect(),
350 });
351 }
352 }
353 }
354
355 Ok(deps)
356}
357
358pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
394 let mut interner = Interner::new();
395 let mut lexer = Lexer::new(source, &mut interner);
396 let tokens = lexer.tokenize();
397
398 let (type_registry, policy_registry) = {
400 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
401 let result = discovery.run_full();
402 (result.types, result.policies)
403 };
404 let codegen_registry = type_registry.clone();
406 let codegen_policies = policy_registry.clone();
407
408 let mut world_state = WorldState::new();
409 let expr_arena = Arena::new();
410 let term_arena = Arena::new();
411 let np_arena = Arena::new();
412 let sym_arena = Arena::new();
413 let role_arena = Arena::new();
414 let pp_arena = Arena::new();
415 let stmt_arena: Arena<Stmt> = Arena::new();
416 let imperative_expr_arena: Arena<Expr> = Arena::new();
417 let type_expr_arena: Arena<TypeExpr> = Arena::new();
418
419 let ast_ctx = AstContext::with_types(
420 &expr_arena,
421 &term_arena,
422 &np_arena,
423 &sym_arena,
424 &role_arena,
425 &pp_arena,
426 &stmt_arena,
427 &imperative_expr_arena,
428 &type_expr_arena,
429 );
430
431 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
433 let stmts = parser.parse_program()?;
434
435 let mut escape_checker = EscapeChecker::new(&interner);
437 escape_checker.check_program(&stmts).map_err(|e| {
438 ParseError {
439 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
440 span: e.span,
441 }
442 })?;
443
444 let mut ownership_checker = OwnershipChecker::new(&interner);
447 ownership_checker.check_program(&stmts).map_err(|e| {
448 ParseError {
449 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
450 span: e.span,
451 }
452 })?;
453
454 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
455
456 Ok(rust_code)
457}
458
459#[cfg(feature = "verification")]
506pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
507 use crate::verification::VerificationPass;
508
509 let mut interner = Interner::new();
510 let mut lexer = Lexer::new(source, &mut interner);
511 let tokens = lexer.tokenize();
512
513 let (type_registry, policy_registry) = {
515 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
516 let result = discovery.run_full();
517 (result.types, result.policies)
518 };
519 let codegen_registry = type_registry.clone();
521 let codegen_policies = policy_registry.clone();
522
523 let mut world_state = WorldState::new();
524 let expr_arena = Arena::new();
525 let term_arena = Arena::new();
526 let np_arena = Arena::new();
527 let sym_arena = Arena::new();
528 let role_arena = Arena::new();
529 let pp_arena = Arena::new();
530 let stmt_arena: Arena<Stmt> = Arena::new();
531 let imperative_expr_arena: Arena<Expr> = Arena::new();
532 let type_expr_arena: Arena<TypeExpr> = Arena::new();
533
534 let ast_ctx = AstContext::with_types(
535 &expr_arena,
536 &term_arena,
537 &np_arena,
538 &sym_arena,
539 &role_arena,
540 &pp_arena,
541 &stmt_arena,
542 &imperative_expr_arena,
543 &type_expr_arena,
544 );
545
546 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
548 let stmts = parser.parse_program()?;
549
550 let mut escape_checker = EscapeChecker::new(&interner);
552 escape_checker.check_program(&stmts).map_err(|e| {
553 ParseError {
554 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
555 span: e.span,
556 }
557 })?;
558
559 let mut verifier = VerificationPass::new(&interner);
561 verifier.verify_program(&stmts).map_err(|e| {
562 ParseError {
563 kind: crate::error::ParseErrorKind::Custom(format!(
564 "Verification Failed:\n\n{}",
565 e
566 )),
567 span: crate::token::Span::default(),
568 }
569 })?;
570
571 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
572
573 Ok(rust_code)
574}
575
576pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
607 let output = compile_program_full(source).map_err(CompileError::Parse)?;
608
609 let src_dir = output_dir.join("src");
611 fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
612
613 let main_path = src_dir.join("main.rs");
615 let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
616 file.write_all(output.rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
617
618 let mut cargo_toml = String::from(r#"[package]
620name = "logos_output"
621version = "0.1.0"
622edition = "2021"
623
624[dependencies]
625logicaffeine-data = { path = "./crates/logicaffeine_data" }
626logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
627tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
628"#);
629
630 for dep in &output.dependencies {
632 if dep.features.is_empty() {
633 let _ = writeln!(cargo_toml, "{} = \"{}\"", dep.name, dep.version);
634 } else {
635 let feats = dep.features.iter()
636 .map(|f| format!("\"{}\"", f))
637 .collect::<Vec<_>>()
638 .join(", ");
639 let _ = writeln!(
640 cargo_toml,
641 "{} = {{ version = \"{}\", features = [{}] }}",
642 dep.name, dep.version, feats
643 );
644 }
645 }
646
647 let cargo_path = output_dir.join("Cargo.toml");
648 let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
649 file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
650
651 copy_runtime_crates(output_dir)?;
653
654 Ok(())
655}
656
657pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
660 let crates_dir = output_dir.join("crates");
661 fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
662
663 let workspace_root = find_workspace_root()?;
665
666 let data_src = workspace_root.join(CRATES_DATA_PATH);
668 let data_dest = crates_dir.join("logicaffeine_data");
669 copy_dir_recursive(&data_src, &data_dest)?;
670
671 let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
673 let system_dest = crates_dir.join("logicaffeine_system");
674 copy_dir_recursive(&system_src, &system_dest)?;
675
676 let base_src = workspace_root.join("crates/logicaffeine_base");
678 let base_dest = crates_dir.join("logicaffeine_base");
679 copy_dir_recursive(&base_src, &base_dest)?;
680
681 Ok(())
682}
683
684fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
686 if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
688 let path = Path::new(&manifest_dir);
690 if let Some(parent) = path.parent().and_then(|p| p.parent()) {
691 if parent.join("Cargo.toml").exists() {
692 return Ok(parent.to_path_buf());
693 }
694 }
695 }
696
697 let mut current = std::env::current_dir()
699 .map_err(|e| CompileError::Io(e.to_string()))?;
700
701 loop {
702 if current.join("Cargo.toml").exists() && current.join("crates").exists() {
703 return Ok(current);
704 }
705 if !current.pop() {
706 return Err(CompileError::Io("Could not find workspace root".to_string()));
707 }
708 }
709}
710
711fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
714 fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
715
716 for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
717 let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
718 let src_path = entry.path();
719 let file_name = entry.file_name();
720 let dst_path = dst.join(&file_name);
721
722 if file_name == "target"
724 || file_name == ".git"
725 || file_name == "Cargo.lock"
726 || file_name == ".DS_Store"
727 {
728 continue;
729 }
730
731 if file_name.to_string_lossy().starts_with('.') {
733 continue;
734 }
735
736 if !src_path.exists() {
738 continue;
739 }
740
741 if src_path.is_dir() {
742 copy_dir_recursive(&src_path, &dst_path)?;
743 } else if file_name == "Cargo.toml" {
744 match fs::read_to_string(&src_path) {
747 Ok(content) => {
748 let filtered: String = content
749 .lines()
750 .filter(|line| !line.trim().starts_with("[workspace]"))
751 .collect::<Vec<_>>()
752 .join("\n");
753 fs::write(&dst_path, filtered)
754 .map_err(|e| CompileError::Io(e.to_string()))?;
755 }
756 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
757 Err(e) => return Err(CompileError::Io(e.to_string())),
758 }
759 } else {
760 match fs::copy(&src_path, &dst_path) {
761 Ok(_) => {}
762 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
763 Err(e) => return Err(CompileError::Io(e.to_string())),
764 }
765 }
766 }
767
768 Ok(())
769}
770
771pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
814 compile_to_dir(source, output_dir)?;
815
816 let build_output = Command::new("cargo")
818 .arg("build")
819 .arg("--message-format=json")
820 .current_dir(output_dir)
821 .output()
822 .map_err(|e| CompileError::Io(e.to_string()))?;
823
824 if !build_output.status.success() {
825 let stderr = String::from_utf8_lossy(&build_output.stderr);
826 let stdout = String::from_utf8_lossy(&build_output.stdout);
827
828 let diagnostics = parse_rustc_json(&stdout);
830
831 if !diagnostics.is_empty() {
832 let source_map = SourceMap::new(source.to_string());
834 let interner = Interner::new();
835
836 if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
837 return Err(CompileError::Ownership(logos_error));
838 }
839 }
840
841 return Err(CompileError::Build(stderr.to_string()));
843 }
844
845 let run_output = Command::new("cargo")
847 .arg("run")
848 .arg("--quiet")
849 .current_dir(output_dir)
850 .output()
851 .map_err(|e| CompileError::Io(e.to_string()))?;
852
853 if !run_output.status.success() {
854 let stderr = String::from_utf8_lossy(&run_output.stderr);
855 return Err(CompileError::Runtime(stderr.to_string()));
856 }
857
858 let stdout = String::from_utf8_lossy(&run_output.stdout);
859 Ok(stdout.to_string())
860}
861
862pub fn compile_file(path: &Path) -> Result<String, CompileError> {
865 let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
866 compile_to_rust(&source).map_err(CompileError::Parse)
867}
868
869pub fn compile_project(entry_file: &Path) -> Result<CompileOutput, CompileError> {
887 use crate::loader::Loader;
888 use crate::analysis::discover_with_imports;
889
890 let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
891 let mut loader = Loader::new(root_path);
892 let mut interner = Interner::new();
893
894 let source = fs::read_to_string(entry_file)
896 .map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
897
898 let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
900 .map_err(|e| CompileError::Io(e))?;
901
902 compile_to_rust_with_registry_full(&source, type_registry, &mut interner)
904 .map_err(CompileError::Parse)
905}
906
907fn compile_to_rust_with_registry_full(
910 source: &str,
911 type_registry: crate::analysis::TypeRegistry,
912 interner: &mut Interner,
913) -> Result<CompileOutput, ParseError> {
914 let mut lexer = Lexer::new(source, interner);
915 let tokens = lexer.tokenize();
916
917 let policy_registry = {
919 let mut discovery = DiscoveryPass::new(&tokens, interner);
920 discovery.run_full().policies
921 };
922
923 let codegen_registry = type_registry.clone();
924 let codegen_policies = policy_registry.clone();
925
926 let mut world_state = WorldState::new();
927 let expr_arena = Arena::new();
928 let term_arena = Arena::new();
929 let np_arena = Arena::new();
930 let sym_arena = Arena::new();
931 let role_arena = Arena::new();
932 let pp_arena = Arena::new();
933 let stmt_arena: Arena<Stmt> = Arena::new();
934 let imperative_expr_arena: Arena<Expr> = Arena::new();
935 let type_expr_arena: Arena<TypeExpr> = Arena::new();
936
937 let ast_ctx = AstContext::with_types(
938 &expr_arena,
939 &term_arena,
940 &np_arena,
941 &sym_arena,
942 &role_arena,
943 &pp_arena,
944 &stmt_arena,
945 &imperative_expr_arena,
946 &type_expr_arena,
947 );
948
949 let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
950 let stmts = parser.parse_program()?;
951
952 let mut dependencies = extract_dependencies(&stmts, interner)?;
954
955 let needs_wasm_bindgen = stmts.iter().any(|stmt| {
957 if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
958 interner.resolve(*target).eq_ignore_ascii_case("wasm")
959 } else {
960 false
961 }
962 });
963 if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
964 dependencies.push(CrateDependency {
965 name: "wasm-bindgen".to_string(),
966 version: "0.2".to_string(),
967 features: vec![],
968 });
969 }
970
971 let mut escape_checker = EscapeChecker::new(interner);
972 escape_checker.check_program(&stmts).map_err(|e| {
973 ParseError {
974 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
975 span: e.span,
976 }
977 })?;
978
979 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner);
980
981 let has_c = stmts.iter().any(|stmt| {
983 if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
984 match export_target {
985 None => true,
986 Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
987 }
988 } else {
989 false
990 }
991 });
992
993 let c_header = if has_c {
994 Some(generate_c_header(&stmts, "module", interner, &codegen_registry))
995 } else {
996 None
997 };
998
999 if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
1000 dependencies.push(CrateDependency {
1001 name: "serde_json".to_string(),
1002 version: "1".to_string(),
1003 features: vec![],
1004 });
1005 }
1006
1007 let python_bindings = if has_c {
1008 Some(generate_python_bindings(&stmts, "module", interner, &codegen_registry))
1009 } else {
1010 None
1011 };
1012
1013 let (typescript_bindings, typescript_types) = if has_c {
1014 let (js, dts) = generate_typescript_bindings(&stmts, "module", interner, &codegen_registry);
1015 (Some(js), Some(dts))
1016 } else {
1017 (None, None)
1018 };
1019
1020 Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
1021}
1022
1023#[derive(Debug)]
1045pub enum CompileError {
1046 Parse(ParseError),
1051
1052 Io(String),
1056
1057 Build(String),
1062
1063 Runtime(String),
1067
1068 Ownership(LogosError),
1074}
1075
1076impl std::fmt::Display for CompileError {
1077 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1078 match self {
1079 CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
1080 CompileError::Io(e) => write!(f, "IO error: {}", e),
1081 CompileError::Build(e) => write!(f, "Build error: {}", e),
1082 CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
1083 CompileError::Ownership(e) => write!(f, "{}", e),
1084 }
1085 }
1086}
1087
1088impl std::error::Error for CompileError {}
1089
1090#[cfg(test)]
1091mod tests {
1092 use super::*;
1093
1094 #[test]
1095 fn test_compile_let_statement() {
1096 let source = "## Main\nLet x be 5.";
1097 let result = compile_to_rust(source);
1098 assert!(result.is_ok(), "Should compile: {:?}", result);
1099 let rust = result.unwrap();
1100 assert!(rust.contains("fn main()"));
1101 assert!(rust.contains("let x = 5;"));
1102 }
1103
1104 #[test]
1105 fn test_compile_return_statement() {
1106 let source = "## Main\nReturn 42.";
1107 let result = compile_to_rust(source);
1108 assert!(result.is_ok(), "Should compile: {:?}", result);
1109 let rust = result.unwrap();
1110 assert!(rust.contains("return 42;"));
1111 }
1112}