use std::sync::{Arc, Mutex};
use sim_kernel::{
CapabilityName, Cx, Expr, HintMetadata, Ref, Result, Symbol, Value,
card::{card_for_ref, card_kind_predicate},
force_list_to_vec,
standard::standard_organ_kind,
};
use sim_shape::{AnyShape, ExprKindShape};
use crate::*;
use sim_kernel::testing::bare_cx as cx;
fn string(cx: &mut Cx, value: &str) -> Value {
cx.factory().string(value.to_owned()).unwrap()
}
fn bool_value(cx: &mut Cx, value: bool) -> Value {
cx.factory().bool(value).unwrap()
}
fn body(label: &'static str) -> MethodBody {
Arc::new(move |cx, _args| cx.factory().string(label.to_owned()))
}
fn trace_body(label: &'static str, trace: Arc<Mutex<Vec<String>>>) -> MethodBody {
Arc::new(move |cx, _args| {
trace.lock().unwrap().push(label.to_owned());
cx.factory().string(label.to_owned())
})
}
fn string_shape() -> Arc<dyn sim_kernel::Shape> {
Arc::new(ExprKindShape::new(sim_kernel::ExprKind::String))
}
fn any_shape() -> Arc<dyn sim_kernel::Shape> {
Arc::new(AnyShape)
}
fn primary(id: &'static str, shapes: Vec<Arc<dyn sim_kernel::Shape>>) -> DispatchMethod {
DispatchMethod::new(
Symbol::qualified("method", id),
MethodRole::Primary,
shapes,
body(id),
)
}
#[test]
fn operation_hints_include_method_metadata() {
let mut generic = GenericFunction::new(Symbol::qualified("dispatch-test", "hints"));
generic
.add_method(
primary("string", vec![string_shape()])
.with_argument_hint(Symbol::new("input"), "value to classify")
.with_capability_requirement(CapabilityName::new("dispatch.inspect"))
.with_codec_safe_form(Symbol::qualified("codec", "lisp"))
.with_example("(dispatch-test/hints input)"),
)
.unwrap();
let text = generic
.operation_hints()
.iter()
.map(HintMetadata::radar_text)
.collect::<Vec<_>>()
.join(" ");
assert!(text.contains("runtime-hint/argument"));
assert!(text.contains("dispatch.inspect"));
assert!(text.contains("codec/lisp"));
assert!(text.contains("(dispatch-test/hints input)"));
}
#[test]
fn failed_selection_pushes_radar_consumable_hints() {
let mut cx = cx();
let mut generic = GenericFunction::new(Symbol::qualified("dispatch-test", "diagnose"));
generic
.add_method(
primary("string", vec![string_shape()])
.with_argument_hint(Symbol::new("input"), "string value"),
)
.unwrap();
let arg = bool_value(&mut cx, true);
assert!(generic.call(&mut cx, &[arg]).is_err());
let diagnostics = cx.diagnostics().messages();
let hints = HintMetadata::collect_from_diagnostic(&diagnostics[0]);
let text = hints
.iter()
.map(HintMetadata::radar_text)
.collect::<Vec<_>>()
.join(" ");
assert!(text.contains("runtime-hint/overload-selection"));
assert!(text.contains("runtime-hint/argument"));
assert!(text.contains("string value"));
}
#[test]
fn most_specific_multimethod_is_selected() {
let mut cx = cx();
let mut generic = GenericFunction::new(Symbol::qualified("dispatch-test", "choose"));
generic
.add_method(primary("broad", vec![any_shape(), any_shape()]))
.unwrap();
generic
.add_method(primary("second-string", vec![any_shape(), string_shape()]))
.unwrap();
let args = [bool_value(&mut cx, true), string(&mut cx, "text")];
let selected = generic.select_primary(&mut cx, &args).unwrap();
assert_eq!(
selected.method(),
&Symbol::qualified("method", "second-string")
);
let result = generic.call(&mut cx, &args).unwrap();
assert_eq!(
result.object().as_expr(&mut cx).unwrap(),
Expr::String("second-string".to_owned())
);
}
#[test]
fn method_combination_order_is_around_before_primary_after() -> Result<()> {
let mut cx = cx();
let trace = Arc::new(Mutex::new(Vec::new()));
let mut generic = GenericFunction::new(Symbol::qualified("dispatch-test", "combine"));
for (id, role, shape) in [
("around-any", MethodRole::Around, any_shape()),
("around-string", MethodRole::Around, string_shape()),
("before-any", MethodRole::Before, any_shape()),
("before-string", MethodRole::Before, string_shape()),
("primary-string", MethodRole::Primary, string_shape()),
("after-any", MethodRole::After, any_shape()),
("after-string", MethodRole::After, string_shape()),
] {
generic.add_method(DispatchMethod::new(
Symbol::qualified("method", id),
role,
vec![shape],
trace_body(id, trace.clone()),
))?;
}
let args = [string(&mut cx, "text")];
let order = generic.dispatch_order(&mut cx, &args)?;
assert_eq!(
order,
vec![
Symbol::qualified("method", "around-string"),
Symbol::qualified("method", "around-any"),
Symbol::qualified("method", "before-string"),
Symbol::qualified("method", "before-any"),
Symbol::qualified("method", "primary-string"),
Symbol::qualified("method", "after-any"),
Symbol::qualified("method", "after-string"),
]
);
let result = generic.call(&mut cx, &args)?;
assert_eq!(
result.object().as_expr(&mut cx).unwrap(),
Expr::String("primary-string".to_owned())
);
assert_eq!(
*trace.lock().unwrap(),
vec![
"around-string",
"around-any",
"before-string",
"before-any",
"primary-string",
"after-any",
"after-string",
]
);
Ok(())
}
#[test]
fn specificity_is_inspectable() {
let mut cx = cx();
let mut generic = GenericFunction::new(Symbol::qualified("dispatch-test", "inspect"));
generic
.add_method(primary("any", vec![any_shape()]))
.unwrap();
generic
.add_method(primary("string", vec![string_shape()]))
.unwrap();
let args = [string(&mut cx, "text")];
let inspected = generic.inspect_specificity(&mut cx, &args).unwrap();
assert_eq!(inspected.len(), 2);
assert_eq!(
inspected[0].method(),
&Symbol::qualified("method", "string")
);
assert_eq!(inspected[1].method(), &Symbol::qualified("method", "any"));
assert!(inspected[0].score() > inspected[1].score());
assert_eq!(
inspected[0].argument_scores(),
&[sim_kernel::MatchScore::exact(10)]
);
}
#[test]
fn cl_julia_and_clojure_profiles_reuse_one_generic() -> Result<()> {
let mut cx = cx();
let mut generic = GenericFunction::new(Symbol::qualified("dispatch-test", "shared"));
generic.add_method(primary("shared-string", vec![string_shape()]))?;
let profiles = [
Symbol::qualified("profile", "common-lisp-lite"),
Symbol::qualified("profile", "julia-lite"),
Symbol::qualified("profile", "clojure-core"),
];
for profile in profiles {
let args = [string(&mut cx, "text")];
let result = generic.call_for_profile(&mut cx, &profile, &args)?;
assert_eq!(
result.object().as_expr(&mut cx).unwrap(),
Expr::String("shared-string".to_owned())
);
}
Ok(())
}
#[test]
fn dispatch_organ_claims_project_to_card() {
let mut cx = cx();
publish_dispatch_organ_claims(&mut cx).unwrap();
let claims = cx
.query_facts(sim_kernel::ClaimPattern {
subject: Some(Ref::Symbol(dispatch_organ_symbol())),
predicate: Some(card_kind_predicate()),
object: Some(Ref::Symbol(standard_organ_kind())),
include_revoked: false,
})
.unwrap();
assert_eq!(claims.len(), 1);
let card = card_for_ref(&mut cx, Ref::Symbol(dispatch_organ_symbol())).unwrap();
let table = card.object().as_table(&mut cx).unwrap();
let entries = table.object().as_table_impl().unwrap();
let ops = entries.get(&mut cx, Symbol::new("ops")).unwrap();
let list = ops.object().as_list().unwrap();
let values = force_list_to_vec(&mut cx, list, "dispatch organ ops").unwrap();
assert!(values.into_iter().any(|value| {
value.object().as_expr(&mut cx).unwrap()
== Expr::Symbol(Symbol::qualified("dispatch", "specificity.v1"))
}));
}