use crate::core::Core;
use crate::matching::*;
use crate::parser;
use crate::rule::Rule;
use crate::state::{self, State};
use crate::string_cache::{Atom, StringCache};
use crate::token::*;
use crate::update::{self, update, SideInput};
use itertools::Itertools;
use rand::{self, rngs::SmallRng, SeedableRng};
use std::fmt;
use std::vec::Vec;
pub struct ContextBuilder<'a> {
text: &'a str,
string_cache: StringCache,
rng: Option<&'a mut SmallRng>,
}
impl<'a> ContextBuilder<'a> {
pub fn new() -> Self {
ContextBuilder {
text: "",
string_cache: StringCache::new(),
rng: None,
}
}
pub fn text(mut self, text: &'a str) -> Self {
self.text = text;
self
}
pub fn string_cache(mut self, string_cache: StringCache) -> Self {
self.string_cache = string_cache;
self
}
pub fn rng(mut self, rng: &'a mut SmallRng) -> Self {
self.rng = Some(rng);
self
}
pub fn build(self) -> Result<Context, parser::Error> {
Context::new(
self.text,
self.string_cache,
self.rng.unwrap_or(&mut default_rng()),
)
}
}
fn default_rng() -> SmallRng {
SmallRng::from_rng(&mut rand::rng())
}
#[derive(Clone)]
#[non_exhaustive]
pub struct Context {
pub core: Core,
pub string_cache: StringCache,
}
impl Context {
pub(crate) fn from_text(text: &str) -> Result<Self, parser::Error> {
ContextBuilder::new().text(text).build()
}
#[cfg(test)]
pub(crate) fn from_text_rng(text: &str, rng: &mut SmallRng) -> Result<Self, parser::Error> {
ContextBuilder::new().text(text).rng(rng).build()
}
fn new(
text: &str,
mut string_cache: StringCache,
rng: &mut SmallRng,
) -> Result<Context, parser::Error> {
let result = parser::parse(text, &mut string_cache, rng)?;
let mut state = State::new();
for phrase in result.state.into_iter() {
state.push(phrase);
}
let qui_atom = string_cache.str_to_atom(parser::QUI);
Ok(Context {
core: Core {
state,
rules: result.rules,
executed_rule_ids: vec![],
rule_repeat_count: 0,
rng: rng.clone(),
qui_atom,
},
string_cache,
})
}
pub fn update(&mut self) -> Result<(), update::Error> {
self.update_with_side_input(|_: &Phrase| None)
}
pub fn update_with_side_input<F>(&mut self, side_input: F) -> Result<(), update::Error>
where
F: SideInput,
{
update(&mut self.core, side_input)
}
pub fn execute_rule(&mut self, rule: &Rule) -> bool {
update::execute_rule(rule, &mut self.core.state, None)
}
pub fn find_matching_rules<F>(
&self,
mut side_input: F,
) -> Result<Vec<Rule>, ExcessivePermutationError>
where
F: SideInput,
{
let state = &mut self.core.state.clone();
let mut rules = vec![];
for rule in &self.core.rules {
if let Some(matching_rule) =
rule_matches_state(&rule, state, &mut side_input)?.map(|result| result.rule)
{
rules.push(matching_rule);
}
}
Ok(rules)
}
pub fn str_to_atom(&mut self, string: &str) -> Atom {
self.string_cache.str_to_atom(string)
}
pub fn str_to_existing_atom(&self, string: &str) -> Option<Atom> {
self.string_cache.str_to_existing_atom(string)
}
pub fn atom_to_str(&self, atom: Atom) -> Option<&str> {
self.string_cache.atom_to_str(atom)
}
pub fn atom_to_integer(&self, atom: Atom) -> Option<i32> {
StringCache::atom_to_integer(atom)
}
#[cfg(test)]
pub fn with_test_rng(mut self) -> Context {
self.core.rng = crate::tests::test_rng();
self
}
pub fn push_state(&mut self, phrase_text: &str) {
self.core
.state
.push(tokenize(phrase_text, &mut self.string_cache));
}
pub fn extend_state_from_context(&mut self, other: &Context) {
for phrase_id in other.core.state.iter() {
let phrase = other.core.state.get(phrase_id);
let new_phrase = phrase
.iter()
.map(|t| {
if StringCache::atom_to_integer(t.atom).is_some() {
t.clone()
} else {
let string = other
.string_cache
.atom_to_str(t.atom)
.expect(&format!("missing token: {:?}", t));
let mut new_token = t.clone();
new_token.atom = self.string_cache.str_to_atom(string);
new_token
}
})
.collect();
self.core.state.push(new_phrase);
}
}
pub fn print(&self) {
println!("{}", self);
}
}
impl fmt::Display for Context {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let state = state::state_to_string(&self.core.state, &self.string_cache);
let mut rules = self
.core
.rules
.iter()
.map(|r| r.to_string(&self.string_cache))
.collect::<Vec<_>>();
rules.sort();
write!(
f,
"state:\n{}\nrules:\n{}\n{}",
state,
rules.join("\n"),
if self.core.executed_rule_ids.is_empty() {
"no rules were executed in the previous update".to_string()
} else {
format!(
"rule ids executed in the previous update:\n{}",
self.core.executed_rule_ids.iter().join(", ")
)
}
)
}
}