A backend for the p≡p Engine built on Sequoia.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

489 lines
14 KiB

//! A reimplementation of the engine's stringlist module in Rust.
//!
//! We could call out to the implementation in the engine, however,
//! then it would only be possible to use this crate when also linking
//! to the engine. This would mean that the CLI and tests would need
//! to link to the engine, which is undesirable.
//!
//! We only implement the functionality that we actually use:
//!
//! - new_stringlist
//! - stringlist_length
//! - stringlist_add
//! - stringlist_add_unique
//! - stringlist_append
use std::ptr;
use std::ffi::CStr;
use libc::c_char;
use crate::ffi::MM;
use crate::buffer::{
malloc_cleared,
rust_str_to_c_str,
};
#[repr(C)]
pub struct StringListItem {
value: *mut c_char,
next: *mut StringListItem,
}
impl StringListItem {
/// Allocates a new string item with no value (i.e., NULL).
///
/// The memory is allocated using the libc allocator. The caller
/// is responsible for freeing it explicitly.
fn empty(mm: MM) -> &'static mut Self {
let buffer = if let Ok(buffer) = malloc_cleared::<Self>(mm) {
buffer
} else {
panic!("Out of memory allocating a StringListItem");
};
unsafe { &mut *(buffer as *mut Self) }
}
/// Allocates a new string item with the specified value and next
/// pointer.
///
/// The memory is allocated using the libc allocator. The caller
/// is responsible for freeing it explicitly.
fn new<S: AsRef<str>>(mm: MM, value: S, next: *mut Self) -> &'static mut Self {
let item = Self::empty(mm);
item.value = rust_str_to_c_str(mm, value);
item.next = next;
item
}
/// Converts the raw pointer to a Rust reference.
///
/// This does not take ownership of the object.
fn as_mut(ptr: *mut Self) -> Option<&'static mut Self> {
unsafe { ptr.as_mut() }
}
}
// We wrap the StringList in a Rust object, because NULL is a valid
// stringlist_t (it's an empty string list).
pub struct StringList {
head: *mut StringListItem,
// If set, when the StringList is dropped, the items are freed.
owned: bool,
mm: MM,
}
impl StringList {
/// Converts the raw pointer to a Rust object.
///
/// `owned` indicates whether the rust code should own the items.
/// If so, when the `StringList` is dropped, the items will also
/// be freed.
pub fn to_rust(mm: MM, sl: *mut StringListItem, owned: bool) -> Self
{
StringList {
head: sl,
owned,
mm,
}
}
/// Converts the Rust object to a raw pointer.
///
/// The items are owned by the raw pointer and need to be freed
/// explicitly using libc's `free`.
pub fn to_c(mut self) -> *mut StringListItem {
std::mem::replace(&mut self.head, ptr::null_mut())
}
/// Creates a new string list.
///
/// The items are owned by the `StringList`, and when it is
/// dropped, they are freed. To take ownership of the items, call
/// `StringList::to_c`.
pub fn new<S: AsRef<str>>(mm: MM, value: S) -> Self {
StringList {
head: StringListItem::new(mm, value, ptr::null_mut()),
owned: true,
mm,
}
}
/// Creates a new, empty string list.
///
/// Any added items are owned by the `StringList`, and when it is
/// dropped, they are freed. To take ownership of the items, call
/// `StringList::to_c`.
pub fn empty(mm: MM) -> Self
{
StringList {
head: ptr::null_mut(),
owned: true,
mm,
}
}
/// There are two ways to make an empty list. Either head is NULL
/// or the first element's value is NULL. This creates the second
/// variant, which we use for testing.
#[cfg(test)]
fn empty_alt() -> Self {
let mm = MM { malloc: libc::malloc, free: libc::free };
StringList {
head: StringListItem::empty(mm),
owned: true,
mm,
}
}
/// Returns an iterator over the items.
pub fn iter<'a>(&'a self) -> impl Iterator<Item = &'a CStr> {
StringListIter {
item: &self.head,
}
}
/// Returns a mutable iterator over the items.
pub fn iter_mut<'a>(&'a mut self) -> StringListIterMut {
StringListIterMut {
item: &mut self.head,
}
}
/// Returns the number of items in the list.
pub fn len(&self) -> usize {
self.iter().count()
}
fn add_<S: AsRef<str>>(&mut self, value: S, dedup: bool) {
let mm = self.mm;
let value = value.as_ref();
// See if the value already exists in the string list.
let mut iter = self.iter_mut();
for i in &mut iter {
if dedup && i.to_bytes() == value.as_bytes() {
return;
}
}
// It's not present yet. Add it.
// There are three cases to consider:
let itemp = iter.item();
if (*itemp).is_null() {
// 1. head is NULL (this is the case if item is NULL).
*itemp = StringListItem::new(mm, value, ptr::null_mut());
} else {
let item: &mut StringListItem
= StringListItem::as_mut(*itemp).expect("just checked");
if item.value.is_null() {
// 2. head is not NULL, but head.value is NULL.
item.value = rust_str_to_c_str(mm, value);
} else {
// 3. neither head nor head.value are NULL.
assert!(item.next.is_null());
item.next = StringListItem::new(mm, value, ptr::null_mut());
}
}
}
/// Appends the item to the list.
///
/// The item's ownership is determined by the list's ownership
/// property.
pub fn add<S: AsRef<str>>(&mut self, value: S) {
self.add_(value, false)
}
/// Appends the item to the list if it isn't already present.
///
/// The item's ownership is determined by the list's ownership
/// property.
pub fn add_unique<S: AsRef<str>>(&mut self, value: S) {
self.add_(value, true)
}
/// Appends `other` to the list.
///
/// The items in other have the same ownership as items in `self`.
/// `other` is reset to an empty list.
pub fn append(&mut self, other: &mut StringList) {
let free = self.mm.free;
let mut iter = self.iter_mut();
(&mut iter).last();
// There are three cases to consider:
let itemp = iter.item();
if (*itemp).is_null() {
// 1. head is NULL (this is the case if item is NULL).
*itemp = other.head;
} else {
let item: &mut StringListItem
= StringListItem::as_mut(*itemp).expect("just checked");
if item.value.is_null() {
// 2. head is not NULL, but head.value is NULL.
unsafe { free((*itemp) as *mut _) };
*itemp = other.head;
} else {
// 3. neither head nor head.value are NULL.
assert!(item.next.is_null());
item.next = other.head;
}
}
other.head = ptr::null_mut();
}
}
impl Drop for StringList {
fn drop(&mut self) {
let free = self.mm.free;
let mut curr: *mut StringListItem = self.head;
self.head = ptr::null_mut();
if self.owned {
loop {
let next = if let Some(curr) = StringListItem::as_mut(curr) {
let next = curr.next;
unsafe { free(curr.value as *mut _) };
curr.value = ptr::null_mut();
next
} else {
break;
};
unsafe { free(curr as *mut _) };
curr = next;
}
}
}
}
pub struct StringListIterMut<'a> {
item: &'a mut *mut StringListItem,
}
impl<'a> StringListIterMut<'a> {
/// Returns a reference to the StringListItem that will be
/// returned next, or, if none, then a reference to the last
/// StringListItem. If the list is empty, this returns a
/// reference to the initial pointer, which will be NULL.
fn item(&'a mut self) -> &'a mut *mut StringListItem {
self.item
}
}
impl<'a> Iterator for StringListIterMut<'a> {
type Item = &'a CStr;
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = StringListItem::as_mut(*self.item) {
if item.value.is_null() {
None
} else {
self.item = &mut item.next;
Some(unsafe { CStr::from_ptr(item.value) })
}
} else {
None
}
}
}
pub struct StringListIter<'a> {
item: &'a *mut StringListItem,
}
impl<'a> Iterator for StringListIter<'a> {
type Item = &'a CStr;
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = StringListItem::as_mut(*self.item) {
if item.value.is_null() {
None
} else {
self.item = &item.next;
Some(unsafe { CStr::from_ptr(item.value) })
}
} else {
None
}
}
}
impl<'a> IntoIterator for &'a StringList {
type Item = &'a CStr;
type IntoIter = StringListIter<'a>;
fn into_iter(self) -> Self::IntoIter {
StringListIter {
item: &self.head,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty() {
let mm = MM { malloc: libc::malloc, free: libc::free };
// There are two ways to make an empty list. Either head is
// NULL or the string list item's value and next are NULL.
let empty = StringList {
head: ptr::null_mut(),
owned: true,
mm: mm,
};
assert_eq!(empty.len(), 0);
let empty = StringList {
head: StringListItem::empty(mm),
owned: true,
mm: mm,
};
assert_eq!(empty.len(), 0);
}
#[test]
fn add() {
let mm = MM { malloc: libc::malloc, free: libc::free };
for variant in 0..3 {
let (mut list, mut v) = match variant {
0 => {
let list = StringList::new(mm, "abc");
assert_eq!(list.len(), 1);
let mut v: Vec<String> = Vec::new();
v.push("abc".into());
(list, v)
},
1 => (StringList::empty(mm), Vec::new()),
2 => (StringList::empty_alt(), Vec::new()),
_ => unreachable!(),
};
let mut add_one = |s: String| {
list.add(&s);
v.push(s);
assert_eq!(list.len(), v.len());
assert_eq!(
&list
.iter()
.map(|s| String::from(s.to_str().unwrap()))
.collect::<Vec<String>>(),
&v);
};
for i in 1..100 {
add_one(format!("{}", i));
}
}
}
#[test]
fn add_unique() {
let mm = MM { malloc: libc::malloc, free: libc::free };
for variant in 0..3 {
let (mut list, mut v) = match variant {
0 => {
let list = StringList::new(mm, "abc");
assert_eq!(list.len(), 1);
let mut v: Vec<String> = Vec::new();
v.push("abc".into());
(list, v)
},
1 => (StringList::empty(mm), Vec::new()),
2 => (StringList::empty_alt(), Vec::new()),
_ => unreachable!(),
};
let mut add_one = |s: String| {
list.add_unique(&s);
// Add adds to the back.
if v.iter().find(|&x| x == &s).is_none() {
v.push(s);
}
assert_eq!(list.len(), v.len());
assert_eq!(
&list
.iter()
.map(|s| String::from(s.to_str().unwrap()))
.collect::<Vec<String>>(),
&v);
};
for i in 1..13 {
add_one(format!("{}", i));
}
for i in 1..19 {
add_one(format!("{}", i));
}
for i in 1..19 {
add_one(format!("{}", i));
}
}
}
#[test]
fn append() {
let mm = MM { malloc: libc::malloc, free: libc::free };
for variant in 0..2 {
// Returns a list and a vector with `count` items whose
// values are `prefix_0`, `prefix_1`, etc.
let list = |count: usize, prefix: &str| -> (StringList, Vec<String>) {
let mut l = match variant {
0 => StringList::empty(mm),
1 => StringList::empty_alt(),
_ => unreachable!(),
};
let mut v = Vec::new();
for i in 0..count {
let value = format!("{}_{}", prefix, i);
l.add(&value);
v.push(value);
}
(l, v)
};
for i in 0..10 {
for j in 0..10 {
let (mut a, mut av) = list(i, "a");
let (mut b, mut bv) = list(j, "b");
a.append(&mut b);
assert_eq!(a.len(), i + j);
av.append(&mut bv);
assert_eq!(av.len(), i + j);
for (i, (a, av)) in a.iter().zip(av.iter()).enumerate() {
assert_eq!(a.to_bytes(), av.as_bytes(),
"index: {}", i);
}
}
}
}
}
}