#![crate_name = "rosie_sys"]
#![doc(html_logo_url = "https://rosie-lang.org/images/rosie-circle-blog.png")]
#![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};
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct RosieString<'a> {
len: u32,
ptr: *const u8, 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())
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum RosieError {
Success = 0,
MiscErr = -1,
OutOfMemory = -2,
SysCallFailed = -3,
EngineCallFailed = -4,
ExpressionError = -1001,
PackageError = -1002,
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
}
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum MatchEncoder {
Status,
Byte,
Color,
JSON,
JSONPretty,
Line,
Matches,
Subs,
Custom(CString),
}
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 {
pub fn custom(name : &str) -> Self {
MatchEncoder::Custom(CString::new(name.as_bytes()).unwrap())
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum TraceFormat {
JSON,
Full,
Condensed
}
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"
}
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct EnginePtr {
pub e: *mut c_void, }
#[repr(C)]
#[derive(Debug)]
pub struct RawMatchResult<'a> {
data: RosieString<'a>,
leftover: i32,
abend: i32,
ttotal: i32,
tmatch: i32
}
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> {
pub fn did_match(&self) -> bool {
if self.data.is_valid() {
return true;
}
if self.data.len() == 1 {
return true;
}
false
}
pub fn as_bytes(&self) -> &[u8] {
self.data.as_bytes()
}
pub fn into_bytes(self) -> &'a [u8] {
self.data.into_bytes()
}
pub fn as_str(&self) -> &str {
self.data.as_str()
}
pub fn into_str(self) -> &'a str {
self.data.into_str()
}
pub fn time_elapsed_total(&self) -> usize {
usize::try_from(self.ttotal).unwrap()
}
pub fn time_elapsed_matching(&self) -> usize {
usize::try_from(self.tmatch).unwrap()
}
}
pub fn rosie_home_default() -> Option<&'static [u8]> {
option_env!("ROSIE_HOME").map(|env_str| env_str.as_bytes())
}
extern "C" {
pub fn rosie_new_string(msg : *const u8, len : size_t) -> RosieString<'static>; pub fn rosie_string_from(msg : *const u8, len : size_t) -> RosieString<'static>; pub fn rosie_free_string(s : RosieString);
pub fn rosie_home_init(home : *const RosieString, messages : *mut RosieString); pub fn rosie_new(messages : *mut RosieString) -> EnginePtr; pub fn rosie_finalize(e : EnginePtr); pub fn rosie_libpath(e : EnginePtr, newpath : *mut RosieString) -> i32; pub fn rosie_alloc_limit(e : EnginePtr, newlimit : *mut i32, usage : *mut i32) -> i32; pub fn rosie_config(e : EnginePtr, retvals : *mut RosieString) -> i32; pub fn rosie_compile(e : EnginePtr, expression : *const RosieString, pat : *mut i32, messages : *mut RosieString) -> i32; pub fn rosie_free_rplx(e : EnginePtr, pat : i32) -> i32; pub fn rosie_match(e : EnginePtr, pat : i32, start : i32, encoder : *const u8, input : *const RosieString, match_result : *mut RawMatchResult) -> i32; 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; pub fn rosie_trace(e : EnginePtr, pat : i32, start : i32, trace_style : *const u8, input : *const RosieString, matched : &mut i32, trace : *mut RosieString) -> i32; pub fn rosie_load(e : EnginePtr, ok : *mut i32, rpl_text : *const RosieString, pkgname : *mut RosieString, messages : *mut RosieString) -> i32; pub fn rosie_loadfile(e : EnginePtr, ok : *mut i32, file_name : *const RosieString, pkgname : *mut RosieString, messages : *mut RosieString) -> i32; pub fn rosie_import(e : EnginePtr, ok : *mut i32, pkgname : *const RosieString, as_name : *const RosieString, actual_pkgname : *mut RosieString, messages : *mut RosieString) -> i32;
pub fn rosie_expression_refs(e : EnginePtr, expression : *const RosieString, refs : *mut RosieString, messages : *mut RosieString) -> i32; pub fn rosie_expression_deps(e : EnginePtr, expression : *const RosieString, deps : *mut RosieString, messages : *mut RosieString) -> i32;
pub fn rosie_import_expression_deps(e : EnginePtr, expression : *const RosieString, pkgs : *mut RosieString, err : *mut i32 , messages : *mut RosieString) -> i32; }
#[test]
fn rosie_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);
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);
drop(hello_string);
}
#[test]
fn librosie() {
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();
let mut message_buf = RosieString::empty();
let engine = unsafe { rosie_new(&mut message_buf) };
message_buf.manual_drop();
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();
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);
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());
let result_code = unsafe { rosie_free_rplx(engine, pat_idx) };
assert_eq!(result_code, 0);
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();
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();
unsafe{ rosie_finalize(engine); }
}