use sim_kernel::{Expr, Symbol};
use crate::kinds::{
AT_TICK_KEY, KIND_KEY, OPERATOR_KEY, ORIGIN_KEY, is_known_kind, required_fields,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Operator {
Human,
Agent,
}
impl Operator {
pub fn symbol(self) -> Symbol {
match self {
Operator::Human => Symbol::new("human"),
Operator::Agent => Symbol::new("agent"),
}
}
pub fn from_name(name: &str) -> Option<Self> {
match name {
"human" => Some(Operator::Human),
"agent" => Some(Operator::Agent),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Origin {
pub operator: Operator,
pub at_tick: u64,
}
impl Origin {
pub fn human(tick: u64) -> Self {
Self {
operator: Operator::Human,
at_tick: tick,
}
}
pub fn agent(tick: u64) -> Self {
Self {
operator: Operator::Agent,
at_tick: tick,
}
}
fn to_expr(self) -> Expr {
sim_value::build::map(vec![
(OPERATOR_KEY, Expr::Symbol(self.operator.symbol())),
(AT_TICK_KEY, sim_value::build::uint(self.at_tick)),
])
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IntentError {
pub path: Vec<String>,
pub message: String,
}
impl IntentError {
fn at(path: &[&str], message: impl Into<String>) -> Self {
Self {
path: path.iter().map(|segment| (*segment).to_owned()).collect(),
message: message.into(),
}
}
pub fn path_string(&self) -> String {
if self.path.is_empty() {
"<root>".to_owned()
} else {
self.path.join(".")
}
}
}
impl core::fmt::Display for IntentError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}: {}", self.path_string(), self.message)
}
}
pub fn intent(kind_name: &str, origin: Origin, fields: Vec<(&str, Expr)>) -> Expr {
let mut pairs = Vec::with_capacity(fields.len() + 2);
pairs.push((
sim_value::build::sym(KIND_KEY),
Expr::Symbol(crate::kinds::intent_kind(kind_name)),
));
pairs.push((sim_value::build::sym(ORIGIN_KEY), origin.to_expr()));
for (key, value) in fields {
pairs.push((sim_value::build::sym(key), value));
}
Expr::Map(pairs)
}
fn entry<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
entries.iter().find_map(|(key, value)| {
matches!(key, Expr::Symbol(symbol) if &*symbol.name == name && symbol.namespace.is_none())
.then_some(value)
})
}
pub fn intent_kind_of(expr: &Expr) -> Option<Symbol> {
let Expr::Map(entries) = expr else {
return None;
};
match entry(entries, KIND_KEY) {
Some(Expr::Symbol(kind)) => Some(kind.clone()),
_ => None,
}
}
pub fn field<'a>(expr: &'a Expr, name: &str) -> Option<&'a Expr> {
sim_value::access::field(expr, name)
}
pub fn origin(expr: &Expr) -> Option<Origin> {
let origin = field(expr, ORIGIN_KEY)?;
let Expr::Map(entries) = origin else {
return None;
};
let operator = match entry(entries, OPERATOR_KEY) {
Some(Expr::Symbol(symbol)) => Operator::from_name(&symbol.name)?,
_ => return None,
};
let at_tick = match entry(entries, AT_TICK_KEY) {
Some(Expr::Number(number)) => number.canonical.parse::<u64>().ok()?,
_ => return None,
};
Some(Origin { operator, at_tick })
}
pub fn validate_intent(expr: &Expr) -> Result<(), IntentError> {
let Expr::Map(entries) = expr else {
return Err(IntentError::at(&[], "an Intent must be a map"));
};
let kind = match entry(entries, KIND_KEY) {
Some(Expr::Symbol(kind)) if is_known_kind(kind) => kind.clone(),
Some(Expr::Symbol(kind)) => {
return Err(IntentError::at(
&[KIND_KEY],
format!("unrecognized Intent kind '{kind}'"),
));
}
Some(_) => {
return Err(IntentError::at(
&[KIND_KEY],
"Intent 'kind' must be a symbol",
));
}
None => return Err(IntentError::at(&[], "Intent is missing a 'kind' tag")),
};
validate_origin(entries)?;
for required in required_fields(&kind.name) {
let Some(value) = entry(entries, required) else {
return Err(IntentError::at(
&[required],
format!("Intent '{kind}' is missing required field '{required}'"),
));
};
if *required == "path" && !matches!(value, Expr::List(_)) {
return Err(IntentError::at(
&["path"],
"edit-field 'path' must be a list of segments",
));
}
}
Ok(())
}
fn validate_origin(entries: &[(Expr, Expr)]) -> Result<(), IntentError> {
let Some(origin) = entry(entries, ORIGIN_KEY) else {
return Err(IntentError::at(
&[ORIGIN_KEY],
"Intent is missing an 'origin'",
));
};
let Expr::Map(origin_entries) = origin else {
return Err(IntentError::at(
&[ORIGIN_KEY],
"Intent 'origin' must be a map",
));
};
match entry(origin_entries, OPERATOR_KEY) {
Some(Expr::Symbol(symbol)) if Operator::from_name(&symbol.name).is_some() => {}
_ => {
return Err(IntentError::at(
&[ORIGIN_KEY, OPERATOR_KEY],
"origin 'operator' must be 'human' or 'agent'",
));
}
}
match entry(origin_entries, AT_TICK_KEY) {
Some(Expr::Number(_)) => Ok(()),
_ => Err(IntentError::at(
&[ORIGIN_KEY, AT_TICK_KEY],
"origin 'at-tick' must be a number",
)),
}
}
pub fn resolve_targets(expr: &Expr, is_known: impl Fn(&Expr) -> bool) -> Result<(), IntentError> {
for (label, target) in referenced_targets(expr) {
if !is_known(&target) {
return Err(IntentError {
path: vec![label],
message: "Intent references an unknown target".to_owned(),
});
}
}
Ok(())
}
pub fn referenced_targets(expr: &Expr) -> Vec<(String, Expr)> {
let Some(kind) = intent_kind_of(expr) else {
return Vec::new();
};
let mut refs = Vec::new();
let mut single = |name: &str| {
if let Some(value) = field(expr, name) {
refs.push((name.to_owned(), value.clone()));
}
};
match &*kind.name {
"tap" | "edit-field" | "invoke" | "scrub" | "set-param" | "performance-event"
| "piano-roll-edit" | "player-rack-edit" | "arranger-edit" => single("target"),
"move" => single("node"),
"unwire" => single("edge"),
"create" => single("class"),
"open" => single("value"),
"approve" | "reject" | "ask" | "split-mission" | "pause-agent" | "rerun-validation"
| "replay-cassette" => single("mission"),
"open-source" => single("location"),
"select" | "delete" => {
if let Some(Expr::List(items)) = field(expr, "targets") {
for (index, item) in items.iter().enumerate() {
refs.push((format!("targets[{index}]"), item.clone()));
}
}
}
"wire" => {
for end in ["from", "to"] {
if let Some(Expr::Map(port)) = field(expr, end)
&& let Some(node) = entry(port, "node")
{
refs.push((format!("{end}.node"), node.clone()));
}
}
}
_ => {}
}
refs
}