rcstring 0.2.1

Dependency-free CString-like implementation for projects with #[no_std]'s
Documentation
/* The MIT License (MIT)
 *
 * Copyright (c) 2015 William Orr <will@worrbase.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#![no_std]

use core::cmp::Ordering::{self,Less,Greater,Equal};
use core::marker::PhantomData;
use core::result::Result;
use core::slice;

/// Platform-specific c_char type. This is defined for cases where
/// the user cannot import libc.
///
/// Defined as u8 on arm and i8 everywhere else
#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
#[allow(non_camel_case_types)]
pub type c_char = i8;
#[cfg(any(target_arch = "arm", target_arch = "aarch64", target_arch = "powerpc"))]
#[allow(non_camel_case_types)]
pub type c_char = u8;


/// Constructs a new `CString` from a string literal
///
/// # Examples
///
/// ```
/// # #[macro_use] extern crate rcstring;
/// # fn main () {
/// let cs = cstr!("foo");
/// # }
/// ```
#[macro_export]
macro_rules! cstr {
    ($arg:expr) => ($crate::CString::new(concat!($arg, "\0")).unwrap());
}

/// Type representing a C-compatible string.
pub struct CString<'a> {
    data: *const c_char,
    len: usize,
    _marker: PhantomData<&'a c_char>
}

/// Error returned when a `CString` was initialized without a trailing
/// `NULL` byte, or if there are non-ASCII characters in the string
#[derive(Debug)]
pub struct Error(&'static str);

fn strlen(ptr: *const c_char) -> isize {
    let mut ctr = 0isize;
    loop {
        if unsafe { *ptr.offset(ctr) == 0 as c_char } {
            break
        }

        ctr += 1;
    }

    ctr
}

fn ascii_guard(ptr: *const c_char, len: usize) -> bool {
    let mut ctr = 0usize;
    while ctr < len {
        if unsafe { *ptr.offset(ctr as isize) < 0 as c_char } {
            return false;
        }

        ctr += 1;
    }

    true
}

impl<'a> CString<'a> {
    /// Constructs a new CString from a string slice
    ///
    /// The caller **MUST** ensure that there is a trailing NULL to terminate
    /// the string. To make this easier, the `cstr!` macro is provided.
    ///
    /// CStrings **MUST NOT** have any non-ASCII characters, even extended
    /// ASCII characters aren't allowed.
    ///
    /// # Examples
    ///
    /// ```
    /// use rcstring::CString;
    ///
    /// let cs = CString::new("foo\0").unwrap();
    /// ```
    pub fn new(s: &'a str) -> Result<CString<'a>, Error> {
        if s.len() == 0 {
            return Err(Error("0-length cstring found"));
        }

        let ret = CString {
            data: s.as_ptr() as *const c_char,
            len: s.len(),
            _marker: PhantomData
        };

        if ! ascii_guard(ret.data, ret.len) {
            return Err(Error("Invalid character in string"));
        }

        if unsafe { *ret.into_raw().offset(ret.len as isize - 1) != 0 } {
            Err(Error("No NULL terminator present"))
        } else {
            Ok(ret)
        }
    }

    /// Converts an exising raw pointer to a string and converts it to a
    /// `CString` with a length determined by an internal `strlen`
    /// implementation.
    pub unsafe fn from_raw(ptr: *const c_char) -> Result<CString<'a>, Error> {
        let siz = strlen(ptr) as usize + 1usize;

        let ret = CString {
            data: ptr,
            len: siz,
            _marker: PhantomData
        };

        if ! ascii_guard(ret.data, ret.len) {
            Err(Error("Invalid character in string"))
        } else {
            Ok(ret)
        }
    }

    /// Returns a mutable pointer to a CString for use in functions taking
    /// a classic C string
    pub unsafe fn into_raw(&self) -> *const c_char {
        self.data
    }

    /// Returns length of string, *including* trailing NULL
    pub fn len(&self) -> usize {
        self.len
    }
}

impl<'a> Eq for CString<'a> { }
impl<'a> PartialEq for CString<'a> {
    fn eq(&self, other: &CString) -> bool {
        unsafe {
            slice::from_raw_parts(self.data, other.len()) ==
                slice::from_raw_parts(other.data, other.len())
        }
    }

    fn ne(&self, other: &CString) -> bool {
        ! self.eq(other)
    }
}

impl<'a> PartialOrd for CString<'a> {
    fn partial_cmp(&self, other: &CString) -> Option<Ordering> {
        unsafe {
            slice::from_raw_parts(self.data, self.len())
                .partial_cmp(slice::from_raw_parts(other.data, other.len()))
        }
    }

    fn le(&self, other: &CString) -> bool {
        match self.partial_cmp(other) {
            Some(Less) => true,
            Some(Equal) => true,
            _ => false
        }
    }

    fn ge(&self, other: &CString) -> bool {
        match self.partial_cmp(other) {
            Some(Greater) => true,
            Some(Equal) => true,
            _ => false
        }
    }

    fn lt(&self, other: &CString) -> bool {
        match self.partial_cmp(other) {
            Some(Less) => true,
            _ => false
        }
    }

    fn gt(&self, other: &CString) -> bool {
        match self.partial_cmp(other) {
            Some(Greater) => true,
            _ => false
        }
    }
}

impl<'a> Ord for CString<'a> {
    fn cmp(&self, other: &CString) -> Ordering {
        self.data.cmp(&other.data)
    }
}

#[cfg(test)] #[macro_use] extern crate std;

#[cfg(test)]
mod test {
    use super::{c_char,CString,Error};
    use std::ffi::CString as OldString;
    use std::slice;
    use std::string::String;

    #[test]
    fn new_cstring() {
        {
            let cstr = cstr!("foo");
            let buf = "foo\0".as_ptr() as *const c_char;

            unsafe {
                assert_eq!(slice::from_raw_parts(cstr.data, cstr.len()),
                    slice::from_raw_parts(buf, 4));
            }
        }
        {
            let raw = OldString::new("foo").unwrap().into_raw();
            let cstr = unsafe { CString::from_raw(raw).unwrap() };
            let buf = "foo\0".as_ptr() as *const c_char;

            unsafe {
                assert_eq!(slice::from_raw_parts(cstr.data, cstr.len()),
                    slice::from_raw_parts(buf, 4));
            }
        }
        {
            let cstr_res = CString::new("foo");

            assert!(! cstr_res.is_ok());
        }
        {
            let cstr_res = CString::new("");

            assert!(! cstr_res.is_ok());
        }
        {
            let cstr_res = CString::new("ñ\0");

            assert!(! cstr_res.is_ok());
        }
        {
            let cstr_res = CString::new("ай да, пирожки\0");

            assert!(! cstr_res.is_ok());
        }
        {
            {
                let s = String::from("foo\0");
                let cstr_res: Result<CString, Error> = CString::new(s.as_str());
                assert!(cstr_res.is_ok());
            }
        }
    }

    #[test]
    fn from_cstring() {
        {
            let cstr = cstr!("foo");
            let raw = unsafe { cstr.into_raw() };
            let other = OldString::new("foo").unwrap().into_raw();

            unsafe {
                assert_eq!(*raw, *other);
            }
        }
    }

    #[test]
    fn cstring_len() {
        {
            let cstr = cstr!("foo");
            assert_eq!(cstr.len(), 4);
        }
        {
            let raw = OldString::new("foo").unwrap().into_raw();
            let cstr = unsafe { CString::from_raw(raw).unwrap() };
            assert_eq!(cstr.len(), 4);
        }
    }

    #[test]
    fn eq() {
        {
            let cstr = cstr!("foo");
            let other = cstr!("bar");

            assert!(cstr != other);
        }
        {
            let cstr = cstr!("foo");
            let other = cstr!("foo");

            assert!(cstr == other);
        }
    }

    #[test]
    fn cmp() {
        {
            let cstr = cstr!("foo");
            let other = cstr!("bar");

            assert!(cstr > other);
        }
        {
            let cstr = cstr!("foo");
            let other = cstr!("bar");

            assert!(other < cstr);
        }
    }
}