]> git.proxmox.com Git - perlmod.git/blobdiff - perlmod/src/scalar.rs
experimental direct substr support
[perlmod.git] / perlmod / src / scalar.rs
index ecddc98f1cc5dcbd6a3af4fda333f53a39cdf2c5..ecb62779afa5e00b74b2c9619b512e5c673282ad 100644 (file)
@@ -1,13 +1,15 @@
 //! Module containing the [`Scalar`] and [`Mortal`] types.
 
-use std::convert::TryInto;
+use std::marker::PhantomData;
 use std::mem;
 
 use bitflags::bitflags;
 
+use crate::error::MagicError;
 use crate::ffi::{self, SV};
-use crate::Error;
-use crate::Value;
+use crate::magic::{Leakable, MagicSpec, MagicValue};
+use crate::raw_value;
+use crate::{Error, Value};
 
 /// An owned reference to a perl value.
 ///
@@ -53,7 +55,7 @@ impl Scalar {
     ///
     /// The caller may still need to decrease the reference count for the `ptr` source value.
     pub unsafe fn from_raw_ref(ptr: *mut SV) -> Self {
-        Self::from_raw_move(ffi::RSPL_SvREFCNT_inc(ptr))
+        unsafe { Self::from_raw_move(ffi::RSPL_SvREFCNT_inc(ptr)) }
     }
 
     /// Create a reference to `PL_sv_undef`.
@@ -88,7 +90,16 @@ impl Scalar {
 
     /// Create a new string value.
     pub fn new_string(s: &str) -> Self {
-        Self::new_bytes(s.as_bytes())
+        if s.as_bytes().iter().any(|&b| b >= 0x80) {
+            unsafe {
+                Self::from_raw_move(ffi::RSPL_newSVpvn_utf8(
+                    s.as_bytes().as_ptr() as *const libc::c_char,
+                    s.as_bytes().len() as libc::size_t,
+                ))
+            }
+        } else {
+            Self::new_bytes(s.as_bytes())
+        }
     }
 
     /// Create a new byte string.
@@ -108,6 +119,13 @@ impl Scalar {
     }
 }
 
+impl Clone for Scalar {
+    #[inline]
+    fn clone(&self) -> Self {
+        unsafe { Self::from_raw_ref(self.sv()) }
+    }
+}
+
 impl Drop for Scalar {
     fn drop(&mut self) {
         unsafe {
@@ -155,7 +173,10 @@ impl core::ops::DerefMut for Mortal {
     }
 }
 
-pub struct ScalarRef;
+/// A reference to a perl value. This is a pre reference type and cannot be constructed manually.
+/// It is meant to provide methods common to `Value`, `Scalar`, `Array`, `Hash`, as these are all
+/// scalar values under the hood.
+pub struct ScalarRef(PhantomData<()>);
 
 bitflags! {
     /// Represents the types a `Value` can contain. Values can usually contain multiple scalar types
@@ -186,28 +207,61 @@ impl ScalarRef {
         self as *const ScalarRef as *const SV as *mut SV
     }
 
-    /// Get some information about the value's type.
-    pub fn ty(&self) -> Type {
+    /// Get the raw `*mut SV` value for this.
+    ///
+    /// This does not affect the reference count of this value. This is up to the user.
+    pub fn as_raw(&self) -> *mut SV {
+        self.sv()
+    }
+
+    fn get_type(sv: *mut SV) -> Type {
         unsafe {
-            if ffi::RSPL_is_reference(self.sv()) {
-                Type::Reference
-            } else {
-                let flags = ffi::RSPL_type_flags(self.sv());
-                if flags > 0xff {
-                    panic!("bad C type returned");
-                } else if flags != 0 {
-                    Type::Scalar(Flags::from_bits(flags as u8).unwrap())
-                } else if ffi::RSPL_is_array(self.sv()) {
-                    Type::Array
-                } else if ffi::RSPL_is_hash(self.sv()) {
-                    Type::Hash
-                } else {
-                    Type::Other(ffi::RSPL_svtype(self.sv()) as u8)
+            if !ffi::RSPL_is_defined(sv) {
+                return Type::Scalar(Flags::empty());
+            }
+
+            // These are simple:
+            if ffi::RSPL_is_reference(sv) {
+                return Type::Reference;
+            } else if ffi::RSPL_is_array(sv) {
+                return Type::Array;
+            } else if ffi::RSPL_is_hash(sv) {
+                return Type::Hash;
+            }
+
+            // Scalars have flags:
+            let flags = ffi::RSPL_type_flags(sv);
+            if flags != 0 {
+                return Type::Scalar(Flags::from_bits_truncate(flags as u8));
+            }
+
+            let ty = ffi::RSPL_svtype(sv);
+            if ty == 0 {
+                // Looks like undef
+                Type::Scalar(Flags::empty())
+            } else if ty == ffi::RSPL_PVLV() {
+                // We don't support all kinds of magic, but some lvalues are simple:
+                // Try to GET the value and then check for definedness.
+                ffi::RSPL_SvGETMAGIC(sv);
+                if !ffi::RSPL_SvOK(sv) {
+                    // This happens when the value points to a non-existing hash element we could
+                    // auto-vivify, but we won't:
+                    return Type::Scalar(Flags::empty());
                 }
+
+                // Otherwise we just try to "recurse", which will work for substrings.
+                Self::get_type(ffi::RSPL_LvTARG(sv))
+            } else {
+                Type::Other(ty as u8)
             }
         }
     }
 
+    /// Get some information about the value's type.
+    pub fn ty(&self) -> Type {
+        Self::get_type(self.sv())
+    }
+
     /// Dereference this reference.
     pub fn dereference(&self) -> Option<Scalar> {
         let ptr = unsafe { ffi::RSPL_dereference(self.sv()) };
@@ -229,7 +283,7 @@ impl ScalarRef {
     }
 
     /// Coerce to an utf8 string value. (perlxs `SvPVutf8`)
-    pub fn pv_utf8(&self) -> &str {
+    pub fn pv_string_utf8(&self) -> &str {
         unsafe {
             let mut len: libc::size_t = 0;
             let ptr = ffi::RSPL_SvPVutf8(self.sv(), &mut len) as *const u8;
@@ -238,7 +292,7 @@ impl ScalarRef {
     }
 
     /// Coerce to a string without utf8 encoding. (perlxs `SvPV`)
-    pub fn pv_string_bytes(&self) -> &[u8] {
+    pub fn pv_bytes(&self) -> &[u8] {
         unsafe {
             let mut len: libc::size_t = 0;
             let ptr = ffi::RSPL_SvPV(self.sv(), &mut len) as *const u8;
@@ -246,12 +300,18 @@ impl ScalarRef {
         }
     }
 
-    /// Coerce to a byte-string. (perlxs `SvPVbyte`)
-    pub fn pv_bytes(&self) -> &[u8] {
+    /// Coerce to a byte-string, downgrading from utf-8. (perlxs `SvPVbyte`)
+    ///
+    /// May fail if there are values which don't fit into bytes in the contained utf-8 string, in
+    /// which case `None` is returned.
+    pub fn pv_utf8_to_bytes(&self) -> Option<&[u8]> {
         unsafe {
             let mut len: libc::size_t = 0;
             let ptr = ffi::RSPL_SvPVbyte(self.sv(), &mut len) as *const u8;
-            std::slice::from_raw_parts(ptr, len)
+            if ptr.is_null() {
+                return None;
+            }
+            Some(std::slice::from_raw_parts(ptr, len))
         }
     }
 
@@ -272,7 +332,7 @@ impl ScalarRef {
     ///
     /// The user is responsible for making sure the underlying pointer is correct.
     pub unsafe fn pv_ref<T>(&self) -> Result<&T, Error> {
-        self.pv_raw().map(|p| &*p)
+        self.pv_raw().map(|p| unsafe { &*p })
     }
 
     /// Interpret the byte string as a pointer and return it as a mutable reference for
@@ -282,7 +342,7 @@ impl ScalarRef {
     ///
     /// The user is responsible for making sure the underlying pointer is correct.
     pub unsafe fn pv_mut_ref<T>(&self) -> Result<&mut T, Error> {
-        self.pv_raw().map(|p| &mut *p)
+        self.pv_raw().map(|p| unsafe { &mut *p })
     }
 
     /// Create another owned reference to this value.
@@ -295,15 +355,231 @@ impl ScalarRef {
         0 == unsafe { ffi::RSPL_type_flags(self.sv()) }
     }
 
+    // FIXME: self consuming on a phantom type... this can probably not be useful
     /// Turn this into a [`Value`].
     pub fn into_value(self) -> Value {
         Value::from_scalar(self.clone_ref())
     }
+
+    /// Get the reference type for this value. (Similar to `ref` in perl).
+    ///
+    /// If `blessed` is true and the value is a blessed reference, the package name will be
+    /// returned, otherwise the scalar type (`"SCALAR"`, `"ARRAY"`, ...) will be returned.
+    pub fn reftype(&self, blessed: bool) -> &'static str {
+        let ptr = unsafe { ffi::RSPL_sv_reftype(self.sv(), if blessed { 1 } else { 0 }) };
+
+        if ptr.is_null() {
+            "<UNKNOWN>"
+        } else {
+            unsafe {
+                std::ffi::CStr::from_ptr(ptr)
+                    .to_str()
+                    .unwrap_or("<NON-UTF8-CLASSNAME>")
+            }
+        }
+    }
+
+    /// Check whether this value is a substring.
+    pub fn is_substr(&self) -> bool {
+        unsafe {
+            self.find_raw_magic(
+                Some(ffi::RSPL_PERL_MAGIC_substr()),
+                Some(&*ffi::RSPL_vtbl_substr()),
+            )
+            .is_some()
+        }
+    }
+
+    /// Create a substring from a string.
+    pub fn substr<I>(&self, index: I) -> Result<Scalar, Error>
+    where
+        I: std::slice::SliceIndex<[u8], Output = [u8]>,
+    {
+        let bytes = self.pv_bytes();
+        let slice: &[u8] = bytes
+            .get(index)
+            .ok_or_else(|| Error::new("substr with out of bounds range"))?;
+        let start = unsafe { slice.as_ptr().offset_from(bytes.as_ptr()) };
+        let start = usize::try_from(start).map_err(|_| Error::new("bad substr index"))?;
+
+        Ok(unsafe {
+            Scalar::from_raw_move(ffi::RSPL_substr(
+                ffi::RSPL_SvREFCNT_inc(self.sv()),
+                start,
+                slice.len(),
+            ))
+        })
+    }
+
+    /// Try to produce a substring from an existing "base" value and a `&str`.
+    ///
+    /// Returns `None` if `substr` is not part of `value`.
+    pub fn substr_from_str_slice(value: &ScalarRef, substr: &str) -> Result<Option<Scalar>, Error> {
+        let value_bytes = value.pv_bytes();
+        let value_beg = value_bytes.as_ptr() as usize;
+        let value_end = value_beg + value_bytes.len();
+        let value_range = value_beg..value_end;
+
+        let str_bytes = substr.as_bytes();
+        let str_beg = str_bytes.as_ptr() as usize;
+        let str_end = str_beg + str_bytes.len();
+        if !value_range.contains(&str_beg) || !value_range.contains(&str_end) {
+            return Ok(None);
+        }
+
+        // we just checked the ranges:
+        let start = unsafe { str_bytes.as_ptr().offset_from(value_bytes.as_ptr()) as usize };
+        Ok(Some(unsafe {
+            Scalar::from_raw_move(ffi::RSPL_substr(
+                ffi::RSPL_SvREFCNT_inc(value.sv()),
+                start,
+                substr.len(),
+            ))
+        }))
+    }
+
+    /// Attach magic to this value.
+    ///
+    /// # Safety
+    ///
+    /// The passed `vtbl` must stay valid for as long as the perl value exists.
+    /// It is up to the user to make sure `how` has a valid value. Passing `None` will create a
+    /// magic value of type `PERL_MAGIC_ext` for convenience (recommended).
+    pub unsafe fn add_raw_magic(
+        &self,
+        obj: Option<&ScalarRef>,
+        how: Option<libc::c_int>,
+        vtbl: Option<&ffi::MGVTBL>,
+        name: *const libc::c_char,
+        namelen: i32,
+    ) {
+        let _magic_ptr = unsafe {
+            ffi::RSPL_sv_magicext(
+                self.sv(),
+                obj.map(Self::sv).unwrap_or(std::ptr::null_mut()),
+                how.unwrap_or_else(|| ffi::RSPL_PERL_MAGIC_ext()),
+                vtbl,
+                name,
+                namelen,
+            )
+        };
+    }
+
+    /// Remove attached magic.
+    ///
+    /// If `ty` is `None`, a `PERL_MAGIC_ext` magic will be removed.
+    ///
+    /// # Safety
+    ///
+    /// It is up to the user that doing this will not crash the perl interpreter.
+    pub unsafe fn remove_raw_magic(&self, ty: Option<libc::c_int>, vtbl: Option<&ffi::MGVTBL>) {
+        unsafe {
+            ffi::RSPL_sv_unmagicext(
+                self.sv(),
+                ty.unwrap_or_else(|| ffi::RSPL_PERL_MAGIC_ext()),
+                vtbl,
+            )
+        }
+    }
+
+    /// Find a magic value, if present.
+    ///
+    /// If `ty` is `None`, a `PERL_MAGIC_ext` magic will be searched for.
+    pub fn find_raw_magic(
+        &self,
+        ty: Option<libc::c_int>,
+        vtbl: Option<&ffi::MGVTBL>,
+    ) -> Option<&ffi::MAGIC> {
+        unsafe {
+            ffi::RSPL_mg_findext(
+                self.sv(),
+                ty.unwrap_or_else(|| ffi::RSPL_PERL_MAGIC_ext()),
+                vtbl,
+            )
+            .as_ref()
+        }
+    }
+
+    /// Attach a magic tag to this value. This is a more convenient alternative to using
+    /// [`add_raw_magic`](ScalarRef::add_raw_magic()) manually.
+    pub fn add_magic<T: Leakable>(&self, spec: MagicValue<'_, '_, 'static, T>) {
+        unsafe {
+            self.add_raw_magic(
+                spec.spec.obj,
+                spec.spec.how,
+                Some(spec.spec.vtbl),
+                spec.ptr.map(Leakable::leak).unwrap_or(std::ptr::null()),
+                0,
+            )
+        }
+    }
+
+    /// Find a magic value attached to this perl value.
+    ///
+    /// # Safety
+    ///
+    /// It is up to the user to ensure the correct types are used in the provided `MagicSpec`.
+    pub fn find_magic<'a, 's, 'm, T: Leakable>(
+        &'s self,
+        spec: &'m MagicSpec<'static, 'static, T>,
+    ) -> Option<&'a T::Pointee> {
+        match self.find_raw_magic(spec.how, Some(spec.vtbl)) {
+            None => None,
+            Some(mg) => {
+                assert_eq!(
+                    mg.vtbl().map(|v| v as *const _),
+                    Some(spec.vtbl as *const _),
+                    "Perl_mg_findext misbehaved horribly",
+                );
+
+                T::get_ref(mg.ptr())
+            }
+        }
+    }
+
+    /// Remove a magic tag from this value previously added via
+    /// [`add_magic`](ScalarRef::add_magic()) and potentially reclaim the contained value of type
+    /// `T`.
+    ///
+    /// When using a "default" magic tag via [`MagicTag::DEFAULT`](crate::magic::MagicTag::DEFAULT)
+    /// such as when using the [`declare_magic!`](crate::declare_magic!) macro, removing the magic
+    /// implicitly causes perl call the `free` method, therefore in this case this method returns
+    /// `None`.
+    ///
+    /// In case the magic was not found, [`MagicError::NotFound("")`] is returned.
+    ///
+    /// This does not need to include the object and type information.
+    pub fn remove_magic<T: Leakable>(
+        &self,
+        spec: &MagicSpec<'static, 'static, T>,
+    ) -> Result<Option<T>, MagicError> {
+        let this = match self.find_raw_magic(spec.how, Some(spec.vtbl)) {
+            None => Err(MagicError::NotFound("")),
+            Some(mg) => {
+                assert_eq!(
+                    mg.vtbl().map(|v| v as *const _),
+                    Some(spec.vtbl as *const _),
+                    "Perl_mg_findext misbehaved horribly",
+                );
+
+                Ok(match mg.vtbl() {
+                    // We assume that a 'free' callback takes care of reclaiming the value!
+                    Some(v) if v.free.is_some() => None,
+                    _ => T::get_ref(mg.ptr()).map(|m| unsafe { T::reclaim(m) }),
+                })
+            }
+        };
+
+        unsafe {
+            self.remove_raw_magic(spec.how, Some(spec.vtbl));
+        }
+        this
+    }
 }
 
 impl std::fmt::Debug for Scalar {
     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-        let this: &ScalarRef = &self;
+        let this: &ScalarRef = self;
         std::fmt::Debug::fmt(this, f)
     }
 }
@@ -314,7 +590,7 @@ impl std::fmt::Debug for ScalarRef {
         match self.ty() {
             Type::Scalar(flags) => {
                 if flags.intersects(Flags::STRING) {
-                    Debug::fmt(self.pv_utf8(), f)
+                    Debug::fmt(self.pv_string_utf8(), f)
                 } else if flags.intersects(Flags::INTEGER) {
                     write!(f, "{}", self.iv())
                 } else if flags.intersects(Flags::DOUBLE) {
@@ -338,21 +614,28 @@ impl serde::Serialize for Scalar {
     {
         use serde::ser::Error;
 
+        if raw_value::is_enabled() {
+            return raw_value::serialize_raw(self, serializer);
+        }
+
         match self.ty() {
             Type::Scalar(flags) => {
                 if flags.contains(Flags::STRING) {
-                    serializer.serialize_str(self.pv_utf8())
+                    serializer.serialize_str(self.pv_string_utf8())
                 } else if flags.contains(Flags::DOUBLE) {
                     serializer.serialize_f64(self.nv())
                 } else if flags.contains(Flags::INTEGER) {
                     serializer.serialize_i64(self.iv() as i64)
+                } else if flags.is_empty() {
+                    serializer.serialize_none()
                 } else {
                     serializer.serialize_unit()
                 }
             }
-            Type::Other(_) => Err(S::Error::custom(
-                "cannot deserialize weird magic perl values",
-            )),
+            Type::Other(other) => Err(S::Error::custom(format!(
+                "cannot serialize weird magic perl values ({})",
+                other,
+            ))),
 
             // These are impossible as they are all handled by different Value enum types:
             Type::Reference => Value::from(