use std::fmt::Write as _;
use super::super::finalize_simple;
use crate::engine::SymbolicAnswer;
use crate::event_log::EventLog;
use crate::link_store::{memory_events_to_link_records, DoubletLink};
use crate::links_substitution_query::{
link_substitution_effect, parse_link_substitution_query, render_link, LinkRewriteProgram,
};
use crate::memory::MemoryStore;
use crate::substitution::CrudEvent;
const MAX_REWRITE_STEPS: usize = 100_000;
pub(super) fn try_link_substitution_query(
prompt: &str,
store: &MemoryStore,
log: &mut EventLog,
) -> Option<SymbolicAnswer> {
let program = explicit_link_query(prompt)?;
log.append(
"link_substitution_query",
crate::links_substitution_query::render_link_substitution_query(&program),
);
let effects: Vec<CrudEvent> = program.rules.iter().map(link_substitution_effect).collect();
if let Some(effect) = effects.iter().find(|effect| **effect != CrudEvent::Read) {
log.append("link_substitution_rejected", format!("{effect:?}"));
return Some(finalize_simple(
prompt,
log,
"memory_link_query_rejected",
"response:memory_link_query_rejected",
&write_boundary(*effect),
0.9,
));
}
let links = projected_links(store);
let matched = program.matched_links(&links);
log.append("link_substitution_matched", matched.len().to_string());
Some(finalize_simple(
prompt,
log,
"memory_link_query",
"response:memory_link_query",
&render_matches(&matched, links.len()),
0.9,
))
}
fn explicit_link_query(prompt: &str) -> Option<LinkRewriteProgram> {
let trimmed = prompt.trim();
if !trimmed.starts_with('(') {
return None;
}
let program = parse_link_substitution_query(trimmed, MAX_REWRITE_STEPS).ok()?;
(!program.rules.is_empty()).then_some(program)
}
fn projected_links(store: &MemoryStore) -> Vec<DoubletLink> {
memory_events_to_link_records(store.events())
.into_iter()
.flat_map(|record| record.links)
.collect()
}
fn render_matches(matched: &[DoubletLink], total: usize) -> String {
let mut body = format!("matched {} of {total} links", matched.len());
for link in matched {
let _ = write!(body, "\n{}", render_link(link));
}
body
}
fn write_boundary(effect: CrudEvent) -> String {
let attempted = match effect {
CrudEvent::Create => "create",
CrudEvent::Delete => "delete",
CrudEvent::Update => "update",
CrudEvent::Read | CrudEvent::Manual => "change",
};
format!(
"This query would {attempted} links, which memory cannot apply: the doublet view is a \
one-way projection of memory events, so an edited link has no way back to the event it \
came from. Reads are supported here -- ((($i: $s $t)) (($i: $s $t))) matches every link. \
To change memory, write to it in natural language, which appends an event the projection \
is then derived from."
)
}