rosie_sys/
lib.rs

1#![crate_name = "rosie_sys"]
2
3#![doc(html_logo_url = "https://rosie-lang.org/images/rosie-circle-blog.png")]
4//Q-06.04, Can Jamie host a version of this logo that doesn't have a border?  i.e. just the circle occupying the whole frame, with an alpha-channel so the corners are transparent
5
6#![doc = include_str!("../README.md")]
7
8use core::fmt;
9use core::fmt::Display;
10use std::marker::PhantomData;
11use std::ptr;
12use std::slice;
13use std::str;
14use std::convert::TryFrom;
15use std::ffi::CString;
16
17extern crate libc;
18use libc::{size_t, c_void};
19
20/// A string type used to communicate with librosie (rstr in rosie.h)
21/// 
22/// Strings in librose can either be allocated by the librosie library or allocated by the client.  The buffer containing
23/// the actual bytes therefore must be freed or not freed depending on knowledge of where the string came from.  This
24/// makes a straightforward safe wrapper in Rust problematic.  It would be possible to expose a smart wrapper with knowledge
25/// about whether a buffer should be freed or not, but this adds extra complexity and overhead.  In fact I already wrote
26/// this and then decided against it after seeing how it looked and realizing there was very little need to expose
27/// librosie strings to Rust directly.
28///
29/// RosieString is used for communicating with the C API.  The rosie high-level crate exposes a specialized variant called
30/// RosieMessage.  A RosieMessage is a RosieString that was allocated by librosie, but where the librosie client is
31/// responsible for freeing it.  Therefore, RosieMessage implements the Rust Drop trait to clean up its buffer when it
32/// is no longer needed.
33///
34/// Simply put, RosieString doesn't own its buffer, and it's basically a glorified pointer.  RosieMessage does own its
35/// buffer, and frees it when dropped.  But the memory layout of both structures is identical.
36///
37#[derive(Debug, Copy, Clone)]
38#[repr(C)]
39pub struct RosieString<'a> {
40    len: u32,
41    ptr: *const u8, //This pointer really has a lifetime of 'a, hence the phantom
42    phantom: PhantomData<&'a u8>,
43}
44
45impl RosieString<'_> {
46    pub fn manual_drop(&mut self) {
47        if self.ptr != ptr::null() {
48            unsafe { rosie_free_string(*self); }
49            self.len = 0;
50            self.ptr = ptr::null();
51        }
52    }
53    pub fn empty() -> RosieString<'static> {
54        RosieString {
55            len: 0,
56            ptr: ptr::null(),
57            phantom: PhantomData
58        }
59    }
60    pub fn into_bytes<'a>(self) -> &'a[u8] {
61        if self.ptr != ptr::null() {
62            unsafe{ slice::from_raw_parts(self.ptr, usize::try_from(self.len).unwrap()) }
63        } else {
64            "".as_bytes()
65        }
66    }
67    pub fn into_str<'a>(self) -> &'a str {
68        self.try_into_str().unwrap()
69    }
70    pub fn from_str<'a>(s: &'a str) -> RosieString<'a> {
71        unsafe { rosie_string_from(s.as_ptr(), s.len()) }
72    }
73    pub fn from_bytes<'a>(b: &'a [u8]) -> RosieString<'a> {
74        unsafe { rosie_string_from(b.as_ptr(), b.len()) }
75    }
76    pub fn is_valid(&self) -> bool {
77        self.ptr != ptr::null()
78    }
79    pub fn as_bytes(&self) -> &[u8] {
80        if self.ptr != ptr::null() {
81            unsafe{ slice::from_raw_parts(self.ptr, usize::try_from(self.len).unwrap()) }
82        } else {
83            "".as_bytes()
84        }
85    }
86    pub fn as_str(&self) -> &str {
87        self.try_as_str().unwrap()
88    }
89    pub fn try_as_str(&self) -> Option<&str> {
90        str::from_utf8(self.as_bytes()).ok()
91    }
92    pub fn try_into_str<'a>(self) -> Option<&'a str> {
93        str::from_utf8(self.into_bytes()).ok()
94    }
95    pub fn len(&self) -> usize {
96        usize::try_from(self.len).unwrap()
97    }
98}
99
100impl Display for RosieString<'_> {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        write!(f, "{}", self.as_str())
103    }
104}
105
106/// An error code from a Rosie operation 
107//
108//WARNING!!!!  This enum is shadowed in the rosie high-level crate, in the `src/sys_shadow.rs` file.  DON'T MODIFY IT WITHOUT UPDATING THE SHADOW
109#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
110pub enum RosieError {
111    /// No error occurred.
112    Success = 0,
113    /// An unknown error occurred.
114    MiscErr = -1,
115    /// The Rosie Engine could not allocate the needed memory, either because the system allocator failed or because the limit
116    /// set by [rosie_alloc_limit] was reached.
117    OutOfMemory = -2,
118    /// A system API call failed.
119    SysCallFailed = -3,
120    /// A failure occurred in the `librosie` engine.
121    EngineCallFailed = -4,
122    /// An error related to a pattern input has occurred, for example, an `rpl` syntax error.
123    ExpressionError = -1001,
124    /// An error related to a package input has occurred, for example a missing package or `.rpl` file,
125    /// a missing package declaration in the file, or another syntax error in the package.rpl file.
126    PackageError = -1002,
127    /// An invalid argument was passed to a rosie function.
128    ArgError = -1003,
129}
130
131impl RosieError {
132    pub fn from(code: i32) -> Self {
133        match code {
134            0 => RosieError::Success,
135            -2 => RosieError::OutOfMemory,
136            -3 => RosieError::SysCallFailed,
137            -4 => RosieError::EngineCallFailed,
138            -1001 => RosieError::ExpressionError,
139            -1002 => RosieError::PackageError,
140            -1003 => RosieError::ArgError,
141            _ => RosieError::MiscErr
142        }
143    }
144}
145
146// NOTE: Leaving undocumented because documenting it here confuses the re-exported documentation. Here is the original doc -> /// An Encoder Module used to format the results, when using [rosie_match].
147#[derive(Clone, Debug, Hash, Eq, PartialEq)]
148pub enum MatchEncoder {
149    /// The simplest and fastest encoder.  Outputs `true` if the pattern matched and `false` otherwise.
150    Status,
151    /// A compact encoding of the match information into an array of bytes.
152    Byte,
153    /// A human-readable format using ANSI text coloring for different elements.  The colors associated with each element
154    /// can be customized by **Q-04.02 QUESTION FOR A ROSIE EXPERT: Where is this documented?**
155    Color,
156    /// A json-encoded match structure.
157    JSON,
158    /// The same data as [JSON](MatchEncoder::JSON), except formatted for better readability.
159    JSONPretty,
160    /// Each matching line from the input will be a line in the output.
161    Line,
162    /// The matching subset of each input line will be a line in the output.
163    Matches,
164    /// The subset of the input matched by each sub-expression of the pattern will be a line in the output.
165    Subs,
166    /// The output of a custom encoder module, implemented in `Lua`.  Documentation on implementing encoder modules can
167    /// be found **Q-04.03 QUESTION FOR A ROSIE EXPERT: Where is this documented?**
168    Custom(CString),
169}
170
171/// [MatchEncoder] methods to interface with librosie.
172/// 
173/// The purpose of this trait, as opposed to including the methods in `impl MatchEncoder` is that [MatchEncoder] will
174/// be re-exported by the `rosie` high-level crate, whereas these methods are used inside the implementation of the crate itself.
175pub trait LibRosieMatchEncoder {
176    fn as_bytes(&self) -> &[u8];
177}
178
179impl LibRosieMatchEncoder for MatchEncoder {
180    fn as_bytes(&self) -> &[u8] {
181        match self {
182            MatchEncoder::Status => b"status\0",
183            MatchEncoder::Byte => b"byte\0",
184            MatchEncoder::Color => b"color\0",
185            MatchEncoder::JSON => b"json\0",
186            MatchEncoder::JSONPretty => b"jsonpp\0",
187            MatchEncoder::Line => b"line\0",
188            MatchEncoder::Matches => b"matches\0",
189            MatchEncoder::Subs => b"subs\0",
190            MatchEncoder::Custom(name) => name.as_bytes_with_nul(),
191        }
192    }
193}
194
195impl MatchEncoder {
196    /// Create a MatchEncoder, that calls the `Lua` function name specified by the argument
197    pub fn custom(name : &str) -> Self {
198        MatchEncoder::Custom(CString::new(name.as_bytes()).unwrap())
199    }
200}
201
202// NOTE: Leaving undocumented because documenting it here confuses the re-exported documentation. Here is the original doc -> /// A format for debugging output, to be used with [rosie_trace]
203#[derive(Clone, Debug, Hash, Eq, PartialEq)]
204pub enum TraceFormat {
205    /// The complete trace data, formatted as JSON 
206    JSON,
207    /// The complete trace data, formatted in a multi-line human-readable format
208    Full,
209    /// Redacted trace data, containing only the parts of the expression most useful for understanding why a
210    /// pattern failed to match, presented in a multi-line human-readable format
211    Condensed
212}
213
214/// [TraceFormat] methods to interface with librosie.
215/// 
216/// The purpose of this trait, as opposed to including the methods in `impl TraceFormat` is that [TraceFormat] will
217/// be re-exported by the `rosie` high-level crate, whereas these methods are used inside the implementation of the crate itself.
218pub trait LibRosieTraceFormat {
219    fn as_bytes(&self) -> &[u8];
220}
221
222impl LibRosieTraceFormat for TraceFormat {
223    fn as_bytes(&self) -> &[u8] {
224        match self {
225            TraceFormat::JSON => b"json\0",
226            TraceFormat::Full => b"full\0",
227            TraceFormat::Condensed => b"condensed\0"
228        }
229    }
230}
231
232/// A pointer to a Rosie engine.  
233/// 
234/// EnginePtr should NOT be re-exported as it contains no safe interfaces
235/// 
236/// **NOTE**: RosieEngines are not internally thread-safe, but you may create more than one RosieEngine in order to use multiple threads.
237/// **NOTE**: Cloning / Copying this ptr type does not copy the engine, just the reference to the engine.
238#[repr(C)]
239#[derive(Clone, Copy)]
240pub struct EnginePtr {
241    pub e: *mut c_void, //This pointer really has a lifetime of 'a, hence the phantom
242}
243
244// NOTE: Leaving undocumented because documenting it here confuses the re-exported documentation. Here is the original doc
245// original docs -> /// A structure containing the match results from a [rosie_match] call.
246// original docs -> /// 
247// original docs -> /// **NOTE**: A RawMatchResult points to memory inside the engine that is associated with the pattern, therefore you may
248// original docs -> /// not perform any additional matching with that pattern until the RawMatchResult has been released.  This is enforced with
249// original docs -> /// borrowing semantics in the rosie high-level crate's `Pattern::raw_match` method, but in the sys crate it's on your honor.
250#[repr(C)]
251#[derive(Debug)]
252pub struct RawMatchResult<'a> {
253    data: RosieString<'a>,
254    leftover: i32,
255    abend: i32,
256    ttotal: i32,
257    tmatch: i32
258}
259
260/// [RawMatchResult] methods to interface with librosie.
261/// 
262/// The purpose of this trait, as opposed to including the methods in `impl RawMatchResult` is that [RawMatchResult] will
263/// be re-exported by the `rosie` high-level crate, whereas these methods are used inside the implementation of the crate itself.
264pub trait LibRosieMatchResult {
265    fn empty() -> Self;
266    fn data(&self) -> &RosieString;
267}
268
269impl LibRosieMatchResult for RawMatchResult<'_> {
270    fn empty() -> Self {
271        Self {
272            data: RosieString::empty(),
273            leftover: 0,
274            abend: 0,
275            ttotal: 0,
276            tmatch: 0
277        }
278    }
279    fn data(&self) -> &RosieString {
280        &self.data
281    }
282}
283
284impl <'a>RawMatchResult<'a> {
285    /// Returns `true` if the pattern was matched in the input, otherwise returns `false`.
286    pub fn did_match(&self) -> bool {
287        if self.data.is_valid() {
288            return true;
289        }
290        //The "bool" encoder returns 1 in the len field to indicate a match, even if the ptr is NULL
291        if self.data.len() == 1 {
292            return true;
293        }
294        false
295    }
296    /// Returns the raw buffer, outputted by the encoder during the match operation
297    pub fn as_bytes(&self) -> &[u8] {
298        self.data.as_bytes()
299    }
300    /// Returns the raw buffer, outputted by the encoder during the match operation, consuming the RawMatchResult
301    pub fn into_bytes(self) -> &'a [u8] {
302        self.data.into_bytes()
303    }
304    /// Returns the match buffer, interpreted as a UTF-8 string
305    pub fn as_str(&self) -> &str {
306        self.data.as_str()
307    }
308    /// Returns the match buffer, interpreted as a UTF-8 string, consuming the RawMatchResult
309    pub fn into_str(self) -> &'a str {
310        self.data.into_str()
311    }
312    /// Returns the total time, in microseconds, elapsed during the call to [rosie_match] inside librosie.
313    pub fn time_elapsed_total(&self) -> usize {
314        usize::try_from(self.ttotal).unwrap()
315    }
316    /// Returns the time, in microseconds, elapsed matching the pattern against the input.
317    /// 
318    /// This value excludes time spend encoding the results
319    pub fn time_elapsed_matching(&self) -> usize {
320        usize::try_from(self.tmatch).unwrap()
321    }
322}
323
324/// Returns the path to a rosie_home dir, that is valid at the time the rosie-sys crate is built
325/// 
326/// The purpose of this function is so that a high-level rosie crate can operate without needing to be configured on
327/// systems where the rosie_home dir isn't installed.  This is not a substitute for installing the rosie_home dir
328/// in a more appropriate location
329///
330/// TODO: In the future, we should embed the CONTENTS of the rosie_home into the binary, not just the path
331pub fn rosie_home_default() -> Option<&'static [u8]> {
332
333    option_env!("ROSIE_HOME").map(|env_str| env_str.as_bytes())
334}
335
336//Interfaces to the raw librosie functions
337//NOTE: Not all interfaces are imported by the Rust driver
338//NOTE: The 'static lifetime in the returned values is a LIE! The calling code needs to assign the lifetimes appropriately
339extern "C" {
340    pub fn rosie_new_string(msg : *const u8, len : size_t) -> RosieString<'static>; // str rosie_new_string(byte_ptr msg, size_t len);
341    // str *rosie_new_string_ptr(byte_ptr msg, size_t len);
342    // str *rosie_string_ptr_from(byte_ptr msg, size_t len);
343    pub fn rosie_string_from(msg : *const u8, len : size_t) -> RosieString<'static>; // str rosie_string_from(byte_ptr msg, size_t len);
344    pub fn rosie_free_string(s : RosieString); // void rosie_free_string(str s);
345    // void rosie_free_string_ptr(str *s);
346
347    /// Specifies a location for the `rosie_home` directory, which contains the Rosie Lua support files and the default Standard Pattern Library.
348    /// 
349    ///  **WARNING**: This function must be called before any other librosie calls, otherwise it will have no effect.
350    /// 
351    pub fn rosie_home_init(home : *const RosieString, messages : *mut RosieString); // void rosie_home_init(str *runtime, str *messages);
352    pub fn rosie_new(messages : *mut RosieString) -> EnginePtr; // Engine *rosie_new(str *messages);
353    pub fn rosie_finalize(e : EnginePtr); // void rosie_finalize(Engine *e);
354    pub fn rosie_libpath(e : EnginePtr, newpath : *mut RosieString) -> i32;// int rosie_libpath(Engine *e, str *newpath);
355    pub fn rosie_alloc_limit(e : EnginePtr, newlimit : *mut i32, usage : *mut i32) -> i32;// int rosie_alloc_limit(Engine *e, int *newlimit, int *usage);
356    pub fn rosie_config(e : EnginePtr, retvals : *mut RosieString) -> i32;// int rosie_config(Engine *e, str *retvals);
357    pub fn rosie_compile(e : EnginePtr, expression : *const RosieString, pat : *mut i32, messages : *mut RosieString) -> i32; // int rosie_compile(Engine *e, str *expression, int *pat, str *messages);
358    pub fn rosie_free_rplx(e : EnginePtr, pat : i32) -> i32; // int rosie_free_rplx(Engine *e, int pat);
359    pub fn rosie_match(e : EnginePtr, pat : i32, start : i32, encoder : *const u8, input : *const RosieString, match_result : *mut RawMatchResult) -> i32; // int rosie_match(Engine *e, int pat, int start, char *encoder, str *input, match *match);
360    pub fn rosie_match2(e : EnginePtr, pat : i32, encoder_name : *const u8, input : *const RosieString, startpos : u32, endpos : u32, match_result : *mut RawMatchResult, collect_times : u8) -> i32; //int rosie_match2(Engine *e, uint32_t pat, char *encoder_name, str *input, uint32_t startpos, uint32_t endpos, struct rosie_matchresult *match, uint8_t collect_times);
361    //pub fn rosie_matchfile(e : EnginePtr, pat : i32, encoder : *const u8, wholefileflag : i32, infilename : *const u8, outfilename : *const u8, errfilename : *const u8, cin : *mut i32, cout : *mut i32, cerr : *mut i32, err : *mut RosieString); // int rosie_matchfile(Engine *e, int pat, char *encoder, int wholefileflag, char *infilename, char *outfilename, char *errfilename, int *cin, int *cout, int *cerr, str *err);
362    pub fn rosie_trace(e : EnginePtr, pat : i32, start : i32, trace_style : *const u8, input : *const RosieString, matched : &mut i32, trace : *mut RosieString) -> i32; // int rosie_trace(Engine *e, int pat, int start, char *trace_style, str *input, int *matched, str *trace);
363    pub fn rosie_load(e : EnginePtr, ok : *mut i32, rpl_text : *const RosieString, pkgname : *mut RosieString, messages : *mut RosieString) -> i32; // int rosie_load(Engine *e, int *ok, str *src, str *pkgname, str *messages);
364    pub fn rosie_loadfile(e : EnginePtr, ok : *mut i32, file_name : *const RosieString, pkgname : *mut RosieString, messages : *mut RosieString) -> i32; // int rosie_loadfile(Engine *e, int *ok, str *fn, str *pkgname, str *messages);
365    pub fn rosie_import(e : EnginePtr, ok : *mut i32, pkgname : *const RosieString, as_name : *const RosieString, actual_pkgname : *mut RosieString, messages : *mut RosieString) -> i32; // int rosie_import(Engine *e, int *ok, str *pkgname, str *as, str *actual_pkgname, str *messages);
366    // int rosie_read_rcfile(Engine *e, str *filename, int *file_exists, str *options, str *messages);
367    // int rosie_execute_rcfile(Engine *e, str *filename, int *file_exists, int *no_errors, str *messages);
368
369    pub fn rosie_expression_refs(e : EnginePtr, expression : *const RosieString, refs : *mut RosieString, messages : *mut RosieString) -> i32; // int rosie_expression_refs(Engine *e, str *input, str *refs, str *messages);
370    // int rosie_block_refs(Engine *e, str *input, str *refs, str *messages);
371    pub fn rosie_expression_deps(e : EnginePtr, expression : *const RosieString, deps : *mut RosieString, messages : *mut RosieString) -> i32; // int rosie_expression_deps(Engine *e, str *input, str *deps, str *messages);
372    // int rosie_block_deps(Engine *e, str *input, str *deps, str *messages);
373    // int rosie_parse_expression(Engine *e, str *input, str *parsetree, str *messages);
374    // int rosie_parse_block(Engine *e, str *input, str *parsetree, str *messages);
375
376    pub fn rosie_import_expression_deps(e : EnginePtr, expression : *const RosieString, pkgs : *mut RosieString, err : *mut i32 , messages : *mut RosieString) -> i32; // int rosie_import_expression_deps (Engine *e, str *expression, str *pkgs, int *err, str *messages);
377}
378
379#[test]
380/// Tests RosieString, but not RosieMessage
381fn rosie_string() {
382
383    //A basic RosieString, pointing to a static string
384    let hello_str = "hello";
385    let rosie_string = RosieString::from_str(hello_str);
386    assert_eq!(rosie_string.len(), hello_str.len());
387    assert_eq!(rosie_string.as_str(), hello_str);
388
389    //A RosieString pointing to a heap-allocated string
390    let hello_string = String::from("hi there");
391    let rosie_string = RosieString::from_str(hello_string.as_str());
392    assert_eq!(rosie_string.len(), hello_string.len());
393    assert_eq!(rosie_string.as_str(), hello_string);
394
395    //Ensure we can't deallocate our rust String without deallocating our RosieString first
396    drop(hello_string);
397    //TODO: Implement a TryBuild harness in order to ensure the line below will not compile 
398    //assert!(rosie_string.is_valid());
399}
400
401#[test]
402/// Tests the native (unsafe) librosie function, mainly to make sure it built and linked properly
403fn librosie() {
404
405    //WARNING: I'm not doing a thorough job with error handling and cleanup in this test.
406    // This is NOT a good template to use for proper use of Rosie.  You really should
407    // use the high-level rosie crate to call Rosie from Rust.
408
409    //Init the Rosie home directory, if we have the rosie_home_default()
410    let mut message_buf = RosieString::empty();
411    if let Some(rosie_home_dir) = rosie_home_default() {
412        unsafe{ rosie_home_init(&RosieString::from_bytes(&rosie_home_dir), &mut message_buf) };
413    }
414    message_buf.manual_drop();
415
416    //Create the rosie engine with rosie_new
417    let mut message_buf = RosieString::empty();
418    let engine = unsafe { rosie_new(&mut message_buf) };
419    message_buf.manual_drop();
420
421    //Check the libpath is relative to the directory we set, if we set a path
422    let mut path_rosie_string = RosieString::empty();
423    let result_code = unsafe { rosie_libpath(engine, &mut path_rosie_string) };
424    assert_eq!(result_code, 0);
425    if let Some(rosie_home_dir) = rosie_home_default() {
426        assert_eq!(path_rosie_string.as_str(), format!("{}/rpl", str::from_utf8(rosie_home_dir).unwrap()));
427    }
428    path_rosie_string.manual_drop();
429
430    //Compile a valid rpl pattern, and confirm there is no error
431    let mut message_buf = RosieString::empty();
432    let mut pat_idx : i32 = 0;
433    let expression_rosie_string = RosieString::from_str("{[012][0-9]}");
434    let result_code = unsafe { rosie_compile(engine, &expression_rosie_string, &mut pat_idx, &mut message_buf) };
435    message_buf.manual_drop();
436    assert_eq!(result_code, 0);
437
438    //Match the pattern against a matching input using rosie_match
439    let input_rosie_string = RosieString::from_str("21");
440    let mut raw_match_result = RawMatchResult::empty();
441    let result_code = unsafe{ rosie_match(engine, pat_idx, 1, MatchEncoder::Status.as_bytes().as_ptr(), &input_rosie_string, &mut raw_match_result) }; 
442    assert_eq!(result_code, 0);
443    assert_eq!(raw_match_result.did_match(), true);
444    assert!(raw_match_result.time_elapsed_matching() <= raw_match_result.time_elapsed_total()); //A little lame as tests go, but validates they are called at least.
445
446    //Make sure we can sucessfully free the pattern
447    let result_code = unsafe { rosie_free_rplx(engine, pat_idx) };
448    assert_eq!(result_code, 0);
449
450    //Get the refs for a pattern expression that references a symbol from the Standard Pattern Library
451    let expression_rosie_string = RosieString::from_str("date.us_long");
452    let mut refs_buf = RosieString::empty();
453    let mut message_buf = RosieString::empty();
454    let result_code = unsafe { rosie_expression_refs(engine, &expression_rosie_string, &mut refs_buf, &mut message_buf) };
455    assert_eq!(result_code, 0);
456    assert_eq!(message_buf.len(), 0);
457    refs_buf.manual_drop();
458    message_buf.manual_drop();
459
460    //Get the deps from the pattern
461    let mut deps_buf = RosieString::empty();
462    let mut message_buf = RosieString::empty();
463    let result_code = unsafe { rosie_expression_deps(engine, &expression_rosie_string, &mut deps_buf, &mut message_buf) };
464    assert_eq!(result_code, 0);
465    assert_eq!(message_buf.len(), 0);
466    assert_eq!(deps_buf.as_str(), "[\"date\"]");
467    deps_buf.manual_drop();
468    message_buf.manual_drop();
469
470    //Clean up the engine with rosie_finalize
471    unsafe{ rosie_finalize(engine); }
472}