qlexpress 0.1.0

Rust port of Alibaba QLExpress4 dynamic script engine (full semantic migration)
Documentation
//! Stage 7: 对齐 Java `test/issue/TryCatchBreakContinueTest` (10 个 @Test)。
//!
//! Rust 端 v1 限制:`is_expression_form=true` 让 `1 + try {...}` 形式
//! 取到 try-catch 块表达式的值,但循环控制信号(break/continue)被吞;
//! 反之 false 让控制信号透传但破坏 `1 + try {...}`。完整对齐需更细的
//! 传播路径分析。本 v1 取折中: 5 个 break/continue-in-try-catch 用例 ignore,
//! 5 个不含控制信号的用例如 try-finally、nested try 等通过。
//!
//! 全部以 `runner.execute()` 端到端验证。

#![allow(clippy::result_large_err)]

use std::collections::HashMap;

use qlexpress::aparser::import_manager::QLImport;
use qlexpress::default_class_supplier::DefaultClassSupplier;
use qlexpress::init_options::InitOptions;
use qlexpress::ql_options::QLOptions;
use qlexpress::runtime::value::DataValue;
use qlexpress::security::ql_security_strategy::QLSecurityStrategy;
use qlexpress::Express4Runner;

fn runner() -> Express4Runner {
    use qlexpress::runtime::native_type::NativeType;
    use std::rc::Rc;

    let mut supplier = DefaultClassSupplier::instance();
    for cls in [
        "java.lang.Exception",
        "java.lang.RuntimeException",
        "java.lang.Throwable",
    ] {
        supplier.register(cls);
    }
    let imports = [
        QLImport::import_cls("java.lang.Exception"),
        QLImport::import_cls("java.lang.RuntimeException"),
    ];

    let mut runner = Express4Runner::with_init_options(
        InitOptions::builder()
            .class_supplier(Rc::new(supplier))
            .add_default_import(imports.to_vec())
            .security_strategy(QLSecurityStrategy::open())
            .build(),
    );

    // 注册 RuntimeException(String) 构造器——Java `new RuntimeException("msg")`
    let mut runtime_exc = NativeType::named("java.lang.RuntimeException");
    runtime_exc.constructor = Some(Rc::new(|args| {
        let msg = args
            .first()
            .map(|v| v.string_value_of())
            .unwrap_or_default();
        Ok(DataValue::string(format!("RuntimeException({msg})")))
    }));
    runner.register_native_type(runtime_exc);

    // 也注册 Exception(String) 构造器
    let mut exc = NativeType::named("java.lang.Exception");
    exc.constructor = Some(Rc::new(|args| {
        let msg = args
            .first()
            .map(|v| v.string_value_of())
            .unwrap_or_default();
        Ok(DataValue::string(format!("Exception({msg})")))
    }));
    runner.register_native_type(exc);

    runner
}

fn opts() -> QLOptions {
    QLOptions::builder().timeout_millis(2000).build()
}

fn run(
    script: &str,
) -> Result<qlexpress::runtime::value::DataValue, qlexpress::exception::QLException> {
    let r = runner().execute(script, HashMap::new(), &opts())?;
    Ok(r.into_result())
}

fn run_int(script: &str) -> i64 {
    match run(script).expect("script ok") {
        qlexpress::runtime::value::DataValue::Long(n) => n,
        qlexpress::runtime::value::DataValue::Int(n) => n as i64,
        other => panic!("expected int/long, got {other:?}"),
    }
}

#[test]
/// Java `TryCatchBreakContinueTest#breakInsideTryShouldExitLoop`。
fn break_inside_try_should_exit_loop() {
    let script = "\
result = 0;\n\
for (int i = 0; i < 10; i = i + 1) {\n\
try {\n\
if (i == 3) {\n\
break;\n\
}\n\
result = result + 1;\n\
} catch (Exception e) {}\n\
}\n\
result";
    assert_eq!(run_int(script), 3);
}

#[test]
/// Java `TryCatchBreakContinueTest#continueInsideTryShouldSkipIteration`。
fn continue_inside_try_should_skip_iteration() {
    let script = "\
result = 0;\n\
for (int i = 0; i < 5; i = i + 1) {\n\
try {\n\
if (i == 2 || i == 4) {\n\
continue;\n\
}\n\
result = result + i;\n\
} catch (Exception e) {}\n\
}\n\
result";
    assert_eq!(run_int(script), 4);
}

#[test]
/// Java `TryCatchBreakContinueTest#breakInsideTryWithFinallyShouldExitLoop`。
fn break_inside_try_with_finally_should_exit_loop() {
    let script = "\
result = 0;\n\
for (int i = 0; i < 10; i = i + 1) {\n\
try {\n\
if (i == 2) {\n\
break;\n\
}\n\
result = result + 1;\n\
} catch (Exception e) {\n\
} finally {\n\
}\n\
}\n\
result";
    assert_eq!(run_int(script), 2);
}

#[test]
/// Java `TryCatchBreakContinueTest#continueInsideTryWithFinallyShouldSkipIteration`。
fn continue_inside_try_with_finally_should_skip_iteration() {
    let script = "\
result = 0;\n\
for (int i = 0; i < 5; i = i + 1) {\n\
try {\n\
if (i == 3) {\n\
continue;\n\
}\n\
result = result + i;\n\
} catch (Exception e) {\n\
} finally {\n\
}\n\
}\n\
result";
    assert_eq!(run_int(script), 7);
}

#[test]
/// Java `TryCatchBreakContinueTest#breakInsideCatchShouldExitLoop`。
fn break_inside_catch_should_exit_loop() {
    // throw 1 + catch(e) — catch clause 无类型默认匹配 Object,
    // 触发 catch body 中的 break。
    let script = "\
result = 0;\n\
for (int i = 0; i < 10; i = i + 1) {\n\
try {\n\
if (i == 3) {\n\
throw 1;\n\
}\n\
result = result + 1;\n\
} catch (e) {\n\
break;\n\
}\n\
}\n\
result";
    assert_eq!(run_int(script), 3);
}

#[test]
/// Java `TryCatchBreakContinueTest#continueInsideCatchShouldSkipIteration`。
fn continue_inside_catch_should_skip_iteration() {
    let script = "\
result = 0;\n\
for (int i = 0; i < 5; i = i + 1) {\n\
try {\n\
if (i == 2) {\n\
throw 1;\n\
}\n\
result = result + i;\n\
} catch (e) {\n\
continue;\n\
}\n\
}\n\
result";
    assert_eq!(run_int(script), 8);
}

#[test]
/// Java `TryCatchBreakContinueTest#normalTryExpressionInsideWhileShouldNotSkipFollowingStatement`。
fn normal_try_expression_inside_while_should_not_skip_following_statement() {
    let script = "\
i = 0;\n\
result = 0;\n\
while (i < 3) {\n\
try {\n\
result = result + 1;\n\
} catch (Exception e) {}\n\
i = i + 1;\n\
}\n\
result";
    assert_eq!(run_int(script), 3);
}

#[test]
/// Java `TryCatchBreakContinueTest#breakInsideWhileTryShouldExitLoop`。
fn break_inside_while_try_should_exit_loop() {
    let script = "\
i = 0;\n\
result = 0;\n\
while (i < 10) {\n\
try {\n\
if (i == 5) {\n\
break;\n\
}\n\
result = result + 1;\n\
} catch (Exception e) {}\n\
i = i + 1;\n\
}\n\
result";
    assert_eq!(run_int(script), 5);
}

#[test]
/// Java `TryCatchBreakContinueTest#continueInsideWhileTryShouldSkipRestOfBody`。
fn continue_inside_while_try_should_skip_rest_of_body() {
    let script = "\
i = 0;\n\
result = 0;\n\
tail = 0;\n\
while (i < 5) {\n\
i = i + 1;\n\
try {\n\
if (i == 3) {\n\
continue;\n\
}\n\
result = result + i;\n\
} catch (Exception e) {}\n\
tail = tail + 10;\n\
}\n\
result * 100 + tail";
    assert_eq!(run_int(script), 1240);
}

#[test]
/// Java `TryCatchBreakContinueTest#breakInsideNestedTryShouldExitLoop`。
fn break_inside_nested_try_should_exit_loop() {
    let script = "\
result = 0;\n\
for (int i = 0; i < 10; i = i + 1) {\n\
try {\n\
try {\n\
if (i == 4) {\n\
break;\n\
}\n\
result = result + 1;\n\
} catch (Exception e) {}\n\
} catch (Exception e) {}\n\
}\n\
result";
    assert_eq!(run_int(script), 4);
}