use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt;
use std::io::{self, Write};
use std::hash::Hash;
use std::any::Any;
pub trait InputHandler {
fn read_line(&mut self) -> Option<String>;
fn has_input(&self) -> bool;
}
pub struct StandardInputHandler;
impl InputHandler for StandardInputHandler {
fn read_line(&mut self) -> Option<String> {
print!("U> ");
io::stdout().flush().unwrap();
let mut input = String::new();
match io::stdin().read_line(&mut input) {
Ok(_) => Some(input.trim().to_string()),
Err(_) => {
println!("EOF");
None
}
}
}
fn has_input(&self) -> bool {
true }
}
pub struct DemoInputHandler {
inputs: VecDeque<String>,
current_index: usize,
}
impl DemoInputHandler {
pub fn new(inputs: Vec<String>) -> Self {
Self {
inputs: inputs.into(),
current_index: 0,
}
}
}
impl InputHandler for DemoInputHandler {
fn read_line(&mut self) -> Option<String> {
if let Some(input) = self.inputs.pop_front() {
println!("U> {}", input); Some(input)
} else {
println!("Demo completed - no more inputs");
None
}
}
fn has_input(&self) -> bool {
!self.inputs.is_empty()
}
}
fn is_sequence<T>(seq: &T) -> bool {
true }
struct Value<T: Clone + PartialEq + Eq + Hash> {
value: Option<T>, allowed_values: HashSet<T>, type_constraint: Option<Box<dyn Fn(&T) -> bool>>, }
impl<T: Clone + PartialEq + Eq + Hash> Clone for Value<T> {
fn clone(&self) -> Self {
Value {
value: self.value.clone(),
allowed_values: self.allowed_values.clone(),
type_constraint: None, }
}
}
impl<T: Clone + PartialEq + Eq + Hash + fmt::Display> Value<T> {
fn new_allowed(allowed: HashSet<T>) -> Self {
Value {
value: None,
allowed_values: allowed,
type_constraint: None,
}
}
fn new_type<F>(type_check: F) -> Self
where
F: Fn(&T) -> bool + 'static,
{
Value {
value: None,
allowed_values: HashSet::new(),
type_constraint: Some(Box::new(type_check)),
}
}
fn set(&mut self, value: T) -> Result<(), String> {
if !self.allowed_values.is_empty() && !self.allowed_values.contains(&value) {
return Err(format!("{} is not among allowed values", value));
}
if let Some(check) = &self.type_constraint {
if !check(&value) {
return Err(format!("{} does not match type constraint", value));
}
}
self.value = Some(value);
Ok(())
}
fn get(&self) -> Option<&T> {
self.value.as_ref()
}
fn clear(&mut self) {
self.value = None;
}
}
impl<T: Clone + PartialEq + Eq + Hash + fmt::Display> fmt::Display for Value<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.value {
Some(v) => write!(f, "<{}>", v),
None => write!(f, "<>"),
}
}
}
struct Record {
typedict: HashMap<String, Box<dyn Fn(&dyn Any) -> bool>>, fields: HashMap<String, Box<dyn Any>>, }
impl Clone for Record {
fn clone(&self) -> Self {
Record {
typedict: HashMap::new(), fields: HashMap::new(), }
}
}
impl Record {
fn new(fields: HashMap<String, Box<dyn Any>>) -> Self {
let mut typedict: HashMap<String, Box<dyn Fn(&dyn Any) -> bool>> = HashMap::new();
for (key, value) in &fields {
let type_id = value.type_id();
typedict.insert(key.clone(), Box::new(move |v: &dyn Any| v.type_id() == type_id) as Box<dyn Fn(&dyn Any) -> bool>);
}
Record { typedict, fields }
}
fn as_dict(&self) -> HashMap<String, &dyn Any> {
self.fields.iter().map(|(k, v)| (k.clone(), v.as_ref())).collect()
}
fn typecheck(&self, key: &str, value: Option<&dyn Any>) -> Result<(), String> {
if let Some(type_fn) = self.typedict.get(key) {
if let Some(val) = value {
if !type_fn(val) {
return Err(format!("{} is not of expected type", key));
}
}
Ok(())
} else {
Err(format!("{} is not a valid key", key))
}
}
fn get(&self, key: &str) -> Option<&dyn Any> {
self.typecheck(key, None).ok()?;
self.fields.get(key).map(|v| v.as_ref())
}
fn set(&mut self, key: &str, value: Box<dyn Any>) -> Result<(), String> {
self.typecheck(key, Some(value.as_ref()))?;
self.fields.insert(key.to_string(), value);
Ok(())
}
fn delete(&mut self, key: &str) -> Result<(), String> {
self.typecheck(key, None)?;
self.fields.remove(key);
Ok(())
}
fn pformat(&self, prefix: &str, indent: &str) -> String {
let mut result = String::new();
for (key, value) in self.as_dict() {
if !result.is_empty() {
result.push('\n');
}
result.push_str(prefix);
result.push_str(&key);
result.push_str(": ");
result.push_str(&format!("{:?}", value));
}
result
}
}
impl fmt::Display for Record {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let items: Vec<String> = self.as_dict().iter().map(|(k, v)| format!("{} = {:?}", k, v)).collect();
write!(f, "{{{}}}", items.join("; "))
}
}
struct Stack<T: Clone> {
elements: Vec<T>, type_constraint: Option<Box<dyn Fn(&T) -> bool>>, }
impl<T: Clone> Clone for Stack<T> {
fn clone(&self) -> Self {
Stack {
elements: self.elements.clone(),
type_constraint: None, }
}
}
impl<T: Clone + PartialEq + fmt::Display> Stack<T> {
fn new() -> Self {
Stack {
elements: Vec::new(),
type_constraint: None,
}
}
fn with_type<F>(type_check: F) -> Self
where
F: Fn(&T) -> bool + 'static,
{
Stack {
elements: Vec::new(),
type_constraint: Some(Box::new(type_check)),
}
}
fn top(&self) -> Result<&T, String> {
self.elements.last().ok_or("Stack is empty".to_string())
}
fn pop(&mut self) -> Result<T, String> {
self.elements.pop().ok_or("Stack is empty".to_string())
}
fn push(&mut self, value: T) -> Result<(), String> {
if let Some(check) = &self.type_constraint {
if !check(&value) {
return Err(format!("{} does not match type constraint", value));
}
}
self.elements.push(value);
Ok(())
}
fn clear(&mut self) {
self.elements.clear();
}
fn len(&self) -> usize {
self.elements.len()
}
}
impl<T: Clone + fmt::Display> fmt::Display for Stack<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let reversed: Vec<String> = self.elements.iter().rev().map(|e| e.to_string()).collect();
write!(f, "<[ {} <]", reversed.join(", "))
}
}
#[derive(Clone)]
struct StackSet<T: Clone + PartialEq + Eq + Hash> {
stack: Stack<T>, }
impl<T: Clone + PartialEq + Eq + Hash + fmt::Display> StackSet<T> {
fn new() -> Self {
StackSet { stack: Stack::new() }
}
fn with_type<F>(type_check: F) -> Self
where
F: Fn(&T) -> bool + 'static,
{
StackSet {
stack: Stack::with_type(type_check),
}
}
fn contains(&self, value: &T) -> bool {
self.stack.elements.contains(value)
}
fn push(&mut self, value: T) -> Result<(), String> {
if self.contains(&value) {
self.stack.elements.retain(|x| x != &value);
}
self.stack.push(value)
}
}
impl<T: Clone + PartialEq + Eq + Hash + fmt::Display> fmt::Display for StackSet<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<{{ {} <}}", self.stack.to_string())
}
}
struct TSet<T: Clone + PartialEq + Eq + Hash> {
elements: HashSet<T>, type_constraint: Option<Box<dyn Fn(&T) -> bool>>, }
impl<T: Clone + PartialEq + Eq + Hash> Clone for TSet<T> {
fn clone(&self) -> Self {
TSet {
elements: self.elements.clone(),
type_constraint: None, }
}
}
impl<T: Clone + PartialEq + Eq + Hash + fmt::Display> TSet<T> {
fn new() -> Self {
TSet {
elements: HashSet::new(),
type_constraint: None,
}
}
fn with_type<F>(type_check: F) -> Self
where
F: Fn(&T) -> bool + 'static,
{
TSet {
elements: HashSet::new(),
type_constraint: Some(Box::new(type_check)),
}
}
fn add(&mut self, value: T) -> Result<(), String> {
if let Some(check) = &self.type_constraint {
if !check(&value) {
return Err(format!("{} does not match type constraint", value));
}
}
self.elements.insert(value);
Ok(())
}
fn clear(&mut self) {
self.elements.clear();
}
fn len(&self) -> usize {
self.elements.len()
}
fn contains(&self, value: &T) -> bool {
self.elements.contains(value)
}
}
impl<T: Clone + PartialEq + Eq + Hash + fmt::Display> fmt::Display for TSet<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let elements: Vec<String> = self.elements.iter().map(|e| e.to_string()).collect();
write!(f, "{{{}}}", elements.join(", "))
}
}
macro_rules! create_enum {
($name:ident, $($variant:ident),+) => {
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
enum $name {
$($variant),+
}
impl $name {
fn new(name: &str) -> Option<Self> {
match name {
$(stringify!($variant) => Some($name::$variant),)+
_ => None,
}
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
$($name::$variant => write!(f, "{}", stringify!($variant)),)+
}
}
}
};
}
create_enum!(Speaker, USR, SYS);
create_enum!(ProgramState, RUN, QUIT);
trait Type: fmt::Display {
fn typecheck(&self, context: &Domain) -> Result<(), String>;
}
#[derive(Clone, PartialEq, Eq, Hash)]
struct Atomic {
content: String, }
impl Atomic {
fn new(atom: &str) -> Result<Self, String> {
if atom.is_empty() || atom == "yes" || atom == "no" {
return Err("Invalid atom".to_string());
}
if !atom.chars().next().unwrap_or(' ').is_alphabetic() {
return Err("Atom must start with a letter".to_string());
}
if !atom.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '+' || c == ':') {
return Err("Invalid characters in atom".to_string());
}
Ok(Atomic { content: atom.to_string() })
}
}
impl fmt::Display for Atomic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.content)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
struct Ind(Atomic);
impl Ind {
fn new(atom: &str) -> Result<Self, String> {
Ok(Ind(Atomic::new(atom)?))
}
}
impl Type for Ind {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
if context.inds.contains_key(&self.0.content) {
Ok(())
} else {
Err(format!("{} not in context individuals", self.0.content))
}
}
}
impl fmt::Display for Ind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
struct Pred0(Atomic);
impl Pred0 {
fn new(atom: &str) -> Result<Self, String> {
Ok(Pred0(Atomic::new(atom)?))
}
}
impl Type for Pred0 {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
if context.preds0.contains(&self.0.content) {
Ok(())
} else {
Err(format!("{} not in context 0-place predicates", self.0.content))
}
}
}
impl fmt::Display for Pred0 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
struct Pred1(Atomic);
impl Pred1 {
fn new(atom: &str) -> Result<Self, String> {
Ok(Pred1(Atomic::new(atom)?))
}
fn apply(&self, ind: &Ind) -> Result<Prop, Box<dyn std::error::Error>> {
Ok(Prop {
pred: Pred0::new(&self.0.content)?,
ind: Some(ind.clone()),
yes: true,
})
}
}
impl Type for Pred1 {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
if context.preds1.contains_key(&self.0.content) {
Ok(())
} else {
Err(format!("{} not in context 1-place predicates", self.0.content))
}
}
}
impl fmt::Display for Pred1 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone)]
struct Sort(Pred1);
impl Sort {
fn new(atom: &str) -> Result<Self, String> {
Ok(Sort(Pred1::new(atom)?))
}
}
impl Type for Sort {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
if context.sorts.contains_key(&self.0 .0.content) {
Ok(())
} else {
Err(format!("{} not in context sorts", self.0 .0.content))
}
}
}
impl fmt::Display for Sort {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
struct Prop {
pred: Pred0, ind: Option<Ind>, yes: bool, }
impl Prop {
fn new(s: &str) -> Result<Self, String> {
let (yes, pred_str, ind_str) = if s.starts_with('-') {
(false, &s[1..], None::<&str>)
} else {
(true, s, None)
};
let (pred_str, ind_str) = if pred_str.ends_with(')') {
let parts: Vec<&str> = pred_str[..pred_str.len() - 1].split('(').collect();
if parts.len() == 2 {
(parts[0], Some(parts[1]))
} else {
(pred_str, None)
}
} else {
(pred_str, None)
};
let pred = if ind_str.is_some() {
Pred0::new(pred_str)? } else {
Pred0::new(pred_str)?
};
let ind = ind_str.map(|s| Ind::new(s).unwrap());
Ok(Prop { pred, ind, yes })
}
}
impl Type for Prop {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
self.pred.typecheck(context)?;
if let Some(ind) = &self.ind {
ind.typecheck(context)?;
if let Some(sort) = context.preds1.get(&self.pred.0.content) {
if context.inds.get(&ind.0.content) != Some(sort) {
return Err("Sort mismatch".to_string());
}
}
}
Ok(())
}
}
impl fmt::Display for Prop {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let prefix = if self.yes { "" } else { "-" };
let ind_str = self.ind.as_ref().map_or("", |ind| &ind.0.content);
write!(f, "{}{}({})", prefix, self.pred, ind_str)
}
}
#[derive(Clone)]
struct ShortAns {
ind: Ind, yes: bool, }
impl ShortAns {
fn new(s: &str) -> Result<Self, String> {
let (yes, ind_str) = if s.starts_with('-') {
(false, &s[1..])
} else {
(true, s)
};
Ok(ShortAns {
ind: Ind::new(ind_str)?,
yes,
})
}
}
impl Type for ShortAns {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
self.ind.typecheck(context)
}
}
impl fmt::Display for ShortAns {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let prefix = if self.yes { "" } else { "-" };
write!(f, "{}{}", prefix, self.ind)
}
}
#[derive(Clone)]
struct YesNo {
yes: bool, }
impl YesNo {
fn new(s: &str) -> Result<Self, String> {
match s {
"yes" => Ok(YesNo { yes: true }),
"no" => Ok(YesNo { yes: false }),
_ => Err(format!("Invalid YesNo: {}", s)),
}
}
}
impl Type for YesNo {
fn typecheck(&self, _context: &Domain) -> Result<(), String> {
Ok(())
}
}
impl fmt::Display for YesNo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", if self.yes { "yes" } else { "no" })
}
}
#[derive(Clone)]
enum Ans {
Prop(Prop), ShortAns(ShortAns), YesNo(YesNo), }
impl Ans {
fn new(s: &str) -> Result<Self, String> {
if s == "yes" || s == "no" {
Ok(Ans::YesNo(YesNo::new(s)?))
} else if !s.contains('(') && !s.contains(')') {
Ok(Ans::ShortAns(ShortAns::new(s)?))
} else if s.contains('(') && s.ends_with(')') {
Ok(Ans::Prop(Prop::new(s)?))
} else {
Err(format!("Could not parse answer: {}", s))
}
}
}
impl Type for Ans {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
match self {
Ans::Prop(p) => p.typecheck(context),
Ans::ShortAns(s) => s.typecheck(context),
Ans::YesNo(y) => y.typecheck(context),
}
}
}
impl fmt::Display for Ans {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Ans::Prop(p) => write!(f, "{}", p),
Ans::ShortAns(s) => write!(f, "{}", s),
Ans::YesNo(y) => write!(f, "{}", y),
}
}
}
#[derive(Clone)]
struct WhQ {
pred: Pred1, }
impl WhQ {
fn new(pred: &str) -> Result<Self, String> {
let pred = if pred.starts_with("?x.") && pred.ends_with("(x)") {
&pred[3..pred.len() - 3]
} else {
pred
};
Ok(WhQ {
pred: Pred1::new(pred)?,
})
}
}
impl Type for WhQ {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
self.pred.typecheck(context)
}
}
impl fmt::Display for WhQ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "?x.{} (x)", self.pred)
}
}
#[derive(Clone)]
struct YNQ {
prop: Prop, }
impl YNQ {
fn new(prop: &str) -> Result<Self, String> {
let prop = if prop.starts_with('?') { &prop[1..] } else { prop };
Ok(YNQ {
prop: Prop::new(prop)?,
})
}
}
impl Type for YNQ {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
self.prop.typecheck(context)
}
}
impl fmt::Display for YNQ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "?{}", self.prop)
}
}
#[derive(Clone)]
struct AltQ {
ynqs: Vec<YNQ>, }
impl AltQ {
fn new(ynqs: Vec<YNQ>) -> Self {
AltQ { ynqs }
}
}
impl Type for AltQ {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
for ynq in &self.ynqs {
ynq.typecheck(context)?;
}
Ok(())
}
}
impl fmt::Display for AltQ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ynq_strs: Vec<String> = self.ynqs.iter().map(|y| y.to_string()).collect();
write!(f, "{{ {} }}", ynq_strs.join(" | "))
}
}
#[derive(Clone)]
pub enum Question {
WhQ(WhQ), YNQ(YNQ), AltQ(AltQ), }
impl Question {
pub fn new(s: &str) -> Result<Self, String> {
if s.starts_with("?x.") && s.ends_with("(x)") {
Ok(Question::WhQ(WhQ::new(&s[3..s.len() - 3])?))
} else if s.starts_with('?') {
Ok(Question::YNQ(YNQ::new(&s[1..])?))
} else {
Err(format!("Could not parse question: {}", s))
}
}
}
impl Type for Question {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
match self {
Question::WhQ(w) => w.typecheck(context),
Question::YNQ(y) => y.typecheck(context),
Question::AltQ(a) => a.typecheck(context),
}
}
}
impl fmt::Display for Question {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Question::WhQ(w) => write!(f, "{}", w),
Question::YNQ(y) => write!(f, "{}", y),
Question::AltQ(a) => write!(f, "{}", a),
}
}
}
#[derive(Clone)]
struct Greet;
impl Type for Greet {
fn typecheck(&self, _context: &Domain) -> Result<(), String> {
Ok(())
}
}
impl fmt::Display for Greet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Greet()")
}
}
#[derive(Clone)]
struct Quit;
impl Type for Quit {
fn typecheck(&self, _context: &Domain) -> Result<(), String> {
Ok(())
}
}
impl fmt::Display for Quit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Quit()")
}
}
#[derive(Clone)]
struct Ask {
content: Question, }
impl Ask {
fn new(content: Question) -> Self {
Ask { content }
}
}
impl Type for Ask {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
self.content.typecheck(context)
}
}
impl fmt::Display for Ask {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Ask('{}')", self.content)
}
}
#[derive(Clone)]
struct Answer {
content: Ans, }
impl Answer {
fn new(content: Ans) -> Self {
Answer { content }
}
}
impl Type for Answer {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
self.content.typecheck(context)
}
}
impl fmt::Display for Answer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Answer({})", self.content)
}
}
#[derive(Clone)]
struct ICM {
level: String, polarity: String, icm_content: Option<String>, }
impl ICM {
fn new(level: &str, polarity: &str, icm_content: Option<String>) -> Self {
ICM {
level: level.to_string(),
polarity: polarity.to_string(),
icm_content,
}
}
}
impl Type for ICM {
fn typecheck(&self, _context: &Domain) -> Result<(), String> {
Ok(())
}
}
impl fmt::Display for ICM {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = format!("icm:{}*{}", self.level, self.polarity);
if let Some(content) = &self.icm_content {
s.push_str(&format!(":'{}'", content));
}
write!(f, "{}", s)
}
}
#[derive(Clone)]
struct Respond {
content: Question, }
impl Respond {
fn new(content: Question) -> Self {
Respond { content }
}
}
impl Type for Respond {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
self.content.typecheck(context)
}
}
impl fmt::Display for Respond {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Respond('{}')", self.content)
}
}
#[derive(Clone)]
pub struct ConsultDB {
content: Question, }
impl ConsultDB {
pub fn new(content: Question) -> Self {
ConsultDB { content }
}
}
impl Type for ConsultDB {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
self.content.typecheck(context)
}
}
impl fmt::Display for ConsultDB {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ConsultDB('{}')", self.content)
}
}
#[derive(Clone)]
pub struct Findout {
content: Question, }
impl Findout {
pub fn new(content: Question) -> Self {
Findout { content }
}
}
impl Type for Findout {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
self.content.typecheck(context)
}
}
impl fmt::Display for Findout {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Findout('{}')", self.content)
}
}
#[derive(Clone)]
struct Raise {
content: Question, }
impl Raise {
fn new(content: Question) -> Self {
Raise { content }
}
}
impl Type for Raise {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
self.content.typecheck(context)
}
}
impl fmt::Display for Raise {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Raise('{}')", self.content)
}
}
#[derive(Clone)]
pub struct If {
cond: Question, iftrue: Vec<String>, iffalse: Vec<String>, }
impl If {
pub fn new(cond: Question, iftrue: Vec<String>, iffalse: Vec<String>) -> Self {
If { cond, iftrue, iffalse }
}
}
impl Type for If {
fn typecheck(&self, context: &Domain) -> Result<(), String> {
self.cond.typecheck(context)?;
Ok(())
}
}
impl fmt::Display for If {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let iftrue_str: Vec<String> = self.iftrue.clone();
let iffalse_str: Vec<String> = self.iffalse.clone();
write!(f, "If('{}', {}, {})", self.cond, iftrue_str.join(", "), iffalse_str.join(", "))
}
}
pub trait PlanConstructor: Type + fmt::Display + Clone {}
impl PlanConstructor for Respond {}
impl PlanConstructor for ConsultDB {}
impl PlanConstructor for Findout {}
impl PlanConstructor for Raise {}
impl PlanConstructor for If {}
trait DialogueManager {
fn trace(&self, message: &str) {
println!("{{{}}}", message);
}
fn run(&mut self) {
self.reset();
self.control();
}
fn reset(&mut self);
fn control(&mut self);
fn print_state(&self);
}
struct StandardMIVS {
input: Value<String>, latest_speaker: Value<Speaker>, latest_moves: TSet<String>, next_moves: Stack<String>, output: Value<String>, program_state: Value<ProgramState>, }
impl StandardMIVS {
fn init_mivs(&mut self) {
self.input = Value::new_type(|_: &String| true);
self.latest_speaker = Value::new_allowed(HashSet::from([Speaker::USR, Speaker::SYS]));
self.latest_moves = TSet::new();
self.next_moves = Stack::new();
self.output = Value::new_type(|_: &String| true);
self.program_state = Value::new_allowed(HashSet::from([ProgramState::RUN, ProgramState::QUIT]));
self.program_state.set(ProgramState::RUN).unwrap();
}
fn print_mivs(&self, prefix: &str) {
println!("{}INPUT: {}", prefix, self.input);
println!("{}LATEST_SPEAKER: {}", prefix, self.latest_speaker);
println!("{}LATEST_MOVES: {}", prefix, self.latest_moves);
println!("{}NEXT_MOVES: {}", prefix, self.next_moves);
println!("{}OUTPUT: {}", prefix, self.output);
println!("{}PROGRAM_STATE: {}", prefix, self.program_state);
}
}
trait Grammar {
fn generate(&self, moves: &TSet<String>) -> String;
fn interpret(&self, input: &str) -> Option<TSet<String>>;
}
pub struct SimpleGenGrammar {
forms: HashMap<String, String>, }
impl SimpleGenGrammar {
pub fn new() -> Self {
let mut grammar = SimpleGenGrammar {
forms: HashMap::new(),
};
grammar.add_form("Greet()", "Hello");
grammar.add_form("icm:neg*sem", "I don't understand");
grammar
}
pub fn add_form(&mut self, move_str: &str, output: &str) {
self.forms.insert(move_str.to_string(), output.to_string());
}
fn generate_move(&self, move_str: &str) -> String {
self.forms.get(move_str).cloned().unwrap_or_else(|| move_str.to_string())
}
fn join_phrases(&self, phrases: &[String]) -> String {
let mut result = String::new();
for p in phrases {
if !result.is_empty() {
result.push(' ');
}
result.push_str(p);
if !p.ends_with('.') && !p.ends_with('?') && !p.ends_with('!') {
result.push('.');
}
}
result
}
}
impl Grammar for SimpleGenGrammar {
fn generate(&self, moves: &TSet<String>) -> String {
let phrases: Vec<String> = moves.elements.iter().map(|m| self.generate_move(m)).collect();
self.join_phrases(&phrases)
}
fn interpret(&self, input: &str) -> Option<TSet<String>> {
let mut moves = TSet::new();
if input == "quit" || input == "exit" {
moves.add("Quit()".to_string()).ok();
}
else if let Ok(_question) = Question::new(input) {
moves.add(format!("Ask('{}')", input)).ok();
}
else if let Ok(_answer) = Ans::new(input) {
moves.add(format!("Answer({})", input)).ok();
}
else {
return None;
}
Some(moves)
}
}
#[derive(Debug, Clone)]
struct CFGRule {
lhs: String, rhs: Vec<String>, features: HashMap<String, String>, }
struct CFGGrammar {
rules: Vec<CFGRule>,
terminals: HashMap<String, Vec<String>>, }
impl CFGGrammar {
fn new() -> Self {
CFGGrammar {
rules: Vec::new(),
terminals: HashMap::new(),
}
}
fn load_from_file(&mut self, _filepath: &str) -> Result<(), Box<dyn std::error::Error>> {
self.rules.push(CFGRule {
lhs: "USR[sem=?s]".to_string(),
rhs: vec!["ANSWER[sem=?s]".to_string(), "ASK[sem=?s]".to_string()],
features: HashMap::from([("sem".to_string(), "?s".to_string())]),
});
self.terminals.insert("price".to_string(), vec!["WHQ[q=price]".to_string()]);
self.terminals.insert("plane".to_string(), vec!["CAT[cat=how, ind=plane]".to_string()]);
self.terminals.insert("train".to_string(), vec!["CAT[cat=how, ind=train]".to_string()]);
Ok(())
}
fn parse(&self, input: &str) -> Option<String> {
if let Some(categories) = self.terminals.get(input) {
return categories.first().cloned();
}
None
}
}
trait Database {
fn consult_db(&self, question: &Question, context: &TSet<Prop>) -> Prop;
}
pub struct TravelDB {
entries: Vec<HashMap<String, String>>, }
impl TravelDB {
pub fn new() -> Self {
TravelDB { entries: Vec::new() }
}
pub fn add_entry(&mut self, entry: HashMap<String, String>) {
self.entries.push(entry);
}
fn get_context(&self, context: &TSet<Prop>, pred: &str) -> Option<String> {
for prop in &context.elements {
if prop.pred.0.content == pred {
return prop.ind.as_ref().map(|ind| ind.0.content.clone());
}
}
None
}
fn lookup_entry(&self, depart_city: &str, dest_city: &str, day: &str) -> Option<&HashMap<String, String>> {
for entry in &self.entries {
if entry.get("from") == Some(&depart_city.to_string())
&& entry.get("to") == Some(&dest_city.to_string())
&& entry.get("day") == Some(&day.to_string())
{
return Some(entry);
}
}
None
}
}
impl Database for TravelDB {
fn consult_db(&self, question: &Question, context: &TSet<Prop>) -> Prop {
let depart_city = self.get_context(context, "depart_city").unwrap_or_default();
let dest_city = self.get_context(context, "dest_city").unwrap_or_default();
let day = self.get_context(context, "depart_day").unwrap_or_default();
let entry = self.lookup_entry(&depart_city, &dest_city, &day).expect("Entry not found");
let price = entry.get("price").expect("Price not found");
Prop {
pred: Pred0::new("price").unwrap(),
ind: Some(Ind::new(price).unwrap()),
yes: true,
}
}
}
pub struct Domain {
preds0: HashSet<String>, preds1: HashMap<String, String>, sorts: HashMap<String, HashSet<String>>, inds: HashMap<String, String>, plans: HashMap<String, Vec<String>>, }
impl Domain {
pub fn new(
preds0: HashSet<String>,
preds1: HashMap<String, String>,
sorts: HashMap<String, HashSet<String>>,
) -> Self {
let inds = sorts
.iter()
.flat_map(|(sort, inds)| inds.iter().map(move |ind| (ind.clone(), sort.clone())))
.collect();
Domain {
preds0,
preds1,
sorts,
inds,
plans: HashMap::new(),
}
}
pub fn add_plan(&mut self, trigger: Question, plan: Vec<String>) {
self.plans.insert(trigger.to_string(), plan);
}
fn relevant(&self, answer: &Ans, question: &Question) -> bool {
match (answer, question) {
(Ans::Prop(prop), Question::WhQ(whq)) => prop.pred.0.content == whq.pred.0.content,
(Ans::ShortAns(short), Question::WhQ(whq)) => {
let sort1 = self.inds.get(&short.ind.0.content);
let sort2 = self.preds1.get(&whq.pred.0.content);
sort1.is_some() && sort2.is_some() && sort1 == sort2
}
(Ans::YesNo(_), Question::YNQ(_)) => true,
(Ans::Prop(prop), Question::YNQ(ynq)) => prop == &ynq.prop,
(Ans::Prop(prop), Question::AltQ(altq)) => {
altq.ynqs.iter().any(|ynq| prop == &ynq.prop)
}
(Ans::YesNo(_), Question::AltQ(_)) => true,
_ => false,
}
}
fn resolves(&self, answer: &Ans, question: &Question) -> bool {
if self.relevant(answer, question) {
match (answer, question) {
(Ans::YesNo(_), Question::YNQ(_)) => true,
(Ans::ShortAns(short), Question::WhQ(_)) => short.yes,
(Ans::Prop(prop), Question::WhQ(_)) => prop.yes,
_ => false,
}
} else {
false
}
}
fn combine(&self, question: &Question, answer: &Ans) -> Result<Prop, Box<dyn std::error::Error>> {
assert!(self.relevant(answer, question));
match (question, answer) {
(Question::WhQ(whq), Ans::ShortAns(short)) => {
let mut prop = whq.pred.apply(&short.ind)?;
if !short.yes {
prop.yes = false;
}
Ok(prop)
}
(Question::YNQ(ynq), Ans::YesNo(yesno)) => {
let mut prop = ynq.prop.clone();
if prop.yes != yesno.yes {
prop.yes = !prop.yes;
}
Ok(prop)
}
_ => match answer {
Ans::Prop(p) => Ok(p.clone()),
_ => panic!("Invalid combination"),
},
}
}
fn get_plan(&self, question: &Question) -> Option<Stack<String>> {
self.plans.get(&question.to_string()).map(|plan| {
let mut stack = Stack::new();
for construct in plan.iter().rev() {
stack.push(construct.clone()).unwrap();
}
stack
})
}
}
struct IBISInfostate {
is: Record, }
impl IBISInfostate {
fn init_is(&mut self) {
let mut fields = HashMap::new();
fields.insert("agenda".to_string(), Box::new(Stack::<String>::new()) as Box<dyn Any>);
fields.insert("plan".to_string(), Box::new(Stack::<String>::new()) as Box<dyn Any>);
fields.insert("bel".to_string(), Box::new(TSet::<String>::new()) as Box<dyn Any>);
fields.insert("com".to_string(), Box::new(TSet::<String>::new()) as Box<dyn Any>);
fields.insert("qud".to_string(), Box::new(StackSet::<String>::new()) as Box<dyn Any>);
self.is = Record::new(fields);
}
fn print_is(&self, prefix: &str) {
println!("{}", self.is.pformat(prefix, " "));
}
}
pub struct IBISController {
is: IBISInfostate, mivs: StandardMIVS, domain: Domain, database: TravelDB, grammar: SimpleGenGrammar, input_handler: Box<dyn InputHandler>, }
impl IBISController {
pub fn new(domain: Domain, database: TravelDB, grammar: SimpleGenGrammar) -> Self {
Self::with_input_handler(domain, database, grammar, Box::new(StandardInputHandler))
}
pub fn with_input_handler(domain: Domain, database: TravelDB, grammar: SimpleGenGrammar, input_handler: Box<dyn InputHandler>) -> Self {
IBISController {
is: IBISInfostate { is: Record::new(HashMap::new()) },
mivs: StandardMIVS {
input: Value::new_type(|_: &String| true),
latest_speaker: Value::new_allowed(HashSet::from([Speaker::USR, Speaker::SYS])),
latest_moves: TSet::new(),
next_moves: Stack::new(),
output: Value::new_type(|_: &String| true),
program_state: Value::new_allowed(HashSet::from([ProgramState::RUN, ProgramState::QUIT])),
},
domain,
database,
grammar,
input_handler,
}
}
fn select(&mut self) {
}
fn generate(&mut self) {
let mut moves_set = TSet::new();
for element in &self.mivs.next_moves.elements {
moves_set.add(element.clone()).ok();
}
let output = self.grammar.generate(&moves_set);
self.mivs.output.set(output).unwrap();
}
fn output(&mut self) {
println!("S> {}", self.mivs.output.get().unwrap_or(&"[---]".to_string()));
println!();
self.mivs.latest_speaker.set(Speaker::SYS).unwrap();
self.mivs.latest_moves.clear();
for element in &self.mivs.next_moves.elements {
self.mivs.latest_moves.add(element.clone()).ok();
}
self.mivs.next_moves.clear();
}
fn input(&mut self) {
if let Some(input) = self.input_handler.read_line() {
self.mivs.input.set(input).unwrap();
self.mivs.latest_speaker.set(Speaker::USR).unwrap();
} else {
self.mivs.program_state.set(ProgramState::QUIT).unwrap();
}
}
fn interpret(&mut self) {
self.mivs.latest_moves.clear();
if let Some(input) = self.mivs.input.get() {
if !input.is_empty() {
if let Some(moves) = self.grammar.interpret(input) {
for move_str in &moves.elements {
self.mivs.latest_moves.add(move_str.clone()).ok();
}
} else {
println!("Did not understand: {}", input);
}
}
}
}
fn update(&mut self) {
}
}
impl DialogueManager for IBISController {
fn reset(&mut self) {
self.is.init_is();
self.mivs.init_mivs();
}
fn control(&mut self) {
self.mivs.next_moves.push("Greet()".to_string()).unwrap();
self.print_state();
while self.mivs.program_state.get() != Some(&ProgramState::QUIT) {
self.select();
if !self.mivs.next_moves.elements.is_empty() {
self.generate();
self.output();
self.update();
self.print_state();
}
self.input();
self.interpret();
self.update();
self.print_state();
}
}
fn print_state(&self) {
println!("+------------------------ - - -");
self.mivs.print_mivs("| ");
println!("|");
self.is.print_is("| ");
println!("+------------------------ - - -");
println!();
}
}
impl IBISController {
pub fn run(&mut self) {
<Self as DialogueManager>::run(self);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_value_creation_and_operations() {
let allowed_values = HashSet::from(["a".to_string(), "b".to_string(), "c".to_string()]);
let mut value = Value::new_allowed(allowed_values);
assert!(value.set("a".to_string()).is_ok());
assert_eq!(value.get(), Some(&"a".to_string()));
assert!(value.set("d".to_string()).is_err());
value.clear();
assert_eq!(value.get(), None);
}
#[test]
fn test_value_with_type_constraint() {
let mut value = Value::new_type(|s: &String| s.len() > 2);
assert!(value.set("hello".to_string()).is_ok());
assert!(value.set("hi".to_string()).is_err());
assert_eq!(value.get(), Some(&"hello".to_string()));
}
#[test]
fn test_stack_operations() {
let mut stack = Stack::new();
assert!(stack.top().is_err());
assert!(stack.pop().is_err());
assert_eq!(stack.len(), 0);
assert!(stack.push("item1".to_string()).is_ok());
assert!(stack.push("item2".to_string()).is_ok());
assert_eq!(stack.len(), 2);
assert_eq!(stack.top().unwrap(), &"item2".to_string());
assert_eq!(stack.pop().unwrap(), "item2".to_string());
assert_eq!(stack.len(), 1);
stack.clear();
assert_eq!(stack.len(), 0);
}
#[test]
fn test_stack_with_type_constraint() {
let mut stack = Stack::with_type(|s: &String| s.starts_with("valid_"));
assert!(stack.push("valid_item".to_string()).is_ok());
assert!(stack.push("invalid_item".to_string()).is_err());
assert_eq!(stack.len(), 1);
}
#[test]
fn test_stackset_operations() {
let mut stackset = StackSet::new();
assert!(stackset.push("item1".to_string()).is_ok());
assert!(stackset.push("item2".to_string()).is_ok());
assert!(stackset.push("item1".to_string()).is_ok());
assert!(stackset.contains(&"item1".to_string()));
assert!(stackset.contains(&"item2".to_string()));
assert!(!stackset.contains(&"item3".to_string()));
}
#[test]
fn test_tset_operations() {
let mut tset = TSet::new();
assert!(tset.add("item1".to_string()).is_ok());
assert!(tset.add("item2".to_string()).is_ok());
assert!(tset.add("item1".to_string()).is_ok());
assert_eq!(tset.len(), 2); assert!(tset.contains(&"item1".to_string()));
assert!(tset.contains(&"item2".to_string()));
tset.clear();
assert_eq!(tset.len(), 0);
}
#[test]
fn test_tset_with_type_constraint() {
let mut tset = TSet::with_type(|s: &String| s.len() <= 5);
assert!(tset.add("short".to_string()).is_ok());
assert!(tset.add("toolongstring".to_string()).is_err());
assert_eq!(tset.len(), 1);
}
#[test]
fn test_atomic_creation() {
assert!(Atomic::new("hello").is_ok());
assert!(Atomic::new("test_atom").is_ok());
assert!(Atomic::new("atom-with-dash").is_ok());
assert!(Atomic::new("atom+with+plus").is_ok());
assert!(Atomic::new("atom:with:colon").is_ok());
assert!(Atomic::new("").is_err()); assert!(Atomic::new("yes").is_err()); assert!(Atomic::new("no").is_err()); assert!(Atomic::new("123invalid").is_err()); assert!(Atomic::new("invalid@char").is_err()); }
#[test]
fn test_ind_creation_and_display() {
let ind = Ind::new("paris").unwrap();
assert_eq!(ind.to_string(), "paris");
assert!(Ind::new("").is_err());
assert!(Ind::new("invalid@").is_err());
}
#[test]
fn test_pred0_creation_and_display() {
let pred = Pred0::new("expensive").unwrap();
assert_eq!(pred.to_string(), "expensive");
assert!(Pred0::new("").is_err());
assert!(Pred0::new("yes").is_err());
}
#[test]
fn test_pred1_creation_and_apply() {
let pred1 = Pred1::new("city").unwrap();
let ind = Ind::new("paris").unwrap();
let prop = pred1.apply(&ind).unwrap();
assert_eq!(prop.to_string(), "city(paris)");
assert!(prop.yes);
assert!(Pred1::new("").is_err());
}
#[test]
fn test_sort_creation() {
let sort = Sort::new("city").unwrap();
assert_eq!(sort.to_string(), "city");
assert!(Sort::new("").is_err());
}
#[test]
fn test_prop_creation_and_parsing() {
let prop = Prop::new("expensive").unwrap();
assert_eq!(prop.to_string(), "expensive()");
assert!(prop.yes);
let prop = Prop::new("-expensive").unwrap();
assert_eq!(prop.to_string(), "-expensive()");
assert!(!prop.yes);
let prop = Prop::new("city(paris)").unwrap();
assert_eq!(prop.to_string(), "city(paris)");
assert!(prop.yes);
assert_eq!(prop.ind.as_ref().unwrap().to_string(), "paris");
}
#[test]
fn test_shortans_creation_and_parsing() {
let ans = ShortAns::new("paris").unwrap();
assert_eq!(ans.to_string(), "paris");
assert!(ans.yes);
let ans = ShortAns::new("-paris").unwrap();
assert_eq!(ans.to_string(), "-paris");
assert!(!ans.yes);
assert!(ShortAns::new("").is_err());
}
#[test]
fn test_yesno_creation() {
let yes_ans = YesNo::new("yes").unwrap();
assert_eq!(yes_ans.to_string(), "yes");
assert!(yes_ans.yes);
let no_ans = YesNo::new("no").unwrap();
assert_eq!(no_ans.to_string(), "no");
assert!(!no_ans.yes);
assert!(YesNo::new("maybe").is_err());
}
#[test]
fn test_ans_enum_parsing() {
let ans = Ans::new("yes").unwrap();
match ans {
Ans::YesNo(yesno) => assert!(yesno.yes),
_ => panic!("Expected YesNo variant"),
}
let ans = Ans::new("paris").unwrap();
match ans {
Ans::ShortAns(short) => {
assert_eq!(short.to_string(), "paris");
assert!(short.yes);
},
_ => panic!("Expected ShortAns variant"),
}
let ans = Ans::new("city(paris)").unwrap();
match ans {
Ans::Prop(prop) => {
assert_eq!(prop.to_string(), "city(paris)");
assert!(prop.yes);
},
_ => panic!("Expected Prop variant"),
}
assert!(Ans::new("invalid(syntax").is_err());
}
#[test]
fn test_whq_creation_and_parsing() {
let whq = WhQ::new("?x.city(x)").unwrap();
assert_eq!(whq.pred.to_string(), "city");
let whq = WhQ::new("city").unwrap();
assert_eq!(whq.pred.to_string(), "city");
assert!(WhQ::new("").is_err());
}
#[test]
fn test_ynq_creation_and_parsing() {
let ynq = YNQ::new("?expensive").unwrap();
assert_eq!(ynq.prop.to_string(), "expensive()");
let ynq = YNQ::new("expensive").unwrap();
assert_eq!(ynq.prop.to_string(), "expensive()");
assert!(YNQ::new("").is_err());
}
#[test]
fn test_question_enum_parsing() {
let q = Question::new("?x.city(x)").unwrap();
match q {
Question::WhQ(whq) => assert_eq!(whq.pred.to_string(), "city"),
_ => panic!("Expected WhQ variant"),
}
let q = Question::new("?expensive").unwrap();
match q {
Question::YNQ(ynq) => assert_eq!(ynq.prop.pred.to_string(), "expensive"),
_ => panic!("Expected YNQ variant"),
}
assert!(Question::new("invalid").is_err());
}
#[test]
fn test_dialogue_moves() {
let greet = Greet;
assert_eq!(greet.to_string(), "Greet()");
let quit = Quit;
assert_eq!(quit.to_string(), "Quit()");
let question = Question::new("?expensive").unwrap();
let ask = Ask::new(question);
assert!(ask.to_string().contains("Ask"));
assert!(ask.to_string().contains("expensive"));
let answer_content = Ans::new("yes").unwrap();
let answer = Answer::new(answer_content);
assert!(answer.to_string().contains("Answer"));
assert!(answer.to_string().contains("yes"));
}
#[test]
fn test_icm_creation() {
let icm = ICM::new("per", "pos", Some("understood".to_string()));
assert_eq!(icm.to_string(), "icm:per*pos:'understood'");
let icm_no_content = ICM::new("sem", "neg", None);
assert_eq!(icm_no_content.to_string(), "icm:sem*neg");
}
#[test]
fn test_plan_constructors() {
let question = Question::new("?expensive").unwrap();
let respond = Respond::new(question.clone());
assert!(respond.to_string().contains("Respond"));
assert!(respond.to_string().contains("expensive"));
let consult = ConsultDB::new(question.clone());
assert!(consult.to_string().contains("ConsultDB"));
assert!(consult.to_string().contains("expensive"));
let findout = Findout::new(question.clone());
assert!(findout.to_string().contains("Findout"));
assert!(findout.to_string().contains("expensive"));
let raise = Raise::new(question.clone());
assert!(raise.to_string().contains("Raise"));
assert!(raise.to_string().contains("expensive"));
let if_plan = If::new(
question,
vec!["ConsultDB".to_string()],
vec!["Greet".to_string()]
);
assert!(if_plan.to_string().contains("If"));
}
#[test]
fn test_simple_gen_grammar() {
let mut grammar = SimpleGenGrammar::new();
grammar.add_form("Ask('?price')", "What is the price?");
grammar.add_form("Answer(paris)", "The answer is Paris.");
let mut moves = TSet::new();
moves.add("Greet()".to_string()).unwrap();
let output = grammar.generate(&moves);
assert_eq!(output, "Hello.");
let interpreted = grammar.interpret("quit");
assert!(interpreted.is_some());
let moves = interpreted.unwrap();
assert!(moves.elements.iter().any(|m| m.contains("Quit")));
let interpreted = grammar.interpret("?expensive");
assert!(interpreted.is_some());
let moves = interpreted.unwrap();
assert!(moves.elements.iter().any(|m| m.contains("Ask") && m.contains("expensive")));
let interpreted = grammar.interpret("yes");
assert!(interpreted.is_some());
let moves = interpreted.unwrap();
assert!(moves.elements.iter().any(|m| m.contains("Answer") && m.contains("yes")));
let interpreted = grammar.interpret("random gibberish");
assert!(interpreted.is_none());
}
#[test]
fn test_travel_db() {
let mut db = TravelDB::new();
let mut entry1 = HashMap::new();
entry1.insert("from".to_string(), "paris".to_string());
entry1.insert("to".to_string(), "london".to_string());
entry1.insert("day".to_string(), "monday".to_string());
entry1.insert("price".to_string(), "200".to_string());
db.add_entry(entry1);
let mut entry2 = HashMap::new();
entry2.insert("from".to_string(), "london".to_string());
entry2.insert("to".to_string(), "paris".to_string());
entry2.insert("day".to_string(), "tuesday".to_string());
entry2.insert("price".to_string(), "180".to_string());
db.add_entry(entry2);
let result = db.lookup_entry("paris", "london", "monday");
assert!(result.is_some());
assert_eq!(result.unwrap().get("price"), Some(&"200".to_string()));
let no_result = db.lookup_entry("invalid", "route", "never");
assert!(no_result.is_none());
let mut context = TSet::new();
let prop1 = Prop {
pred: Pred0::new("depart_city").unwrap(),
ind: Some(Ind::new("paris").unwrap()),
yes: true,
};
context.add(prop1).unwrap();
let context_value = db.get_context(&context, "depart_city");
assert_eq!(context_value, Some("paris".to_string()));
let no_context = db.get_context(&context, "nonexistent");
assert_eq!(no_context, None);
}
#[test]
fn test_domain_creation_and_operations() {
let preds0 = HashSet::from(["expensive".to_string(), "available".to_string()]);
let mut preds1 = HashMap::new();
preds1.insert("city".to_string(), "location".to_string());
preds1.insert("transport".to_string(), "vehicle".to_string());
let mut sorts = HashMap::new();
sorts.insert("location".to_string(), HashSet::from(["paris".to_string(), "london".to_string()]));
sorts.insert("vehicle".to_string(), HashSet::from(["plane".to_string(), "train".to_string()]));
let mut domain = Domain::new(preds0, preds1, sorts);
assert_eq!(domain.inds.get("paris"), Some(&"location".to_string()));
assert_eq!(domain.inds.get("plane"), Some(&"vehicle".to_string()));
let question = Question::new("?expensive").unwrap();
let plan = vec!["ConsultDB".to_string(), "Respond".to_string()];
domain.add_plan(question.clone(), plan);
let retrieved_plan = domain.get_plan(&question);
assert!(retrieved_plan.is_some());
let plan_stack = retrieved_plan.unwrap();
assert_eq!(plan_stack.len(), 2);
let ans_yes = Ans::new("yes").unwrap();
let ynq = Question::new("?expensive").unwrap();
assert!(domain.relevant(&ans_yes, &ynq));
let ans_paris = Ans::new("paris").unwrap();
let whq = Question::new("?x.city(x)").unwrap();
assert!(domain.relevant(&ans_paris, &whq));
let ans_yes = Ans::new("yes").unwrap();
let ynq = Question::new("?expensive").unwrap();
assert!(domain.resolves(&ans_yes, &ynq));
let ans_no = Ans::new("no").unwrap();
assert!(domain.resolves(&ans_no, &ynq));
let combined = domain.combine(&whq, &ans_paris);
assert!(combined.is_ok());
let prop = combined.unwrap();
assert_eq!(prop.pred.to_string(), "city");
assert_eq!(prop.ind.as_ref().unwrap().to_string(), "paris");
}
#[test]
fn test_speaker_enum() {
let usr = Speaker::new("USR").unwrap();
assert_eq!(usr.to_string(), "USR");
let sys = Speaker::new("SYS").unwrap();
assert_eq!(sys.to_string(), "SYS");
assert!(Speaker::new("INVALID").is_none());
}
#[test]
fn test_program_state_enum() {
let run = ProgramState::new("RUN").unwrap();
assert_eq!(run.to_string(), "RUN");
let quit = ProgramState::new("QUIT").unwrap();
assert_eq!(quit.to_string(), "QUIT");
assert!(ProgramState::new("INVALID").is_none());
}
#[test]
fn test_demo_input_handler() {
let inputs = vec!["hello".to_string(), "?expensive".to_string(), "quit".to_string()];
let mut handler = DemoInputHandler::new(inputs);
assert!(handler.has_input());
assert_eq!(handler.read_line(), Some("hello".to_string()));
assert!(handler.has_input());
assert_eq!(handler.read_line(), Some("?expensive".to_string()));
assert!(handler.has_input());
assert_eq!(handler.read_line(), Some("quit".to_string()));
assert!(!handler.has_input());
assert_eq!(handler.read_line(), None);
}
#[test]
fn test_ibis_controller_creation() {
let preds0 = HashSet::from(["expensive".to_string()]);
let preds1 = HashMap::from([("city".to_string(), "location".to_string())]);
let sorts = HashMap::from([("location".to_string(), HashSet::from(["paris".to_string()]))]);
let domain = Domain::new(preds0, preds1, sorts);
let database = TravelDB::new();
let grammar = SimpleGenGrammar::new();
let inputs = vec!["hello".to_string(), "quit".to_string()];
let input_handler = Box::new(DemoInputHandler::new(inputs));
let controller = IBISController::with_input_handler(domain, database, grammar, input_handler);
assert!(matches!(controller.mivs.program_state.get(), None)); }
}