diff --git a/benches/iseriser_bench.rs b/benches/iseriser_bench.rs index 67d2eed..5741596 100644 --- a/benches/iseriser_bench.rs +++ b/benches/iseriser_bench.rs @@ -21,8 +21,9 @@ // Run with: // cargo bench --bench iseriser_bench -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use iseriser::manifest::{parse_manifest, validate}; +use std::hint::black_box; // --------------------------------------------------------------------------- // Manifest TOML fixtures @@ -100,8 +101,8 @@ description = "Gleam interop -iser targeting the BEAM runtime" fn bench_parse_minimal(c: &mut Criterion) { c.bench_function("parse_manifest/minimal", |b| { b.iter(|| { - let m = parse_manifest(black_box(MINIMAL_MANIFEST)) - .expect("minimal manifest must parse"); + let m = + parse_manifest(black_box(MINIMAL_MANIFEST)).expect("minimal manifest must parse"); black_box(m); }); }); @@ -111,8 +112,7 @@ fn bench_parse_minimal(c: &mut Criterion) { fn bench_parse_rich(c: &mut Criterion) { c.bench_function("parse_manifest/rich_20_primitives", |b| { b.iter(|| { - let m = parse_manifest(black_box(RICH_MANIFEST)) - .expect("rich manifest must parse"); + let m = parse_manifest(black_box(RICH_MANIFEST)).expect("rich manifest must parse"); black_box(m); }); }); @@ -122,8 +122,7 @@ fn bench_parse_rich(c: &mut Criterion) { fn bench_parse_gleam(c: &mut Criterion) { c.bench_function("parse_manifest/gleam_beam_target", |b| { b.iter(|| { - let m = parse_manifest(black_box(GLEAM_MANIFEST)) - .expect("gleam manifest must parse"); + let m = parse_manifest(black_box(GLEAM_MANIFEST)).expect("gleam manifest must parse"); black_box(m); }); }); @@ -139,7 +138,8 @@ fn bench_validate_valid(c: &mut Criterion) { c.bench_function("validate/valid_manifest", |b| { b.iter(|| { let result = validate(black_box(&manifest)); - black_box(result.expect("valid manifest must pass validation")); + result.expect("valid manifest must pass validation"); + black_box(()); }); }); } @@ -150,7 +150,8 @@ fn bench_validate_rich(c: &mut Criterion) { c.bench_function("validate/rich_20_primitives", |b| { b.iter(|| { let result = validate(black_box(&manifest)); - black_box(result.expect("rich manifest must pass validation")); + result.expect("rich manifest must pass validation"); + black_box(()); }); }); } @@ -200,8 +201,7 @@ fn bench_scan_repo(c: &mut Criterion) { c.bench_function("scan_repo/iseriser_root", |b| { b.iter(|| { - let recs = iseriser::scan::scan_repo(black_box(&repo_root)) - .expect("scan must succeed"); + let recs = iseriser::scan::scan_repo(black_box(&repo_root)).expect("scan must succeed"); black_box(recs); }); }); @@ -218,11 +218,7 @@ criterion_group!( bench_parse_gleam, ); -criterion_group!( - validate_benches, - bench_validate_valid, - bench_validate_rich, -); +criterion_group!(validate_benches, bench_validate_valid, bench_validate_rich,); criterion_group!( abi_benches, diff --git a/src/abi/idris_emitter.rs b/src/abi/idris_emitter.rs index 2d43d26..fe346ae 100644 --- a/src/abi/idris_emitter.rs +++ b/src/abi/idris_emitter.rs @@ -198,11 +198,9 @@ fn find_data_keyword(src: &str) -> Option { let mut search_from = 0; while let Some(pos) = src[search_from..].find("data") { let abs = search_from + pos; - let before_ok = abs == 0 - || matches!(bytes[abs - 1], b'\n' | b' ' | b'\t'); + let before_ok = abs == 0 || matches!(bytes[abs - 1], b'\n' | b' ' | b'\t'); let after = abs + 4; - let after_ok = after < bytes.len() - && matches!(bytes[after], b' ' | b'\t' | b'\n'); + let after_ok = after < bytes.len() && matches!(bytes[after], b' ' | b'\t' | b'\n'); if before_ok && after_ok { return Some(abs); } @@ -292,10 +290,7 @@ fn skip_gadt_block(src: &str) -> usize { let header_end = match where_pos { Some(w) => { // Consume through the rest of that line. - src[w..] - .find('\n') - .map(|i| w + i + 1) - .unwrap_or(src.len()) + src[w..].find('\n').map(|i| w + i + 1).unwrap_or(src.len()) } None => { // No `where` — single-line `data Foo : ...`. Consume through eol. @@ -372,10 +367,7 @@ fn parse_to_int_equations(src: &str) -> Result Option { - let p = pattern - .trim_start_matches('(') - .trim_end_matches(')') - .trim(); + let p = pattern.trim_start_matches('(').trim_end_matches(')').trim(); let head_end = p.find(|c: char| !is_ident_char(c)).unwrap_or(p.len()); if head_end == 0 { return None; diff --git a/src/abi/manifest_schema.rs b/src/abi/manifest_schema.rs index 5d682f3..2f3dc9a 100644 --- a/src/abi/manifest_schema.rs +++ b/src/abi/manifest_schema.rs @@ -73,14 +73,56 @@ pub fn to_snake_case(s: &str) -> String { /// the cartridge convention is to rename the variant in Zig — the /// verifier accepts the cartridge convention as a valid alternative. const ZIG_RESERVED: &[&str] = &[ - "addrspace", "align", "allowzero", "and", "anyframe", "anytype", "asm", - "async", "await", "break", "callconv", "catch", "comptime", "const", - "continue", "defer", "else", "enum", "errdefer", "error", "export", - "extern", "fn", "for", "if", "inline", "linksection", "noalias", - "noinline", "nosuspend", "null", "opaque", "or", "orelse", "packed", - "pub", "resume", "return", "struct", "suspend", "switch", "test", - "threadlocal", "try", "union", "unreachable", "usingnamespace", "var", - "volatile", "while", + "addrspace", + "align", + "allowzero", + "and", + "anyframe", + "anytype", + "asm", + "async", + "await", + "break", + "callconv", + "catch", + "comptime", + "const", + "continue", + "defer", + "else", + "enum", + "errdefer", + "error", + "export", + "extern", + "fn", + "for", + "if", + "inline", + "linksection", + "noalias", + "noinline", + "nosuspend", + "null", + "opaque", + "or", + "orelse", + "packed", + "pub", + "resume", + "return", + "struct", + "suspend", + "switch", + "test", + "threadlocal", + "try", + "union", + "unreachable", + "usingnamespace", + "var", + "volatile", + "while", ]; pub fn is_zig_reserved(word: &str) -> bool { diff --git a/src/abi/verify.rs b/src/abi/verify.rs index 059eb5d..7df8c40 100644 --- a/src/abi/verify.rs +++ b/src/abi/verify.rs @@ -279,18 +279,43 @@ mod tests { enums: vec![EnumDecl { name: "S".into(), variants: vec![ - EnumVariant { name: "Empty".into(), value: 0 }, - EnumVariant { name: "Ready".into(), value: 1 }, - EnumVariant { name: "Done".into(), value: 2 }, + EnumVariant { + name: "Empty".into(), + value: 0, + }, + EnumVariant { + name: "Ready".into(), + value: 1, + }, + EnumVariant { + name: "Done".into(), + value: 2, + }, ], }], transition_table: Some(TransitionTable { state_enum: "S".into(), rows: vec![ - TransitionRow { from: "Empty".into(), to: "Ready".into(), allowed: true }, - TransitionRow { from: "Ready".into(), to: "Done".into(), allowed: true }, - TransitionRow { from: "Done".into(), to: "Empty".into(), allowed: true }, - TransitionRow { from: "Empty".into(), to: "Done".into(), allowed: false }, + TransitionRow { + from: "Empty".into(), + to: "Ready".into(), + allowed: true, + }, + TransitionRow { + from: "Ready".into(), + to: "Done".into(), + allowed: true, + }, + TransitionRow { + from: "Done".into(), + to: "Empty".into(), + allowed: true, + }, + TransitionRow { + from: "Empty".into(), + to: "Done".into(), + allowed: false, + }, ], }), } @@ -331,8 +356,18 @@ mod tests { } "#; let z = parse_zig(src).unwrap(); - let report = verify(&make_manifest(), &z, Path::new("m.json"), Path::new("z.zig")); - assert!(report.findings.iter().any(|f| f.kind == "variant-value-mismatch")); + let report = verify( + &make_manifest(), + &z, + Path::new("m.json"), + Path::new("z.zig"), + ); + assert!( + report + .findings + .iter() + .any(|f| f.kind == "variant-value-mismatch") + ); } #[test] @@ -348,9 +383,17 @@ mod tests { } "#; let z = parse_zig(src).unwrap(); - let report = verify(&make_manifest(), &z, Path::new("m.json"), Path::new("z.zig")); + let report = verify( + &make_manifest(), + &z, + Path::new("m.json"), + Path::new("z.zig"), + ); assert!( - report.findings.iter().any(|f| f.kind == "transition-forbidden-but-accepted"), + report + .findings + .iter() + .any(|f| f.kind == "transition-forbidden-but-accepted"), "{:#?}", report.findings ); @@ -369,9 +412,17 @@ mod tests { } "#; let z = parse_zig(src).unwrap(); - let report = verify(&make_manifest(), &z, Path::new("m.json"), Path::new("z.zig")); + let report = verify( + &make_manifest(), + &z, + Path::new("m.json"), + Path::new("z.zig"), + ); assert!( - report.findings.iter().any(|f| f.kind == "transition-accepted-but-undeclared"), + report + .findings + .iter() + .any(|f| f.kind == "transition-accepted-but-undeclared"), "{:#?}", report.findings ); diff --git a/src/abi/zig_ffi_parser.rs b/src/abi/zig_ffi_parser.rs index 5ae73b5..9bb8ad5 100644 --- a/src/abi/zig_ffi_parser.rs +++ b/src/abi/zig_ffi_parser.rs @@ -103,9 +103,9 @@ fn parse_enum_body(body: &str) -> Result> { .next() .ok_or_else(|| anyhow!("variant `{}` missing `= ` value", name))? .trim(); - let value: i64 = value_str - .parse() - .with_context(|| format!("variant `{}` value `{}` is not an integer", name, value_str))?; + let value: i64 = value_str.parse().with_context(|| { + format!("variant `{}` value `{}` is not an integer", name, value_str) + })?; if variants.insert(name.clone(), value).is_some() { return Err(anyhow!("duplicate variant `{}` in enum body", name)); } @@ -148,8 +148,7 @@ fn parse_transition_table(src: &str) -> Result> { _ => {} } } - let end = - end_idx.ok_or_else(|| anyhow!("`switch (from)` body is unterminated"))?; + let end = end_idx.ok_or_else(|| anyhow!("`switch (from)` body is unterminated"))?; let body = &body_src[..end]; let arms = parse_switch_arms(body)?; Ok(Some(ZigTransitionTable { @@ -234,9 +233,8 @@ fn parse_switch_arms(body: &str) -> Result>> { .strip_prefix('.') .ok_or_else(|| anyhow!("switch arm `{}` does not start with `.`", from))? .to_string(); - let tos = parse_arm_targets(body_part).with_context(|| { - format!("parsing targets of switch arm for `{}`", from) - })?; + let tos = parse_arm_targets(body_part) + .with_context(|| format!("parsing targets of switch arm for `{}`", from))?; if arms.insert(from.clone(), tos).is_some() { return Err(anyhow!("duplicate switch arm for `{}`", from)); } diff --git a/src/codegen/cartridge.rs b/src/codegen/cartridge.rs index b266032..17d73ed 100644 --- a/src/codegen/cartridge.rs +++ b/src/codegen/cartridge.rs @@ -89,10 +89,7 @@ impl CartridgeRepo { /// Scaffold a boj-server cartridge skeleton for the given manifest. /// /// Writes `/-mcp/` and all its contents. -pub fn scaffold_cartridge( - manifest: &Manifest, - output_dir: &Path, -) -> CartridgeScaffoldResult { +pub fn scaffold_cartridge(manifest: &Manifest, output_dir: &Path) -> CartridgeScaffoldResult { let model = manifest.to_language_model(); let iser_name = model.iser_name(); let cartridge_name = format!("{}-mcp", iser_name); diff --git a/src/codegen/scaffold.rs b/src/codegen/scaffold.rs index db2f140..4168775 100644 --- a/src/codegen/scaffold.rs +++ b/src/codegen/scaffold.rs @@ -812,7 +812,6 @@ jobs: } } - /// Generate `.github/workflows/ci.yml` for the new -iser. fn generate_ci_workflow(_model: &LanguageModel, iser_name: &str) -> GeneratedFile { let content = format!( @@ -1634,10 +1633,12 @@ description = "Chapel distributed computing -iser" ); } // Guard against the old flat, non-compiling form regressing. - assert!(!repo - .files - .iter() - .any(|f| f.path == PathBuf::from("src/interface/abi/Types.idr"))); + assert!( + !repo + .files + .iter() + .any(|f| f.path == *"src/interface/abi/Types.idr") + ); } /// End-to-end: a freshly generated -iser's Idris2 ABI must compile clean. @@ -1697,20 +1698,30 @@ description = "Chapel distributed computing -iser" assert!(repo_root.join("src/main.rs").exists()); // ABI modules live at /ABI/*.idr so the path matches the namespace, // plus the -abi.ipkg that builds them. - assert!(repo_root - .join("src/interface/abi/Chapeliser/ABI/Types.idr") - .exists()); - assert!(repo_root - .join("src/interface/abi/Chapeliser/ABI/Proofs.idr") - .exists()); - assert!(repo_root - .join("src/interface/abi/chapeliser-abi.ipkg") - .exists()); + assert!( + repo_root + .join("src/interface/abi/Chapeliser/ABI/Types.idr") + .exists() + ); + assert!( + repo_root + .join("src/interface/abi/Chapeliser/ABI/Proofs.idr") + .exists() + ); + assert!( + repo_root + .join("src/interface/abi/chapeliser-abi.ipkg") + .exists() + ); assert!(repo_root.join("ffi/zig/src/main.zig").exists()); assert!(repo_root.join(".github/workflows/ci.yml").exists()); // standards#89 sub-issue 1: regen trigger only. // The unified adapter belongs to the boj-server cartridge, not this repo. - assert!(repo_root.join(".github/workflows/chapeliser-regen.yml").exists()); + assert!( + repo_root + .join(".github/workflows/chapeliser-regen.yml") + .exists() + ); assert!(repo_root.join("README.adoc").exists()); assert!(repo_root.join("LICENSE").exists()); } diff --git a/src/main.rs b/src/main.rs index c15de00..8f2d32e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -149,7 +149,11 @@ fn main() -> Result<()> { scan::print_table(&recommendations); } } - Commands::AbiVerify { manifest, zig_ffi, json } => { + Commands::AbiVerify { + manifest, + zig_ffi, + json, + } => { let report = abi::verify::verify_paths( std::path::Path::new(&manifest), std::path::Path::new(&zig_ffi), @@ -163,7 +167,12 @@ fn main() -> Result<()> { std::process::exit(2); } } - Commands::AbiEmitManifest { idris, cartridge, source_path, out } => { + Commands::AbiEmitManifest { + idris, + cartridge, + source_path, + out, + } => { let source_path_for_manifest = source_path.as_deref().unwrap_or(&idris); let manifest = abi::idris_emitter::emit_from_idris_path( std::path::Path::new(&idris), @@ -174,10 +183,16 @@ fn main() -> Result<()> { match out { Some(path) => { std::fs::write(&path, format!("{}\n", json))?; - eprintln!("abi-emit-manifest: wrote {} ({} enums, {} transitions)", + eprintln!( + "abi-emit-manifest: wrote {} ({} enums, {} transitions)", path, manifest.enums.len(), - manifest.transition_table.as_ref().map(|t| t.rows.len()).unwrap_or(0)); + manifest + .transition_table + .as_ref() + .map(|t| t.rows.len()) + .unwrap_or(0) + ); } None => println!("{}", json), } diff --git a/src/scan/mod.rs b/src/scan/mod.rs index 63f9e18..60be966 100644 --- a/src/scan/mod.rs +++ b/src/scan/mod.rs @@ -68,8 +68,8 @@ pub fn print_table(recs: &[Recommendation]) { return; } println!( - "{:<22} {:<10} {:<9} {}", - "ISER", "CONFIDENCE", "APPLIED", "REASON" + "{:<22} {:<10} {:<9} REASON", + "ISER", "CONFIDENCE", "APPLIED" ); println!("{}", "-".repeat(90)); for r in recs { @@ -113,10 +113,12 @@ fn path_contains(root: &Path, subdir: &str, pattern: &str) -> bool { .into_iter() .filter_map(|e| e.ok()) .filter(|e| { - e.path().extension().map_or(false, |ext| { + e.path().extension().is_some_and(|ext| { matches!( ext.to_str(), - Some("rs" | "ex" | "exs" | "res" | "ts" | "js" | "zig" | "idr" | "gleam" | "elm") + Some( + "rs" | "ex" | "exs" | "res" | "ts" | "js" | "zig" | "idr" | "gleam" | "elm" + ) ) }) }) @@ -184,8 +186,7 @@ fn check_eclexiaiser(root: &Path, recs: &mut Vec) { recs.push(Recommendation { iser: "eclexiaiser".into(), confidence: "high", - reason: "Containerfile found — eclexiaiser adds provenance and policy checking." - .into(), + reason: "Containerfile found — eclexiaiser adds provenance and policy checking.".into(), already_applied: is_applied(root, "eclexiaiser"), }); } @@ -231,7 +232,11 @@ fn check_wokelangiser(root: &Path, recs: &mut Vec) { }; recs.push(Recommendation { iser: "wokelangiser".into(), - confidence: if has_res && has_i18n { "high" } else { "medium" }, + confidence: if has_res && has_i18n { + "high" + } else { + "medium" + }, reason: reason.into(), already_applied: is_applied(root, "wokelangiser"), }); @@ -261,14 +266,18 @@ fn check_alloyiser(root: &Path, recs: &mut Vec) { recs.push(Recommendation { iser: "alloyiser".into(), confidence: "high", - reason: "API schema files (OpenAPI/GraphQL/Protobuf) found — alloyiser adds model checking.".into(), + reason: + "API schema files (OpenAPI/GraphQL/Protobuf) found — alloyiser adds model checking." + .into(), already_applied: is_applied(root, "alloyiser"), }); } else if has_complex_invariants { recs.push(Recommendation { iser: "alloyiser".into(), confidence: "medium", - reason: "Complex invariants described in source comments — alloyiser can formalise them.".into(), + reason: + "Complex invariants described in source comments — alloyiser can formalise them." + .into(), already_applied: is_applied(root, "alloyiser"), }); } @@ -298,14 +307,16 @@ fn check_tlaiser(root: &Path, recs: &mut Vec) { recs.push(Recommendation { iser: "tlaiser".into(), confidence: "high", - reason: "State machine patterns + concurrent protocol code — tlaiser adds TLA⁺ specs.".into(), + reason: "State machine patterns + concurrent protocol code — tlaiser adds TLA⁺ specs." + .into(), already_applied: is_applied(root, "tlaiser"), }); } else if has_state_enum { recs.push(Recommendation { iser: "tlaiser".into(), confidence: "medium", - reason: "State machine / FSM patterns found — tlaiser can specify temporal behaviour.".into(), + reason: "State machine / FSM patterns found — tlaiser can specify temporal behaviour." + .into(), already_applied: is_applied(root, "tlaiser"), }); } else if has_concurrency { @@ -342,7 +353,8 @@ fn check_idrisiser(root: &Path, recs: &mut Vec) { recs.push(Recommendation { iser: "idrisiser".into(), confidence: "medium", - reason: "Public API functions found with no formal proof wrappers (no src/abi/).".into(), + reason: "Public API functions found with no formal proof wrappers (no src/abi/)." + .into(), already_applied: is_applied(root, "idrisiser"), }); } @@ -370,14 +382,16 @@ fn check_typedqliser(root: &Path, recs: &mut Vec) { recs.push(Recommendation { iser: "typedqliser".into(), confidence: "high", - reason: "Raw SQL strings found in source — typedqliser adds compile-time type safety.".into(), + reason: "Raw SQL strings found in source — typedqliser adds compile-time type safety." + .into(), already_applied: is_applied(root, "typedqliser"), }); } else if has_query_builder { recs.push(Recommendation { iser: "typedqliser".into(), confidence: "medium", - reason: "Query builder patterns found — typedqliser can strengthen type guarantees.".into(), + reason: "Query builder patterns found — typedqliser can strengthen type guarantees." + .into(), already_applied: is_applied(root, "typedqliser"), }); } @@ -410,7 +424,8 @@ fn check_chapeliser(root: &Path, recs: &mut Vec) { recs.push(Recommendation { iser: "chapeliser".into(), confidence: "medium", - reason: "Concurrent task spawning found — chapeliser can add structured parallelism.".into(), + reason: "Concurrent task spawning found — chapeliser can add structured parallelism." + .into(), already_applied: is_applied(root, "chapeliser"), }); } @@ -507,7 +522,9 @@ fn check_otpiser(root: &Path, recs: &mut Vec) { recs.push(Recommendation { iser: "otpiser".into(), confidence: "low", - reason: "Elixir project (mix.exs) found — otpiser can audit OTP patterns if they are added.".into(), + reason: + "Elixir project (mix.exs) found — otpiser can audit OTP patterns if they are added." + .into(), already_applied: is_applied(root, "otpiser"), }); } @@ -566,14 +583,17 @@ fn check_dafniser(root: &Path, recs: &mut Vec) { recs.push(Recommendation { iser: "dafniser".into(), confidence: "high", - reason: "Cryptographic algorithm code found — dafniser adds Dafny correctness proofs.".into(), + reason: "Cryptographic algorithm code found — dafniser adds Dafny correctness proofs." + .into(), already_applied: is_applied(root, "dafniser"), }); } else if has_safety_critical { recs.push(Recommendation { iser: "dafniser".into(), confidence: "medium", - reason: "Safety-critical algorithm patterns found — dafniser can add formal verification.".into(), + reason: + "Safety-critical algorithm patterns found — dafniser can add formal verification." + .into(), already_applied: is_applied(root, "dafniser"), }); } diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 504cdb2..7e66e3c 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -97,7 +97,8 @@ fn test_full_pipeline_chapel() { assert!(root.join("Cargo.toml").exists(), "Cargo.toml missing"); assert!(root.join("src/main.rs").exists(), "src/main.rs missing"); assert!( - root.join("src/interface/abi/Chapeliser/ABI/Types.idr").exists(), + root.join("src/interface/abi/Chapeliser/ABI/Types.idr") + .exists(), "Types.idr missing" ); assert!( @@ -114,10 +115,7 @@ fn test_full_pipeline_chapel() { // Verify Cargo.toml content let cargo = std::fs::read_to_string(root.join("Cargo.toml")).unwrap(); assert!(cargo.contains("chapeliser"), "Cargo.toml missing repo name"); - assert!( - cargo.contains("MPL-2.0"), - "Cargo.toml missing license" - ); + assert!(cargo.contains("MPL-2.0"), "Cargo.toml missing license"); } // ---------------------------------------------------------------------------