Skip to main content

sim_lib_lang_lua/
conformance.rs

1//! Lua matrix-row conformance runner.
2
3use sim_kernel::{Cx, Expr, Result, Symbol, Value};
4use sim_lib_standard_core::{
5    LanguageProfile, MatrixRunReport, MatrixRunner, SourceConformanceCase, SourceExpectation,
6    SourceObservation,
7};
8
9use crate::{load::eval_lua_source, lua_core_matrix_row, lua_core_profile, lua_rawget};
10
11/// One row in the Lua shared-substrate reuse ledger.
12///
13/// The ledger is deliberately small and concrete: each entry names the shared
14/// runtime extension Lua uses, a passing non-Lua or substrate test that proves
15/// the extension is not Lua-only, and sibling language scaffolds that can adopt
16/// the same extension without forking it.
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct ReuseLedgerEntry {
19    /// Shared extension reused by the Lua runtime.
20    pub shared_extension: &'static str,
21    /// Passing non-Lua or substrate test that backs the shared claim.
22    pub passing_non_lua_test: &'static str,
23    /// Sibling scaffolds that can adopt the same extension.
24    pub sibling_scaffolds: &'static [&'static str],
25}
26
27/// Lua reuse ledger for the shared language-runtime substrate.
28pub const REUSE_LEDGER: &[ReuseLedgerEntry] = &[
29    ReuseLedgerEntry {
30        shared_extension: "GuestRuntimeKit",
31        passing_non_lua_test: "sim-lib-standard-core::guest_kit_tests::guest_runtime_kits_cover_distinct_truth_and_arity_rules",
32        sibling_scaffolds: &["Ruby blocks", "Julia calls", "typed-lazy forcing"],
33    },
34    ReuseLedgerEntry {
35        shared_extension: "BindingCell",
36        passing_non_lua_test: "sim-lib-binding::tests::captured_binding_cell_is_shared_by_two_closures",
37        sibling_scaffolds: &["Scheme closures", "Common Lisp lexical functions"],
38    },
39    ReuseLedgerEntry {
40        shared_extension: "RuntimeKey/MutableRuntimeTable",
41        passing_non_lua_test: "sim-lib-mutation::tests::runtime_table_accepts_dict_keys_and_projects_array",
42        sibling_scaffolds: &["Ruby hash", "Clojure map literals"],
43    },
44    ReuseLedgerEntry {
45        shared_extension: "MetaObjectProtocol",
46        passing_non_lua_test: "sim-lib-dispatch::tests::meta_index_walks_prototype_chain_through_protocol_override",
47        sibling_scaffolds: &["Ruby method lookup", "Julia property access"],
48    },
49    ReuseLedgerEntry {
50        shared_extension: "protected_call/coroutine/close",
51        passing_non_lua_test: "sim-lib-control::frame_tests::coroutine_frame_produces_and_consumes_without_surface_names",
52        sibling_scaffolds: &["Ruby ensure blocks", "Scheme continuations"],
53    },
54    ReuseLedgerEntry {
55        shared_extension: "text-pattern VM",
56        passing_non_lua_test: "sim-lib-pattern::text_tests::lua_dialect_preserves_captures_and_budget_limits",
57        sibling_scaffolds: &["glob codec", "Ruby regexp facade"],
58    },
59];
60
61/// Runs one Lua core source conformance case.
62pub fn run_lua_core_conformance_case(
63    cx: &mut Cx,
64    case: &SourceConformanceCase,
65) -> Result<SourceObservation> {
66    if case.source == "profile" {
67        return Ok(observe_profile_backed_case(
68            case,
69            &lua_core_profile(),
70            Symbol::qualified("lua", "unsupported-source-case"),
71            "case is outside Lua profile descriptor coverage",
72        ));
73    }
74    if matches!(case.expectation, SourceExpectation::ExpectedGap { .. }) {
75        return run_lua_expected_gap_case(cx, case);
76    }
77    if matches!(case.expectation, SourceExpectation::LowersTo(_)) {
78        let values = eval_lua_source(cx, &case.source)?;
79        return Ok(SourceObservation::LowersTo(values_display(cx, &values)?));
80    }
81    Ok(observe_profile_backed_case(
82        case,
83        &lua_core_profile(),
84        Symbol::qualified("lua", "unsupported-source-case"),
85        "case is outside Lua profile descriptor coverage",
86    ))
87}
88
89/// Runs the Lua core matrix row and publishes claim-backed cells.
90pub fn run_lua_core_matrix_row(cx: &mut Cx) -> Result<MatrixRunReport> {
91    let row = lua_core_matrix_row();
92    let report = MatrixRunner::run_source_row(cx, &row, run_lua_core_conformance_case);
93    report.publish_claims(cx)?;
94    Ok(report)
95}
96
97fn run_lua_expected_gap_case(
98    cx: &mut Cx,
99    case: &SourceConformanceCase,
100) -> Result<SourceObservation> {
101    let SourceExpectation::ExpectedGap { .. } = &case.expectation else {
102        unreachable!("expected gap runner called for non-gap case");
103    };
104    let values = eval_lua_source(cx, &case.source)?;
105    if let Some(value) = values.first()
106        && let Some((code, reason)) = expected_gap_value(cx, value)?
107    {
108        return Ok(SourceObservation::Gap { code, reason });
109    }
110    Ok(SourceObservation::LowersTo(values_display(cx, &values)?))
111}
112
113fn observe_profile_backed_case(
114    case: &SourceConformanceCase,
115    profile: &LanguageProfile,
116    unsupported_code: Symbol,
117    unsupported_reason: &str,
118) -> SourceObservation {
119    match &case.expectation {
120        SourceExpectation::ExpectedGap { code, reason } => SourceObservation::Gap {
121            code: code.clone(),
122            reason: reason.clone(),
123        },
124        SourceExpectation::LowersTo(_) if case.source == "profile" => {
125            SourceObservation::LowersTo(profile_display(profile))
126        }
127        SourceExpectation::LowersTo(_) => SourceObservation::Gap {
128            code: unsupported_code,
129            reason: unsupported_reason.to_owned(),
130        },
131    }
132}
133
134fn profile_display(profile: &LanguageProfile) -> String {
135    format!(
136        "profile:{} reader:{} lowering:{}",
137        profile.symbol, profile.reader, profile.lowering
138    )
139}
140
141fn expected_gap_value(cx: &mut Cx, value: &Value) -> Result<Option<(Symbol, String)>> {
142    if table_string_field(cx, value, "kind")?.as_deref() != Some("ExpectedGap") {
143        return Ok(None);
144    }
145    let code = table_string_field(cx, value, "code")?
146        .unwrap_or_else(|| "lua.unknown-expected-gap".to_owned());
147    let reason = table_string_field(cx, value, "reason")?.unwrap_or_default();
148    Ok(Some((Symbol::new(code), reason)))
149}
150
151fn table_string_field(cx: &mut Cx, value: &Value, field: &str) -> Result<Option<String>> {
152    let key = cx.factory().string(field.to_owned())?;
153    let Some(value) = lua_rawget(cx, value, &key)? else {
154        return Ok(None);
155    };
156    match value.object().as_expr(cx)? {
157        Expr::Nil => Ok(None),
158        Expr::String(text) => Ok(Some(text)),
159        _ => value.object().display(cx).map(Some),
160    }
161}
162
163fn values_display(cx: &mut Cx, values: &[Value]) -> Result<String> {
164    if values.len() == 1 {
165        return value_display(cx, &values[0]);
166    }
167    values
168        .iter()
169        .map(|value| value_display(cx, value))
170        .collect::<Result<Vec<_>>>()
171        .map(|values| values.join(", "))
172}
173
174fn value_display(cx: &mut Cx, value: &Value) -> Result<String> {
175    match value.object().as_expr(cx)? {
176        Expr::Nil => Ok("nil".to_owned()),
177        Expr::Bool(value) => Ok(value.to_string()),
178        Expr::Number(number) => Ok(number.canonical),
179        Expr::String(value) => Ok(value),
180        other => Ok(format!("{other:?}")),
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use sim_kernel::testing::bare_cx as cx;
187    use sim_lib_standard_core::standard_test_capability;
188
189    use super::*;
190
191    #[test]
192    fn lua_core_matrix_row_runner_reports_profile_pass_and_load_source_pass() {
193        let mut cx = cx();
194        cx.grant(standard_test_capability());
195        cx.grant(sim_lib_mutation::standard_mutate_capability());
196
197        let report = run_lua_core_matrix_row(&mut cx).unwrap();
198
199        assert_eq!(report.cells.len(), 9);
200        assert_eq!(report.pass_count(), 5, "{:#?}", report.cells);
201        assert_eq!(report.gap_count(), 3, "{:#?}", report.cells);
202        assert_eq!(report.fail_count(), 0, "{:#?}", report.cells);
203        assert_eq!(report.language_fidelity(&Symbol::new("lua")), Some(1.0));
204    }
205
206    #[test]
207    fn lua_reuse_ledger_names_shared_substrate_and_adopters() {
208        assert_eq!(REUSE_LEDGER.len(), 6);
209        assert!(REUSE_LEDGER.iter().any(|entry| {
210            entry.shared_extension == "RuntimeKey/MutableRuntimeTable"
211                && entry
212                    .sibling_scaffolds
213                    .iter()
214                    .any(|name| name.contains("Ruby"))
215        }));
216        assert!(REUSE_LEDGER.iter().all(|entry| {
217            !entry.passing_non_lua_test.is_empty() && !entry.sibling_scaffolds.is_empty()
218        }));
219    }
220}