rosie-sys 1.3.1

A crate to build or link to librosie to access the Rosie Pattern Language
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
#![crate_name = "rosie_sys"]

#![doc(html_logo_url = "https://rosie-lang.org/images/rosie-circle-blog.png")]
//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

#![doc = include_str!("../README.md")]

use core::fmt;
use core::fmt::Display;
use std::marker::PhantomData;
use std::ptr;
use std::slice;
use std::str;
use std::convert::TryFrom;
use std::ffi::CString;

extern crate libc;
use libc::{size_t, c_void};

/// A string type used to communicate with librosie (rstr in rosie.h)
/// 
/// Strings in librose can either be allocated by the librosie library or allocated by the client.  The buffer containing
/// the actual bytes therefore must be freed or not freed depending on knowledge of where the string came from.  This
/// makes a straightforward safe wrapper in Rust problematic.  It would be possible to expose a smart wrapper with knowledge
/// about whether a buffer should be freed or not, but this adds extra complexity and overhead.  In fact I already wrote
/// this and then decided against it after seeing how it looked and realizing there was very little need to expose
/// librosie strings to Rust directly.
///
/// RosieString is used for communicating with the C API.  The rosie high-level crate exposes a specialized variant called
/// RosieMessage.  A RosieMessage is a RosieString that was allocated by librosie, but where the librosie client is
/// responsible for freeing it.  Therefore, RosieMessage implements the Rust Drop trait to clean up its buffer when it
/// is no longer needed.
///
/// Simply put, RosieString doesn't own its buffer, and it's basically a glorified pointer.  RosieMessage does own its
/// buffer, and frees it when dropped.  But the memory layout of both structures is identical.
///
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct RosieString<'a> {
    len: u32,
    ptr: *const u8, //This pointer really has a lifetime of 'a, hence the phantom
    phantom: PhantomData<&'a u8>,
}

impl RosieString<'_> {
    pub fn manual_drop(&mut self) {
        if self.ptr != ptr::null() {
            unsafe { rosie_free_string(*self); }
            self.len = 0;
            self.ptr = ptr::null();
        }
    }
    pub fn empty() -> RosieString<'static> {
        RosieString {
            len: 0,
            ptr: ptr::null(),
            phantom: PhantomData
        }
    }
    pub fn into_bytes<'a>(self) -> &'a[u8] {
        if self.ptr != ptr::null() {
            unsafe{ slice::from_raw_parts(self.ptr, usize::try_from(self.len).unwrap()) }
        } else {
            "".as_bytes()
        }
    }
    pub fn into_str<'a>(self) -> &'a str {
        self.try_into_str().unwrap()
    }
    pub fn from_str<'a>(s: &'a str) -> RosieString<'a> {
        unsafe { rosie_string_from(s.as_ptr(), s.len()) }
    }
    pub fn from_bytes<'a>(b: &'a [u8]) -> RosieString<'a> {
        unsafe { rosie_string_from(b.as_ptr(), b.len()) }
    }
    pub fn is_valid(&self) -> bool {
        self.ptr != ptr::null()
    }
    pub fn as_bytes(&self) -> &[u8] {
        if self.ptr != ptr::null() {
            unsafe{ slice::from_raw_parts(self.ptr, usize::try_from(self.len).unwrap()) }
        } else {
            "".as_bytes()
        }
    }
    pub fn as_str(&self) -> &str {
        self.try_as_str().unwrap()
    }
    pub fn try_as_str(&self) -> Option<&str> {
        str::from_utf8(self.as_bytes()).ok()
    }
    pub fn try_into_str<'a>(self) -> Option<&'a str> {
        str::from_utf8(self.into_bytes()).ok()
    }
    pub fn len(&self) -> usize {
        usize::try_from(self.len).unwrap()
    }
}

impl Display for RosieString<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

/// An error code from a Rosie operation 
//
//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
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum RosieError {
    /// No error occurred.
    Success = 0,
    /// An unknown error occurred.
    MiscErr = -1,
    /// The Rosie Engine could not allocate the needed memory, either because the system allocator failed or because the limit
    /// set by [rosie_alloc_limit] was reached.
    OutOfMemory = -2,
    /// A system API call failed.
    SysCallFailed = -3,
    /// A failure occurred in the `librosie` engine.
    EngineCallFailed = -4,
    /// An error related to a pattern input has occurred, for example, an `rpl` syntax error.
    ExpressionError = -1001,
    /// An error related to a package input has occurred, for example a missing package or `.rpl` file,
    /// a missing package declaration in the file, or another syntax error in the package.rpl file.
    PackageError = -1002,
    /// An invalid argument was passed to a rosie function.
    ArgError = -1003,
}

impl RosieError {
    pub fn from(code: i32) -> Self {
        match code {
            0 => RosieError::Success,
            -2 => RosieError::OutOfMemory,
            -3 => RosieError::SysCallFailed,
            -4 => RosieError::EngineCallFailed,
            -1001 => RosieError::ExpressionError,
            -1002 => RosieError::PackageError,
            -1003 => RosieError::ArgError,
            _ => RosieError::MiscErr
        }
    }
}

// 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].
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum MatchEncoder {
    /// The simplest and fastest encoder.  Outputs `true` if the pattern matched and `false` otherwise.
    Status,
    /// A compact encoding of the match information into an array of bytes.
    Byte,
    /// A human-readable format using ANSI text coloring for different elements.  The colors associated with each element
    /// can be customized by **Q-04.02 QUESTION FOR A ROSIE EXPERT: Where is this documented?**
    Color,
    /// A json-encoded match structure.
    JSON,
    /// The same data as [JSON](MatchEncoder::JSON), except formatted for better readability.
    JSONPretty,
    /// Each matching line from the input will be a line in the output.
    Line,
    /// The matching subset of each input line will be a line in the output.
    Matches,
    /// The subset of the input matched by each sub-expression of the pattern will be a line in the output.
    Subs,
    /// The output of a custom encoder module, implemented in `Lua`.  Documentation on implementing encoder modules can
    /// be found **Q-04.03 QUESTION FOR A ROSIE EXPERT: Where is this documented?**
    Custom(CString),
}

/// [MatchEncoder] methods to interface with librosie.
/// 
/// The purpose of this trait, as opposed to including the methods in `impl MatchEncoder` is that [MatchEncoder] will
/// be re-exported by the `rosie` high-level crate, whereas these methods are used inside the implementation of the crate itself.
pub trait LibRosieMatchEncoder {
    fn as_bytes(&self) -> &[u8];
}

impl LibRosieMatchEncoder for MatchEncoder {
    fn as_bytes(&self) -> &[u8] {
        match self {
            MatchEncoder::Status => b"status\0",
            MatchEncoder::Byte => b"byte\0",
            MatchEncoder::Color => b"color\0",
            MatchEncoder::JSON => b"json\0",
            MatchEncoder::JSONPretty => b"jsonpp\0",
            MatchEncoder::Line => b"line\0",
            MatchEncoder::Matches => b"matches\0",
            MatchEncoder::Subs => b"subs\0",
            MatchEncoder::Custom(name) => name.as_bytes_with_nul(),
        }
    }
}

impl MatchEncoder {
    /// Create a MatchEncoder, that calls the `Lua` function name specified by the argument
    pub fn custom(name : &str) -> Self {
        MatchEncoder::Custom(CString::new(name.as_bytes()).unwrap())
    }
}

// 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]
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum TraceFormat {
    /// The complete trace data, formatted as JSON 
    JSON,
    /// The complete trace data, formatted in a multi-line human-readable format
    Full,
    /// Redacted trace data, containing only the parts of the expression most useful for understanding why a
    /// pattern failed to match, presented in a multi-line human-readable format
    Condensed
}

/// [TraceFormat] methods to interface with librosie.
/// 
/// The purpose of this trait, as opposed to including the methods in `impl TraceFormat` is that [TraceFormat] will
/// be re-exported by the `rosie` high-level crate, whereas these methods are used inside the implementation of the crate itself.
pub trait LibRosieTraceFormat {
    fn as_bytes(&self) -> &[u8];
}

impl LibRosieTraceFormat for TraceFormat {
    fn as_bytes(&self) -> &[u8] {
        match self {
            TraceFormat::JSON => b"json\0",
            TraceFormat::Full => b"full\0",
            TraceFormat::Condensed => b"condensed\0"
        }
    }
}

/// A pointer to a Rosie engine.  
/// 
/// EnginePtr should NOT be re-exported as it contains no safe interfaces
/// 
/// **NOTE**: RosieEngines are not internally thread-safe, but you may create more than one RosieEngine in order to use multiple threads.
/// **NOTE**: Cloning / Copying this ptr type does not copy the engine, just the reference to the engine.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct EnginePtr {
    pub e: *mut c_void, //This pointer really has a lifetime of 'a, hence the phantom
}

// NOTE: Leaving undocumented because documenting it here confuses the re-exported documentation. Here is the original doc
// original docs -> /// A structure containing the match results from a [rosie_match] call.
// original docs -> /// 
// original docs -> /// **NOTE**: A RawMatchResult points to memory inside the engine that is associated with the pattern, therefore you may
// original docs -> /// not perform any additional matching with that pattern until the RawMatchResult has been released.  This is enforced with
// 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.
#[repr(C)]
#[derive(Debug)]
pub struct RawMatchResult<'a> {
    data: RosieString<'a>,
    leftover: i32,
    abend: i32,
    ttotal: i32,
    tmatch: i32
}

/// [RawMatchResult] methods to interface with librosie.
/// 
/// The purpose of this trait, as opposed to including the methods in `impl RawMatchResult` is that [RawMatchResult] will
/// be re-exported by the `rosie` high-level crate, whereas these methods are used inside the implementation of the crate itself.
pub trait LibRosieMatchResult {
    fn empty() -> Self;
    fn data(&self) -> &RosieString;
}

impl LibRosieMatchResult for RawMatchResult<'_> {
    fn empty() -> Self {
        Self {
            data: RosieString::empty(),
            leftover: 0,
            abend: 0,
            ttotal: 0,
            tmatch: 0
        }
    }
    fn data(&self) -> &RosieString {
        &self.data
    }
}

impl <'a>RawMatchResult<'a> {
    /// Returns `true` if the pattern was matched in the input, otherwise returns `false`.
    pub fn did_match(&self) -> bool {
        if self.data.is_valid() {
            return true;
        }
        //The "bool" encoder returns 1 in the len field to indicate a match, even if the ptr is NULL
        if self.data.len() == 1 {
            return true;
        }
        false
    }
    /// Returns the raw buffer, outputted by the encoder during the match operation
    pub fn as_bytes(&self) -> &[u8] {
        self.data.as_bytes()
    }
    /// Returns the raw buffer, outputted by the encoder during the match operation, consuming the RawMatchResult
    pub fn into_bytes(self) -> &'a [u8] {
        self.data.into_bytes()
    }
    /// Returns the match buffer, interpreted as a UTF-8 string
    pub fn as_str(&self) -> &str {
        self.data.as_str()
    }
    /// Returns the match buffer, interpreted as a UTF-8 string, consuming the RawMatchResult
    pub fn into_str(self) -> &'a str {
        self.data.into_str()
    }
    /// Returns the total time, in microseconds, elapsed during the call to [rosie_match] inside librosie.
    pub fn time_elapsed_total(&self) -> usize {
        usize::try_from(self.ttotal).unwrap()
    }
    /// Returns the time, in microseconds, elapsed matching the pattern against the input.
    /// 
    /// This value excludes time spend encoding the results
    pub fn time_elapsed_matching(&self) -> usize {
        usize::try_from(self.tmatch).unwrap()
    }
}

/// Returns the path to a rosie_home dir, that is valid at the time the rosie-sys crate is built
/// 
/// The purpose of this function is so that a high-level rosie crate can operate without needing to be configured on
/// systems where the rosie_home dir isn't installed.  This is not a substitute for installing the rosie_home dir
/// in a more appropriate location
///
/// TODO: In the future, we should embed the CONTENTS of the rosie_home into the binary, not just the path
pub fn rosie_home_default() -> Option<&'static [u8]> {

    option_env!("ROSIE_HOME").map(|env_str| env_str.as_bytes())
}

//Interfaces to the raw librosie functions
//NOTE: Not all interfaces are imported by the Rust driver
//NOTE: The 'static lifetime in the returned values is a LIE! The calling code needs to assign the lifetimes appropriately
extern "C" {
    pub fn rosie_new_string(msg : *const u8, len : size_t) -> RosieString<'static>; // str rosie_new_string(byte_ptr msg, size_t len);
    // str *rosie_new_string_ptr(byte_ptr msg, size_t len);
    // str *rosie_string_ptr_from(byte_ptr msg, size_t len);
    pub fn rosie_string_from(msg : *const u8, len : size_t) -> RosieString<'static>; // str rosie_string_from(byte_ptr msg, size_t len);
    pub fn rosie_free_string(s : RosieString); // void rosie_free_string(str s);
    // void rosie_free_string_ptr(str *s);

    /// Specifies a location for the `rosie_home` directory, which contains the Rosie Lua support files and the default Standard Pattern Library.
    /// 
    ///  **WARNING**: This function must be called before any other librosie calls, otherwise it will have no effect.
    /// 
    pub fn rosie_home_init(home : *const RosieString, messages : *mut RosieString); // void rosie_home_init(str *runtime, str *messages);
    pub fn rosie_new(messages : *mut RosieString) -> EnginePtr; // Engine *rosie_new(str *messages);
    pub fn rosie_finalize(e : EnginePtr); // void rosie_finalize(Engine *e);
    pub fn rosie_libpath(e : EnginePtr, newpath : *mut RosieString) -> i32;// int rosie_libpath(Engine *e, str *newpath);
    pub fn rosie_alloc_limit(e : EnginePtr, newlimit : *mut i32, usage : *mut i32) -> i32;// int rosie_alloc_limit(Engine *e, int *newlimit, int *usage);
    pub fn rosie_config(e : EnginePtr, retvals : *mut RosieString) -> i32;// int rosie_config(Engine *e, str *retvals);
    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);
    pub fn rosie_free_rplx(e : EnginePtr, pat : i32) -> i32; // int rosie_free_rplx(Engine *e, int pat);
    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);
    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);
    //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);
    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);
    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);
    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);
    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);
    // int rosie_read_rcfile(Engine *e, str *filename, int *file_exists, str *options, str *messages);
    // int rosie_execute_rcfile(Engine *e, str *filename, int *file_exists, int *no_errors, str *messages);

    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);
    // int rosie_block_refs(Engine *e, str *input, str *refs, str *messages);
    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);
    // int rosie_block_deps(Engine *e, str *input, str *deps, str *messages);
    // int rosie_parse_expression(Engine *e, str *input, str *parsetree, str *messages);
    // int rosie_parse_block(Engine *e, str *input, str *parsetree, str *messages);

    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);
}

#[test]
/// Tests RosieString, but not RosieMessage
fn rosie_string() {

    //A basic RosieString, pointing to a static string
    let hello_str = "hello";
    let rosie_string = RosieString::from_str(hello_str);
    assert_eq!(rosie_string.len(), hello_str.len());
    assert_eq!(rosie_string.as_str(), hello_str);

    //A RosieString pointing to a heap-allocated string
    let hello_string = String::from("hi there");
    let rosie_string = RosieString::from_str(hello_string.as_str());
    assert_eq!(rosie_string.len(), hello_string.len());
    assert_eq!(rosie_string.as_str(), hello_string);

    //Ensure we can't deallocate our rust String without deallocating our RosieString first
    drop(hello_string);
    //TODO: Implement a TryBuild harness in order to ensure the line below will not compile 
    //assert!(rosie_string.is_valid());
}

#[test]
/// Tests the native (unsafe) librosie function, mainly to make sure it built and linked properly
fn librosie() {

    //WARNING: I'm not doing a thorough job with error handling and cleanup in this test.
    // This is NOT a good template to use for proper use of Rosie.  You really should
    // use the high-level rosie crate to call Rosie from Rust.

    //Init the Rosie home directory, if we have the rosie_home_default()
    let mut message_buf = RosieString::empty();
    if let Some(rosie_home_dir) = rosie_home_default() {
        unsafe{ rosie_home_init(&RosieString::from_bytes(&rosie_home_dir), &mut message_buf) };
    }
    message_buf.manual_drop();

    //Create the rosie engine with rosie_new
    let mut message_buf = RosieString::empty();
    let engine = unsafe { rosie_new(&mut message_buf) };
    message_buf.manual_drop();

    //Check the libpath is relative to the directory we set, if we set a path
    let mut path_rosie_string = RosieString::empty();
    let result_code = unsafe { rosie_libpath(engine, &mut path_rosie_string) };
    assert_eq!(result_code, 0);
    if let Some(rosie_home_dir) = rosie_home_default() {
        assert_eq!(path_rosie_string.as_str(), format!("{}/rpl", str::from_utf8(rosie_home_dir).unwrap()));
    }
    path_rosie_string.manual_drop();

    //Compile a valid rpl pattern, and confirm there is no error
    let mut message_buf = RosieString::empty();
    let mut pat_idx : i32 = 0;
    let expression_rosie_string = RosieString::from_str("{[012][0-9]}");
    let result_code = unsafe { rosie_compile(engine, &expression_rosie_string, &mut pat_idx, &mut message_buf) };
    message_buf.manual_drop();
    assert_eq!(result_code, 0);

    //Match the pattern against a matching input using rosie_match
    let input_rosie_string = RosieString::from_str("21");
    let mut raw_match_result = RawMatchResult::empty();
    let result_code = unsafe{ rosie_match(engine, pat_idx, 1, MatchEncoder::Status.as_bytes().as_ptr(), &input_rosie_string, &mut raw_match_result) }; 
    assert_eq!(result_code, 0);
    assert_eq!(raw_match_result.did_match(), true);
    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.

    //Make sure we can sucessfully free the pattern
    let result_code = unsafe { rosie_free_rplx(engine, pat_idx) };
    assert_eq!(result_code, 0);

    //Get the refs for a pattern expression that references a symbol from the Standard Pattern Library
    let expression_rosie_string = RosieString::from_str("date.us_long");
    let mut refs_buf = RosieString::empty();
    let mut message_buf = RosieString::empty();
    let result_code = unsafe { rosie_expression_refs(engine, &expression_rosie_string, &mut refs_buf, &mut message_buf) };
    assert_eq!(result_code, 0);
    assert_eq!(message_buf.len(), 0);
    refs_buf.manual_drop();
    message_buf.manual_drop();

    //Get the deps from the pattern
    let mut deps_buf = RosieString::empty();
    let mut message_buf = RosieString::empty();
    let result_code = unsafe { rosie_expression_deps(engine, &expression_rosie_string, &mut deps_buf, &mut message_buf) };
    assert_eq!(result_code, 0);
    assert_eq!(message_buf.len(), 0);
    assert_eq!(deps_buf.as_str(), "[\"date\"]");
    deps_buf.manual_drop();
    message_buf.manual_drop();

    //Clean up the engine with rosie_finalize
    unsafe{ rosie_finalize(engine); }
}