thalir-parser 0.1.0

Parser for ThalIR files
Documentation
use thalir_parser::{annotations::*, parse, Rule, ThalirParser};

#[test]
fn test_parser_roundtrip_simple() {
    let input = r#"
function %test(i32) -> i32 {
block0(v0: i32):
    v1 = iadd v0, v0
    return v1
}
"#;

    let result = parse(input);
    assert!(
        result.is_ok(),
        "Failed to parse standard ThalIR: {:?}",
        result.err()
    );
}

#[test]
fn test_parser_roundtrip_with_position_markers() {
    let input = r#"
function %test(i32) -> i32 {
block0(v0: i32):
    [0] v1 = iadd v0, v0
    [1] v2 = iadd v1, v1
    [2] return v2
}
"#;

    let result = parse(input);
    assert!(
        result.is_ok(),
        "Failed to parse ThalIR with position markers: {:?}",
        result.err()
    );
}

#[test]
fn test_parser_roundtrip_with_visual_cues() {
    let input = r#"
function %test(i32) -> i32 {
block0(v0: i32):
    [0] v1 = iadd v0, v0
    [1] 🔴 v2 = call fn0(v1)
    [2] 🟡 storage_store slot0, v2
    [3] return v2
}
"#;

    let result = parse(input);
    assert!(
        result.is_ok(),
        "Failed to parse ThalIR with visual cues: {:?}",
        result.err()
    );
}

#[test]
fn test_parser_roundtrip_with_analysis_comments() {
    let input = r#"
; ### Function: test (Public)
;  ORDERING ANALYSIS:
; - External call at position [1]
; - State modification at position [2]
; - [1] < [2] → REENTRANCY RISK

function %test(i32) -> i32 {
block0(v0: i32):
    [0] v1 = iadd v0, v0
    [1] 🔴 v2 = call fn0(v1)
    [2] 🟡 storage_store slot0, v2
    [3] return v2
}
"#;

    let result = parse(input);
    assert!(
        result.is_ok(),
        "Failed to parse ThalIR with analysis comments: {:?}",
        result.err()
    );
}

#[test]
fn test_parser_roundtrip_mixed_annotations() {
    let input = r#"
function %test(i32) -> i32 {
block0(v0: i32):
    [0] v1 = iadd v0, v0
    v2 = iadd v1, v1
    [2] 🔴 v3 = call fn0(v2)
    v4 = iadd v3, v3
    [4] return v4
}
"#;

    let result = parse(input);
    assert!(
        result.is_ok(),
        "Failed to parse mixed annotated/standard ThalIR: {:?}",
        result.err()
    );
}

#[test]
fn test_parser_roundtrip_ascii_visual_cues() {
    let input = r#"
function %test(i32) -> i32 {
block0(v0: i32):
    [0] v1 = iadd v0, v0
    [1] [EXTERNAL_CALL] v2 = call fn0(v1)
    [2] [STATE_WRITE] storage_store slot0, v2
    [3] return v2
}
"#;

    let result = parse(input);
    assert!(
        result.is_ok(),
        "Failed to parse ThalIR with ASCII visual cues: {:?}",
        result.err()
    );
}

#[test]
fn test_parser_roundtrip_extract_annotations() {
    use pest::Parser;

    let input = "[5] 🔴 v3 = call fn0(v0)";

    let pairs = ThalirParser::parse(Rule::instruction, input);
    assert!(
        pairs.is_ok(),
        "Failed to parse instruction: {:?}",
        pairs.err()
    );

    let pair = pairs.unwrap().into_iter().next().unwrap();
    let annotations = extract_instruction_annotations(&pair);

    assert_eq!(annotations.position, Some(5), "Position should be 5");
    assert_eq!(
        annotations.visual_cue,
        Some(VisualCue::ExternalCall),
        "Visual cue should be ExternalCall"
    );
}

#[test]
fn test_parser_roundtrip_contract() {
    let input = r#"
contract MyContract {
    function %transfer(address, i256) -> i256 public {
    block0(v0: address, v1: i256):
        [0] v2 = mapping_load balances[msg.sender]
        [1] 🟡 mapping_store balances[v0] <- v1
        [2] return v1
    }
}
"#;

    let result = parse(input);
    assert!(
        result.is_ok(),
        "Failed to parse annotated contract: {:?}",
        result.err()
    );
}

#[test]
fn test_parser_roundtrip_complex_function() {
    let input = r#"
; ### Function: withdraw (Public)
; - External Calls: 1
; - State Modifications: 1
;  ORDERING ANALYSIS:
; - External call at position [3]
; - State modification at position [5]
; - [3] < [5] → REENTRANCY RISK

function %withdraw(i256) -> i256 public {
block0(v0: i256):
    [0] v1 = mapping_load balances[msg.sender]
    [1] v2 = icmp slt v1, v0
    [2] v3 = require v2, "Insufficient balance"
    [3] 🔴 v4 = call_ext fn0(v0)
    [4] v5 = require v4, "Transfer failed"
    [5] 🟡 mapping_store balances[msg.sender] <- v0
    [6] return v0
}
"#;

    let result = parse(input);
    assert!(
        result.is_ok(),
        "Failed to parse complex annotated function: {:?}",
        result.err()
    );
}

#[test]
fn test_parser_roundtrip_example_file() {
    let input = std::fs::read_to_string("examples/annotated_withdraw.thalir");

    if let Ok(content) = input {
        let result = parse(&content);
        assert!(
            result.is_ok(),
            "Failed to parse examples/annotated_withdraw.thalir: {:?}",
            result.err()
        );
    } else {
        println!("Skipping example file test - file not found");
    }
}

#[test]
fn test_parser_roundtrip_all_visual_cues() {
    let input = r#"
function %test(i32) -> i32 {
block0(v0: i32):
    [0] 🔴 v1 = call fn0(v0)
    [1] 🟡 storage_store slot0, v1
    [2] ⚠️ v2 = iadd v0, v1
    [3] ✓ v3 = checked_add v0, v2
    [4] 🟢 v4 = iadd v3, v3
    [5] ❌ v5 = iadd v4, v4
    [6] return v5
}
"#;

    let result = parse(input);
    assert!(
        result.is_ok(),
        "Failed to parse all visual cues: {:?}",
        result.err()
    );
}

#[test]
fn test_parser_roundtrip_entities_with_annotations() {
    let input = r#"
function %f(i64, i32) -> i32 {
    gv0 = vmctx
    gv1 = load.i64 notrap readonly aligned gv0+8
    fn0 = %g(i64)

block0(v0: i64, v1: i32):
    [0] v2 = global_value.i64 gv1
    [1] v3 = load.i32 v2+8
    [2] return v3
}
"#;

    let result = parse(input);
    assert!(
        result.is_ok(),
        "Failed to parse entities with annotations: {:?}",
        result.err()
    );
}

#[test]
fn test_parser_roundtrip_backwards_compatible() {
    let inputs = vec![
        r#"
function %test(i32) -> i32 {
block0(v0: i32):
    v1 = iadd v0, v0
    return v1
}
"#,
        r#"
function %f(i64) -> i64 {
    gv0 = vmctx
    fn0 = %g(i64)

block0(v0: i64):
    v1 = global_value.i64 gv0
    return v1
}
"#,
        r#"
function %f(i64) -> i32 {
block0(v0: i64):
    v1 = load.i32 v0+8
    store.i32 v1, v0+16
    return v1
}
"#,
        r#"
contract Test {
    function %test(i32) -> i32 public {
    block0(v0: i32):
        return v0
    }
}
"#,
    ];

    for (i, input) in inputs.iter().enumerate() {
        let result = parse(input);
        assert!(
            result.is_ok(),
            "Backwards compatibility test {} failed: {:?}",
            i,
            result.err()
        );
    }
}