1use 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct ReuseLedgerEntry {
19 pub shared_extension: &'static str,
21 pub passing_non_lua_test: &'static str,
23 pub sibling_scaffolds: &'static [&'static str],
25}
26
27pub 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
61pub 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
89pub 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}