1pub extern crate clips_sys;
2#[macro_use]
3extern crate bitflags;
4#[macro_use]
5extern crate derive_more;
6
7use std::borrow::Cow;
8use std::error::Error;
9use std::ffi::{CStr, CString};
10use std::marker;
11use std::path::Path;
12
13pub enum SaveScope {
14 Local = clips_sys::SaveScope_LOCAL_SAVE as isize,
15 Visible = clips_sys::SaveScope_VISIBLE_SAVE as isize,
16}
17
18#[derive(Debug, Display)]
19#[display(fmt = "{}", kind)]
20pub struct ClipsError {
21 pub kind: ClipsErrorKind,
22 source: Option<Box<dyn Error + Send + Sync + 'static>>,
23}
24
25#[derive(Debug, Display)]
26pub enum ClipsErrorKind {
27 #[display(fmt = "Failed to create CLIPS environment")]
28 CreateEnvironmentError,
29 #[display(fmt = "Failed to clear CLIPS environment")]
30 ClearError,
31 #[display(fmt = "Failed to load from {}", "0")]
32 LoadFromStringError(String),
33 #[display(fmt = "Failed to remove User Defined Function")]
34 RemoveUDFError(String),
35 #[display(fmt = "Failed to batch load from file")]
36 BatchStarError(String),
37 #[display(fmt = "Failed to load from {}", "0")]
38 LoadOpenFileError(String),
39 #[display(fmt = "Failed to load from {}", "0")]
40 LoadParsingError(String),
41 #[display(fmt = "Failed to add UDF {}: Min exceeds max", "0")]
42 AddUDFMinExceedsMaxError(String),
43 #[display(fmt = "Failed to add UDF {}: Function name in use", "0")]
44 AddUDFFunctionNameInUseError(String),
45 #[display(fmt = "Failed to add UDF {}: Invalid Argument Type", "0")]
46 AddUDFInvalidArgumentTypeError(String),
47 #[display(fmt = "Failed to add UDF {}: Invalid Return Type", "0")]
48 AddUDFInvalidReturnTypeError(String),
49}
50
51impl Error for ClipsError {
52 fn source(&self) -> Option<&(dyn Error + 'static)> {
53 self
54 .source
55 .as_ref()
56 .map(|boxed| boxed.as_ref() as &(dyn Error + 'static))
57 }
58}
59
60impl From<ClipsErrorKind> for ClipsError {
61 fn from(kind: ClipsErrorKind) -> Self {
62 ClipsError { kind, source: None }
63 }
64}
65
66#[derive(Debug)]
76pub struct Environment {
77 raw: *mut clips_sys::Environment,
78 cleanup: bool,
79}
80
81impl Environment {
82 pub fn from_ptr(raw: *mut clips_sys::Environment) -> Self {
83 Environment {
84 raw,
85 cleanup: false,
86 }
87 }
88
89 pub fn clear(&mut self) -> Result<(), ClipsError> {
90 if unsafe { clips_sys::Clear(self.raw) } {
91 Ok(())
92 } else {
93 Err(ClipsErrorKind::ClearError.into())
94 }
95 }
96
97 pub fn load_from_str(&mut self, string: &str) -> Result<(), ClipsError> {
98 if unsafe { clips_sys::LoadFromString(self.raw, string.as_ptr() as *const i8, string.len() as u64) } {
99 Ok(())
100 } else {
101 Err(ClipsErrorKind::LoadFromStringError(string.to_owned()).into())
102 }
103 }
104
105 pub fn load(&mut self, path: &Path) -> Result<(), ClipsError> {
106 let path_str = CString::new(path.to_str().unwrap()).unwrap();
107 let load_error = unsafe { clips_sys::Load(self.raw, path_str.as_ptr() as *const i8) };
108
109 match load_error {
110 x if x == clips_sys::LoadError_LE_NO_ERROR => Ok(()),
111 x if x == clips_sys::LoadError_LE_OPEN_FILE_ERROR as u32 => {
112 Err(ClipsErrorKind::LoadOpenFileError(path.to_str().unwrap().to_owned()).into())
113 }
114 x if x == clips_sys::LoadError_LE_PARSING_ERROR as u32 => {
115 Err(ClipsErrorKind::LoadParsingError(path.to_str().unwrap().to_owned()).into())
116 }
117 _ => unreachable!(),
118 }
119 }
120
121 pub fn reset(&mut self) {
122 unsafe { clips_sys::Reset(self.raw) };
123 }
124
125 pub fn run(&mut self, limit: i64) -> i64 {
126 unsafe { clips_sys::Run(self.raw, limit) }
127 }
128
129 pub fn instances_iter(&self) -> impl Iterator<Item = Instance> {
130 InstanceIterator {
131 env: self,
132 current: std::ptr::null_mut::<clips_sys::Instance>(),
133 }
134 }
135
136 pub fn command_loop(&mut self) {
137 unsafe { clips_sys::CommandLoop(self.raw) };
138 }
139
140 pub fn route_command(&mut self, command: &str) {
141 let command = CString::new(command).unwrap();
142 unsafe { clips_sys::RouteCommand(self.raw, command.as_ptr() as *const i8, true) };
143 }
144
145 pub fn add_udf(
158 &mut self,
159 name: &str,
160 return_types: Option<Type>,
161 min_args: u16,
162 max_args: u16,
163 arg_types: Vec<Type>,
164 function: &dyn FnMut(&mut Self, &mut UDFContext) -> ClipsValue<'static>,
165 ) -> Result<(), ClipsError>
166where {
167 let c_name = CString::new(name).unwrap();
168
169 let function = Box::new(Box::new(function));
171
172 let arg_types = &CString::new(
173 arg_types
174 .iter()
175 .map(|type_bitflag| -> String { type_bitflag.format() })
176 .collect::<Vec<String>>()
177 .join(";"),
178 )
179 .unwrap();
180
181 let error = unsafe {
182 clips_sys::AddUDF(
183 self.raw, c_name.as_ptr() as *const i8, match return_types {
186 Some(return_types) => return_types.format().as_ptr() as *const i8,
187 None => std::ptr::null(),
188 }, min_args, max_args, arg_types.as_ptr(), Some(udf_handler), c_name.as_ptr() as *const i8, Box::into_raw(function) as *mut _, )
196 };
197
198 match error {
199 clips_sys::AddUDFError_AUE_NO_ERROR => Ok(()),
200 clips_sys::AddUDFError_AUE_MIN_EXCEEDS_MAX_ERROR => {
201 Err(ClipsErrorKind::AddUDFMinExceedsMaxError(name.to_owned()).into())
202 }
203 clips_sys::AddUDFError_AUE_FUNCTION_NAME_IN_USE_ERROR => {
204 Err(ClipsErrorKind::AddUDFFunctionNameInUseError(name.to_owned()).into())
205 }
206 clips_sys::AddUDFError_AUE_INVALID_ARGUMENT_TYPE_ERROR => {
207 Err(ClipsErrorKind::AddUDFInvalidArgumentTypeError(name.to_owned()).into())
208 }
209 clips_sys::AddUDFError_AUE_INVALID_RETURN_TYPE_ERROR => {
210 Err(ClipsErrorKind::AddUDFInvalidReturnTypeError(name.to_owned()).into())
211 }
212 _ => unreachable!(),
213 }
214 }
215
216 pub fn remove_udf(&mut self, name: &str) -> Result<(), ClipsError> {
217 let c_name = CString::new(name).unwrap();
218 if unsafe { clips_sys::RemoveUDF(self.raw, c_name.as_ptr() as *const i8) } {
219 Ok(())
220 } else {
221 Err(ClipsErrorKind::RemoveUDFError(name.to_owned()).into())
222 }
223 }
224
225 fn void_constant(&self) -> *mut clips_sys::CLIPSVoid {
226 unsafe { (*self.raw).VoidConstant }
227 }
228
229 pub fn save_instances(&mut self, filename: &str, scope: SaveScope) -> i64 {
230 let filename = CString::new(filename).unwrap();
231 unsafe { clips_sys::SaveInstances(self.raw, filename.as_ptr() as *const i8, scope as u32) }
232 }
233
234 pub fn batch_star(&mut self, filename: &str) -> Result<(), ClipsError> {
235 let c_filename = CString::new(filename).unwrap();
236 if unsafe { clips_sys::BatchStar(self.raw, c_filename.as_ptr() as *const i8) } {
237 Ok(())
238 } else {
239 Err(ClipsErrorKind::BatchStarError(filename.to_owned()).into())
240 }
241 }
242}
243
244extern "C" fn udf_handler(
246 raw_environment: *mut clips_sys::Environment,
247 raw_context: *mut clips_sys::UDFContext,
248 return_value: *mut clips_sys::UDFValue,
249) {
250 let closure = unsafe {
251 &mut *(raw_context.as_ref().unwrap().context
252 as *mut Box<Box<dyn FnMut(&mut Environment, &mut UDFContext) -> ClipsValue<'static>>>)
254 };
255 let mut environment = Environment::from_ptr(raw_environment);
256 let mut context = UDFContext {
257 raw: raw_context,
258 _marker: marker::PhantomData,
259 };
260
261 let rust_return_value = closure(&mut environment, &mut context);
262 unsafe { *return_value }.set_from((&environment, rust_return_value));
264}
265
266pub struct ArgumentIterator<'env> {
267 context: &'env UDFContext<'env>,
268}
269
270impl<'env> ArgumentIterator<'env> {
271 pub fn new(context: &'env UDFContext) -> Self {
272 ArgumentIterator { context }
273 }
274}
275
276impl<'env> Iterator for ArgumentIterator<'env> {
277 type Item = ClipsValue<'env>;
278
279 fn next(&mut self) -> Option<Self::Item> {
280 let mut out_value: clips_sys::UDFValue = Default::default();
282
283 if self.context.has_next_argument() {
284 unsafe {
285 clips_sys::UDFNextArgument(self.context.raw, Type::all().bits(), &mut out_value);
287 }
288
289 return Some(out_value.into());
291 }
292
293 None
294 }
295}
296
297#[derive(Debug)]
298pub struct UDFContext<'env> {
299 raw: *mut clips_sys::UDFContext,
300 _marker: marker::PhantomData<&'env Environment>,
301}
302
303impl<'env> UDFContext<'env> {
304 pub fn argument_iter(&'env self) -> ArgumentIterator<'env> {
305 ArgumentIterator::new(self)
306 }
307
308 pub fn has_next_argument(&'env self) -> bool {
309 unsafe { !(*self.raw).lastArg.is_null() }
310 }
311}
312
313#[derive(Debug)]
314pub struct ExternalAddress;
315
316#[derive(Debug)]
317pub enum ClipsValue<'env> {
318 Symbol(Cow<'env, str>),
319 Lexeme(Cow<'env, str>),
320 Float(f64),
321 Integer(i64),
322 Void(),
323 Multifield(Vec<ClipsValue<'env>>),
324 Fact(Fact<'env>),
325 InstanceName(Cow<'env, str>),
326 Instance(Instance<'env>),
327 ExternalAddress(ExternalAddress),
328}
329
330impl<'env> From<clips_sys::clipsValue> for ClipsValue<'env> {
331 fn from(clips_value: clips_sys::clipsValue) -> Self {
332 match u32::from(unsafe { (*clips_value.__bindgen_anon_1.header).type_ }) {
333 clips_sys::FLOAT_TYPE => {
334 let value = unsafe { (*clips_value.__bindgen_anon_1.floatValue).contents };
335 ClipsValue::Float(value)
336 }
337 clips_sys::INTEGER_TYPE => {
338 let value = unsafe { (*clips_value.__bindgen_anon_1.integerValue).contents };
339 ClipsValue::Integer(value)
340 }
341 clips_sys::SYMBOL_TYPE => {
342 let value = unsafe { CStr::from_ptr((*clips_value.__bindgen_anon_1.lexemeValue).contents) }
343 .to_string_lossy();
344 ClipsValue::Symbol(value)
345 }
346 clips_sys::STRING_TYPE => {
347 let value = unsafe { CStr::from_ptr((*clips_value.__bindgen_anon_1.lexemeValue).contents) }
348 .to_string_lossy();
349 ClipsValue::Lexeme(value)
350 }
351 clips_sys::MULTIFIELD_TYPE => ClipsValue::Multifield(
352 (0..unsafe { (*clips_value.__bindgen_anon_1.multifieldValue).length })
353 .map(|index| {
354 unsafe {
355 *(*clips_value.__bindgen_anon_1.multifieldValue)
356 .contents
357 .get_unchecked(index as usize)
358 }
359 .into()
360 })
361 .collect::<Vec<_>>(),
362 ),
363 clips_sys::EXTERNAL_ADDRESS_TYPE => unimplemented!("external address"),
364 clips_sys::FACT_ADDRESS_TYPE => unimplemented!("fact address"),
365 clips_sys::INSTANCE_ADDRESS_TYPE => unimplemented!("instance address"),
366 clips_sys::INSTANCE_NAME_TYPE => {
367 let value = unsafe { CStr::from_ptr((*clips_value.__bindgen_anon_1.lexemeValue).contents) }
368 .to_string_lossy();
369 ClipsValue::InstanceName(value)
370 }
371 clips_sys::VOID_TYPE => ClipsValue::Void(),
372 _ => {
373 println!(
374 "{:?}",
375 u32::from(unsafe { (*clips_value.__bindgen_anon_1.header).type_ })
376 );
377 panic!()
378 }
379 }
380 }
381}
382
383impl<'env> From<clips_sys::UDFValue> for ClipsValue<'env> {
384 fn from(udf_value: clips_sys::UDFValue) -> Self {
385 let union = udf_value.__bindgen_anon_1;
386
387 match u32::from(unsafe { (*udf_value.__bindgen_anon_1.header).type_ }) {
388 clips_sys::FLOAT_TYPE => {
389 let value = unsafe { (*union.floatValue).contents };
390 ClipsValue::Float(value)
391 }
392 clips_sys::INTEGER_TYPE => {
393 let value = unsafe { (*union.integerValue).contents };
394 ClipsValue::Integer(value)
395 }
396 clips_sys::SYMBOL_TYPE => {
397 let value = unsafe { CStr::from_ptr((*union.lexemeValue).contents) }.to_string_lossy();
398 ClipsValue::Symbol(value)
399 }
400 clips_sys::STRING_TYPE => {
401 let value = unsafe { CStr::from_ptr((*union.lexemeValue).contents) }.to_string_lossy();
402 ClipsValue::Lexeme(value)
403 }
404 clips_sys::MULTIFIELD_TYPE => {
405 let multifield = unsafe { *union.multifieldValue };
406 ClipsValue::Multifield(
407 (0..multifield.length)
408 .map(|index| unsafe { *multifield.contents.get_unchecked(index as usize) }.into())
409 .collect::<Vec<_>>(),
410 )
411 }
412 clips_sys::EXTERNAL_ADDRESS_TYPE => unimplemented!("external address"),
413 clips_sys::FACT_ADDRESS_TYPE => unimplemented!("fact address"),
414 clips_sys::INSTANCE_ADDRESS_TYPE => unimplemented!("instance address"),
415 clips_sys::INSTANCE_NAME_TYPE => {
416 let value = unsafe { CStr::from_ptr((*union.lexemeValue).contents) }.to_string_lossy();
417 ClipsValue::InstanceName(value)
418 }
419 clips_sys::VOID_TYPE => ClipsValue::Void(),
420 _ => panic!(),
421 }
422 }
423}
424
425trait SetFrom<T> {
426 fn set_from(&mut self, T);
427}
428
429impl<'env> SetFrom<(&'env Environment, ClipsValue<'env>)> for clips_sys::UDFValue {
430 fn set_from(&mut self, (env, clips_value): (&'env Environment, ClipsValue<'env>)) {
431 match clips_value {
432 ClipsValue::Symbol(_symbol) => unimplemented!("Symbol"),
433 ClipsValue::Lexeme(_lexeme) => unimplemented!("Lexeme"),
434 ClipsValue::Float(_float) => unimplemented!("Float"),
435 ClipsValue::Integer(_integer) => unimplemented!("Integer"),
436 ClipsValue::Void() => self.__bindgen_anon_1.voidValue = env.void_constant(),
437 ClipsValue::Multifield(_values) => unimplemented!("Multifield"),
438 ClipsValue::Fact(_fact) => unimplemented!("Fact"),
439 ClipsValue::Instance(_instance) => unimplemented!("Instance"),
440 ClipsValue::InstanceName(_instance) => unimplemented!("Instance"),
441 ClipsValue::ExternalAddress(_address) => unimplemented!("ExternalAddress"),
442 }
443 }
444}
445
446bitflags! {
447 pub struct Type: u32 {
448 const FLOAT = clips_sys::CLIPSType_FLOAT_BIT as u32;
449 const INTEGER = clips_sys::CLIPSType_INTEGER_BIT as u32;
450 const SYMBOL = clips_sys::CLIPSType_SYMBOL_BIT as u32;
451 const STRING = clips_sys::CLIPSType_STRING_BIT as u32;
452 const MULTIFIELD = clips_sys::CLIPSType_MULTIFIELD_BIT as u32;
453 const EXTERNAL_ADDRESS = clips_sys::CLIPSType_EXTERNAL_ADDRESS_BIT as u32;
454 const FACT_ADDRESS = clips_sys::CLIPSType_FACT_ADDRESS_BIT as u32;
455 const INSTANCE_ADDRESS = clips_sys::CLIPSType_INSTANCE_ADDRESS_BIT as u32;
456 const INSTANCE_NAME = clips_sys::CLIPSType_INSTANCE_NAME_BIT as u32;
457 const VOID = clips_sys::CLIPSType_VOID_BIT as u32;
458 const BOOLEAN = clips_sys::CLIPSType_BOOLEAN_BIT as u32;
459 const ANY = 0b0;
460 }
461}
462
463impl Type {
464 fn format(self) -> String {
465 if self.is_empty() || self.contains(Self::ANY) {
466 return "*".to_owned();
467 }
468
469 let mut result = String::with_capacity(12);
470
471 if self.contains(Self::BOOLEAN) {
472 result.push('b')
473 }
474 if self.contains(Self::FLOAT) {
475 result.push('d')
476 }
477 if self.contains(Self::EXTERNAL_ADDRESS) {
478 result.push('e')
479 }
480 if self.contains(Self::FACT_ADDRESS) {
481 result.push('f')
482 }
483 if self.contains(Self::INSTANCE_ADDRESS) {
484 result.push('i')
485 }
486 if self.contains(Self::INTEGER) {
487 result.push('l')
488 }
489 if self.contains(Self::MULTIFIELD) {
490 result.push('m')
491 }
492 if self.contains(Self::INSTANCE_NAME) {
493 result.push('n')
494 }
495 if self.contains(Self::STRING) {
496 result.push('s')
497 }
498 if self.contains(Self::SYMBOL) {
499 result.push('y')
500 }
501 if self.contains(Self::VOID) {
502 result.push('v')
503 }
504 result.shrink_to_fit();
505 result
506 }
507}
508
509pub struct InstanceIterator<'env> {
510 env: &'env Environment,
511 current: *mut clips_sys::Instance,
512}
513
514impl<'env> Iterator for InstanceIterator<'env> {
515 type Item = Instance<'env>;
516
517 fn next(&mut self) -> Option<Self::Item> {
518 self.current = unsafe { clips_sys::GetNextInstance(self.env.raw, self.current) };
519
520 if self.current.is_null() {
521 return None;
522 };
523
524 Some(Instance {
525 raw: self.current,
526 _marker: marker::PhantomData,
527 })
528 }
529}
530
531#[derive(Debug)]
532pub struct Fact<'env> {
533 raw: *const clips_sys::Fact,
534 _marker: marker::PhantomData<&'env Environment>,
535}
536
537#[derive(Debug)]
538pub struct Instance<'env> {
539 raw: *mut clips_sys::Instance,
540 _marker: marker::PhantomData<&'env Environment>,
541}
542
543impl<'env> Instance<'env> {
544 pub fn name(&'env self) -> &'env str {
545 unsafe {
546 CStr::from_ptr(clips_sys::InstanceName(self.raw))
547 .to_str()
548 .unwrap()
549 }
550 }
551
552 pub fn class_name(&'env self) -> &'env str {
553 unsafe {
554 CStr::from_ptr(clips_sys::DefclassName(clips_sys::InstanceClass(self.raw)))
555 .to_str()
556 .unwrap()
557 }
558 }
559
560 fn slot_names_c(&'env self) -> Vec<*const i8> {
561 let inherit = true;
563
564 let mut slot_list: clips_sys::clipsValue = Default::default();
565 unsafe {
566 let class = clips_sys::InstanceClass(self.raw);
567 clips_sys::ClassSlots(class, &mut slot_list as *mut clips_sys::clipsValue, inherit)
568 };
569
570 let num_slots = unsafe { *slot_list.__bindgen_anon_1.multifieldValue }.length;
571
572 (0..num_slots)
573 .map(|index| unsafe {
574 (*(*slot_list.__bindgen_anon_1.multifieldValue)
575 .contents
576 .get_unchecked(index as usize)
577 .__bindgen_anon_1
578 .lexemeValue)
579 .contents
580 })
581 .collect::<Vec<_>>()
582 }
583
584 pub fn slot_names(&'env self) -> Vec<Cow<'env, str>> {
585 self
586 .slot_names_c()
587 .iter()
588 .map(|cstr| unsafe { CStr::from_ptr(*cstr) }.to_string_lossy())
589 .collect::<Vec<_>>()
590 }
591
592 pub fn slots(&'env self) -> Vec<InstanceSlot<'env>> {
593 let value: *mut clips_sys::clipsValue = &mut Default::default();
594
595 self
596 .slot_names_c()
597 .iter()
598 .map(|slot_name| {
599 unsafe { clips_sys::DirectGetSlot(self.raw, *slot_name, value) };
600
601 InstanceSlot {
602 name: unsafe { CStr::from_ptr(*slot_name) }.to_string_lossy(),
603 value: unsafe { *value }.into(),
604 }
605 })
606 .map(|slot| slot)
607 .collect::<Vec<_>>()
608 }
609}
610
611#[derive(Debug)]
612pub struct InstanceSlot<'env> {
613 pub name: Cow<'env, str>,
614 pub value: ClipsValue<'env>,
615}
616
617pub fn create_environment() -> Result<Environment, ClipsError> {
618 unsafe { clips_sys::CreateEnvironment().as_mut() }
619 .ok_or_else(|| ClipsErrorKind::CreateEnvironmentError.into())
620 .map(|environment_data| Environment {
621 raw: environment_data,
622 cleanup: true,
623 })
624}
625
626impl Drop for Environment {
627 fn drop(&mut self) {
628 if self.cleanup {
629 unsafe { clips_sys::DestroyEnvironment(self.raw) };
630 }
631 }
632}