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}