From 52f9d40050aed4647182e63d48ad1f91b7f2485d Mon Sep 17 00:00:00 2001 From: David Date: Wed, 10 Mar 2021 11:43:17 +1300 Subject: [PATCH] Added support for functions (#1) * started work on functions can now add functions, but can't return from them * arg defaults to required --- example/skel/src/lib.rs | 18 +++++- src/functions.rs | 4 +- src/php/args.rs | 64 ++++++++++++++++++ src/php/enums.rs | 18 ++++++ src/php/function.rs | 139 ++++++++++++++++++++++++++++++++++++++++ src/php/mod.rs | 3 + src/php/module.rs | 28 ++------ src/php/types.rs | 85 ++++++++++++++++++++++++ 8 files changed, 335 insertions(+), 24 deletions(-) create mode 100644 src/php/args.rs create mode 100644 src/php/enums.rs create mode 100644 src/php/types.rs diff --git a/example/skel/src/lib.rs b/example/skel/src/lib.rs index fcd64f2..60b4e7a 100644 --- a/example/skel/src/lib.rs +++ b/example/skel/src/lib.rs @@ -1,6 +1,11 @@ use php_rs::{ info_table_end, info_table_row, info_table_start, - php::module::{ModuleBuilder, ModuleEntry}, + php::{ + args::Arg, + enums::DataType, + function::{ExecutionData, FunctionBuilder, Zval}, + module::{ModuleBuilder, ModuleEntry}, + }, }; #[no_mangle] @@ -12,8 +17,19 @@ pub extern "C" fn php_module_info(_module: *mut ModuleEntry) { #[no_mangle] pub extern "C" fn get_module() -> *mut php_rs::php::module::ModuleEntry { + let funct = FunctionBuilder::new("skeleton_version", skeleton_version) + .arg(Arg::new("test", DataType::String)) + .returns(DataType::Long, false, false) + .build(); + ModuleBuilder::new("ext-skel", "0.1.0") .info_function(php_module_info) + .function(funct) .build() .into_raw() } + +#[no_mangle] +pub extern "C" fn skeleton_version(_execute_data: *mut ExecutionData, _retval: *mut Zval) { + panic!("it worked?"); +} diff --git a/src/functions.rs b/src/functions.rs index e6a1a86..a87df1d 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -28,9 +28,9 @@ use std::ffi::{CStr, CString}; /// ``` pub fn c_str(s: S) -> *const i8 where - S: Into, + S: AsRef, { - CString::into_raw(CString::new(s.into()).unwrap()) + CString::into_raw(CString::new(s.as_ref()).unwrap()) } /// Fetches the `build_id` for a Zend extension module. diff --git a/src/php/args.rs b/src/php/args.rs new file mode 100644 index 0000000..42df5d5 --- /dev/null +++ b/src/php/args.rs @@ -0,0 +1,64 @@ +use super::enums::DataType; + +use crate::bindings::zend_internal_arg_info; + +/// Represents an argument to a function. +pub struct Arg { + pub(crate) name: String, + pub(crate) _type: DataType, + pub(crate) required: bool, + pub(crate) as_ref: bool, + pub(crate) allow_null: bool, + pub(crate) default_value: Option, +} + +impl Arg { + /// Creates a new argument. + /// + /// # Parameters + /// + /// * `name` - The name of the parameter. + /// * `_type` - The type of the parameter. + pub fn new(name: S, _type: DataType) -> Self + where + S: ToString, + { + Arg { + name: name.to_string(), + _type, + required: true, + as_ref: false, + allow_null: false, + default_value: None, + } + } + + /// Sets the argument as not required. + pub fn not_required(mut self) -> Self { + self.required = false; + self + } + + /// Sets the argument as a reference. + pub fn as_ref(mut self) -> Self { + self.as_ref = true; + self + } + + /// Sets the argument as nullable. + pub fn allow_null(mut self) -> Self { + self.allow_null = true; + self + } + + /// Sets the default value for the argument. + pub fn default(mut self, default: S) -> Self + where + S: ToString, + { + self.default_value = Some(default.to_string()); + self + } +} + +pub type ArgInfo = zend_internal_arg_info; diff --git a/src/php/enums.rs b/src/php/enums.rs new file mode 100644 index 0000000..a5abb2e --- /dev/null +++ b/src/php/enums.rs @@ -0,0 +1,18 @@ +/// Valid data types for PHP. +#[derive(Clone, Copy)] +pub enum DataType { + Undef = 0, + Null = 1, + False = 2, + True = 3, + Long = 4, + Double = 5, + String = 6, + Array = 7, + Object = 8, + Resource = 9, + Reference = 10, + ConstantExpression = 11, + + Void = 14, +} diff --git a/src/php/function.rs b/src/php/function.rs index 8b13789..d327fd7 100644 --- a/src/php/function.rs +++ b/src/php/function.rs @@ -1 +1,140 @@ +use std::{any::Any, borrow::Borrow, os::raw::c_char, ptr}; +use crate::{ + bindings::{zend_execute_data, zend_function_entry, zval}, + functions::c_str, +}; + +use super::{ + args::{Arg, ArgInfo}, + enums::DataType, + types::ZendType, +}; + +/// A Zend function entry. Alias. +pub type FunctionEntry = zend_function_entry; + +impl FunctionEntry { + /// Returns an empty function entry, signifing the end of a function list. + pub fn end() -> Self { + Self { + fname: ptr::null() as *const c_char, + handler: None, + arg_info: ptr::null(), + num_args: 0, + flags: 0, + } + } + + /// Converts the function entry into a raw pointer, releasing it to the C world. + pub fn into_raw(self) -> *mut Self { + Box::into_raw(Box::new(self)) + } +} + +/// Execution data passed when a function is called from Zend. +pub type ExecutionData = zend_execute_data; + +/// Zend value. +pub type Zval = zval; + +/// Function representation in Rust. +pub type FunctionHandler = extern "C" fn(execute_data: *mut ExecutionData, retval: *mut Zval); + +/// Builds a function to be exported as a PHP function. +pub struct FunctionBuilder { + function: FunctionEntry, + args: Vec, + n_req: u32, + retval: Option, + ret_as_ref: bool, + ret_as_null: bool, +} + +impl FunctionBuilder { + /// Creates a new function builder, used to build functions + /// to be exported to PHP. + /// + /// # Parameters + /// + /// * `name` - The name of the function. + /// * `handler` - The handler to be called when the function is invoked from PHP. + pub fn new(name: N, handler: FunctionHandler) -> Self + where + N: AsRef, + { + Self { + function: FunctionEntry { + fname: c_str(name), + handler: Some(handler), + arg_info: ptr::null(), + num_args: 0, + flags: 0, // TBD? + }, + args: vec![], + n_req: 0, + retval: None, + ret_as_ref: false, + ret_as_null: false, + } + } + + /// Adds an argument to the function. + /// + /// # Parameters + /// + /// * `arg` - The argument to add to the function. + pub fn arg(mut self, arg: Arg) -> Self { + if arg.required { + self.n_req += 1; + } + self.args.push(arg); + self + } + + /// Sets the return value of the function. + /// + /// # Parameters + /// + /// * `type_` - The return type of the function. + /// * `as_ref` - Whether the fucntion returns a reference. + /// * `allow_null` - Whether the function return value is nullable. + pub fn returns(mut self, type_: DataType, as_ref: bool, allow_null: bool) -> Self { + self.retval = Some(type_); + self.ret_as_ref = as_ref; + self.ret_as_null = allow_null; + self + } + + /// Builds the function converting it into a Zend function entry. + pub fn build(mut self) -> FunctionEntry { + let mut args: Vec = vec![]; + + // argument header, retval etc + args.push(ArgInfo { + name: c_str(self.n_req.to_string()), + type_: match self.retval { + Some(retval) => { + ZendType::empty_from_type(retval, self.ret_as_ref, false, self.ret_as_null) + } + None => ZendType::empty(false, false), + }, + default_value: ptr::null(), + }); + + // arguments + for arg in self.args.iter() { + args.push(ArgInfo { + name: c_str(arg.name.clone()), + type_: ZendType::empty_from_type(arg._type, arg.as_ref, false, arg.allow_null), + default_value: match &arg.default_value { + Some(val) => c_str(val), + None => ptr::null(), + }, + }); + } + + self.function.arg_info = Box::into_raw(args.into_boxed_slice()) as *const ArgInfo; + self.function + } +} diff --git a/src/php/mod.rs b/src/php/mod.rs index 5eb15a0..3b7578f 100644 --- a/src/php/mod.rs +++ b/src/php/mod.rs @@ -1,2 +1,5 @@ +pub mod args; +pub mod enums; pub mod function; pub mod module; +pub mod types; diff --git a/src/php/module.rs b/src/php/module.rs index 152252f..5130e32 100644 --- a/src/php/module.rs +++ b/src/php/module.rs @@ -1,22 +1,14 @@ -use std::{ - ffi::c_void, - mem, - os::raw::{c_char, c_int}, - ptr, -}; +use std::{ffi::c_void, mem, os::raw::c_int, ptr}; use crate::{ - bindings::{ - zend_function_entry, zend_module_entry, zend_result, USING_ZTS, ZEND_DEBUG, - ZEND_MODULE_API_NO, - }, + bindings::{zend_module_entry, zend_result, USING_ZTS, ZEND_DEBUG, ZEND_MODULE_API_NO}, functions::{build_id, c_str}, }; +use super::function::FunctionEntry; + /// A Zend module entry. Alias. pub type ModuleEntry = zend_module_entry; -/// A Zend function entry. Alias. -pub type FunctionEntry = zend_function_entry; /// A function to be called when the extension is starting up or shutting down. pub type StartupShutdownFunc = extern "C" fn(type_: c_int, module_number: c_int) -> zend_result; /// A function to be called when `phpinfo();` is called. @@ -55,8 +47,8 @@ impl ModuleBuilder { /// * `version` - The current version of the extension. TBD: Deprecate in favour of the `Cargo.toml` version? pub fn new(name: N, version: V) -> Self where - N: Into, - V: Into, + N: AsRef, + V: AsRef, { Self { module: ModuleEntry { @@ -152,13 +144,7 @@ impl ModuleBuilder { /// Builds the extension and returns a `ModuleEntry`. pub fn build(mut self) -> ModuleEntry { // TODO: move to seperate function - self.functions.push(FunctionEntry { - fname: ptr::null() as *const c_char, - handler: None, - arg_info: ptr::null(), - num_args: 0, - flags: 0, - }); + self.functions.push(FunctionEntry::end()); self.module.functions = Box::into_raw(self.functions.into_boxed_slice()) as *const FunctionEntry; self.module diff --git a/src/php/types.rs b/src/php/types.rs new file mode 100644 index 0000000..c99929f --- /dev/null +++ b/src/php/types.rs @@ -0,0 +1,85 @@ +use std::{ffi::c_void, ptr}; + +use crate::bindings::{ + zend_type, IS_MIXED, MAY_BE_ANY, MAY_BE_BOOL, _IS_BOOL, _ZEND_IS_VARIADIC_BIT, + _ZEND_SEND_MODE_SHIFT, _ZEND_TYPE_NULLABLE_BIT, +}; + +use super::enums::DataType; + +pub type ZendType = zend_type; + +impl ZendType { + /// Builds an empty Zend type container. + /// + /// # Parameters + /// + /// * `pass_by_ref` - Whether the value should be passed by reference. + /// * `is_variadic` - Whether this type represents a variadic argument. + pub fn empty(pass_by_ref: bool, is_variadic: bool) -> Self { + Self { + ptr: ptr::null::() as *mut c_void, + type_mask: Self::arg_info_flags(pass_by_ref, is_variadic), + } + } + + pub fn empty_from_type( + type_: DataType, + pass_by_ref: bool, + is_variadic: bool, + allow_null: bool, + ) -> Self { + Self { + ptr: ptr::null::() as *mut c_void, + type_mask: Self::type_init_code(type_, pass_by_ref, is_variadic, allow_null), + } + } + + /// Calculates the internal flags of the type. + /// Translation of of the `_ZEND_ARG_INFO_FLAGS` macro from zend_API.h:110. + /// + /// # Parameters + /// + /// * `pass_by_ref` - Whether the value should be passed by reference. + /// * `is_variadic` - Whether this type represents a variadic argument. + pub(crate) fn arg_info_flags(pass_by_ref: bool, is_variadic: bool) -> u32 { + ((pass_by_ref as u32) << _ZEND_SEND_MODE_SHIFT) + | (if is_variadic { + _ZEND_IS_VARIADIC_BIT + } else { + 0 + }) + } + + /// Calculates the internal flags of the type. + /// Translation of the `ZEND_TYPE_INIT_CODE` macro from zend_API.h:163. + /// + /// # Parameters + /// + /// * `type_` - The type to initialize the Zend type with. + /// * `pass_by_ref` - Whether the value should be passed by reference. + /// * `is_variadic` - Whether this type represents a variadic argument. + /// * `allow_null` - Whether the value can be null. + pub(crate) fn type_init_code( + type_: DataType, + pass_by_ref: bool, + is_variadic: bool, + allow_null: bool, + ) -> u32 { + let type_ = type_ as u32; + + (if type_ == _IS_BOOL { + MAY_BE_BOOL + } else { + if type_ == IS_MIXED { + MAY_BE_ANY + } else { + 1 << type_ + } + }) | (if allow_null { + _ZEND_TYPE_NULLABLE_BIT + } else { + 0 + }) | Self::arg_info_flags(pass_by_ref, is_variadic) + } +}