use std::collections::BTreeMap;
use crate::types::homoiconic::{
FeatureInputConstrainedVariable, FeatureInputTermRef, FeatureInputValueDto,
FeatureInputVariable, TermInputDto,
};
use crate::types::inference::GuardOp;
pub fn psi<I, K>(sort_name: impl Into<String>, features: I) -> TermInputDto
where
I: IntoIterator<Item = (K, FeatureInputValueDto)>,
K: Into<String>,
{
let mut map = BTreeMap::new();
for (k, v) in features {
map.insert(k.into(), v);
}
if map.is_empty() {
TermInputDto::by_name(sort_name)
} else {
TermInputDto::by_name_with(sort_name, map)
}
}
pub fn psi_by_id<I, K>(sort_id: impl Into<String>, features: I) -> TermInputDto
where
I: IntoIterator<Item = (K, FeatureInputValueDto)>,
K: Into<String>,
{
let mut map = BTreeMap::new();
for (k, v) in features {
map.insert(k.into(), v);
}
TermInputDto::by_sort_id(sort_id, map)
}
pub fn var(name: impl Into<String>) -> FeatureInputValueDto {
FeatureInputValueDto::Variable(FeatureInputVariable { name: name.into() })
}
pub fn constrained(name: impl Into<String>, constraint: TermInputDto) -> FeatureInputValueDto {
FeatureInputValueDto::ConstrainedVariable(FeatureInputConstrainedVariable {
name: name.into(),
constraint: Box::new(constraint),
})
}
pub fn term_ref(term_id: impl Into<String>) -> FeatureInputValueDto {
FeatureInputValueDto::TermRef(FeatureInputTermRef {
term_id: term_id.into(),
})
}
pub fn guard(op: GuardOp, right: impl Into<GuardRhs>) -> TermInputDto {
let rhs: GuardRhs = right.into();
let mut features: BTreeMap<String, FeatureInputValueDto> = BTreeMap::new();
features.insert(
"op".into(),
FeatureInputValueDto::String(op.as_str().to_string()),
);
features.insert("right".into(), rhs.into_feature());
TermInputDto::by_name_with("guard_constraint", features)
}
#[derive(Debug, Clone)]
pub enum GuardRhs {
Integer(i64),
Real(f64),
String(String),
Boolean(bool),
}
impl GuardRhs {
fn into_feature(self) -> FeatureInputValueDto {
match self {
Self::Integer(i) => FeatureInputValueDto::Integer(i),
Self::Real(r) => FeatureInputValueDto::Real(r),
Self::String(s) => FeatureInputValueDto::String(s),
Self::Boolean(b) => FeatureInputValueDto::Boolean(b),
}
}
}
impl From<i64> for GuardRhs {
fn from(i: i64) -> Self {
Self::Integer(i)
}
}
impl From<i32> for GuardRhs {
fn from(i: i32) -> Self {
Self::Integer(i.into())
}
}
impl From<f64> for GuardRhs {
fn from(f: f64) -> Self {
Self::Real(f)
}
}
impl From<bool> for GuardRhs {
fn from(b: bool) -> Self {
Self::Boolean(b)
}
}
impl From<&str> for GuardRhs {
fn from(s: &str) -> Self {
Self::String(s.to_string())
}
}
impl From<String> for GuardRhs {
fn from(s: String) -> Self {
Self::String(s)
}
}
impl From<&str> for FeatureInputValueDto {
fn from(s: &str) -> Self {
Self::String(s.to_string())
}
}
impl From<String> for FeatureInputValueDto {
fn from(s: String) -> Self {
Self::String(s)
}
}
impl From<i64> for FeatureInputValueDto {
fn from(i: i64) -> Self {
Self::Integer(i)
}
}
impl From<i32> for FeatureInputValueDto {
fn from(i: i32) -> Self {
Self::Integer(i.into())
}
}
impl From<f64> for FeatureInputValueDto {
fn from(f: f64) -> Self {
Self::Real(f)
}
}
impl From<bool> for FeatureInputValueDto {
fn from(b: bool) -> Self {
Self::Boolean(b)
}
}
impl<T: Into<FeatureInputValueDto>> From<Vec<T>> for FeatureInputValueDto {
fn from(v: Vec<T>) -> Self {
FeatureInputValueDto::List(v.into_iter().map(Into::into).collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn psi_by_name_with_features() {
let term = psi(
"person",
[
("name", FeatureInputValueDto::string("Alice")),
("age", FeatureInputValueDto::Integer(30)),
],
);
let j = serde_json::to_value(&term).unwrap();
assert_eq!(
j,
json!({"sort_name": "person", "features": {"age": 30, "name": "Alice"}})
);
}
#[test]
fn psi_by_name_without_features() {
let term = psi::<[(String, _); 0], String>("person", []);
let j = serde_json::to_value(&term).unwrap();
assert_eq!(j, json!({"sort_name": "person"}));
}
#[test]
fn guard_produces_expected_shape() {
let g = guard(GuardOp::Gt, 80_000_i64);
let j = serde_json::to_value(&g).unwrap();
assert_eq!(
j,
json!({
"sort_name": "guard_constraint",
"features": { "op": "gt", "right": 80000 }
})
);
}
#[test]
fn constrained_variable_roundtrip() {
let cv = constrained("?S", guard(GuardOp::Gte, 100_i64));
let j = serde_json::to_value(&cv).unwrap();
let back: FeatureInputValueDto = serde_json::from_value(j).unwrap();
match back {
FeatureInputValueDto::ConstrainedVariable(cv) => {
assert_eq!(cv.name, "?S");
}
other => panic!("expected ConstrainedVariable, got {other:?}"),
}
}
}