From f7cc8c37fcc40ae06ed33a0272cf5b7155d73acc Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Thu, 9 Jan 2020 10:37:44 +0100 Subject: [PATCH] import Signed-off-by: Wolfgang Bumiller --- .cargo/config | 5 + .gitignore | 3 + Cargo.toml | 6 + perlmod-macro/Cargo.toml | 14 + perlmod-macro/debian/cargo-checksum.json | 1 + perlmod-macro/debian/changelog | 5 + perlmod-macro/debian/compat | 1 + perlmod-macro/debian/control | 33 ++ perlmod-macro/debian/copyright | 16 + perlmod-macro/debian/rules | 3 + perlmod-macro/src/lib.rs | 330 ++++++++++++++ perlmod-test/Cargo.toml | 12 + perlmod-test/src/lib.rs | 13 + perlmod/Cargo.toml | 20 + perlmod/build.rs | 71 +++ perlmod/src/array.rs | 223 +++++++++ perlmod/src/de.rs | 551 +++++++++++++++++++++++ perlmod/src/error.rs | 45 ++ perlmod/src/ffi.rs | 158 +++++++ perlmod/src/glue.c | 295 ++++++++++++ perlmod/src/hash.rs | 208 +++++++++ perlmod/src/lib.rs | 29 ++ perlmod/src/scalar.rs | 335 ++++++++++++++ perlmod/src/ser.rs | 376 ++++++++++++++++ perlmod/src/value.rs | 355 +++++++++++++++ rust-toolchain | 1 + rustfmt.toml | 1 + 27 files changed, 3110 insertions(+) create mode 100644 .cargo/config create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 perlmod-macro/Cargo.toml create mode 100644 perlmod-macro/debian/cargo-checksum.json create mode 100644 perlmod-macro/debian/changelog create mode 100644 perlmod-macro/debian/compat create mode 100644 perlmod-macro/debian/control create mode 100644 perlmod-macro/debian/copyright create mode 100755 perlmod-macro/debian/rules create mode 100644 perlmod-macro/src/lib.rs create mode 100644 perlmod-test/Cargo.toml create mode 100644 perlmod-test/src/lib.rs create mode 100644 perlmod/Cargo.toml create mode 100644 perlmod/build.rs create mode 100644 perlmod/src/array.rs create mode 100644 perlmod/src/de.rs create mode 100644 perlmod/src/error.rs create mode 100644 perlmod/src/ffi.rs create mode 100644 perlmod/src/glue.c create mode 100644 perlmod/src/hash.rs create mode 100644 perlmod/src/lib.rs create mode 100644 perlmod/src/scalar.rs create mode 100644 perlmod/src/ser.rs create mode 100644 perlmod/src/value.rs create mode 100644 rust-toolchain create mode 100644 rustfmt.toml diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..3b5b6e4 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,5 @@ +[source] +[source.debian-packages] +directory = "/usr/share/cargo/registry" +[source.crates-io] +replace-with = "debian-packages" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8f1b4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +/RSPM diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6ed7d1a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = [ + "perlmod", + "perlmod-macro", + "perlmod-test", +] diff --git a/perlmod-macro/Cargo.toml b/perlmod-macro/Cargo.toml new file mode 100644 index 0000000..6729c81 --- /dev/null +++ b/perlmod-macro/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "perlmod-macro" +version = "0.1.0" +authors = ["Wolfgang Bumiller "] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +failure = "0.1" +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "1.0", features = [ "full" ] } diff --git a/perlmod-macro/debian/cargo-checksum.json b/perlmod-macro/debian/cargo-checksum.json new file mode 100644 index 0000000..7b5c5ec --- /dev/null +++ b/perlmod-macro/debian/cargo-checksum.json @@ -0,0 +1 @@ +{"package":"proxmox-api-macro","files":{}} diff --git a/perlmod-macro/debian/changelog b/perlmod-macro/debian/changelog new file mode 100644 index 0000000..6ce027b --- /dev/null +++ b/perlmod-macro/debian/changelog @@ -0,0 +1,5 @@ +rust-perlmod-macro (0.1.0) proxmox-rust; urgency=medium + + * Initial packaging. + + -- Proxmox Support Team Thu, 09 Jan 2020 13:24:33 +0100 diff --git a/perlmod-macro/debian/compat b/perlmod-macro/debian/compat new file mode 100644 index 0000000..48082f7 --- /dev/null +++ b/perlmod-macro/debian/compat @@ -0,0 +1 @@ +12 diff --git a/perlmod-macro/debian/control b/perlmod-macro/debian/control new file mode 100644 index 0000000..fca9c7e --- /dev/null +++ b/perlmod-macro/debian/control @@ -0,0 +1,33 @@ +Source: rust-perlmod-macro +Section: rust +Priority: optional +Build-Depends: debhelper (>= 12), + dh-cargo (>= 21~), + cargo:native , + rustc:native , + libstd-rust-dev , + librust-failure-0.1+default-dev , + librust-proc-macro2-1.0+default-dev , + librust-quote-1.0+default-dev , + librust-syn-1.0+full-dev , +Maintainer: Proxmox Support Team +Standards-Version: 4.4.1 + +Package: librust-proxmox-api-macro-dev +Architecture: any +Depends: + ${misc:Depends}, + librust-failure-0.1+default-dev, + librust-proc-macro2-1.0+default-dev, + librust-quote-1.0+default-dev, + librust-syn-1.0+full-dev, +Provides: + librust-perlmod-macro+default-dev (= ${binary:Version}), + librust-perlmod-macro-0-dev (= ${binary:Version}), + librust-perlmod-macro-0+default-dev (= ${binary:Version}), + librust-perlmod-macro-0.1-dev (= ${binary:Version}), + librust-perlmod-macro-0.1+default-dev (= ${binary:Version}), + librust-perlmod-macro-0.1.0-dev (= ${binary:Version}), + librust-perlmod-macro-0.1.0+default-dev (= ${binary:Version}), +Description: Macro to create perl modules to exported rust functions. - Rust source code + This package contains the source for the Rust perlmod crate. diff --git a/perlmod-macro/debian/copyright b/perlmod-macro/debian/copyright new file mode 100644 index 0000000..4d6f36a --- /dev/null +++ b/perlmod-macro/debian/copyright @@ -0,0 +1,16 @@ +Copyright (C) 2020 Proxmox Server Solutions GmbH + +This software is written by Proxmox Server Solutions GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/perlmod-macro/debian/rules b/perlmod-macro/debian/rules new file mode 100755 index 0000000..044c1c2 --- /dev/null +++ b/perlmod-macro/debian/rules @@ -0,0 +1,3 @@ +#!/usr/bin/make -f +%: + dh $@ --buildsystem cargo diff --git a/perlmod-macro/src/lib.rs b/perlmod-macro/src/lib.rs new file mode 100644 index 0000000..9318e82 --- /dev/null +++ b/perlmod-macro/src/lib.rs @@ -0,0 +1,330 @@ +extern crate proc_macro; +extern crate proc_macro2; + +use std::convert::TryFrom; + +use failure::Error; + +use proc_macro::TokenStream as TokenStream_1; +use proc_macro2::{Ident, Span, TokenStream}; + +use quote::quote; +use syn::parse_macro_input; +use syn::AttributeArgs; + +macro_rules! format_err { + ($span:expr => $($msg:tt)*) => { syn::Error::new_spanned($span, format!($($msg)*)) }; + ($span:expr, $($msg:tt)*) => { syn::Error::new($span, format!($($msg)*)) }; +} + +macro_rules! bail { + ($span:expr => $($msg:tt)*) => { return Err(format_err!($span => $($msg)*).into()) }; + ($span:expr, $($msg:tt)*) => { return Err(format_err!($span, $($msg)*).into()) }; +} + +fn handle_error(mut item: TokenStream, data: Result) -> TokenStream { + match data { + Ok(output) => output, + Err(err) => match err.downcast::() { + Ok(err) => { + item.extend(err.to_compile_error()); + item + } + Err(err) => panic!("error in api/router macro: {}", err), + }, + } +} + +struct XSub { + rust_name: Ident, + xs_name: Ident, + tokens: TokenStream, +} + +/// Macro for exporting rust functions as perl xsubs. +#[proc_macro_attribute] +pub fn package(attr: TokenStream_1, item: TokenStream_1) -> TokenStream_1 { + let attr = parse_macro_input!(attr as AttributeArgs); + let item: TokenStream = item.into(); + handle_error(item.clone(), perlmod_impl(attr, item)).into() +} + +fn perlmod_impl(attr: AttributeArgs, item: TokenStream) -> Result { + let item: syn::Item = syn::parse2(item)?; + + match item { + syn::Item::Fn(func) => { + let func = handle_function(func)?; + Ok(func.tokens) + } + syn::Item::Mod(module) => handle_module(attr, module), + _ => bail!(item => "expected module or function"), + } +} + +fn handle_function(func: syn::ItemFn) -> Result { + //let vis = core::mem::replace(&mut func.vis, syn::Visibility::Inherited); + //if let syn::Visibility::Public(_) = vis { + // // ok + //} else { + // bail!(func.sig.fn_token => "only public functions can be exported as xsubs"); + //} + + let sig = &func.sig; + if !sig.generics.params.is_empty() { + bail!(&sig.generics => "generic functions cannot be exported as xsubs"); + } + + if sig.asyncness.is_some() { + bail!(&sig.asyncness => "async fns cannot be exported as xsubs"); + } + + let name = &sig.ident; + let xs_name = Ident::new(&format!("xs_{}", name), name.span()); + let impl_xs_name = Ident::new(&format!("impl_xs_{}", name), name.span()); + + let mut extract_arguments = TokenStream::new(); + let mut deserialized_arguments = TokenStream::new(); + let mut passed_arguments = TokenStream::new(); + for arg in &sig.inputs { + let pat_ty = match arg { + syn::FnArg::Receiver(_) => bail!(arg => "cannot export self-taking methods as xsubs"), + syn::FnArg::Typed(pt) => pt, + }; + + let arg_name = match &*pat_ty.pat { + syn::Pat::Ident(ident) => { + if ident.by_ref.is_some() { + bail!(ident => "xsub does not support by-ref parameters"); + } + if ident.subpat.is_some() { + bail!(ident => "xsub does not support sub-patterns on parameters"); + } + &ident.ident + } + _ => bail!(&pat_ty.pat => "xsub does not support this kind of parameter"), + }; + + let arg_type = &*pat_ty.ty; + + let extracted_name = Ident::new(&format!("extracted_arg_{}", arg_name), arg_name.span()); + let deserialized_name = + Ident::new(&format!("deserialized_arg_{}", arg_name), arg_name.span()); + + let missing_message = syn::LitStr::new("missing required parameter: '{}'", arg_name.span()); + + extract_arguments.extend(quote! { + let #extracted_name: ::perlmod::Value = match args.next() { + Some(arg) => ::perlmod::Value::from(arg), + None => { + return Err(::perlmod::Value::new_string(#missing_message) + .into_mortal() + .into_raw()); + } + }; + }); + + deserialized_arguments.extend(quote! { + let #deserialized_name: #arg_type = match ::perlmod::from_value(#extracted_name) { + Ok(data) => data, + Err(err) => { + return Err(::perlmod::Value::new_string(&err.to_string()) + .into_mortal() + .into_raw()); + } + }; + }); + + if passed_arguments.is_empty() { + passed_arguments.extend(quote! { #deserialized_name }); + } else { + passed_arguments.extend(quote! {, #deserialized_name }); + } + } + + let tokens = quote! { + #func + + #[no_mangle] + pub extern "C" fn #xs_name(cv: &::perlmod::ffi::CV) { + unsafe { + match #impl_xs_name(cv) { + Ok(sv) => ::perlmod::ffi::stack_push_raw(sv), + Err(sv) => ::perlmod::ffi::croak(sv), + } + } + } + + fn #impl_xs_name( + _cv: &::perlmod::ffi::CV, + ) -> Result<*mut ::perlmod::ffi::SV, *mut ::perlmod::ffi::SV> { + let argmark = unsafe { ::perlmod::ffi::pop_arg_mark() }; + let mut args = argmark.iter(); + + #extract_arguments + + drop(args); + + #deserialized_arguments + + unsafe { + argmark.set_stack(); + } + + let result = match #name(#passed_arguments) { + Ok(output) => output, + Err(err) => { + return Err(::perlmod::Value::new_string(&err.to_string()) + .into_mortal() + .into_raw()); + } + }; + + match ::perlmod::to_value(&result) { + Ok(value) => Ok(value.into_mortal().into_raw()), + Err(err) => Err(::perlmod::Value::new_string(&err.to_string()) + .into_mortal() + .into_raw()), + } + } + }; + + Ok(XSub { + rust_name: name.to_owned(), + xs_name, + tokens, + }) +} + +const LIB_NAME_DEFAULT: &str = r#"($pkg =~ /(?:^|::)([^:]+)$/)"#; + +const MODULE_HEAD: &str = r#" +use strict; +use warnings; +use DynaLoader (); + +my $LIB; + +sub __load_shared_lib { + return if $LIB; + + my ($pkg) = @_; + + my $auto_path = ($pkg =~ s!::!/!gr); + my ($mod_name) = {{LIB_NAME}}; + + my @dirs = (map "-L$_/auto/$auto_path", @INC); + my (@mod_files) = DynaLoader::dl_findfile(@dirs, '-L./target/debug', $mod_name); + die "failed to locate shared library for '$pkg' (lib${mod_name}.so)\n" if !@mod_files; + + $LIB = DynaLoader::dl_load_file($mod_files[0]) + or die "failed to load library '$mod_files[0]'\n"; +} + +sub newXS { + my ($perl_func_name, $full_symbol_name, $filename) = @_; + + my $sym = DynaLoader::dl_find_symbol($LIB, $full_symbol_name); + die "failed to locate '$full_symbol_name'\n" if !defined $sym; + DynaLoader::dl_install_xsub($perl_func_name, $sym, $filename); +} + +BEGIN { + __load_shared_lib(__PACKAGE__); +"#; + +const MODULE_TAIL: &str = "}\n"; + +struct ModuleArgs { + package_name: String, + file_name: String, + lib_name: Option, +} + +impl TryFrom for ModuleArgs { + type Error = syn::Error; + + fn try_from(args: AttributeArgs) -> Result { + let mut package_name = None; + let mut file_name = None; + let mut lib_name = None; + + for arg in args { + match arg { + syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { + path, + eq_token: _, + lit: syn::Lit::Str(litstr), + })) => { + if path.is_ident("name") { + package_name = Some(litstr.value()); + } else if path.is_ident("file") { + file_name = Some(litstr.value()); + } else if path.is_ident("lib") { + lib_name = Some(litstr.value()); + } else { + bail!(path => "unknown argument"); + } + } + _ => bail!(Span::call_site(), "unexpected attribute argument"), + } + } + + let package_name = package_name + .ok_or_else(|| format_err!(Span::call_site(), "missing 'package' argument"))?; + + let file_name = + file_name.unwrap_or_else(|| format!("{}.pm", package_name.replace("::", "/"))); + + Ok(Self { + package_name, + file_name, + lib_name, + }) + } +} + +fn handle_module(attr: AttributeArgs, mut module: syn::ItemMod) -> Result { + let args = ModuleArgs::try_from(attr)?; + + let mut module_source = format!("package {};\n{}", args.package_name, MODULE_HEAD); + + if let Some((_brace, ref mut items)) = module.content { + for item in items.iter_mut() { + match core::mem::replace(item, syn::Item::Verbatim(TokenStream::new())) { + syn::Item::Fn(mut func) => { + let count = func.attrs.len(); + func.attrs.retain(|attr| !attr.path.is_ident("export")); + // if we removed an #[export] macro this is an exported function: + if count != func.attrs.len() { + let func = handle_function(func)?; + *item = syn::Item::Verbatim(func.tokens); + module_source = format!( + "{} newXS('{}', '{}', 'src/FIXME.rs');\n", + module_source, func.rust_name, func.xs_name, + ); + } else { + *item = syn::Item::Fn(func); + } + } + other => *item = other, + } + } + } + + module_source.push_str(MODULE_TAIL); + + if let Some(lib) = args.lib_name { + module_source = module_source.replace("{{LIB_NAME}}", &format!("('{}')", lib)); + } else { + module_source = module_source.replace("{{LIB_NAME}}", LIB_NAME_DEFAULT); + } + + let path = std::path::Path::new(&args.file_name); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(path, module_source.as_bytes())?; + + Ok(quote! { #module }) +} diff --git a/perlmod-test/Cargo.toml b/perlmod-test/Cargo.toml new file mode 100644 index 0000000..c853b16 --- /dev/null +++ b/perlmod-test/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "perlmod-test" +version = "0.1.0" +authors = ["Wolfgang Bumiller "] +edition = "2018" + +[lib] +crate-type = [ "cdylib" ] + +[dependencies] +failure = "0.1" +perlmod = { path = "../perlmod", features = [ "exporter" ] } diff --git a/perlmod-test/src/lib.rs b/perlmod-test/src/lib.rs new file mode 100644 index 0000000..7e80253 --- /dev/null +++ b/perlmod-test/src/lib.rs @@ -0,0 +1,13 @@ +#[perlmod::package(name = "RSPM::Foo", lib = "perlmod_test")] +mod export { + use failure::{bail, Error}; + + #[export] + fn foo(a: u32, b: u32) -> Result { + if a == 42 { + bail!("dying on magic number"); + } + + Ok(a + b) + } +} diff --git a/perlmod/Cargo.toml b/perlmod/Cargo.toml new file mode 100644 index 0000000..6ba82e8 --- /dev/null +++ b/perlmod/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "perlmod" +version = "0.1.0" +authors = ["Wolfgang Bumiller "] +edition = "2018" +build = "build.rs" + +[dependencies] +bitflags = "1.2.1" +failure = "0.1" +libc = "0.2" +serde = { version = "1.0", features = ["derive"] } +perlmod-macro = { path = "../perlmod-macro", optional = true } + +[features] +default = ["exporter"] +exporter = ["perlmod-macro"] + +[build-dependencies] +cc = "1.0.46" diff --git a/perlmod/build.rs b/perlmod/build.rs new file mode 100644 index 0000000..7db9d93 --- /dev/null +++ b/perlmod/build.rs @@ -0,0 +1,71 @@ +extern crate cc; + +use std::path::Path; +use std::process::Command; +use std::{env, fs, io}; + +fn main() { + let out_dir = env::var("OUT_DIR").expect("expected OUT_DIR to be set by cargo"); + + let include_dir = format!("{}/include", out_dir); + let ppport_h_file = format!("{}/ppport.h", include_dir); + // quoted, without exterial double qoutes + let ppport_h_file_string_inner = ppport_h_file.replace('"', "\\\""); + + if let Err(err) = fs::create_dir(Path::new(&include_dir)) { + if err.kind() != io::ErrorKind::AlreadyExists { + panic!("failed to make include dir in OUT_DIR"); + } + } + + // perl -MDevel::PPPort -e 'Devel::PPPort::WriteFile("include/ppport.h");' + Command::new("perl") + .arg("-MDevel::PPPort") + .arg("-e") + .arg(&format!( + r#"Devel::PPPort::WriteFile("{}");"#, + ppport_h_file_string_inner + )) + .output() + .expect("failed to create ppport.h file using perl's Devel::PPPort"); + + // get include path: + // perl -MConfig -e 'print $Config{archlib}' + let perl_archlib = Command::new("perl") + .arg("-MConfig") + .arg("-e") + .arg("print $Config{archlib}") + .output() + .expect("failed to get perl arch include directory"); + // technically not a true Path, but we expect a system path which should be utf8-compatible + let archlib_include_path = format!( + "{}/CORE", + std::str::from_utf8(&perl_archlib.stdout).expect("expected perl include path to be utf8") + ); + + // get perl cflags: + // perl -MConfig -e 'print $Config{ccflags}' + let perl_ccflags = Command::new("perl") + .arg("-MConfig") + .arg("-e") + .arg("print $Config{ccflags}") + .output() + .expect("failed to get perl cflags"); + // technically garbage as it may contain paths, but since this should only contain system + // paths and otherwise also might contain quotes and what not let's just not really care: + let ccflags = std::str::from_utf8(&perl_ccflags.stdout).expect("expected cflags to be utf8"); + + let mut cc = cc::Build::new(); + + cc.pic(true) + .shared_flag(false) + .include(include_dir) + .include(archlib_include_path); + + for flag in ccflags.split_ascii_whitespace() { + cc.flag(flag); + } + + // now build the static library: + cc.file("src/glue.c").compile("libglue.a"); +} diff --git a/perlmod/src/array.rs b/perlmod/src/array.rs new file mode 100644 index 0000000..84e1020 --- /dev/null +++ b/perlmod/src/array.rs @@ -0,0 +1,223 @@ +use std::convert::TryFrom; +use std::marker::PhantomData; + +use crate::error::CastError; +use crate::ffi::{self, AV, SV}; +use crate::scalar::{Scalar, ScalarRef}; +use crate::Value; + +/// An owned reference to a perl array value (AV). +/// +/// This keeps a reference to a value which lives in the perl interpreter. +#[repr(transparent)] +pub struct Array(Scalar); + +#[allow(clippy::new_without_default)] +impl Array { + /// Create a new array value. + pub fn new() -> Self { + unsafe { Self::from_raw_move(ffi::RSPL_newAV()) } + } + + /// Turn this into a `Scalar`. The underlying perl value does not change, this is a pure type + /// cast down to a less specific "pointer" type. + pub fn into_scalar(self) -> Scalar { + self.0 + } + + /// Get the internal perl value as a low-level `AV` pointer. + pub fn av(&self) -> *mut AV { + self.0.sv() as *mut AV + } + + /// "Downcast" a `Scalar` into an `Array`. + /// + /// # Safety + /// + /// The caller must verify that this is legal. + pub unsafe fn from_scalar(scalar: Scalar) -> Self { + Self(scalar) + } + + /// Take over a raw `AV` value, assuming that we then own a reference to it. + /// + /// # Safety + /// + /// This does not change the value's reference count, it is assumed that we're taking ownership + /// of one reference. + /// + /// The caller must ensure that it is safe to decrease the reference count later on, or use + /// `into_raw()` instead of letting the `Array` get dropped. + pub unsafe fn from_raw_move(ptr: *mut AV) -> Self { + Self(Scalar::from_raw_move(ptr as *mut SV)) + } + + /// Create a new reference to an existing `AV` value. This will increase the value's reference + /// count. + /// + /// # Safety + /// + /// The caller may still need to decrease the reference count for the `ptr` source value. + pub unsafe fn from_raw_ref(ptr: *mut AV) -> Self { + Self(Scalar::from_raw_ref(ptr as *mut SV)) + } + + /// Create a new reference to this value. + pub fn clone_ref(&self) -> Self { + Self(self.0.clone_ref()) + } + + /// Get the length of the array. + pub fn len(&self) -> usize { + // perl returns the highest index, not the length! + unsafe { ffi::RSPL_av_len(self.av()).wrapping_add(1) } + } + + /// Check if this is an empty array. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get a value from the array. + pub fn get(&self, index: usize) -> Option { + let index = index as libc::ssize_t; + let sv: *mut *mut SV = unsafe { ffi::RSPL_av_fetch(self.av(), index, 0) }; + if sv.is_null() { + None + } else { + Some(unsafe { Value::from_raw_ref(*sv) }) + } + } + + /// Create an iterator over this array's values. + pub fn iter(&self) -> Iter { + Iter { + array: self.clone_ref(), + at: 0, + _phantom: PhantomData, + } + } + + /// Pre-extend the array to up to the specified length.. + pub fn reserve(&self, more: usize) { + let idx = self.len() + more - 1; + unsafe { + ffi::RSPL_av_extend(self.av(), idx as libc::ssize_t); + } + } + + /// Push a value onto the array. + pub fn push(&self, value: Value) { + unsafe { + ffi::RSPL_av_push(self.av(), value.into_raw()); + } + } + + /// Pop a value off of the array's end. + pub fn pop(&self) -> Option { + if self.is_empty() { + None + } else { + Some(unsafe { Value::from_raw_move(ffi::RSPL_av_pop(self.av())) }) + } + } +} + +impl core::ops::Deref for Array { + type Target = ScalarRef; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl core::ops::DerefMut for Array { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.0 + } +} + +impl TryFrom for Array { + type Error = CastError; + + fn try_from(scalar: Scalar) -> Result { + if unsafe { ffi::RSPL_is_array(scalar.sv()) } { + Ok(Self(scalar)) + } else { + Err(CastError) + } + } +} + +impl std::fmt::Debug for Array { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "[")?; + let mut comma = false; + for i in self { + if comma { + write!(f, ", {:?}", i)?; + } else { + comma = true; + write!(f, "{:?}", i)?; + } + } + write!(f, "]")?; + Ok(()) + } +} + +pub struct Iter<'a> { + array: Array, + at: usize, + _phantom: PhantomData<&'a Array>, +} + +impl<'a> Iterator for Iter<'a> { + type Item = Value; + + fn next(&mut self) -> Option { + let at = self.at; + if at < self.array.len() { + self.at += 1; + self.array.get(at) + } else { + None + } + } +} + +impl IntoIterator for Array { + type Item = Value; + type IntoIter = Iter<'static>; + + fn into_iter(self) -> Self::IntoIter { + Iter { + array: self, + at: 0, + _phantom: PhantomData, + } + } +} + +impl<'a> IntoIterator for &'a Array { + type Item = Value; + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl serde::Serialize for Array { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeSeq; + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for i in self { + seq.serialize_element(&i)?; + } + seq.end() + } +} diff --git a/perlmod/src/de.rs b/perlmod/src/de.rs new file mode 100644 index 0000000..16d5f22 --- /dev/null +++ b/perlmod/src/de.rs @@ -0,0 +1,551 @@ +use serde::de::{self, DeserializeOwned, DeserializeSeed, MapAccess, SeqAccess, Visitor}; + +use crate::error::Error; +use crate::scalar::Type; +use crate::Value; +use crate::{array, ffi, hash}; + +pub struct Deserializer { + input: Value, + option_allowed: bool, +} + +pub fn from_value(input: Value) -> Result +where + T: DeserializeOwned, +{ + let mut deserializer = Deserializer::from_value(input); + let out = T::deserialize(&mut deserializer)?; + Ok(out) +} + +impl Deserializer { + pub fn from_value(input: Value) -> Self { + Deserializer { + input, + option_allowed: true, + } + } + + pub fn from_some_value(input: Value) -> Self { + Deserializer { + input, + option_allowed: false, + } + } + + fn deref_current(&mut self) -> Result<(), Error> { + while let Value::Reference(_) = &self.input { + self.input = self.input.dereference().ok_or_else(|| { + Error::new("failed to dereference a reference while deserializing") + })?; + } + Ok(()) + } + + fn sanity_check(&mut self) -> Result<(), Error> { + if let Value::Scalar(value) = &self.input { + match value.ty() { + Type::Scalar(_) => Ok(()), + Type::Other(_) => Error::fail("cannot deserialize weird magic perl values"), + // These are impossible as they are all handled by different Value enum types: + Type::Reference => Error::fail("Value::Scalar: containing a reference"), + Type::Array => Error::fail("Value::Scalar: containing an array"), + Type::Hash => Error::fail("Value::Scalar: containing a hash"), + } + } else { + Ok(()) + } + } + + fn get(&mut self) -> Result<&Value, Error> { + self.deref_current()?; + self.sanity_check()?; + Ok(&self.input) + } +} + +impl Deserializer { + /// deserialize_any, preferring a string value + fn deserialize_any_string<'de, V>(&mut self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.get()? { + Value::Scalar(value) => match value.ty() { + Type::Scalar(flags) => { + use crate::scalar::Flags; + + if flags.contains(Flags::STRING) { + visitor.visit_str(value.pv_utf8()) + } else if flags.contains(Flags::DOUBLE) { + visitor.visit_f64(value.nv()) + } else if flags.contains(Flags::INTEGER) { + visitor.visit_i64(value.iv() as i64) + } else { + visitor.visit_unit() + } + } + _ => unreachable!(), + }, + Value::Hash(value) => visitor.visit_map(HashAccess::new(value)), + Value::Array(value) => visitor.visit_seq(ArrayAccess::new(value)), + Value::Reference(_) => unreachable!(), + } + } + + /// deserialize_any, preferring an integer value + fn deserialize_any_iv<'de, V>(&mut self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.get()? { + Value::Scalar(value) => match value.ty() { + Type::Scalar(flags) => { + use crate::scalar::Flags; + + if flags.contains(Flags::INTEGER) { + visitor.visit_i64(value.iv() as i64) + } else if flags.contains(Flags::DOUBLE) { + visitor.visit_f64(value.nv()) + } else if flags.contains(Flags::STRING) { + visitor.visit_str(value.pv_utf8()) + } else { + visitor.visit_unit() + } + } + _ => unreachable!(), + }, + Value::Hash(value) => visitor.visit_map(HashAccess::new(value)), + Value::Array(value) => visitor.visit_seq(ArrayAccess::new(value)), + Value::Reference(_) => unreachable!(), + } + } + + /// deserialize_any, preferring a float value + fn deserialize_any_nv<'de, V>(&mut self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.get()? { + Value::Scalar(value) => match value.ty() { + Type::Scalar(flags) => { + use crate::scalar::Flags; + + if flags.contains(Flags::DOUBLE) { + visitor.visit_f64(value.nv()) + } else if flags.contains(Flags::INTEGER) { + visitor.visit_i64(value.iv() as i64) + } else if flags.contains(Flags::STRING) { + visitor.visit_str(value.pv_utf8()) + } else { + visitor.visit_unit() + } + } + _ => unreachable!(), + }, + Value::Hash(value) => visitor.visit_map(HashAccess::new(value)), + Value::Array(value) => visitor.visit_seq(ArrayAccess::new(value)), + Value::Reference(_) => unreachable!(), + } + } +} + +impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any_string(visitor) + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.get()? { + Value::Scalar(value) => match value.ty() { + Type::Scalar(flags) => { + use crate::scalar::Flags; + + if flags.is_empty() || flags.intersects(Flags::INTEGER | Flags::DOUBLE) { + visitor.visit_bool(unsafe { ffi::RSPL_SvTRUE(value.sv()) }) + } else { + Error::fail("expected bool value") + } + } + _ => unreachable!(), + }, + Value::Hash(value) => visitor.visit_map(HashAccess::new(value)), + Value::Array(value) => visitor.visit_seq(ArrayAccess::new(value)), + Value::Reference(_) => unreachable!(), + } + } + + fn deserialize_i8(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any_iv(visitor) + } + + fn deserialize_u8(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any_iv(visitor) + } + + fn deserialize_i16(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any_iv(visitor) + } + + fn deserialize_u16(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any_iv(visitor) + } + + fn deserialize_i32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any_iv(visitor) + } + + fn deserialize_u32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any_iv(visitor) + } + + fn deserialize_i64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any_iv(visitor) + } + + fn deserialize_u64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any_iv(visitor) + } + + fn deserialize_f32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any_nv(visitor) + } + + fn deserialize_f64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any_nv(visitor) + } + + fn deserialize_char(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.get()? { + Value::Scalar(value) => match value.ty() { + Type::Scalar(flags) => { + use crate::scalar::Flags; + + if flags.contains(Flags::INTEGER) { + let c = value.iv(); + if c < 0x100 { + visitor.visit_char(c as u8 as char) + } else { + visitor.visit_i64(c as i64) + } + } else if flags.contains(Flags::DOUBLE) { + visitor.visit_f64(value.nv()) + } else if flags.contains(Flags::STRING) { + let s = value.pv_utf8(); + let mut chars = s.chars(); + match chars.next() { + Some(ch) if chars.next().is_none() => visitor.visit_char(ch), + _ => visitor.visit_str(value.pv_utf8()), + } + } else { + visitor.visit_unit() + } + } + _ => unreachable!(), + }, + Value::Hash(value) => visitor.visit_map(HashAccess::new(value)), + Value::Array(value) => visitor.visit_seq(ArrayAccess::new(value)), + Value::Reference(_) => unreachable!(), + } + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.get()? { + Value::Scalar(value) => match value.ty() { + Type::Scalar(flags) => { + use crate::scalar::Flags; + + if flags.contains(Flags::STRING) { + visitor.visit_bytes(value.pv_bytes()) + } else if flags.contains(Flags::DOUBLE) { + visitor.visit_f64(value.nv()) + } else if flags.contains(Flags::INTEGER) { + visitor.visit_i64(value.iv() as i64) + } else { + visitor.visit_unit() + } + } + _ => unreachable!(), + }, + Value::Hash(value) => visitor.visit_map(HashAccess::new(value)), + Value::Array(value) => visitor.visit_seq(ArrayAccess::new(value)), + Value::Reference(_) => unreachable!(), + } + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_bytes(visitor) + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.option_allowed { + if let Value::Scalar(value) = self.get()? { + if let Type::Scalar(flags) = value.ty() { + if flags.is_empty() { + return visitor.visit_none(); + } + } + } + self.option_allowed = false; + let res = visitor.visit_some(&mut *self); + self.option_allowed = true; + res + } else { + self.deserialize_any(visitor) + } + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } + + fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } + + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } + + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } + + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } +} + +pub struct HashAccess<'a> { + hash: &'a hash::Hash, + entry: *mut ffi::HE, + finished: bool, + at_value: bool, +} + +impl<'a> HashAccess<'a> { + pub fn new(value: &'a hash::Hash) -> Self { + drop(value.shared_iter()); // reset iterator + Self { + hash: &value, + entry: std::ptr::null_mut(), + finished: false, + at_value: false, + } + } +} + +impl<'de, 'a> MapAccess<'de> for HashAccess<'a> { + type Error = Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Error> + where + K: DeserializeSeed<'de>, + { + if self.finished { + return Ok(None); + } + + if self.entry.is_null() { + self.entry = unsafe { ffi::RSPL_hv_iternext(self.hash.hv()) }; + if self.entry.is_null() { + self.finished = true; + return Ok(None); + } + } else if self.at_value { + return Error::fail("map access value skipped"); + } + + self.at_value = true; + + let key = unsafe { Value::from_raw_ref(ffi::RSPL_hv_iterkeysv(self.entry)) }; + seed.deserialize(&mut Deserializer::from_value(key)) + .map(Some) + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: DeserializeSeed<'de>, + { + if self.finished { + return Error::fail("map access value requested after end"); + } + + if self.entry.is_null() || !self.at_value { + return Error::fail("map access key skipped"); + } + + self.at_value = false; + + let value = + unsafe { Value::from_raw_ref(ffi::RSPL_hv_iterval(self.hash.hv(), self.entry)) }; + self.entry = std::ptr::null_mut(); + + seed.deserialize(&mut Deserializer::from_value(value)) + } +} + +pub struct ArrayAccess<'a> { + iter: array::Iter<'a>, +} + +impl<'a> ArrayAccess<'a> { + pub fn new(value: &'a array::Array) -> Self { + Self { iter: value.iter() } + } +} + +impl<'de, 'a> SeqAccess<'de> for ArrayAccess<'a> { + type Error = Error; + + fn next_element_seed(&mut self, seed: K) -> Result, Error> + where + K: DeserializeSeed<'de>, + { + self.iter + .next() + .map(move |value| seed.deserialize(&mut Deserializer::from_value(value))) + .transpose() + } +} diff --git a/perlmod/src/error.rs b/perlmod/src/error.rs new file mode 100644 index 0000000..51f567a --- /dev/null +++ b/perlmod/src/error.rs @@ -0,0 +1,45 @@ +#[derive(Debug)] +pub struct CastError; + +impl std::fmt::Display for CastError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "wrong type") + } +} + +impl std::error::Error for CastError {} + +#[derive(Clone, Debug)] +pub struct Error(String); + +impl Error { + #[inline] + pub fn new(s: &str) -> Self { + Self(s.to_string()) + } + + #[inline] + pub fn fail(s: &str) -> Result { + Err(Self(s.to_string())) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "error: {}", self.0) + } +} + +impl std::error::Error for Error {} + +impl serde::de::Error for Error { + fn custom(msg: T) -> Self { + Self(msg.to_string()) + } +} + +impl serde::ser::Error for Error { + fn custom(msg: T) -> Self { + Self(msg.to_string()) + } +} diff --git a/perlmod/src/ffi.rs b/perlmod/src/ffi.rs new file mode 100644 index 0000000..18caca8 --- /dev/null +++ b/perlmod/src/ffi.rs @@ -0,0 +1,158 @@ +//! Unsafe ffi code we use. +//! +//! You should not use this code directly. This is used by the binding generator to implement xsubs +//! for exported functions. + +#[repr(C)] +pub struct CV { + _ffi: usize, +} + +#[repr(C)] +pub struct SV { + _ffi: usize, +} + +#[repr(C)] +pub struct AV { + _ffi: usize, +} + +#[repr(C)] +pub struct HV { + _ffi: usize, +} + +#[repr(C)] +pub struct HE { + _ffi: usize, +} + +// in our glue: +#[link(name = "glue", kind = "static")] +extern "C" { + pub fn RSPL_StackMark_count(this: usize) -> usize; + + pub fn RSPL_stack_get(offset: usize) -> *mut SV; + + pub fn RSPL_croak_sv(sv: *mut SV) -> !; + pub fn RSPL_SvNV(sv: *mut SV) -> f64; + pub fn RSPL_SvIV(sv: *mut SV) -> isize; + pub fn RSPL_SvPVutf8(sv: *mut SV, len: *mut libc::size_t) -> *const libc::c_char; + pub fn RSPL_SvPV(sv: *mut SV, len: *mut libc::size_t) -> *const libc::c_char; + pub fn RSPL_SvPVbyte(sv: *mut SV, len: *mut libc::size_t) -> *const libc::c_char; + pub fn RSPL_sv_2mortal(sv: *mut SV) -> *mut SV; + pub fn RSPL_get_undef() -> *mut SV; + pub fn RSPL_get_yes() -> *mut SV; + pub fn RSPL_get_no() -> *mut SV; + pub fn RSPL_pop_markstack_ptr() -> usize; + pub fn RSPL_stack_resize_by(count: isize); + pub fn RSPL_stack_shrink_to(count: usize); + pub fn RSPL_stack_sp() -> *mut *mut SV; + pub fn RSPL_newRV_inc(sv: *mut SV) -> *mut SV; + pub fn RSPL_newSViv(v: isize) -> *mut SV; + pub fn RSPL_newSVuv(v: usize) -> *mut SV; + pub fn RSPL_newSVnv(v: f64) -> *mut SV; + pub fn RSPL_newSVpvn(v: *const libc::c_char, len: libc::size_t) -> *mut SV; + pub fn RSPL_SvREFCNT_inc(sv: *mut SV) -> *mut SV; + pub fn RSPL_SvREFCNT_dec(sv: *mut SV); + pub fn RSPL_is_reference(sv: *mut SV) -> bool; + pub fn RSPL_dereference(sv: *mut SV) -> *mut SV; + pub fn RSPL_is_array(sv: *mut SV) -> bool; + pub fn RSPL_is_hash(sv: *mut SV) -> bool; + pub fn RSPL_type_flags(sv: *mut SV) -> u32; + pub fn RSPL_svtype(sv: *mut SV) -> u32; + pub fn RSPL_SvTRUE(sv: *mut SV) -> bool; + + pub fn RSPL_newAV() -> *mut AV; + pub fn RSPL_av_extend(av: *mut AV, len: libc::ssize_t); + pub fn RSPL_av_push(av: *mut AV, sv: *mut SV); + pub fn RSPL_av_pop(av: *mut AV) -> *mut SV; + pub fn RSPL_av_len(av: *mut AV) -> usize; + pub fn RSPL_av_fetch(av: *mut AV, index: libc::ssize_t, lval: i32) -> *mut *mut SV; + + pub fn RSPL_newHV() -> *mut HV; + pub fn RSPL_HvTOTALKEYS(hv: *mut HV) -> usize; + pub fn RSPL_hv_fetch( + hv: *mut HV, + key: *const libc::c_char, + klen: i32, + lval: i32, + ) -> *mut *mut SV; + /// Always consumes ownership of `value`. + pub fn RSPL_hv_store(hv: *mut HV, key: *const libc::c_char, klen: i32, value: *mut SV) -> bool; + pub fn RSPL_hv_store_ent(hv: *mut HV, key: *mut SV, value: *mut SV) -> bool; + pub fn RSPL_hv_iterinit(hv: *mut HV); + pub fn RSPL_hv_iternextsv( + hv: *mut HV, + key: *mut *mut libc::c_char, + retlen: *mut i32, + ) -> *mut SV; + pub fn RSPL_hv_iternext(hv: *mut HV) -> *mut HE; + pub fn RSPL_hv_iterkeysv(he: *mut HE) -> *mut SV; + pub fn RSPL_hv_iterval(hv: *mut HV, he: *mut HE) -> *mut SV; +} + +/// Argument marker for the stack. +pub struct StackMark(usize); + +impl StackMark { + pub fn count(&self) -> usize { + unsafe { RSPL_StackMark_count(self.0) } + } + + pub fn iter(&self) -> StackIter { + StackIter { + at: self.0 + 1, + end: self.0 + 1 + self.count(), + } + } + + pub unsafe fn set_stack(self) { + RSPL_stack_shrink_to(self.0); + } +} + +pub struct StackIter { + at: usize, + end: usize, +} + +impl Iterator for StackIter { + type Item = crate::Scalar; + + fn next(&mut self) -> Option { + let at = self.at; + if at == self.end { + return None; + } + unsafe { + let ptr = RSPL_stack_get(self.at); + self.at += 1; + if ptr.is_null() { + None + } else { + Some(crate::Scalar::from_raw_ref(ptr)) + } + } + } +} + +pub unsafe fn pop_arg_mark() -> StackMark { + StackMark(RSPL_pop_markstack_ptr()) +} + +pub unsafe fn stack_push_raw(value: *mut SV) { + RSPL_stack_resize_by(1); + *RSPL_stack_sp() = value; +} + +pub fn stack_push(value: crate::Mortal) { + unsafe { + stack_push_raw(value.into_raw()); + } +} + +pub unsafe fn croak(sv: *mut SV) { + RSPL_croak_sv(sv); +} diff --git a/perlmod/src/glue.c b/perlmod/src/glue.c new file mode 100644 index 0000000..a12f507 --- /dev/null +++ b/perlmod/src/glue.c @@ -0,0 +1,295 @@ +#include +#include +#include +#include +#include +#include + +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" +#include "ppport.h" + +typedef uintptr_t usize; +typedef intptr_t isize; + +extern usize RSPL_StackMark_count(usize self) { + SV **ptr = PL_stack_base + self; + if (ptr > PL_stack_sp) { + return 0; + } + return PL_stack_sp - ptr; +} + +extern SV* RSPL_stack_get(usize offset) { + SV **ptr = PL_stack_base + offset; + if (ptr > PL_stack_sp) { + return NULL; + } + return *ptr; +} + +extern void RSPL_croak_sv(SV *sv) { + croak_sv(sv); +} + +extern double RSPL_SvNV(SV *sv) { + return SvNV(sv); +} + +extern isize RSPL_SvIV(SV *sv) { + return SvIV(sv); +} + +extern const char* RSPL_SvPVutf8(SV *sv, size_t *out_len) { + size_t length; + const char *out = SvPVutf8(sv, length); + *out_len = length; + return out; +} + +extern const char* RSPL_SvPV(SV *sv, size_t *out_len) { + size_t length; + const char *out = SvPV(sv, length); + *out_len = length; + return out; +} + +extern const char* RSPL_SvPVbyte(SV *sv, size_t *out_len) { + size_t length; + const char *out = SvPVbyte(sv, length); + *out_len = length; + return out; +} + +extern SV* RSPL_sv_2mortal(SV *sv) { + return sv_2mortal(sv); +} + +extern SV* RSPL_get_undef() { + return &PL_sv_undef; +} + +extern SV* RSPL_get_yes() { + return &PL_sv_yes; +} + +extern SV* RSPL_get_no() { + return &PL_sv_no; +} + +extern usize RSPL_PL_markstack_ptr() { + return *PL_markstack_ptr; +} + +extern usize RSPL_pop_markstack_ptr() { + return *PL_markstack_ptr--; +} + +extern void RSPL_stack_shrink_to(usize count) { + PL_stack_sp = PL_stack_base + count; +} + +extern void RSPL_stack_resize_by(isize count) { + if (count > 0) { + isize space = PL_stack_max - PL_stack_sp; + if (space < count) { + Perl_stack_grow(aTHX_ PL_stack_sp, PL_stack_sp, count - space); + } + } + PL_stack_sp += count; +} + +extern SV** RSPL_stack_sp() { + return PL_stack_sp; +} + +extern SV* RSPL_newRV_inc(SV *rv) { + return newRV_inc(rv); +} + +extern SV* RSPL_newSViv(isize v) { + return newSViv(v); +} + +extern SV* RSPL_newSVuv(usize v) { + return newSVuv(v); +} + +extern SV* RSPL_newSVnv(double v) { + return newSVnv(v); +} + +extern SV* RSPL_newSVpvn(const char *v, size_t len) { + return newSVpvn(v, len); +} + +extern SV* RSPL_SvREFCNT_inc(SV *sv) { + return SvREFCNT_inc(sv); +} + +extern void RSPL_SvREFCNT_dec(SV *sv) { + return SvREFCNT_dec(sv); +} + +extern bool RSPL_is_scalar(SV *sv) { + return SvTYPE(sv) < SVt_PVAV; +} + +extern bool RSPL_SvTRUE(SV *sv) { + return SvTRUE(sv); +} + +// This must be the same as in rust! +#define TYPE_FLAG_INT 1 +#define TYPE_FLAG_DOUBLE 2 +#define TYPE_FLAG_STRING 4 + +static const uint32_t type_flags[16] = { + [SVt_NULL] = 0, + [SVt_IV] = TYPE_FLAG_INT, + [SVt_NV] = TYPE_FLAG_INT | TYPE_FLAG_DOUBLE, + [SVt_PV] = TYPE_FLAG_STRING, + [SVt_PVIV] = TYPE_FLAG_STRING | TYPE_FLAG_INT, + [SVt_PVNV] = TYPE_FLAG_STRING | TYPE_FLAG_INT | TYPE_FLAG_DOUBLE, + [SVt_PVMG] = ~0, +}; + +extern uint32_t RSPL_svtype(SV *sv) { + return SvTYPE(sv); +} + +extern uint32_t RSPL_type_flags(SV *sv) { + return type_flags[SvTYPE(sv)]; +} + +extern bool RSPL_has_integer(SV *sv) { + return 0 != (type_flags[SvTYPE(sv)] & TYPE_FLAG_INT); +} + +extern bool RSPL_has_double(SV *sv) { + return 0 != (type_flags[SvTYPE(sv)] & TYPE_FLAG_DOUBLE); +} + +extern bool RSPL_has_string(SV *sv) { + return 0 != (type_flags[SvTYPE(sv)] & TYPE_FLAG_STRING); +} + +extern SV* RSPL_SvRV(SV *sv) { + return SvRV(sv); +} + +extern SV* RSPL_dereference(SV *sv) { + return SvROK(sv) ? SvRV(sv) : NULL; +} + +extern bool RSPL_is_reference(SV *sv) { + return SvROK(sv); +} + +extern bool RSPL_is_array(SV *sv) { + return SvTYPE(sv) == SVt_PVAV; +} + +extern bool RSPL_is_hash(SV *sv) { + return SvTYPE(sv) == SVt_PVHV; +} + +extern AV* RSPL_newAV() { + return newAV(); +} + +extern usize RSPL_av_len(AV *av) { + return av_len(av); +} + +extern void RSPL_av_extend(AV *av, ssize_t len) { + av_extend(av, len); +} + +extern void RSPL_av_push(AV *av, SV *sv) { + av_push(av, sv); +} + +extern SV* RSPL_av_pop(AV *av) { + return av_pop(av); +} + +extern SV** RSPL_av_fetch(AV *av, ssize_t index, int32_t lval) { + return av_fetch(av, index, lval); +} + +extern HV* RSPL_newHV() { + return newHV(); +} + +extern usize RSPL_HvTOTALKEYS(HV *hv) { + return HvTOTALKEYS(hv); +} + +extern SV** RSPL_hv_fetch(HV *hv, const char *key, int32_t klen, int32_t lval) { + return hv_fetch(hv, key, klen, lval); +} + +/// ALWAYS takes ownership of 'value'. +extern bool RSPL_hv_store(HV *hv, const char *key, int32_t klen, SV *value) { + if (hv_store(hv, key, klen, value, 0) == NULL) { + SvREFCNT_dec(value); + return false; + } else { + return true; + } +} + +extern bool RSPL_hv_store_ent(HV *hv, SV *key, SV *value) { + if (hv_store_ent(hv, key, value, 0) == NULL) { + SvREFCNT_dec(value); + return false; + } else { + return true; + } +} + +extern void RSPL_hv_iterinit(HV *hv) { + hv_iterinit(hv); +} + +extern SV* RSPL_hv_iternextsv(HV *hv, char **key, int32_t *retlen) { + return hv_iternextsv(hv, key, retlen); +} + +extern HE* RSPL_hv_iternext(HV *hv) { + return hv_iternext(hv); +} + +extern SV* RSPL_hv_iterkeysv(HE *he) { + return hv_iterkeysv(he); +} + +extern SV* RSPL_hv_iterval(HV *hv, HE *he) { + return hv_iterval(hv, he); +} + +/* +These make are convoluted brainfarts: + SVt_NULL undef + SVt_IV all the above or int + SVt_NV all the above or a double + SVt_PV undef or a string + SVt_PVIV PV or IV + SVt_PVNV PV or NV + SVt_PVMG all of the above with tentacles, 2 heads and unicorn poop on top + +These make some sense + SVt_INVLIST Bleeding smelly perl guts + SVt_REGEXP Sandpaper + SVt_PVGV Typeglob + SVt_PVLV C++ style reference to another scalar (implicit deref) + +These make sense + SVt_PVAV Arrays + SVt_PVHV Hashes + SVt_PVCV Subroutine + SVt_PVFM Formats + SVt_PVIO I/O objects +*/ diff --git a/perlmod/src/hash.rs b/perlmod/src/hash.rs new file mode 100644 index 0000000..4abc399 --- /dev/null +++ b/perlmod/src/hash.rs @@ -0,0 +1,208 @@ +use std::convert::TryFrom; + +use crate::error::CastError; +use crate::ffi::{self, HV, SV}; +use crate::scalar::{Scalar, ScalarRef}; +use crate::Value; + +/// An owned reference to a perl hash value (HV). +/// +/// This keeps a reference to a value which lives in the perl interpreter. +#[repr(transparent)] +pub struct Hash(Scalar); + +#[allow(clippy::new_without_default)] +impl Hash { + /// Create a new array value. + pub fn new() -> Self { + unsafe { Self::from_raw_move(ffi::RSPL_newHV()) } + } + + /// Turn this into a `Scalar`. The underlying perl value does not change, this is a pure type + /// cast down to a less specific "pointer" type. + pub fn into_scalar(self) -> Scalar { + self.0 + } + + /// Get the internal perl value as a low-level `HV` pointer. + pub fn hv(&self) -> *mut HV { + self.0.sv() as *mut HV + } + + /// "Downcast" a `Scalar` into a `Hash`. The caller must verify that this is legal. + /// + /// # Safety + /// + /// The caller must verify that this is legal. + pub unsafe fn from_scalar(scalar: Scalar) -> Self { + Self(scalar) + } + + /// Take over a raw `HV` value, assuming that we then own a reference to it. + /// + /// # Safety + /// + /// This does not change the value's reference count, it is assumed that we're taking ownership + /// of one reference. + /// + /// The caller must ensure that it is safe to decrease the reference count later on, or use + /// `into_raw()` instead of letting the `Hash` get dropped. + pub unsafe fn from_raw_move(ptr: *mut HV) -> Self { + Self(Scalar::from_raw_move(ptr as *mut SV)) + } + + /// Create a new reference to an existing `HV` value. This will increase the value's reference + /// count. + /// + /// # Safety + /// + /// The caller may still need to decrease the reference count for the `ptr` source value. + pub unsafe fn from_raw_ref(ptr: *mut HV) -> Self { + Self(Scalar::from_raw_ref(ptr as *mut SV)) + } + + /// Create a new reference to this value. + pub fn clone_ref(&self) -> Self { + Self(self.0.clone_ref()) + } + + /// Get the number of keys in this hash. + pub fn len(&self) -> usize { + unsafe { ffi::RSPL_HvTOTALKEYS(self.hv()) } + } + + /// Check if this is an empty hash. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get a value from the hash. Note that this only uses utf8 strings. For a more generic method + /// see `get_by_bytes`. + pub fn get(&self, key: &str) -> Option { + self.get_by_bytes(key.as_bytes()) + } + + /// Get a value from the hash, but with a raw byte string as index. + pub fn get_by_bytes(&self, key: &[u8]) -> Option { + let sv: *mut *mut SV = unsafe { + ffi::RSPL_hv_fetch( + self.hv(), + key.as_ptr() as *const libc::c_char, + key.len() as i32, + 0, + ) + }; + if sv.is_null() { + None + } else { + Some(unsafe { Value::from_raw_ref(*sv) }) + } + } + + /// Insert a value into the hash. + pub fn insert(&self, key: &str, value: Value) { + self.insert_by_bytes(key.as_bytes(), value); + } + + /// Insert a value into the hash with a byte string as key. + pub fn insert_by_bytes(&self, key: &[u8], value: Value) { + unsafe { + ffi::RSPL_hv_store( + self.hv(), + key.as_ptr() as *const u8 as *const libc::c_char, + key.len() as i32, + value.into_raw(), + ); + } + } + + /// Insert a value using an existin value as a key. + pub fn insert_by_value(&self, key: &Value, value: Value) { + unsafe { + ffi::RSPL_hv_store_ent(self.hv(), key.sv(), value.sv()); + } + } + + /// Get the *shared* iterator over this hash's elements. + /// + /// Note that this uses the hash's internal iterator, so any other iterator as well as `each` + /// statement within perl code is affected by it, and it is usually a bad idea to have multiple + /// iterators over the same hash simultaneously. + pub fn shared_iter(&self) -> Iter { + unsafe { + ffi::RSPL_hv_iterinit(self.hv()); + } + Iter { hash: self } + } +} + +impl core::ops::Deref for Hash { + type Target = ScalarRef; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl core::ops::DerefMut for Hash { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.0 + } +} + +impl TryFrom for Hash { + type Error = CastError; + + fn try_from(scalar: Scalar) -> Result { + if unsafe { ffi::RSPL_is_hash(scalar.sv()) } { + Ok(Self(scalar)) + } else { + Err(CastError) + } + } +} + +impl std::fmt::Debug for Hash { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{{HASH}}") + } +} + +pub struct Iter<'a> { + hash: &'a Hash, +} + +impl<'a> Iterator for Iter<'a> { + type Item = (&'a [u8], Value); + + fn next(&mut self) -> Option { + let mut key: *mut libc::c_char = std::ptr::null_mut(); + let mut keylen: i32 = 0; + let value = unsafe { ffi::RSPL_hv_iternextsv(self.hash.hv(), &mut key, &mut keylen) }; + if value.is_null() { + return None; + } + + unsafe { + Some(( + std::slice::from_raw_parts(key as *mut u8, keylen as usize), + Value::from_raw_ref(value), + )) + } + } +} + +impl serde::Serialize for Hash { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeMap; + let mut map = serializer.serialize_map(Some(self.len()))?; + for (k, v) in self.shared_iter() { + map.serialize_key(&k)?; + map.serialize_value(&v)?; + } + map.end() + } +} diff --git a/perlmod/src/lib.rs b/perlmod/src/lib.rs new file mode 100644 index 0000000..73faafc --- /dev/null +++ b/perlmod/src/lib.rs @@ -0,0 +1,29 @@ +pub(crate) mod error; + +pub mod de; +pub mod ffi; +pub mod ser; + +#[doc(inline)] +pub use de::from_value; +#[doc(inline)] +pub use ser::to_value; + +pub mod scalar; +#[doc(inline)] +pub use scalar::{Mortal, Scalar}; + +pub mod array; +#[doc(inline)] +pub use array::Array; + +pub mod hash; +#[doc(inline)] +pub use hash::Hash; + +pub mod value; +#[doc(inline)] +pub use value::Value; + +#[cfg(feature = "exporter")] +pub use perlmod_macro::package; diff --git a/perlmod/src/scalar.rs b/perlmod/src/scalar.rs new file mode 100644 index 0000000..f057929 --- /dev/null +++ b/perlmod/src/scalar.rs @@ -0,0 +1,335 @@ +use bitflags::bitflags; + +use crate::ffi::{self, SV}; +use crate::Value; + +/// An owned reference to a perl value. +/// +/// This keeps a reference to a value which lives in the perl interpreter. +/// This derefs to a `ScalarRef` which implements most of the basic functionality common to all +/// `SV` related types. +/// +/// This implements `Send` but not `Sync`, because it is effectively the same as an `Arc` to a +/// value which is inherently `Send` but not `Sync`. +#[repr(transparent)] +pub struct Scalar(*mut SV); + +unsafe impl Send for Scalar {} + +impl Scalar { + /// Turn this into a "mortal" value. This will move this value's owned reference onto the + /// mortal stack to be cleaned up after the next perl statement if no more references exist. + /// + /// (To be garbage collected after this perl-statement.) + pub fn into_mortal(self) -> Mortal { + Mortal(unsafe { ffi::RSPL_sv_2mortal(self.into_raw()) }) + } + + /// Turn this into a raw `SV` transferring control of one reference count. + pub fn into_raw(self) -> *mut SV { + let ptr = self.0; + core::mem::forget(self); + ptr + } + + /// Create a wrapping `Scalar` from an `SV` pointer. The `Scalar` takes over the owned + /// reference from the passed `SV`, which means it must not be a mortal reference. + /// + /// # Safety + /// + /// This does not change the value's reference count, it is assumed that we're taking ownership + /// of one reference. + /// + /// The caller must ensure that it is safe to decrease the reference count later on, or use + /// `into_raw()` instead of letting the `Scalar` get dropped. + pub unsafe fn from_raw_move(ptr: *mut SV) -> Self { + Self(ptr) + } + + /// Increase the reference count on an `SV` pointer. + /// + /// # Safety + /// + /// 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)) + } + + /// Create a reference to `PL_sv_undef`. + pub fn new_undef() -> Self { + unsafe { Self::from_raw_ref(ffi::RSPL_get_undef()) } + } + + /// Create a reference to `PL_sv_yes`. + pub fn new_yes() -> Self { + unsafe { Self::from_raw_ref(ffi::RSPL_get_yes()) } + } + + /// Create a reference to `PL_sv_no`. + pub fn new_no() -> Self { + unsafe { Self::from_raw_ref(ffi::RSPL_get_no()) } + } + + /// Create a new integer value: + pub fn new_int(v: isize) -> Self { + unsafe { Self::from_raw_move(ffi::RSPL_newSViv(v)) } + } + + /// Create a new unsigned integer value: + pub fn new_uint(v: usize) -> Self { + unsafe { Self::from_raw_move(ffi::RSPL_newSVuv(v)) } + } + + /// Create a new floating point value. + pub fn new_float(v: f64) -> Self { + unsafe { Self::from_raw_move(ffi::RSPL_newSVnv(v)) } + } + + /// Create a new string value. + pub fn new_string(s: &str) -> Self { + Self::new_bytes(s.as_bytes()) + } + + /// Create a new byte string. + pub fn new_bytes(s: &[u8]) -> Self { + unsafe { + Self::from_raw_move(ffi::RSPL_newSVpvn( + s.as_ptr() as *const libc::c_char, + s.len() as libc::size_t, + )) + } + } +} + +impl Drop for Scalar { + fn drop(&mut self) { + unsafe { + ffi::RSPL_SvREFCNT_dec(self.sv()); + } + } +} + +impl core::ops::Deref for Scalar { + type Target = ScalarRef; + + fn deref(&self) -> &Self::Target { + unsafe { &*(self.0 as *mut ScalarRef) } + } +} + +impl core::ops::DerefMut for Scalar { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *(self.0 as *mut ScalarRef) } + } +} + +#[repr(transparent)] +pub struct Mortal(*mut SV); + +impl Mortal { + /// Get the inner value. + pub fn into_raw(self) -> *mut SV { + self.0 + } +} + +impl core::ops::Deref for Mortal { + type Target = ScalarRef; + + fn deref(&self) -> &Self::Target { + unsafe { &*(self.0 as *mut ScalarRef) } + } +} + +impl core::ops::DerefMut for Mortal { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *(self.0 as *mut ScalarRef) } + } +} + +pub struct ScalarRef; + +bitflags! { + /// Represents the types a `Value` can contain. Values can usually contain multiple scalar types + /// at once and it is unclear which is the "true" type, so we can only check whether a value + /// contains something, not what it is originally meant to be! + /// + /// NOTE: The values must be the same as in our c glue code! + pub struct Flags: u8 { + const INTEGER = 1; + const DOUBLE = 2; + const STRING = 4; + } +} + +/// While scalar types aren't clearly different from another, complex types are, so we do +/// distinguish between these: +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Type { + Scalar(Flags), + Reference, + Array, + Hash, + Other(u8), +} + +impl ScalarRef { + pub(crate) fn sv(&self) -> *mut SV { + self as *const ScalarRef as *const SV as *mut SV + } + + /// Get some information about the value's type. + pub fn ty(&self) -> 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) + } + } + } + } + + /// Dereference this reference. + pub fn dereference(&self) -> Option { + let ptr = unsafe { ffi::RSPL_dereference(self.sv()) }; + if ptr.is_null() { + None + } else { + Some(unsafe { Scalar::from_raw_ref(ptr) }) + } + } + + /// Coerce to a double value. (perlxs SvNV). + pub fn nv(&self) -> f64 { + unsafe { ffi::RSPL_SvNV(self.sv()) } + } + + /// Coerce to an integer value. (perlxs SvIV). + pub fn iv(&self) -> isize { + unsafe { ffi::RSPL_SvIV(self.sv()) } + } + + /// Coerce to an utf8 string value. (perlxs SvPVutf8) + pub fn pv_utf8(&self) -> &str { + unsafe { + let mut len: libc::size_t = 0; + let ptr = ffi::RSPL_SvPVutf8(self.sv(), &mut len) as *const u8; + std::str::from_utf8_unchecked(std::slice::from_raw_parts(ptr, len)) + } + } + + /// Coerce to a string without utf8 encoding. (perlxs SvPV) + pub fn pv_string_bytes(&self) -> &[u8] { + unsafe { + let mut len: libc::size_t = 0; + let ptr = ffi::RSPL_SvPV(self.sv(), &mut len) as *const u8; + std::slice::from_raw_parts(ptr, len) + } + } + + /// Coerce to a byte-string. (perlxs SvPVbyte) + pub fn pv_bytes(&self) -> &[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) + } + } + + /// Create another owned reference to this value. + pub fn clone_ref(&self) -> Scalar { + unsafe { Scalar::from_raw_ref(self.sv()) } + } + + /// Convenience check for SVt_NULL + pub fn is_undef(&self) -> bool { + 0 == unsafe { ffi::RSPL_type_flags(self.sv()) } + } + + /// Turn this into a `Value`. + pub fn into_value(self) -> Value { + Value::from_scalar(self.clone_ref()) + } +} + +impl std::fmt::Debug for Scalar { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let this: &ScalarRef = &self; + std::fmt::Debug::fmt(this, f) + } +} + +impl std::fmt::Debug for ScalarRef { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use std::fmt::Debug; + match self.ty() { + Type::Scalar(flags) => { + if flags.intersects(Flags::STRING) { + Debug::fmt(self.pv_utf8(), f) + } else if flags.intersects(Flags::INTEGER) { + write!(f, "{}", self.iv()) + } else if flags.intersects(Flags::DOUBLE) { + write!(f, "{}", self.nv()) + } else { + write!(f, "") + } + } + Type::Reference => write!(f, "<*REFERENCE>"), + Type::Array => write!(f, "<*ARRAY>"), + Type::Hash => write!(f, "<*HASH>"), + Type::Other(_) => write!(f, "<*PERLTYPE>"), + } + } +} + +impl serde::Serialize for Scalar { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::Error; + + match self.ty() { + Type::Scalar(flags) => { + if flags.contains(Flags::STRING) { + serializer.serialize_str(self.pv_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 { + serializer.serialize_unit() + } + } + Type::Other(_) => Err(S::Error::custom( + "cannot deserialize weird magic perl values", + )), + + // These are impossible as they are all handled by different Value enum types: + Type::Reference => Value::from( + self.dereference() + .ok_or_else(|| S::Error::custom("failed to dereference perl value"))?, + ) + .serialize(serializer), + Type::Array => { + let this = unsafe { crate::Array::from_raw_ref(self.sv() as *mut ffi::AV) }; + this.serialize(serializer) + } + Type::Hash => { + let this = unsafe { crate::Hash::from_raw_ref(self.sv() as *mut ffi::HV) }; + this.serialize(serializer) + } + } + } +} diff --git a/perlmod/src/ser.rs b/perlmod/src/ser.rs new file mode 100644 index 0000000..d6f0746 --- /dev/null +++ b/perlmod/src/ser.rs @@ -0,0 +1,376 @@ +use serde::{ser, Serialize}; + +use crate::error::Error; +use crate::Value; +use crate::{array, hash}; + +pub struct Serializer; + +pub fn to_value(value: &T) -> Result +where + T: Serialize, +{ + value.serialize(&mut Serializer) +} + +pub struct SerHash { + hash: hash::Hash, + key: Option, +} + +pub struct SerArray { + array: array::Array, +} + +pub struct SerVariant { + hash: hash::Hash, + inner: T, +} + +impl<'a> ser::Serializer for &'a mut Serializer { + type Ok = Value; + type Error = Error; + + type SerializeSeq = SerArray; + type SerializeTuple = SerArray; + type SerializeTupleStruct = SerArray; + type SerializeTupleVariant = SerVariant; + type SerializeMap = SerHash; + type SerializeStruct = SerHash; + type SerializeStructVariant = SerVariant; + + fn serialize_bool(self, v: bool) -> Result { + Ok(Value::new_uint(if v { 1 } else { 0 })) + } + + fn serialize_i8(self, v: i8) -> Result { + self.serialize_i64(i64::from(v)) + } + + fn serialize_i16(self, v: i16) -> Result { + self.serialize_i64(i64::from(v)) + } + + fn serialize_i32(self, v: i32) -> Result { + self.serialize_i64(i64::from(v)) + } + + fn serialize_i64(self, v: i64) -> Result { + Ok(Value::new_int(v as isize)) + } + + fn serialize_u8(self, v: u8) -> Result { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u16(self, v: u16) -> Result { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u32(self, v: u32) -> Result { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u64(self, v: u64) -> Result { + Ok(Value::new_uint(v as usize)) + } + + fn serialize_f32(self, v: f32) -> Result { + self.serialize_f64(f64::from(v)) + } + + fn serialize_f64(self, v: f64) -> Result { + Ok(Value::new_float(v)) + } + + fn serialize_char(self, v: char) -> Result { + self.serialize_str(&v.to_string()) + } + + fn serialize_str(self, v: &str) -> Result { + Ok(Value::new_string(v)) + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + Ok(Value::new_bytes(v)) + } + + fn serialize_none(self) -> Result { + self.serialize_unit() + } + + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + Ok(Value::new_undef()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.serialize_unit() + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + self.serialize_str(variant) + } + + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + let value = value.serialize(&mut Serializer)?; + let hash = hash::Hash::new(); + hash.insert(variant, value); + Ok(Value::from(hash)) + } + + fn serialize_seq(self, len: Option) -> Result { + Ok(SerArray::new(len)) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + Ok(SerVariant::::new(variant, Some(len))) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(SerHash::new()) + } + + fn serialize_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_map(Some(len)) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Ok(SerVariant::::new(variant)) + } +} + +impl SerArray { + fn new(len: Option) -> Self { + let array = array::Array::new(); + if let Some(len) = len { + array.reserve(len); + } + Self { array } + } +} + +impl ser::SerializeSeq for SerArray { + type Ok = Value; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Error> + where + T: ?Sized + Serialize, + { + self.array.push(value.serialize(&mut Serializer)?); + Ok(()) + } + + fn end(self) -> Result { + Ok(Value::new_ref(&self.array)) + } +} + +impl ser::SerializeTuple for SerArray { + type Ok = Value; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Error> + where + T: ?Sized + Serialize, + { + self.array.push(value.serialize(&mut Serializer)?); + Ok(()) + } + + fn end(self) -> Result { + Ok(Value::new_ref(&self.array)) + } +} + +impl ser::SerializeTupleStruct for SerArray { + type Ok = Value; + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Error> + where + T: ?Sized + Serialize, + { + self.array.push(value.serialize(&mut Serializer)?); + Ok(()) + } + + fn end(self) -> Result { + Ok(Value::new_ref(&self.array)) + } +} + +impl SerHash { + fn new() -> Self { + Self { + hash: hash::Hash::new(), + key: None, + } + } +} + +impl ser::SerializeMap for SerHash { + type Ok = Value; + type Error = Error; + + fn serialize_key(&mut self, value: &T) -> Result<(), Error> + where + T: ?Sized + Serialize, + { + if self.key.is_some() { + Error::fail("serialize_key called twice") + } else { + self.key = Some(value.serialize(&mut Serializer)?); + Ok(()) + } + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Error> + where + T: ?Sized + Serialize, + { + match self.key.take() { + None => Error::fail("serialize_value called without key"), + Some(key) => { + let value = value.serialize(&mut Serializer)?; + self.hash.insert_by_value(&key, value); + Ok(()) + } + } + } + + fn end(self) -> Result { + if self.key.is_some() { + Error::fail("missing value for key") + } else { + Ok(Value::new_ref(&self.hash)) + } + } +} + +impl ser::SerializeStruct for SerHash { + type Ok = Value; + type Error = Error; + + fn serialize_field(&mut self, field: &'static str, value: &T) -> Result<(), Error> + where + T: ?Sized + Serialize, + { + self.hash.insert(field, value.serialize(&mut Serializer)?); + Ok(()) + } + + fn end(self) -> Result { + Ok(Value::new_ref(&self.hash)) + } +} + +impl SerVariant { + fn new(variant: &str, len: Option) -> Self { + let inner = SerArray::new(len); + let hash = hash::Hash::new(); + hash.insert(variant, Value::new_ref(&inner.array)); + Self { hash, inner } + } +} + +impl ser::SerializeTupleVariant for SerVariant { + type Ok = Value; + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Error> + where + T: ?Sized + Serialize, + { + self.inner.array.push(value.serialize(&mut Serializer)?); + Ok(()) + } + + fn end(self) -> Result { + Ok(Value::new_ref(&self.inner.array)) + } +} + +impl SerVariant { + fn new(variant: &str) -> Self { + let inner = SerHash::new(); + let hash = hash::Hash::new(); + hash.insert(variant, Value::new_ref(&inner.hash)); + Self { hash, inner } + } +} + +impl ser::SerializeStructVariant for SerVariant { + type Ok = Value; + type Error = Error; + + fn serialize_field(&mut self, field: &'static str, value: &T) -> Result<(), Error> + where + T: ?Sized + Serialize, + { + self.inner + .hash + .insert(field, value.serialize(&mut Serializer)?); + Ok(()) + } + + fn end(self) -> Result { + Ok(Value::new_ref(&self.hash)) + } +} diff --git a/perlmod/src/value.rs b/perlmod/src/value.rs new file mode 100644 index 0000000..1ecda4d --- /dev/null +++ b/perlmod/src/value.rs @@ -0,0 +1,355 @@ +use std::fmt; + +use serde::{Deserialize, Serialize}; + +use crate::ffi::{self, SV}; +use crate::scalar::ScalarRef; +use crate::{Array, Hash, Scalar}; + +/// A higher level value. This is basically an `SV` already cast to `AV` or `HV` for arrays and +/// hashes. +pub enum Value { + Scalar(Scalar), + Reference(Scalar), + Array(Array), + Hash(Hash), +} + +impl Value { + /// Create a new undef value: + pub fn new_undef() -> Self { + Value::Scalar(Scalar::new_undef()) + } + + /// Create a new integer value: + pub fn new_int(v: isize) -> Self { + Value::Scalar(Scalar::new_int(v)) + } + + /// Create a new unsigned integer value: + pub fn new_uint(v: usize) -> Self { + Value::Scalar(Scalar::new_uint(v)) + } + + /// Create a new floating point value. + pub fn new_float(v: f64) -> Self { + Value::Scalar(Scalar::new_float(v)) + } + + /// Create a new string value. + pub fn new_string(s: &str) -> Self { + Value::Scalar(Scalar::new_string(s)) + } + + /// Create a new byte string. + pub fn new_bytes(s: &[u8]) -> Self { + Value::Scalar(Scalar::new_bytes(s)) + } + + /// Create an actual perl reference to the value. (The equivalent of perl's backslash + /// operator). + pub fn new_ref(value: &T) -> Self + where + T: std::ops::Deref, + { + Value::Reference(unsafe { Scalar::from_raw_move(ffi::RSPL_newRV_inc(value.sv())) }) + } + + /// Take over a raw `SV` value, assuming that we then own a reference to it. + /// + /// # Safety + /// + /// This does not change the value's reference count, it is assumed that we're taking ownership + /// of one reference. + /// + /// The caller must ensure that it is safe to decrease the reference count later on, or use + /// `into_raw()` instead of letting the `Value` get dropped. + pub unsafe fn from_raw_move(ptr: *mut SV) -> Self { + Self::from_scalar(Scalar::from_raw_move(ptr as *mut SV)) + } + + /// Create a new reference to an existing `SV` value. This will increase the value's reference + /// count. + /// + /// # Safety + /// + /// 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_scalar(Scalar::from_raw_ref(ptr as *mut SV)) + } + + pub fn from_scalar(scalar: Scalar) -> Self { + Self::from(scalar) + } + + /// Create a new reference to this value. + pub fn clone_ref(&self) -> Self { + match self { + Value::Scalar(v) => Value::Scalar(v.clone_ref()), + Value::Reference(v) => Value::Reference(v.clone_ref()), + Value::Array(v) => Value::Array(v.clone_ref()), + Value::Hash(v) => Value::Hash(v.clone_ref()), + } + } + + /// Dereference this reference value. + pub fn dereference(&self) -> Option { + match self { + Value::Reference(v) => v.dereference().map(Value::from_scalar), + _ => None, + } + } + + /// Turn this into a raw `SV` transferring control of one reference count. + pub fn into_raw(self) -> *mut SV { + match self { + Value::Scalar(v) => v.into_raw(), + Value::Reference(v) => v.into_raw(), + Value::Array(v) => v.into_scalar().into_raw(), + Value::Hash(v) => v.into_scalar().into_raw(), + } + } + + pub fn into_mortal(self) -> crate::scalar::Mortal { + match self { + Value::Scalar(v) => v.into_mortal(), + Value::Reference(v) => v.into_mortal(), + Value::Array(v) => v.into_scalar().into_mortal(), + Value::Hash(v) => v.into_scalar().into_mortal(), + } + } + + /// If this is value is an array, get the value at the specified index. + pub fn get(&self, index: usize) -> Option { + if let Value::Array(a) = self { + a.get(index) + } else { + None + } + } +} + +impl From for Value { + fn from(scalar: Scalar) -> Self { + unsafe { + if ffi::RSPL_is_array(scalar.sv()) { + Value::Array(Array::from_scalar(scalar)) + } else if ffi::RSPL_is_hash(scalar.sv()) { + Value::Hash(Hash::from_scalar(scalar)) + } else if ffi::RSPL_is_reference(scalar.sv()) { + Value::Reference(scalar) + } else { + Value::Scalar(scalar) + } + } + } +} + +impl From for Value { + fn from(hash: Hash) -> Self { + Value::Hash(hash) + } +} + +impl From for Value { + fn from(array: Array) -> Self { + Value::Array(array) + } +} + +impl fmt::Debug for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use fmt::Debug; + match self { + Value::Scalar(v) => Debug::fmt(v, f), + Value::Reference(v) => Debug::fmt(v, f), + Value::Array(v) => Debug::fmt(v, f), + Value::Hash(v) => Debug::fmt(v, f), + } + } +} + +impl core::ops::Deref for Value { + type Target = ScalarRef; + + fn deref(&self) -> &Self::Target { + match self { + Value::Scalar(v) => &*v, + Value::Reference(v) => &*v, + Value::Array(v) => &*v, + Value::Hash(v) => &*v, + } + } +} + +impl core::ops::DerefMut for Value { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + Value::Scalar(v) => &mut *v, + Value::Reference(v) => &mut *v, + Value::Array(v) => &mut *v, + Value::Hash(v) => &mut *v, + } + } +} + +impl Serialize for Value { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::Error; + + match self { + Value::Scalar(this) => this.serialize(serializer), + Value::Reference(this) => Value::from( + this.dereference() + .ok_or_else(|| S::Error::custom("failed to dereference perl value"))?, + ) + .serialize(serializer), + Value::Array(value) => value.serialize(serializer), + Value::Hash(value) => value.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for Value { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Visitor; + + struct ValueVisitor; + + impl<'de> Visitor<'de> for ValueVisitor { + type Value = Value; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("any valid PERL value") + } + + #[inline] + fn visit_bool(self, value: bool) -> Result { + Ok(Value::new_int(if value { 1 } else { 0 })) + } + + #[inline] + fn visit_i64(self, value: i64) -> Result { + Ok(Value::new_int(value as isize)) + } + + #[inline] + fn visit_u64(self, value: u64) -> Result { + Ok(Value::new_uint(value as usize)) + } + + #[inline] + fn visit_f64(self, value: f64) -> Result { + Ok(Value::new_float(value)) + } + + #[inline] + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + Ok(Value::new_string(value)) + } + + #[inline] + fn visit_string(self, value: String) -> Result + where + E: serde::de::Error, + { + self.visit_str(&value) + } + + #[inline] + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(Value::new_undef()) + } + + #[inline] + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Deserialize::deserialize(deserializer) + } + + #[inline] + fn visit_unit(self) -> Result { + Ok(Value::new_undef()) + } + + #[inline] + fn visit_seq(self, mut visitor: V) -> Result + where + V: serde::de::SeqAccess<'de>, + { + let array = Array::new(); + + while let Some(elem) = visitor.next_element()? { + array.push(elem); + } + + Ok(Value::Array(array)) + } + + fn visit_map(self, mut visitor: V) -> Result + where + V: serde::de::MapAccess<'de>, + { + // We use this to hint the deserializer that we're expecting a string-ish value. + struct KeyClassifier; + struct KeyClass(String); + + impl<'de> serde::de::DeserializeSeed<'de> for KeyClassifier { + type Value = KeyClass; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(self) + } + } + + impl<'de> Visitor<'de> for KeyClassifier { + type Value = KeyClass; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string key") + } + + fn visit_str(self, s: &str) -> Result + where + E: serde::de::Error, + { + Ok(KeyClass(s.to_owned())) + } + + fn visit_string(self, s: String) -> Result + where + E: serde::de::Error, + { + Ok(KeyClass(s)) + } + } + + let hash = Hash::new(); + while let Some(key) = visitor.next_key_seed(KeyClassifier)? { + let value: Value = visitor.next_value()?; + hash.insert(&key.0, value); + } + Ok(Value::from(hash)) + } + } + + deserializer.deserialize_any(ValueVisitor) + } +} diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..32a9786 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +edition = "2018"