function gen wip

This commit is contained in:
Robin Appelman 2019-03-20 00:15:55 +01:00
commit 224c648852
17 changed files with 495 additions and 277 deletions

View file

@ -1,12 +1,12 @@
[package]
name = "helloworld"
name = "ivory-helloworld"
version = "0.1.0"
authors = ["Jin Hu <bixuehujin@gmail.com>"]
authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2018"
[dependencies]
libc = "0.2.50"
php-rs = { path = "../..", version = "0.1.0" }
ivory = { path = "../../ivory", version = "0.1.0" }
[lib]
name = "helloworld"

View file

@ -1,51 +1,15 @@
use libc::*;
use php_rs::info::{print_table_start, print_table_row, print_table_end};
use php_rs::zend::*;
use php_rs::*;
use ivory::*;
use ivory::externs::printf;
use ivory::zend::{ExecuteData, Value};
extern "C" {
pub fn php_printf(format: *const c_char, ...) -> size_t;
#[ivory_export]
fn hello_world() {
printf("Hello world, Rust!");
}
#[no_mangle]
pub extern "C" fn php_module_startup(_type: c_int, _module_number: c_int) -> c_int {
0
}
#[no_mangle]
pub extern "C" fn php_module_shutdown(_type: c_int, _module_number: c_int) -> c_int {
0
}
#[no_mangle]
pub extern "C" fn php_module_info() {
print_table_start();
print_table_row(&["A demo PHP extension written in Rust", "enabled"]);
print_table_end();
}
#[no_mangle]
pub extern "C" fn helloworld(_data: &ExecuteData, _retval: &Value) {
unsafe { php_printf(c_str!("Hello world, Rust!")) };
}
#[no_mangle]
pub extern "C" fn get_module() -> *mut zend::Module {
let mut entry = Box::new(zend::Module::new(c_str!("demo"), c_str!("0.1.0-dev")));
entry.set_info_func(php_module_info);
let args = vec![
ArgInfo::new(c_str!("name"), false, false, false),
ArgInfo::new(c_str!("foo"), false, false, false),
];
let funcs = vec![
Function::new(c_str!("helloworld"), helloworld),
Function::new_with_args(c_str!("helloworld2"), helloworld, args),
];
entry.set_functions(funcs);
Box::into_raw(entry)
}
ivory_module!({
name: "demo",
version: "0.0.1",
functions: &[hello_world],
info: &[("demo extension", "enabled")]
});

View file

@ -1,7 +1,7 @@
[package]
name = "php-rs"
name = "ivory"
version = "0.1.0"
authors = ["Jin Hu <bixuehujin@gmail.com>"]
authors = ["Robin Appelman <robin@icewind.nl>"]
keywords = ["php", "php-extension"]
description = "A library to build PHP extensions in Rust."
license = "MIT"
@ -10,7 +10,9 @@ edition = "2018"
[dependencies]
libc = "0.2.50"
ivory-macro = { version = "0.1", path = "macro" }
[lib]
name = "php_rs"
name = "ivory"
[workspace]

21
ivory/macro/Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "ivory-macro"
version = "0.1.0"
authors = ["Robin Appelman <robin@icewind.nl>"]
keywords = ["php", "php-extension"]
description = "A library to build PHP extensions in Rust."
license = "MIT"
repository = "https://github.com/rethinkphp/php-rs"
edition = "2018"
[dependencies]
syn = { version = "0.15", features = ["full"] }
quote = "0.6"
proc-macro2 = "0.4"
[dev-dependencies]
ivory = { version = "0.1", path = ".." }
[lib]
name = "ivory_macro"
proc-macro = true

208
ivory/macro/src/lib.rs Normal file
View file

@ -0,0 +1,208 @@
#![recursion_limit = "128"]
extern crate proc_macro;
use std::collections::HashMap;
use std::process::Command;
use core::fmt::Debug;
use proc_macro2::{Span, TokenStream, TokenTree};
use proc_macro2::token_stream::IntoIter;
use quote::{quote, quote_spanned};
use syn::{
Attribute, AttributeArgs, Data, Expr, ExprStruct, Fields, FieldValue, Ident, Item, ItemFn, Lit, LitStr,
Meta, parse2, parse_macro_input, parse_quote, parse_str, Pat, Path,
};
use syn::parse_quote::parse;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
/// See the [crate documentation](index.html) for details
#[proc_macro_attribute]
pub fn ivory_export(attr: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: TokenStream = input.into();
let item = syn::parse2::<Item>(input).unwrap();
let attr = parse_macro_input!(attr as AttributeArgs);
let output = match item {
Item::Fn(itemFn) => {
export_fn(itemFn).into()
}
_ => unimplemented!()
};
// panic!("{}", output);
output
}
fn export_fn(item: ItemFn) -> TokenStream {
let span = item.span();
let name = item.ident;
let name_str = name.to_string();
let meta_name = Ident::new(&format!("FUNCTION_META_{}", name_str.to_uppercase()), span);
let body = item.block;
let decl = item.decl;
if decl.generics.gt_token.is_some() {
unimplemented!("generics are not supported for exported functions");
}
quote! {
#[no_mangle]
pub extern "C" fn #name(data: &ExecuteData, retval: &Value) {
let result = #body;
}
const #meta_name: ::ivory::zend::FunctionMeta = ::ivory::zend::FunctionMeta{
name: {concat!(#name_str, "\0").as_ptr() as *const ::libc::c_char}
};
}
}
/// See the [crate documentation](index.html) for details
#[proc_macro]
pub fn ivory_module(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: TokenStream = input.into();
let span = input.span();
let mut tokens = input.into_iter();
let token = tokens.next().unwrap();
let group = match token {
TokenTree::Group(group) => {
group
}
_ => panic!("macro input must be a group")
};
let fields = group.stream();
let struct_def = quote! {
::ivory::zend::PhpModule {
#fields
}
};
let function_names = get_function_names(struct_def);
let funcs = get_funcs(function_names, span);
let fields = into_c_str(fields);
let result = quote! {
const MODULE_INFO: ::ivory::zend::PhpModule = ::ivory::zend::PhpModule {
#fields
};
extern "C" fn php_module_info() {
::ivory::info::php_print_module_info(&MODULE_INFO.info);
}
#[no_mangle]
pub extern "C" fn get_module() -> *mut ::ivory::zend::ModuleInternal {
let mut entry = Box::new(::ivory::zend::ModuleInternal::new(MODULE_INFO.name, MODULE_INFO.version));
entry.set_info_func(php_module_info);
let args = vec![
::ivory::zend::ArgInfo::new(::ivory::c_str!("name"), false, false, false),
::ivory::zend::ArgInfo::new(::ivory::c_str!("foo"), false, false, false),
];
#funcs;
entry.set_functions(funcs);
Box::into_raw(entry)
}
};
// panic!("{}", result);
result.into()
}
fn into_c_str(input: TokenStream) -> TokenStream {
let tokens: Vec<TokenTree> = input.into_iter().map(|token| {
match token.clone() {
TokenTree::Literal(lit) => {
let mut tokens = TokenStream::new();
tokens.extend(vec![token.clone()]);
match syn::parse2::<LitStr>(tokens) {
Ok(litStr) => {
let val = litStr.value();
let tokens = quote! {
{ concat!(#val, "\0").as_ptr() as *const ::libc::c_char }
};
if let Some(tree) = tokens.into_iter().next() {
tree
} else {
panic!();
}
}
Err(_) => token
}
}
_ => token
}
}).collect();
let mut output = TokenStream::new();
output.extend(tokens.into_iter());
output
}
fn get_function_names(struct_def: TokenStream) -> Vec<String> {
let expr: Expr = parse2(struct_def).unwrap();
let expr = get_field_expr(expr, "functions").unwrap();
match expr {
Expr::Reference(ref_expr) => {
match *ref_expr.expr {
Expr::Array(arr) => {
arr.elems.into_iter().map(|element: Expr| {
let tokens: TokenStream = quote!(#element);
let tree = tokens.into_iter().next().unwrap();
match tree {
TokenTree::Ident(ident) => {
ident.to_string()
},
_ => panic!()
}
}).collect()
}
_ => panic!()
}
}
_ => panic!()
}
}
fn get_field_expr(expr: Expr, field_name: &str) -> Option<Expr> {
let fields: Punctuated<FieldValue, syn::token::Comma> = match expr {
Expr::Struct(expr) => expr.fields,
_ => panic!("invalid struct")
};
for field in fields {
if let syn::Member::Named(ident) = &field.member {
let name = ident.to_string();
if &name == field_name {
return Some(field.expr);
}
}
}
None
}
fn get_funcs(names: Vec<String>, span: Span) -> TokenStream {
let definitions = names.into_iter().map(|name| {
// TODO: args
//let meta_name = Ident::new(&format!("FUNCTION_META_{}", name.to_uppercase()), span);
let func_ident = Ident::new(&name, span);
quote! {
::ivory::zend::Function::new(::ivory::c_str!(#name), #func_ident),
}
});
quote! {
let funcs = vec![
#(#definitions),*
];
}
}

12
ivory/src/externs/mod.rs Normal file
View file

@ -0,0 +1,12 @@
use std::ffi::{CString};
use libc::*;
extern "C" {
pub fn php_printf(format: *const c_char, ...) -> size_t;
}
pub fn printf<T: Into<Vec<u8>>>(string: T) {
let cstr = CString::new(string).unwrap();
unsafe { php_printf(cstr.as_ptr()); }
}

20
ivory/src/info.rs Normal file
View file

@ -0,0 +1,20 @@
use libc::*;
use std::ffi::CString;
extern "C" {
pub fn php_info_print_table_start();
pub fn php_info_print_table_row(num_cols: c_int, ...) -> c_void;
pub fn php_info_print_table_end();
}
pub fn php_print_module_info(info: &[(&'static str, &'static str)]) {
unsafe {
php_info_print_table_start();
for (key, value) in info {
let v1 = CString::new(key.to_string()).unwrap();
let v2 = CString::new(value.to_string()).unwrap();
php_info_print_table_row(2, v1, v2);
}
php_info_print_table_end();
}
}

7
ivory/src/lib.rs Normal file
View file

@ -0,0 +1,7 @@
#[macro_use]
pub mod macros;
pub mod externs;
pub mod info;
pub mod zend;
pub use ivory_macro::{ivory_export, ivory_module};

View file

@ -1,6 +1,6 @@
#[macro_export]
macro_rules! c_str {
($s:expr) => {{
concat!($s, "\0").as_ptr() as *const c_char
concat!($s, "\0").as_ptr() as *const ::libc::c_char
}};
}

View file

@ -0,0 +1,84 @@
use std;
use libc::*;
use crate::zend::HandlerFunc;
#[repr(C)]
pub struct ArgInfo {
name: *const c_char,
class_name: *const c_char,
type_hint: c_uchar,
pass_by_reference: c_uchar,
allow_null: c_uchar,
is_variadic: c_uchar,
}
impl ArgInfo {
pub fn new(
name: *const c_char,
allow_null: bool,
is_variadic: bool,
by_reference: bool,
) -> ArgInfo {
ArgInfo {
name,
class_name: std::ptr::null(),
type_hint: 0,
pass_by_reference: by_reference as c_uchar,
allow_null: allow_null as c_uchar,
is_variadic: is_variadic as c_uchar,
}
}
}
#[repr(C)]
pub struct Function {
fname: *const c_char,
handler: Option<HandlerFunc>,
arg_info: *const ArgInfo,
num_args: u32,
flags: u32,
}
impl Function {
pub fn new(name: *const c_char, handler: HandlerFunc) -> Function {
Function {
fname: name,
handler: Some(handler),
arg_info: std::ptr::null(),
num_args: 0,
flags: 0,
}
}
pub fn new_with_args(
name: *const c_char,
handler: HandlerFunc,
mut args: Vec<ArgInfo>,
) -> Function {
let num_args = args.len() as u32;
let arg_count = ArgInfo::new(num_args as *const c_char, false, false, false);
args.insert(0, arg_count);
let arg_ptr = Box::into_raw(args.into_boxed_slice()) as *const ArgInfo;
Function {
fname: name,
handler: Some(handler),
arg_info: arg_ptr,
num_args,
flags: 0,
}
}
pub(crate) fn end() -> Function {
Function {
fname: std::ptr::null(),
handler: None,
arg_info: std::ptr::null(),
num_args: 0,
flags: 0,
}
}
}

5
ivory/src/zend/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub use self::module::*;
pub use self::function::*;
mod module;
mod function;

117
ivory/src/zend/module.rs Normal file
View file

@ -0,0 +1,117 @@
use std;
use std::mem;
use libc::*;
use crate::zend::function::Function;
use std::ffi::{CString, CStr};
pub(crate) type StartupFunc = extern "C" fn(type_: c_int, module_number: c_int) -> c_int;
pub(crate) type ShutdownFunc = extern "C" fn(type_: c_int, module_number: c_int) -> c_int;
pub(crate) type InfoFunc = extern "C" fn();
pub(crate) type GlobalsCtorFunc = extern "C" fn(global: *const c_void) -> c_void;
pub(crate) type GlobalsDtorFunc = extern "C" fn(global: *const c_void) -> c_void;
pub(crate) type PostDeactivateFunc = extern "C" fn() -> c_int;
pub(crate) type HandlerFunc = extern "C" fn(execute_data: &ExecuteData, retval: &Value);
pub struct ExecuteData {}
pub struct Value {}
pub struct ModuleDep {}
pub struct INI {}
#[repr(C)]
pub struct ModuleInternal {
size: c_ushort,
zend_api: c_uint,
zend_debug: c_uchar,
zts: c_uchar,
ini_entry: *const INI,
deps: *const ModuleDep,
name: *const c_char,
functions: *const Function,
module_startup_func: Option<StartupFunc>,
module_shutdown_func: Option<ShutdownFunc>,
request_startup_func: Option<StartupFunc>,
request_shutdown_func: Option<ShutdownFunc>,
info_func: Option<InfoFunc>,
version: *const c_char,
globals_size: size_t,
globals_ptr: *const c_void,
globals_ctor: Option<GlobalsCtorFunc>,
globals_dtor: Option<GlobalsDtorFunc>,
post_deactivate_func: Option<PostDeactivateFunc>,
module_started: c_int,
type_: c_uchar,
handle: *const c_void,
module_number: c_int,
build_id: *const c_char,
}
impl ModuleInternal {
pub fn new(name: *const c_char, version: *const c_char) -> ModuleInternal {
ModuleInternal {
size: mem::size_of::<ModuleInternal>() as u16,
zend_api: 20180731,
zend_debug: 0,
zts: 0,
ini_entry: std::ptr::null(),
deps: std::ptr::null(),
name,
functions: std::ptr::null(),
module_startup_func: None,
module_shutdown_func: None,
request_startup_func: None,
request_shutdown_func: None,
info_func: None,
version,
globals_size: 0,
globals_ptr: std::ptr::null(),
globals_ctor: None,
globals_dtor: None,
post_deactivate_func: None,
module_started: 0,
type_: 0,
handle: std::ptr::null(),
module_number: 0,
build_id: c_str!("API20180731,NTS"),
}
}
pub fn set_startup_func(&mut self, func: StartupFunc) {
self.module_startup_func = Some(func);
}
pub fn set_shutdown_func(&mut self, func: ShutdownFunc) {
self.module_shutdown_func = Some(func);
}
pub fn set_info_func(&mut self, func: InfoFunc) {
self.info_func = Some(func);
}
pub fn set_functions(&mut self, mut funcs: Vec<Function>) {
funcs.push(Function::end());
self.functions = Box::into_raw(funcs.into_boxed_slice()) as *const Function;
}
}
pub struct FunctionMeta {
pub name: *const c_char,
pub arg_names: &'static [*const c_char]
}
impl FunctionMeta {
pub fn into_function(self, func: HandlerFunc) -> Function {
Function::new(self.name, func)
}
}
pub struct PhpModule {
pub name: *const c_char,
pub version: *const c_char,
pub functions: &'static [HandlerFunc],
pub info: &'static [(&'static str, &'static str)]
}

View file

@ -1,31 +0,0 @@
use libc::*;
use std::ffi::CString;
extern "C" {
pub fn php_info_print_table_start();
pub fn php_info_print_table_row(num_cols: c_int, ...) -> c_void;
pub fn php_info_print_table_end();
}
pub fn print_table_row(values: &[&str]) {
let nargs = values.len() as i32;
if nargs != 2 {
unimplemented!();
}
let v1 = CString::new(values[0]).unwrap();
let v2 = CString::new(values[1]).unwrap();
unsafe {
php_info_print_table_row(nargs, v1.as_ptr(), v2.as_ptr());
};
}
pub fn print_table_start() {
unsafe { php_info_print_table_start() };
}
pub fn print_table_end() {
unsafe { php_info_print_table_end() }
}

View file

@ -1,7 +0,0 @@
extern crate libc;
#[macro_use]
pub mod macros;
pub mod info;
pub mod zend;

View file

@ -1,3 +0,0 @@
pub use self::module::*;
mod module;

View file

@ -1,181 +0,0 @@
use std;
use std::mem;
use libc::*;
use std::ffi::{CString, CStr};
type StartupFunc = extern "C" fn(type_: c_int, module_number: c_int) -> c_int;
type ShutdownFunc = extern "C" fn(type_: c_int, module_number: c_int) -> c_int;
type InfoFunc = extern "C" fn();
type GlobalsCtorFunc = extern "C" fn(global: *const c_void) -> c_void;
type GlobalsDtorFunc = extern "C" fn(global: *const c_void) -> c_void;
type PostDeactivateFunc = extern "C" fn() -> c_int;
type HandlerFunc = extern "C" fn(execute_data: &ExecuteData, retval: &Value);
pub struct ExecuteData {}
pub struct Value {}
pub struct ModuleDep {}
#[repr(C)]
pub struct ArgInfo {
name: *const c_char,
class_name: *const c_char,
type_hint: c_uchar,
pass_by_reference: c_uchar,
allow_null: c_uchar,
is_variadic: c_uchar,
}
impl ArgInfo {
pub fn new(
name: *const c_char,
allow_null: bool,
is_variadic: bool,
by_reference: bool,
) -> ArgInfo {
ArgInfo {
name,
class_name: std::ptr::null(),
type_hint: 0,
pass_by_reference: by_reference as c_uchar,
allow_null: allow_null as c_uchar,
is_variadic: is_variadic as c_uchar,
}
}
}
#[repr(C)]
pub struct Function {
fname: *const c_char,
handler: Option<HandlerFunc>,
arg_info: *const ArgInfo,
num_args: u32,
flags: u32,
}
impl Function {
pub fn new(name: *const c_char, handler: HandlerFunc) -> Function {
Function {
fname: name,
handler: Some(handler),
arg_info: std::ptr::null(),
num_args: 0,
flags: 0,
}
}
pub fn new_with_args(
name: *const c_char,
handler: HandlerFunc,
mut args: Vec<ArgInfo>,
) -> Function {
let num_args = args.len() as u32;
let arg_count = ArgInfo::new(num_args as *const c_char, false, false, false);
args.insert(0, arg_count);
let arg_ptr = args.as_ptr();
mem::forget(args);
Function {
fname: name,
handler: Some(handler),
arg_info: arg_ptr,
num_args,
flags: 0,
}
}
fn end() -> Function {
Function {
fname: std::ptr::null(),
handler: None,
arg_info: std::ptr::null(),
num_args: 0,
flags: 0,
}
}
}
pub struct INI {}
#[repr(C)]
pub struct Module {
size: c_ushort,
zend_api: c_uint,
zend_debug: c_uchar,
zts: c_uchar,
ini_entry: *const INI,
deps: *const ModuleDep,
name: *const c_char,
functions: *const Function,
module_startup_func: Option<StartupFunc>,
module_shutdown_func: Option<ShutdownFunc>,
request_startup_func: Option<StartupFunc>,
request_shutdown_func: Option<ShutdownFunc>,
info_func: Option<InfoFunc>,
version: *const c_char,
globals_size: size_t,
globals_ptr: *const c_void,
globals_ctor: Option<GlobalsCtorFunc>,
globals_dtor: Option<GlobalsDtorFunc>,
post_deactivate_func: Option<PostDeactivateFunc>,
module_started: c_int,
type_: c_uchar,
handle: *const c_void,
module_number: c_int,
build_id: *const c_char,
}
impl Module {
pub fn new(name: *const c_char, version: *const c_char) -> Module {
Module {
size: mem::size_of::<Module>() as u16,
zend_api: 20180731,
zend_debug: 0,
zts: 0,
ini_entry: std::ptr::null(),
deps: std::ptr::null(),
name,
functions: std::ptr::null(),
module_startup_func: None,
module_shutdown_func: None,
request_startup_func: None,
request_shutdown_func: None,
info_func: None,
version,
globals_size: 0,
globals_ptr: std::ptr::null(),
globals_ctor: None,
globals_dtor: None,
post_deactivate_func: None,
module_started: 0,
type_: 0,
handle: std::ptr::null(),
module_number: 0,
build_id: c_str!("API20180731,NTS"),
}
}
pub fn set_startup_func(&mut self, func: StartupFunc) {
self.module_startup_func = Some(func);
}
pub fn set_shutdown_func(&mut self, func: ShutdownFunc) {
self.module_shutdown_func = Some(func);
}
pub fn set_info_func(&mut self, func: InfoFunc) {
self.info_func = Some(func);
}
pub fn set_functions(&mut self, mut funcs: Vec<Function>) {
funcs.push(Function::end());
self.functions = funcs.as_ptr();
mem::forget(funcs);
}
}
unsafe impl Sync for Module {}