Translate project content to English
Make English the project-wide language: all comments, string literals, CLI help text, design docs, journal, agent prompts, and README. Only CLAUDE.md (the user's own instruction file) stays German, and the live conversation between user and Claude continues in German. Adds a new "Project language: English" section to docs/DESIGN.md as the durable convention. No logic changes — translation only. Four internal error strings in the typechecker were retranslated; no test asserts on their wording. Verified: cargo test --workspace passes (44/44). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+39
-39
@@ -1,7 +1,7 @@
|
||||
//! End-to-End-Test: lade Beispielmodul, kompiliere zu Binary, führe es aus.
|
||||
//! End-to-end test: load example module, compile to binary, run it.
|
||||
//!
|
||||
//! Dieses Test schützt die wichtigste Eigenschaft der gesamten Pipeline:
|
||||
//! AST → typecheck → LLVM IR → clang → Binary → korrekter stdout.
|
||||
//! This test guards the most important property of the entire pipeline:
|
||||
//! AST → typecheck → LLVM IR → clang → binary → correct stdout.
|
||||
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
@@ -11,7 +11,7 @@ fn ail_bin() -> &'static str {
|
||||
}
|
||||
|
||||
fn build_and_run(example: &str) -> String {
|
||||
// Workspace-Root liegt zwei Ebenen über dem Crate-Manifest.
|
||||
// Workspace root is two levels above the crate manifest.
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||
let src = workspace.join("examples").join(example);
|
||||
@@ -43,8 +43,8 @@ fn sum_1_to_10_is_55() {
|
||||
assert_eq!(stdout.trim(), "55");
|
||||
}
|
||||
|
||||
/// Schützt das Block-Tracking im Codegen: max3 hat verschachtelte `if`s,
|
||||
/// und falsches phi-Block-Tracking würde hier zu falschem Ergebnis führen.
|
||||
/// Guards block tracking in codegen: max3 has nested `if`s, and wrong
|
||||
/// phi block tracking would produce a wrong result here.
|
||||
#[test]
|
||||
fn max3_picks_largest() {
|
||||
let stdout = build_and_run("max3.ail.json");
|
||||
@@ -57,24 +57,24 @@ fn hello_world_str_lit() {
|
||||
assert_eq!(stdout.trim(), "Hallo, AILang.");
|
||||
}
|
||||
|
||||
/// Schützt ADT-Codegen + Match: rekursive Liste, sum_list via match auf Cons/Nil.
|
||||
/// Guards ADT codegen + match: recursive list, sum_list via match on Cons/Nil.
|
||||
#[test]
|
||||
fn list_sum_via_match() {
|
||||
let stdout = build_and_run("list.ail.json");
|
||||
assert_eq!(stdout.trim(), "42");
|
||||
}
|
||||
|
||||
/// Schützt `ail diff`: ein modifizierter Body ändert den Hash von `sum`,
|
||||
/// während `main` unverändert bleibt. Erwartet Exit-Code 1, `changed`
|
||||
/// enthält genau `sum`, `unchanged` enthält `main`, `added`/`removed` leer.
|
||||
/// Guards `ail diff`: a modified body changes the hash of `sum`, while
|
||||
/// `main` stays unchanged. Expects exit code 1, `changed` contains exactly
|
||||
/// `sum`, `unchanged` contains `main`, `added`/`removed` empty.
|
||||
#[test]
|
||||
fn diff_detects_changed_def() {
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||
let src_a = workspace.join("examples").join("sum.ail.json");
|
||||
|
||||
// Variante: lade sum.ail.json, mutiere den `then`-Zweig (statt 0 nun 1)
|
||||
// in der `sum`-Definition. `main` bleibt bitidentisch.
|
||||
// Variant: load sum.ail.json, mutate the `then` branch (1 instead of 0)
|
||||
// in the `sum` definition. `main` stays bit-identical.
|
||||
let raw = std::fs::read(&src_a).expect("read sum.ail.json");
|
||||
let mut module: serde_json::Value = serde_json::from_slice(&raw).expect("parse sum.ail.json");
|
||||
{
|
||||
@@ -84,7 +84,7 @@ fn diff_detects_changed_def() {
|
||||
.expect("defs array");
|
||||
for def in defs.iter_mut() {
|
||||
if def.get("name").and_then(|n| n.as_str()) == Some("sum") {
|
||||
// Ersetze den then-Zweig literal 0 → literal 1.
|
||||
// Replace the then branch literal 0 → literal 1.
|
||||
let new_then = serde_json::json!({
|
||||
"t": "lit",
|
||||
"lit": { "kind": "int", "value": 1 }
|
||||
@@ -149,7 +149,7 @@ fn diff_detects_changed_def() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Diff eines Moduls mit sich selbst: Exit 0, alle Listen außer `unchanged` leer.
|
||||
/// Diff of a module with itself: exit 0, all lists except `unchanged` empty.
|
||||
#[test]
|
||||
fn diff_no_changes_exit_zero() {
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
@@ -187,10 +187,10 @@ fn diff_no_changes_exit_zero() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Schützt den Workspace-Loader (Iter 5a): das Eintrittsmodul `ws_main`
|
||||
/// importiert `ws_lib`, beide müssen vom Loader gefunden, geladen und im
|
||||
/// JSON-Output aufgelistet sein. Cross-Module-Typcheck/Codegen ist explizit
|
||||
/// nicht Teil dieses Tests — er verifiziert nur die Lader-Pipeline.
|
||||
/// Guards the workspace loader (Iter 5a): the entry module `ws_main`
|
||||
/// imports `ws_lib`; both must be found by the loader, loaded, and listed
|
||||
/// in the JSON output. Cross-module typecheck/codegen is explicitly not
|
||||
/// part of this test — it only verifies the loader pipeline.
|
||||
#[test]
|
||||
fn workspace_lists_imported_modules() {
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
@@ -229,9 +229,9 @@ fn workspace_lists_imported_modules() {
|
||||
assert!(names.contains(&"ws_lib"), "ws_lib missing: {names:?}");
|
||||
}
|
||||
|
||||
/// Schützt Iter 5b: `ail check examples/ws_main.ail.json --json` muss den
|
||||
/// Cross-Module-Aufruf `ws_lib.add` auflösen können. Erwartet: Exit 0,
|
||||
/// stdout exakt `[]` (leeres Diagnostic-Array).
|
||||
/// Guards Iter 5b: `ail check examples/ws_main.ail.json --json` must
|
||||
/// resolve the cross-module call `ws_lib.add`. Expected: exit 0,
|
||||
/// stdout exactly `[]` (empty diagnostic array).
|
||||
#[test]
|
||||
fn check_workspace_resolves_import() {
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
@@ -256,17 +256,17 @@ fn check_workspace_resolves_import() {
|
||||
assert_eq!(stdout.trim(), "[]", "expected empty diagnostic array");
|
||||
}
|
||||
|
||||
/// Schützt Iter 5c (Cross-Module-Codegen): `ail build` über
|
||||
/// `examples/ws_main.ail.json` muss das Workspace inkl. `ws_lib` lowern,
|
||||
/// `@ail_ws_main_main` muss `@ail_ws_lib_add(2,3)` aufrufen und `5` drucken.
|
||||
/// Guards Iter 5c (cross-module codegen): `ail build` over
|
||||
/// `examples/ws_main.ail.json` must lower the workspace incl. `ws_lib`;
|
||||
/// `@ail_ws_main_main` must call `@ail_ws_lib_add(2,3)` and print `5`.
|
||||
#[test]
|
||||
fn workspace_build_runs_imported_fn() {
|
||||
let stdout = build_and_run("ws_main.ail.json");
|
||||
assert_eq!(stdout.trim(), "5", "ws_lib.add(2,3) should print 5");
|
||||
}
|
||||
|
||||
/// Schützt Iter 5d: `ail manifest --workspace --json` listet Defs aus allen
|
||||
/// Modulen des Workspaces — sichtbar an einem `module`-Feld pro Eintrag.
|
||||
/// Guards Iter 5d: `ail manifest --workspace --json` lists defs from all
|
||||
/// modules of the workspace — visible via a `module` field per entry.
|
||||
#[test]
|
||||
fn manifest_workspace_lists_all_defs() {
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
@@ -308,8 +308,8 @@ fn manifest_workspace_lists_all_defs() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Schützt Iter 5d: `ail describe --workspace ws_lib.add` löst die
|
||||
/// qualifizierte Punkt-Notation in das tatsächlich importierte Modul auf.
|
||||
/// Guards Iter 5d: `ail describe --workspace ws_lib.add` resolves the
|
||||
/// qualified dot notation to the actually imported module.
|
||||
#[test]
|
||||
fn describe_workspace_resolves_qualified_name() {
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
@@ -349,8 +349,8 @@ fn describe_workspace_resolves_qualified_name() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Schützt Iter 5d: `ail deps --workspace` enthält eine Cross-Module-Edge
|
||||
/// `ws_main.main -> ws_lib.add`, weil `ws_main` `ws_lib.add(2,3)` aufruft.
|
||||
/// Guards Iter 5d: `ail deps --workspace` contains a cross-module edge
|
||||
/// `ws_main.main -> ws_lib.add`, because `ws_main` calls `ws_lib.add(2,3)`.
|
||||
#[test]
|
||||
fn deps_workspace_includes_cross_module() {
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
@@ -385,8 +385,8 @@ fn deps_workspace_includes_cross_module() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Schützt Iter 5d: `ail diff --workspace` erkennt ein zusätzliches Modul
|
||||
/// im B-Workspace als `added_modules`-Eintrag und exitet mit Code 1.
|
||||
/// Guards Iter 5d: `ail diff --workspace` detects an additional module
|
||||
/// in the B workspace as an `added_modules` entry and exits with code 1.
|
||||
#[test]
|
||||
fn diff_workspace_added_module() {
|
||||
use std::fs;
|
||||
@@ -409,9 +409,9 @@ fn diff_workspace_added_module() {
|
||||
fs::create_dir_all(&dir_a).unwrap();
|
||||
fs::create_dir_all(&dir_b).unwrap();
|
||||
|
||||
// Beide Workspaces haben ein leeres Modul `root`. B importiert
|
||||
// zusätzlich `extra`. Loader-Konvention: `<root_dir>/<name>.ail.json`,
|
||||
// Modul-Name muss zum Dateinamen passen.
|
||||
// Both workspaces have an empty module `root`. B additionally
|
||||
// imports `extra`. Loader convention: `<root_dir>/<name>.ail.json`,
|
||||
// module name must match the file name.
|
||||
let empty_root = serde_json::json!({
|
||||
"schema": "ailang/v0",
|
||||
"name": "root",
|
||||
@@ -477,10 +477,10 @@ fn diff_workspace_added_module() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Schützt das `--json`-Diagnostic-Format für Tooling-Konsumenten.
|
||||
/// `broken_unbound.ail.json` referenziert eine nicht-existente Variable;
|
||||
/// erwartet wird Exit-Code 1 und mindestens ein Diagnostic mit
|
||||
/// `severity == "error"` und `code == "unbound-var"`.
|
||||
/// Guards the `--json` diagnostic format for tooling consumers.
|
||||
/// `broken_unbound.ail.json` references a non-existent variable;
|
||||
/// exit code 1 is expected, plus at least one diagnostic with
|
||||
/// `severity == "error"` and `code == "unbound-var"`.
|
||||
#[test]
|
||||
fn check_json_unbound_var() {
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! IR-Snapshot-Tests: schützen die Codegen-Pipeline vor unbeabsichtigten
|
||||
//! Veränderungen am LLVM-IR. Snapshots in tests/snapshots/. Update mit
|
||||
//! IR snapshot tests: guard the codegen pipeline against unintended
|
||||
//! changes to the LLVM IR. Snapshots in tests/snapshots/. Update with
|
||||
//! UPDATE_SNAPSHOTS=1 cargo test ir_snapshot_.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -9,17 +9,17 @@ fn ail_bin() -> &'static str {
|
||||
env!("CARGO_BIN_EXE_ail")
|
||||
}
|
||||
|
||||
/// Normalisiert IR-Text für Plattform-Stabilität:
|
||||
/// Normalizes IR text for platform stability:
|
||||
/// - `target triple = "..."` -> `target triple = "<NORMALIZED>"`
|
||||
/// - trailing whitespace pro Zeile entfernen
|
||||
/// - LF-Zeilenenden, Datei endet mit genau einem `\n`
|
||||
/// - strip trailing whitespace per line
|
||||
/// - LF line endings, file ends with exactly one `\n`
|
||||
fn normalize(ir: &str) -> String {
|
||||
let mut out = String::with_capacity(ir.len());
|
||||
for line in ir.split('\n') {
|
||||
let line = line.strip_suffix('\r').unwrap_or(line);
|
||||
let trimmed = line.trim_end();
|
||||
if let Some(rest) = trimmed.strip_prefix("target triple = ") {
|
||||
// rest ist `"..."` (mit Anführungszeichen) — komplett normalisieren.
|
||||
// rest is `"..."` (with quotes) — fully normalize.
|
||||
let _ = rest;
|
||||
out.push_str("target triple = \"<NORMALIZED>\"");
|
||||
} else {
|
||||
@@ -27,9 +27,9 @@ fn normalize(ir: &str) -> String {
|
||||
}
|
||||
out.push('\n');
|
||||
}
|
||||
// split('\n') erzeugt nach einem trailing `\n` ein leeres letztes Stück,
|
||||
// das oben mit `\n` abgeschlossen wurde. Dadurch enthält `out` typischerweise
|
||||
// genau ein abschließendes `\n`. Doppelte `\n\n` am Ende reduzieren.
|
||||
// split('\n') after a trailing `\n` produces an empty last piece,
|
||||
// which was closed with `\n` above. So `out` typically ends in
|
||||
// exactly one trailing `\n`. Reduce double `\n\n` at the end.
|
||||
while out.ends_with("\n\n") {
|
||||
out.pop();
|
||||
}
|
||||
@@ -152,10 +152,10 @@ fn ir_snapshot_list() {
|
||||
check_ir_snapshot("list.ail.json", "list.ll");
|
||||
}
|
||||
|
||||
/// Schützt das Workspace-Lowering (Iter 5c): das Eintrittsmodul `ws_main`
|
||||
/// importiert `ws_lib`, beide werden in derselben `.ll` emittiert,
|
||||
/// `@ail_ws_main_main` ruft `@ail_ws_lib_add` auf, und das Trampoline
|
||||
/// `@main` ruft `@ail_ws_main_main`.
|
||||
/// Guards workspace lowering (Iter 5c): the entry module `ws_main`
|
||||
/// imports `ws_lib`; both are emitted into the same `.ll`,
|
||||
/// `@ail_ws_main_main` calls `@ail_ws_lib_add`, and the trampoline
|
||||
/// `@main` calls `@ail_ws_main_main`.
|
||||
#[test]
|
||||
fn ir_snapshot_ws_main() {
|
||||
check_ir_snapshot("ws_main.ail.json", "ws_main.ll");
|
||||
|
||||
Reference in New Issue
Block a user