use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_void};
use std::ptr;
use crate::config::Configuration;
use crate::error::{ClingoError, Error, check};
use crate::fun::{Fun, SymbolicArgs};
use crate::observer::{GroundStatement, ObserverState, make_observer};
use crate::solve::{SolveHandle, solve_yielding};
use crate::symbol::Symbol;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Warning {
OperationUndefined,
RuntimeError,
AtomUndefined,
FileIncluded,
VariableUnbounded,
GlobalVariable,
Other,
}
impl Warning {
fn from_raw(code: clingo_sys::clingo_warning_t) -> Self {
match code as u32 {
clingo_sys::clingo_warning_e_clingo_warning_operation_undefined => {
Warning::OperationUndefined
}
clingo_sys::clingo_warning_e_clingo_warning_runtime_error => Warning::RuntimeError,
clingo_sys::clingo_warning_e_clingo_warning_atom_undefined => Warning::AtomUndefined,
clingo_sys::clingo_warning_e_clingo_warning_file_included => Warning::FileIncluded,
clingo_sys::clingo_warning_e_clingo_warning_variable_unbounded => {
Warning::VariableUnbounded
}
clingo_sys::clingo_warning_e_clingo_warning_global_variable => Warning::GlobalVariable,
_ => Warning::Other,
}
}
}
unsafe extern "C" fn logger_trampoline(
code: clingo_sys::clingo_warning_t,
message: *const c_char,
data: *mut c_void,
) {
let closure = unsafe { &mut *(data as *mut Box<dyn FnMut(Warning, &str)>) };
let warning = Warning::from_raw(code);
let msg = unsafe { CStr::from_ptr(message) }
.to_str()
.unwrap_or("<invalid UTF-8>");
closure(warning, msg);
}
pub struct Control {
pub(crate) ptr: ptr::NonNull<clingo_sys::clingo_control_t>,
#[expect(clippy::type_complexity)]
_logger: Option<Box<Box<dyn FnMut(Warning, &str)>>>,
observer_state: Box<RefCell<Option<ObserverState>>>,
}
impl Drop for Control {
fn drop(&mut self) {
unsafe {
clingo_sys::clingo_control_free(self.ptr.as_ptr());
}
}
}
impl Control {
pub fn new(args: &[&str]) -> Result<Self, Error> {
Self::with_logger(args, None::<fn(Warning, &str)>, 20)
}
pub fn with_logger(
args: &[&str],
logger: Option<impl FnMut(Warning, &str) + 'static>,
message_limit: u32,
) -> Result<Self, Error> {
let c_args: Vec<CString> = args
.iter()
.map(|s| CString::new(*s))
.collect::<Result<_, _>>()?;
let c_ptrs: Vec<*const c_char> = c_args.iter().map(|s| s.as_ptr()).collect();
let (logger_fn, logger_data, boxed_logger) = match logger {
Some(f) => {
#[expect(clippy::type_complexity)]
let mut boxed: Box<Box<dyn FnMut(Warning, &str)>> = Box::new(Box::new(f));
let data = &mut *boxed as *mut Box<dyn FnMut(Warning, &str)> as *mut c_void;
(Some(logger_trampoline as _), data, Some(boxed))
}
None => (None, ptr::null_mut(), None),
};
let mut raw: *mut clingo_sys::clingo_control_t = ptr::null_mut();
check(unsafe {
clingo_sys::clingo_control_new(
c_ptrs.as_ptr(),
c_ptrs.len(),
logger_fn,
logger_data,
message_limit as std::os::raw::c_uint,
&mut raw,
)
})?;
let observer_state = Box::new(RefCell::new(None));
let observer = make_observer();
let ctl_ptr =
ptr::NonNull::new(raw).expect("clingo_control_new returned null without error");
check(unsafe {
clingo_sys::clingo_control_register_observer(
ctl_ptr.as_ptr(),
&observer,
false,
&*observer_state as *const RefCell<Option<ObserverState>> as *mut c_void,
)
})?;
Ok(Control {
ptr: ctl_ptr,
_logger: boxed_logger,
observer_state,
})
}
pub fn add(&mut self, name: &str, parameters: &[&str], program: &str) -> Result<(), Error> {
let c_name = CString::new(name)?;
let c_params: Vec<CString> = parameters
.iter()
.map(|s| CString::new(*s))
.collect::<Result<_, _>>()?;
let c_param_ptrs: Vec<*const c_char> = c_params.iter().map(|s| s.as_ptr()).collect();
let c_program = CString::new(program)?;
check(unsafe {
clingo_sys::clingo_control_add(
self.ptr.as_ptr(),
c_name.as_ptr(),
c_param_ptrs.as_ptr(),
c_param_ptrs.len(),
c_program.as_ptr(),
)
})?;
Ok(())
}
pub fn ground(&mut self, parts: &[(&str, &[Symbol])]) -> Result<(), Error> {
let c_names: Vec<CString> = parts
.iter()
.map(|(name, _)| CString::new(*name))
.collect::<Result<_, _>>()?;
let raw_params: Vec<Vec<clingo_sys::clingo_symbol_t>> = parts
.iter()
.map(|(_, params)| params.iter().map(|s| s.0).collect())
.collect();
let c_parts: Vec<clingo_sys::clingo_part_t> = c_names
.iter()
.zip(raw_params.iter())
.map(|(c_name, params)| clingo_sys::clingo_part_t {
name: c_name.as_ptr(),
params: params.as_ptr(),
size: params.len(),
})
.collect();
check(unsafe {
clingo_sys::clingo_control_ground(
self.ptr.as_ptr(),
c_parts.as_ptr(),
c_parts.len(),
None,
ptr::null_mut(),
)
})?;
Ok(())
}
pub fn ground_base(&mut self) -> Result<(), Error> {
self.ground(&[("base", &[])])
}
pub fn ground_observed(
&mut self,
parts: &[(&str, &[Symbol])],
) -> Result<Vec<GroundStatement>, Error> {
*self.observer_state.borrow_mut() = Some(ObserverState::new());
self.ground(parts)?;
let state = self.observer_state.borrow_mut().take().unwrap();
Ok(state.into_statements())
}
pub fn ground_base_observed(&mut self) -> Result<Vec<GroundStatement>, Error> {
self.ground_observed(&[("base", &[])])
}
pub fn solve_iter(&mut self) -> Result<SolveHandle<'_>, ClingoError> {
solve_yielding(self)
}
pub fn configuration(&mut self) -> Result<Configuration<'_>, ClingoError> {
let mut cfg: *mut clingo_sys::clingo_configuration_t = ptr::null_mut();
check(unsafe { clingo_sys::clingo_control_configuration(self.ptr.as_ptr(), &mut cfg) })?;
let mut root: clingo_sys::clingo_id_t = 0;
check(unsafe { clingo_sys::clingo_configuration_root(cfg, &mut root) })?;
Ok(unsafe { Configuration::new(cfg, root) })
}
fn find_symbolic_atom(
&self,
symbol: Symbol,
) -> Result<
Option<(
*const clingo_sys::clingo_symbolic_atoms_t,
clingo_sys::clingo_symbolic_atom_iterator_t,
)>,
ClingoError,
> {
let mut atoms: *const clingo_sys::clingo_symbolic_atoms_t = ptr::null();
check(unsafe { clingo_sys::clingo_control_symbolic_atoms(self.ptr.as_ptr(), &mut atoms) })?;
let mut iter: clingo_sys::clingo_symbolic_atom_iterator_t = 0;
check(unsafe { clingo_sys::clingo_symbolic_atoms_find(atoms, symbol.raw(), &mut iter) })?;
let mut end: clingo_sys::clingo_symbolic_atom_iterator_t = 0;
check(unsafe { clingo_sys::clingo_symbolic_atoms_end(atoms, &mut end) })?;
let mut equal = false;
check(unsafe {
clingo_sys::clingo_symbolic_atoms_iterator_is_equal_to(atoms, iter, end, &mut equal)
})?;
if equal {
Ok(None)
} else {
Ok(Some((atoms, iter)))
}
}
fn literal_for_symbol(
&self,
symbol: Symbol,
) -> Result<Option<clingo_sys::clingo_literal_t>, ClingoError> {
let Some((atoms, iter)) = self.find_symbolic_atom(symbol)? else {
return Ok(None);
};
let mut literal: clingo_sys::clingo_literal_t = 0;
check(unsafe { clingo_sys::clingo_symbolic_atoms_literal(atoms, iter, &mut literal) })?;
Ok(Some(literal))
}
pub fn is_fact(&self, symbol: Symbol) -> Result<Option<bool>, ClingoError> {
let Some((atoms, iter)) = self.find_symbolic_atom(symbol)? else {
return Ok(None);
};
let mut fact = false;
check(unsafe { clingo_sys::clingo_symbolic_atoms_is_fact(atoms, iter, &mut fact) })?;
Ok(Some(fact))
}
pub fn atoms<Args: SymbolicArgs>(&self, name: &str) -> Result<Vec<(Symbol, Args)>, Error> {
let c_name = CString::new(name)?;
let arity = Args::arity() as u32;
let mut signature: clingo_sys::clingo_signature_t = 0;
check(unsafe {
clingo_sys::clingo_signature_create(c_name.as_ptr(), arity, true, &mut signature)
})?;
let mut atoms_table: *const clingo_sys::clingo_symbolic_atoms_t = ptr::null();
check(unsafe {
clingo_sys::clingo_control_symbolic_atoms(self.ptr.as_ptr(), &mut atoms_table)
})?;
let mut iter: clingo_sys::clingo_symbolic_atom_iterator_t = 0;
check(unsafe {
clingo_sys::clingo_symbolic_atoms_begin(atoms_table, &signature, &mut iter)
})?;
let mut end: clingo_sys::clingo_symbolic_atom_iterator_t = 0;
check(unsafe { clingo_sys::clingo_symbolic_atoms_end(atoms_table, &mut end) })?;
let mut results = Vec::new();
loop {
let mut equal = false;
check(unsafe {
clingo_sys::clingo_symbolic_atoms_iterator_is_equal_to(
atoms_table,
iter,
end,
&mut equal,
)
})?;
if equal {
break;
}
let mut sym: clingo_sys::clingo_symbol_t = 0;
check(unsafe {
clingo_sys::clingo_symbolic_atoms_symbol(atoms_table, iter, &mut sym)
})?;
let symbol = unsafe { Symbol::from_raw(sym) };
let Fun(n, fun) = symbol.as_fun::<Args>().ok_or_else(|| {
Error::TypeMismatch(format!(
"atom {} does not match expected argument types",
symbol.to_string_lossy().unwrap_or_else(|_| "?".into()),
))
})?;
assert_eq!(n, name);
results.push((symbol, fun));
check(unsafe { clingo_sys::clingo_symbolic_atoms_next(atoms_table, iter, &mut iter) })?;
}
Ok(results)
}
pub fn assign_external(&mut self, symbol: Symbol, value: TruthValue) -> Result<bool, Error> {
let Some(literal) = self.literal_for_symbol(symbol)? else {
return Ok(false);
};
check(unsafe {
clingo_sys::clingo_control_assign_external(self.ptr.as_ptr(), literal, value.to_raw())
})?;
Ok(true)
}
pub fn release_external(&mut self, symbol: Symbol) -> Result<bool, Error> {
let Some(literal) = self.literal_for_symbol(symbol)? else {
return Ok(false);
};
check(unsafe { clingo_sys::clingo_control_release_external(self.ptr.as_ptr(), literal) })?;
Ok(true)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TruthValue {
Free,
True,
False,
}
impl TruthValue {
fn to_raw(self) -> clingo_sys::clingo_truth_value_t {
match self {
TruthValue::Free => clingo_sys::clingo_truth_value_e_clingo_truth_value_free as i32,
TruthValue::True => clingo_sys::clingo_truth_value_e_clingo_truth_value_true as i32,
TruthValue::False => clingo_sys::clingo_truth_value_e_clingo_truth_value_false as i32,
}
}
}