clips/
lib.rs

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// impl ClipsError {
67//   fn load_error(err: impl Error + Send + Sync + 'static) -> Self {
68//     ClipsError {
69//       kind: ClipsErrorKind::LoadError,.into()
70//       source: Some(Box::new(err)),
71//     }
72//   }
73// }
74
75#[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  // AddUDFError AddUDF(
146  //   Environment *env,
147  //   const char *clipsName,
148  //   const char *returnTypes,
149  //   unsigned short minArgs,
150  //   unsigned short maxArgs,
151  //   const char *argTypes,
152  //   UserDefinedFunction *cfp,
153  //   const char *cName,
154  //   void *context);
155
156  // AddUDF(env,"e","d",0,0,NULL,EulersNumber,"EulersNumber",NULL);
157  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    // Double Box because Box<FnMut> is a Trait Object i.e. fat pointer
170    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,                     // environment pointer
184        c_name.as_ptr() as *const i8, // CString with CLIPS function name to expose this UDF as
185        match return_types {
186          Some(return_types) => return_types.format().as_ptr() as *const i8,
187          None => std::ptr::null(),
188        }, // CString with CLIPS return types
189        min_args,                     // Min number of arguments that needs to be passed to UDF
190        max_args,                     // Max number of arguments that can be passed to UDF
191        arg_types.as_ptr(),           // String with required argument types
192        Some(udf_handler),            // Wrapper extern C fn that calls Rust Fn
193        c_name.as_ptr() as *const i8, // name of the 'real function' that's purely for debugging
194        Box::into_raw(function) as *mut _, // UDF Context contains pointer to Rust Fn for use by handler
195      )
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
244// https://stackoverflow.com/questions/32270030/how-do-i-convert-a-rust-closure-to-a-c-style-callback#32270215
245extern "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      // Double Box because Box<FnMut> is a Trait Object i.e. fat pointer
253      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  // Set value from clips::ClipsValue on clips_sys::UDFValue
263  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    // Create empty clips::UDFValue for CLIPS to write to
281    let mut out_value: clips_sys::UDFValue = Default::default();
282
283    if self.context.has_next_argument() {
284      unsafe {
285        // TODO specify argument types
286        clips_sys::UDFNextArgument(self.context.raw, Type::all().bits(), &mut out_value);
287      }
288
289      // Convert clips_sys::UDFValue into clips::UDFValue
290      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    // TODO argument?
562    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}