표현식과 제어 흐름 문서의 검증 코드다.

//! 04 표현식과 제어 흐름: 블록·if·match·loop가 값을 내는 것, 세미콜론이 값을 ()로 바꾸는 것,
//! loop의 break 값, 레이블 break, 레이블 블록, for와 범위, 그리고 이와 관련된 컴파일 오류 코드를 확인한다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/04_expressions-control-flow_verify.rs"

fn compile_errors(code: &str) -> String {
    let dir = std::env::temp_dir().join(format!("vr_{}_{}", std::process::id(), code.len()));
    std::fs::create_dir_all(&dir).unwrap();
    let file = dir.join("snippet.rs");
    std::fs::write(&file, code).unwrap();
    let out = std::process::Command::new("rustc")
        .args([
            "--edition",
            "2024",
            "--crate-type=lib",
            "--emit=metadata",
            "--out-dir",
        ])
        .arg(&dir)
        .arg(&file)
        .output()
        .expect("rustc 실행");
    String::from_utf8_lossy(&out.stderr).into_owned()
}

fn plus_one(x: i32) -> i32 {
    x + 1
}

/// match로 점수를 등급으로 바꾼다. 모든 u8 값을 덮어야 컴파일된다.
fn grade(score: u8) -> char {
    match score {
        90..=100 => 'A',
        80..=89 => 'B',
        70..=79 => 'C',
        0..=69 => 'F',
        _ => '?', // 101..=255
    }
}

/// 2차원 격자에서 처음으로 target을 찾은 (행, 열). 레이블 블록으로 값을 낸다.
fn find(grid: &[[i32; 3]], target: i32) -> Option<(usize, usize)> {
    'search: {
        for (r, row) in grid.iter().enumerate() {
            for (c, &v) in row.iter().enumerate() {
                if v == target {
                    break 'search Some((r, c));
                }
            }
        }
        None
    }
}

fn main() {
    // 1. 블록은 마지막 표현식의 값을 낸다.
    let y = {
        let x = 3;
        x + 1
    };
    assert_eq!(y, 4);
    assert_eq!(plus_one(5), 6);
    println!("블록 값 {y}, plus_one(5) = {}", plus_one(5));

    // 2. 세미콜론을 붙이면 값이 ()가 된다: E0308과 "remove this semicolon".
    let e = compile_errors("pub fn plus_one(x: i32) -> i32 {\n    x + 1;\n}\n");
    assert!(e.contains("error[E0308]: mismatched types"));
    assert!(e.contains("expected `i32`, found `()`"));
    assert!(e.contains("help: remove this semicolon to return this value"));
    println!("x + 1; -> E0308 (expected `i32`, found `()`)");

    // 대입식의 값은 ()다. C처럼 a = b = 0으로 쓸 수 없다.
    let e = compile_errors(
        "pub fn f() -> i32 {\n    let mut x = 1;\n    let y = (x = 2);\n    let _ = x;\n    y\n}\n",
    );
    assert!(e.contains("error[E0308]"), "{e}");
    assert!(e.contains("expected `i32`, found `()`"), "{e}");
    println!("let y = (x = 2); -> y는 ()");

    // let은 문장이라 값이 없다.
    let e = compile_errors("pub fn f() {\n    let x = (let y = 6);\n}\n");
    assert!(e.contains("expected expression, found `let` statement"));

    // 3. if는 값을 낸다. 두 갈래의 타입이 같아야 하고, 값으로 쓰면 else가 있어야 한다.
    let n = std::hint::black_box(7);
    let parity = if n % 2 == 0 { "짝수" } else { "홀수" };
    assert_eq!(parity, "홀수");
    let e =
        compile_errors("pub fn f(c: bool) {\n    let number = if c { 5 } else { \"six\" };\n}\n");
    assert!(e.contains("error[E0308]: `if` and `else` have incompatible types"));
    let e = compile_errors("pub fn f(c: bool) -> i32 {\n    let x = if c { 1 };\n    x\n}\n");
    assert!(e.contains("error[E0317]"));
    let e = compile_errors("pub fn f() {\n    let number = 3;\n    if number {}\n}\n");
    assert!(e.contains("expected `bool`, found integer"));
    println!("if 값 {parity}; 타입 다름 -> E0308, else 없음 -> E0317, if 3 -> E0308");

    // 4. match는 모든 경우를 덮어야 한다.
    assert_eq!(
        [grade(95), grade(85), grade(72), grade(10), grade(200)],
        ['A', 'B', 'C', 'F', '?']
    );
    let e = compile_errors(
        "pub fn f(n: u8) -> &'static str {\n    match n {\n        0 => \"zero\",\n        1..=9 => \"small\",\n    }\n}\n",
    );
    assert!(e.contains("error[E0004]: non-exhaustive patterns: `10_u8..=u8::MAX` not covered"));
    println!("match 빠진 경우 -> E0004 (`10_u8..=u8::MAX` not covered)");

    // 5. loop는 break 값으로 값을 낸다.
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2;
        }
    };
    assert_eq!(result, 20);
    // while·for에서는 break에 값을 줄 수 없다.
    let e = compile_errors(
        "pub fn f() -> i32 {\n    let mut i = 0;\n    while i < 3 {\n        i += 1;\n        break i;\n    }\n    0\n}\n",
    );
    assert!(e.contains("error[E0571]"));
    println!("loop break 값 {result}; while에서 break 값 -> E0571");

    // 6. 레이블 break: 바깥 반복을 끝낸다.
    let mut trace = Vec::new();
    'outer: for i in 0..3 {
        for j in 0..3 {
            if i + j == 3 {
                break 'outer;
            }
            if j > i {
                continue 'outer;
            }
            trace.push((i, j));
        }
    }
    assert_eq!(trace, vec![(0, 0), (1, 0), (1, 1)]);
    println!("레이블 break/continue 기록 {trace:?}");

    // 7. 레이블 블록으로 값을 낸다.
    let grid = [[1, 2, 3], [4, 5, 6]];
    assert_eq!(find(&grid, 5), Some((1, 1)));
    assert_eq!(find(&grid, 9), None);
    println!(
        "find 5 -> {:?}, find 9 -> {:?}",
        find(&grid, 5),
        find(&grid, 9)
    );

    // 8. for와 범위. a..b는 b를 빼고, a..=b는 b를 넣는다.
    let down: Vec<i32> = (1..4).rev().collect();
    assert_eq!(down, vec![3, 2, 1]);
    let mut sum = 0;
    for k in 1..=10 {
        sum += k;
    }
    assert_eq!(sum, 55);
    assert_eq!((1..10).count(), 9);
    assert_eq!((1..=10).count(), 10);
    assert_eq!((5..5).count(), 0);
    println!("(1..4).rev() = {down:?}, 1..=10 합 = {sum}");

    // 9. 세미콜론으로 끝난 블록의 타입은 ()다. ()는 크기가 0이다.
    let e = compile_errors("pub fn f() -> i32 {\n    let v = {\n        1;\n    };\n    v\n}\n");
    assert!(e.contains("expected `i32`, found `()`"), "{e}");
    assert_eq!(size_of::<()>(), 0);
    println!("{{ 1; }}의 타입 -> (), 크기 {}", size_of::<()>());

    // 10. 확인 문제의 답.
    let mut n = 0;
    let v = loop {
        n += 3;
        if n > 10 {
            break n - 10;
        }
    };
    assert_eq!(v, 2);
    let e = compile_errors("pub fn half(x: i32) -> i32 {\n    if x > 0 { x / 2 }\n}\n");
    assert!(e.contains("error[E0317]"), "{e}");
    println!("C2 loop 값 {v}, C1 half -> E0317");

    println!("ALL CHECKS PASSED");
}