a8c29d130b
New top-level experiments/2026-05-12-cross-model-authoring/ (out of
root Cargo workspace; nested-crate standalone via empty [workspace]
table per Cargo idiom). The renderer projects one canonical
master/spec.md into two form-specific files (rendered/json.md and
rendered/ailx.md) by walking three directive forms ({form-only:
json}, {form-only: ailx}, {example: <id>}) and emitting per-example
fenced blocks sourced from 13 curated .ail.json fixtures.
Four test gates green (10/10 total):
- splitter_unit (7) — directive parser, malformed inputs panic
- spec_completeness (1) — AST-variant visitor lifted verbatim from
schema_coverage.rs; 34/34 variants covered by the curated subset
- example_roundtrip (1) — every fixture roundtrips through
ailang_surface::{print, parse} to identical canonical bytes
- token_balance (1) — form-only block totals balanced to 3.22% under
tiktoken-rs::cl100k_base (gate is ±5%)
Six of the 34 variants did not have a dedicated fixture topic in the
plan and were absorbed into the closest existing fixture per the
plan's Step 4.14 escape hatch (Let/Clone → fn_calls_prelude; LetRec
→ data_with_match; If → match_literal_pattern; ReuseAs →
data_simple; Const → floats). Surfaced as a journal note, not a
plan revision.
Two ancillary additions vs. plan, both structural Cargo necessities,
spec-compatible: render/Cargo.toml carries an empty [workspace] table
so the nested crate refuses parent-workspace discovery, and
render/.gitignore drops /target + Cargo.lock. Both surfaced inline
in the per-iter journal's Concerns section.
cma.2 (harness) and cma.3 (live IONOS run + DESIGN.md addendum +
journal + roadmap edits) remain out of scope per the spec.
74 lines
2.4 KiB
Rust
74 lines
2.4 KiB
Rust
//! Roundtrip gate for master/examples/.
|
|
//!
|
|
//! Mirrors crates/ailang-surface/tests/round_trip.rs but scoped to
|
|
//! the master examples directory. For each .ail.json fixture: load,
|
|
//! print via ailang_surface::print, reparse, canonicalise both sides,
|
|
//! assert byte equality.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
fn examples_dir() -> PathBuf {
|
|
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
manifest.parent().unwrap().join("master").join("examples")
|
|
}
|
|
|
|
fn list_examples() -> Vec<PathBuf> {
|
|
let mut paths: Vec<PathBuf> = std::fs::read_dir(examples_dir())
|
|
.expect("master/examples/ should exist")
|
|
.filter_map(|entry| entry.ok())
|
|
.map(|entry| entry.path())
|
|
.filter(|path| path.extension().and_then(|s| s.to_str()) == Some("json"))
|
|
.filter(|path| {
|
|
path.file_name()
|
|
.and_then(|s| s.to_str())
|
|
.map(|name| name.ends_with(".ail.json"))
|
|
.unwrap_or(false)
|
|
})
|
|
.collect();
|
|
paths.sort();
|
|
paths
|
|
}
|
|
|
|
fn round_trip_one(path: &Path) -> Result<(), String> {
|
|
let original = ailang_core::load_module(path)
|
|
.map_err(|e| format!("{}: load_module failed: {e}", path.display()))?;
|
|
let text = ailang_surface::print(&original);
|
|
let parsed = ailang_surface::parse(&text)
|
|
.map_err(|e| format!("{}: surface::parse failed: {e}", path.display()))?;
|
|
let bytes_original = ailang_core::canonical::to_bytes(&original);
|
|
let bytes_parsed = ailang_core::canonical::to_bytes(&parsed);
|
|
if bytes_original != bytes_parsed {
|
|
return Err(format!(
|
|
"{}: roundtrip diverged.\n--- printed AILX ---\n{}\n--- original bytes len {} vs parsed bytes len {} ---",
|
|
path.display(),
|
|
text,
|
|
bytes_original.len(),
|
|
bytes_parsed.len(),
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn every_example_roundtrips_via_ailx() {
|
|
let examples = list_examples();
|
|
assert!(
|
|
!examples.is_empty(),
|
|
"master/examples/ should contain at least one .ail.json fixture"
|
|
);
|
|
let mut failures: Vec<String> = Vec::new();
|
|
for path in &examples {
|
|
if let Err(msg) = round_trip_one(path) {
|
|
failures.push(msg);
|
|
}
|
|
}
|
|
if !failures.is_empty() {
|
|
panic!(
|
|
"{} of {} examples failed roundtrip:\n\n{}",
|
|
failures.len(),
|
|
examples.len(),
|
|
failures.join("\n\n")
|
|
);
|
|
}
|
|
}
|