#![crate_name = "c_string"]
#![crate_type = "lib"]
#![feature(collections)]
#![feature(core)]
#![feature(hash)]
#![feature(libc)]
#![feature(std_misc)]
#![cfg_attr(test, feature(test))]
extern crate libc;
#[cfg(test)]
extern crate test;
use std::cmp::Ordering;
use std::error::Error;
use std::fmt;
use std::hash;
use std::marker;
use std::mem;
use std::ops::Deref;
use std::slice;
use std::str;
const NUL: u8 = 0;
pub unsafe fn parse_as_bytes<'a, T: ?Sized>(raw: *const libc::c_char,
life_anchor: &'a T)
-> &'a [u8]
{
assert!(!raw.is_null());
from_raw_ptr(raw, life_anchor).parse_as_bytes()
}
#[inline]
pub unsafe fn parse_as_utf8<'a, T: ?Sized>(raw: *const libc::c_char,
life_anchor: &'a T)
-> Result<&'a str, str::Utf8Error>
{
str::from_utf8(parse_as_bytes(raw, life_anchor))
}
pub struct OwnedCString {
ptr: *const libc::c_char,
dtor: DestroyFn
}
impl Drop for OwnedCString {
fn drop(&mut self) {
let dtor = self.dtor;
unsafe { dtor(self.ptr) };
}
}
impl Deref for OwnedCString {
type Target = CStr;
fn deref(&self) -> &CStr {
unsafe { from_ptr_internal(self.ptr, self) }
}
}
impl PartialEq for OwnedCString {
fn eq(&self, other: &OwnedCString) -> bool {
unsafe { libc::strcmp(self.ptr, other.ptr) == 0 }
}
}
impl Eq for OwnedCString {
}
impl PartialOrd for OwnedCString {
#[inline]
fn partial_cmp(&self, other: &OwnedCString) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OwnedCString {
fn cmp(&self, other: &OwnedCString) -> Ordering {
let res = unsafe { libc::strcmp(self.ptr, other.ptr) };
res.cmp(&(0 as libc::c_int))
}
}
impl<H> hash::Hash<H> for OwnedCString
where H: hash::Hasher, H: hash::Writer
{
fn hash(&self, state: &mut H) {
self.parse_as_bytes().hash(state)
}
}
pub type DestroyFn = unsafe fn(*const libc::c_char);
pub unsafe fn libc_free(ptr: *const libc::c_char) {
libc::free(ptr as *mut libc::c_void);
}
impl OwnedCString {
pub unsafe fn new(ptr: *const libc::c_char, dtor: DestroyFn) -> OwnedCString {
assert!(!ptr.is_null());
OwnedCString { ptr: ptr, dtor: dtor }
}
}
impl fmt::Debug for OwnedCString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
(**self).fmt(f)
}
}
#[derive(Copy, Debug)]
pub struct NulError {
pub position: usize
}
impl Error for NulError {
fn description(&self) -> &str {
"invalid data for C string: contains a NUL byte"
}
}
impl fmt::Display for NulError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid data for C string: NUL at position {}", self.position)
}
}
#[derive(Debug)]
pub struct IntoCStrError {
cause: NulError,
bytes: Vec<u8>
}
impl IntoCStrError {
pub fn nul_error(&self) -> &NulError {
&self.cause
}
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
}
impl Error for IntoCStrError {
fn description(&self) -> &str {
self.cause.description()
}
}
impl fmt::Display for IntoCStrError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.cause)
}
}
const IN_PLACE_SIZE: usize = 31;
#[derive(Clone)]
enum CStrData {
Owned(Vec<u8>),
InPlace([u8; IN_PLACE_SIZE])
}
#[derive(Clone)]
pub struct CStrBuf {
data: CStrData
}
#[repr(C)]
pub struct CStr {
lead: libc::c_char,
marker: marker::NoCopy
}
fn bytes_into_c_str(s: &[u8]) -> CStrBuf {
let mut out = CStrBuf {
data: unsafe { blank_str_data() }
};
if !copy_in_place(s, &mut out.data) {
out = vec_into_c_str(s.to_vec());
}
out
}
#[inline]
unsafe fn blank_str_data() -> CStrData {
CStrData::InPlace(mem::uninitialized())
}
fn copy_in_place(s: &[u8], out: &mut CStrData) -> bool {
let len = s.len();
if len >= IN_PLACE_SIZE {
return false;
}
match *out {
CStrData::InPlace(ref mut buf) => {
slice::bytes::copy_memory(buf, s);
buf[len] = 0;
}
_ => unreachable!()
}
true
}
fn vec_into_c_str(mut v: Vec<u8>) -> CStrBuf {
v.push(NUL);
CStrBuf { data: CStrData::Owned(v) }
}
fn escape_bytestring(s: &[u8]) -> String {
use std::ascii;
let mut acc = Vec::with_capacity(s.len());
for c in s.iter() {
ascii::escape_default(*c, |esc| {
acc.push(esc);
})
}
unsafe { String::from_utf8_unchecked(acc) }
}
#[macro_export]
macro_rules! c_str {
($lit:expr) => {
$crate::from_static_str(concat!($lit, "\0"))
}
}
impl CStrBuf {
pub fn from_bytes(s: &[u8]) -> Result<CStrBuf, NulError> {
if let Some(pos) = s.position_elem(&NUL) {
return Err(NulError { position: pos });
}
Ok(bytes_into_c_str(s))
}
pub unsafe fn from_bytes_unchecked(s: &[u8]) -> CStrBuf {
bytes_into_c_str(s)
}
#[inline]
pub fn from_str(s: &str) -> Result<CStrBuf, NulError> {
CStrBuf::from_bytes(s.as_bytes())
}
#[inline]
pub unsafe fn from_str_unchecked(s: &str) -> CStrBuf {
CStrBuf::from_bytes_unchecked(s.as_bytes())
}
pub fn from_vec(vec: Vec<u8>) -> Result<CStrBuf, IntoCStrError> {
if let Some(pos) = vec.as_slice().position_elem(&NUL) {
return Err(IntoCStrError {
cause: NulError { position: pos },
bytes: vec
});
}
Ok(vec_into_c_str(vec))
}
pub unsafe fn from_vec_unchecked(vec: Vec<u8>) -> CStrBuf {
vec_into_c_str(vec)
}
pub fn into_vec(mut self) -> Vec<u8> {
let mut data = unsafe { blank_str_data() };
mem::swap(&mut self.data, &mut data);
match data {
CStrData::Owned(mut v) => {
let len = v.len() - 1;
v.truncate(len);
v
}
CStrData::InPlace(ref a) => {
a[.. a.position_elem(&NUL).unwrap()].to_vec()
}
}
}
fn as_bytes(&self) -> &[u8] {
match self.data {
CStrData::Owned(ref v) => &v[.. v.len() - 1],
CStrData::InPlace(ref a) => &a[.. a.position_elem(&NUL).unwrap()]
}
}
}
impl fmt::Debug for CStrBuf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "\"{}\"", escape_bytestring(self.as_bytes()))
}
}
unsafe fn from_ptr_internal<'a, T: ?Sized>(ptr: *const libc::c_char,
life_anchor: &'a T)
-> &'a CStr
{
mem::copy_lifetime(life_anchor, &*(ptr as *const CStr))
}
pub fn from_static_bytes(bytes: &'static [u8]) -> &'static CStr {
assert!(bytes.last() == Some(&NUL),
"static byte string is not null-terminated: \"{}\"",
escape_bytestring(bytes));
let ptr = bytes.as_ptr() as *const libc::c_char;
unsafe { from_ptr_internal(ptr, bytes) }
}
pub fn from_static_str(s: &'static str) -> &'static CStr {
assert!(s.ends_with("\0"),
"static string is not null-terminated: \"{}\"", s);
let ptr = s.as_ptr() as *const libc::c_char;
unsafe { from_ptr_internal(ptr, s) }
}
pub unsafe fn from_raw_ptr<'a, T: ?Sized>(ptr: *const libc::c_char,
life_anchor: &'a T)
-> &'a CStr
{
assert!(!ptr.is_null());
from_ptr_internal(ptr, life_anchor)
}
impl CStr {
#[inline]
pub fn as_ptr(&self) -> *const libc::c_char {
&self.lead as *const libc::c_char
}
pub fn parse_as_bytes(&self) -> &[u8] {
unsafe {
let r = mem::copy_lifetime(self, &(self.as_ptr() as *const u8));
slice::from_raw_buf(r, libc::strlen(self.as_ptr()) as usize)
}
}
#[inline]
pub fn parse_as_utf8(&self) -> Result<&str, str::Utf8Error> {
str::from_utf8(self.parse_as_bytes())
}
#[inline]
pub fn iter<'a>(&'a self) -> CChars<'a> {
CChars {
ptr: self.as_ptr(),
lifetime: marker::ContravariantLifetime
}
}
#[inline]
pub fn is_empty(&self) -> bool { self.lead == 0 }
}
impl fmt::Debug for CStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "\"{}\"", escape_bytestring(self.parse_as_bytes()))
}
}
impl Deref for CStrBuf {
type Target = CStr;
fn deref(&self) -> &CStr {
let p = match self.data {
CStrData::Owned(ref v) => (*v).as_ptr(),
CStrData::InPlace(ref a) => a.as_ptr()
} as *const libc::c_char;
unsafe { from_ptr_internal(p, self) }
}
}
pub struct CChars<'a> {
ptr: *const libc::c_char,
lifetime: marker::ContravariantLifetime<'a>,
}
impl<'a> Copy for CChars<'a> { }
impl<'a> Iterator for CChars<'a> {
type Item = libc::c_char;
fn next(&mut self) -> Option<libc::c_char> {
let ch = unsafe { *self.ptr };
if ch == 0 {
None
} else {
self.ptr = unsafe { self.ptr.offset(1) };
Some(ch)
}
}
}
pub unsafe fn parse_c_multistring<F>(buf: *const libc::c_char,
limit: Option<usize>,
mut f: F) -> usize
where F: FnMut(&[u8])
{
let mut curr_ptr = buf;
let mut ctr = 0us;
let (limited_count, limit) = match limit {
Some(limit) => (true, limit),
None => (false, 0)
};
while (!limited_count || ctr < limit) && *curr_ptr != 0 {
let bytes = parse_as_bytes(curr_ptr, &buf);
f(bytes);
curr_ptr = curr_ptr.offset(bytes.len() as isize + 1);
ctr += 1;
}
return ctr;
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod bench;