Add operator overloading for Complex.

This commit is contained in:
CismonX 2019-04-16 19:00:50 +08:00
parent e6b572a3eb
commit 8163e4af9d
16 changed files with 303 additions and 109 deletions

View File

@ -27,6 +27,6 @@ install:
script:
- phpize
- ./configure --enable-arma-operator
- ./configure --enable-arma-operators
- make
- make test

View File

@ -1,8 +1,8 @@
PHP_ARG_ENABLE(arma, for armadillo support,
[ --enable-arma Enable armadillo support])
PHP_ARG_ENABLE(arma, for armadillo support,
[ --enable-arma Enable armadillo support])
PHP_ARG_ENABLE(arma-operator, for operator overloading support in armadillo,
[ --enable-arma-operator Enable operator overloading for armadillo ], no, no)
PHP_ARG_ENABLE(arma-operators, for operator overloading support in armadillo,
[ --enable-arma-operators Enable operator overloading for armadillo ], no, no)
if test "$PHP_ARMA" != "no"; then
PHP_REQUIRE_CXX()
@ -15,9 +15,9 @@ if test "$PHP_ARMA" != "no"; then
src/mapval.cc \
"
if test "$PHP_ARMA_OPERATOR" != "no"; then
AC_DEFINE(PHP_ARMA_OPERATOR, 1, [ Defined if operator overloading is enabled for armadillo. ])
ARMA_SRC+="src/operator.cc"
if test "$PHP_ARMA_OPERATORS" != "no"; then
AC_DEFINE(PHP_ARMA_OPERATORS, 1, [ Defined if operator overloading is enabled for armadillo. ])
ARMA_SRC+="src/operators.cc"
fi
PHP_NEW_EXTENSION(arma, $ARMA_SRC, $ext_shared, , -std=c++17)

View File

@ -77,19 +77,9 @@ namespace php_arma
Z_PARAM_ZVAL(other)
ZEND_PARSE_PARAMETERS_END();
auto current = zval_get_scalar<native_t>(getThis());
if (zval_is_scalar<native_t>(other)) {
zval_set_scalar(return_value, current + zval_get_scalar<native_t>(other));
return;
if (!operators::add(getThis(), other, return_value)) {
ex_bad_type<T>(other);
}
if (zval_is_scalar<T>(other)) {
zval_set_scalar(return_value, current + zval_get_scalar<T>(other));
return;
}
ex_bad_type<T>(other);
}
template <typename T>
@ -100,19 +90,9 @@ namespace php_arma
Z_PARAM_ZVAL(other)
ZEND_PARSE_PARAMETERS_END();
auto current = zval_get_scalar<native_t>(getThis());
if (zval_is_scalar<native_t>(other)) {
zval_set_scalar(return_value, current - zval_get_scalar<native_t>(other));
return;
if (!operators::sub(getThis(), other, return_value)) {
ex_bad_type<T>(other);
}
if (zval_is_scalar<T>(other)) {
zval_set_scalar(return_value, current - zval_get_scalar<T>(other));
return;
}
ex_bad_type<T>(other);
}
template <typename T>
@ -131,19 +111,9 @@ namespace php_arma
Z_PARAM_ZVAL(other)
ZEND_PARSE_PARAMETERS_END();
auto current = zval_get_scalar<native_t>(getThis());
if (zval_is_scalar<native_t>(other)) {
zval_set_scalar(return_value, current * zval_get_scalar<native_t>(other));
return;
if (!operators::mul(getThis(), other, return_value)) {
ex_bad_type<T>(other);
}
if (zval_is_scalar<T>(other)) {
zval_set_scalar(return_value, current * zval_get_scalar<T>(other));
return;
}
ex_bad_type<T>(other);
}
template <typename T>
@ -154,19 +124,9 @@ namespace php_arma
Z_PARAM_ZVAL(other)
ZEND_PARSE_PARAMETERS_END();
auto current = zval_get_scalar<native_t>(getThis());
if (zval_is_scalar<native_t>(other)) {
zval_set_scalar(return_value, current / zval_get_scalar<native_t>(other));
return;
if (!operators::div(getThis(), other, return_value)) {
ex_bad_type<T>(other);
}
if (zval_is_scalar<T>(other)) {
zval_set_scalar(return_value, current / zval_get_scalar<T>(other));
return;
}
ex_bad_type<T>(other);
}
template <typename T>
@ -196,9 +156,7 @@ namespace php_arma
template <typename T>
PHP_ARMA_METHOD(complex, conj, T)
{
auto current = zval_get_scalar<native_t>(getThis());
zval_set_scalar(return_value, std::conj(current));
operators::conj(getThis(), nullptr, return_value);
}
template <typename T>
@ -233,19 +191,9 @@ namespace php_arma
Z_PARAM_ZVAL(other)
ZEND_PARSE_PARAMETERS_END();
auto current = zval_get_scalar<native_t>(getThis());
if (zval_is_scalar<native_t>(other)) {
zval_set_scalar(return_value, std::pow(current, zval_get_scalar<native_t>(other)));
return;
if (!operators::pow(getThis(), other, return_value)) {
ex_bad_type<T>(other);
}
if (zval_is_scalar<T>(other)) {
zval_set_scalar(return_value, std::pow(current, zval_get_scalar<T>(other)));
return;
}
ex_bad_type<T>(other);
}
template <typename T>

View File

@ -11,6 +11,24 @@
#include <complex>
#ifdef PHP_ARMA_OPERATORS
#define PHP_ARMA_COMPLEX_OPERATOR(type, func) \
if (instanceof_function(_ce, complex<type>::ce)) { \
return complex<type>::operators::func(zv1, zv2, retval); \
}
#define PHP_ARMA_COMPLEX_OPERATOR_ASSIGN(type, func) \
if (instanceof_function(_ce, complex<type>::ce)) { \
auto v = complex<type>::operators::func(zv1, zv2, zv1); \
if (EX(opline)->result_type != IS_UNUSED) { \
ZVAL_COPY(retval, zv1); \
} \
return v; \
}
#endif // PHP_ARMA_OPERATORS
using cx_double = std::complex<double>;
namespace php_arma
@ -20,6 +38,8 @@ namespace php_arma
{
using native_t = std::complex<T>;
struct operators;
friend void complex_init();
PHP_ARMA_CE_HANDLRES_DECLARE();
@ -114,6 +134,102 @@ namespace php_arma
ex_bad_type(expected, got);
free(expected);
}
template <typename T>
struct complex<T>::operators
{
zend_always_inline
static bool add(zval *zv1, zval *zv2, zval *retval)
{
auto v1 = zval_get_scalar<native_t>(zv1);
if (zval_is_scalar<native_t>(zv2)) {
zval_set_scalar(retval, v1 + zval_get_scalar<native_t>(zv2));
return true;
}
if (zval_is_scalar<T>(zv2)) {
zval_set_scalar(retval, v1 + zval_get_scalar<T>(zv2));
return true;
}
return false;
}
zend_always_inline
static bool sub(zval *zv1, zval *zv2, zval *retval)
{
auto v1 = zval_get_scalar<native_t>(zv1);
if (zval_is_scalar<native_t>(zv2)) {
zval_set_scalar(retval, v1 - zval_get_scalar<native_t>(zv2));
return true;
}
if (zval_is_scalar<T>(zv2)) {
zval_set_scalar(retval, v1 - zval_get_scalar<T>(zv2));
return true;
}
return false;
}
zend_always_inline
static bool mul(zval *zv1, zval *zv2, zval *retval)
{
auto v1 = zval_get_scalar<native_t>(zv1);
if (zval_is_scalar<native_t>(zv2)) {
zval_set_scalar(retval, v1 * zval_get_scalar<native_t>(zv2));
return true;
}
if (zval_is_scalar<T>(zv2)) {
zval_set_scalar(retval, v1 * zval_get_scalar<T>(zv2));
return true;
}
return false;
}
zend_always_inline
static bool div(zval *zv1, zval *zv2, zval *retval)
{
auto v1 = zval_get_scalar<native_t>(zv1);
if (zval_is_scalar<native_t>(zv2)) {
zval_set_scalar(retval, v1 / zval_get_scalar<native_t>(zv2));
return true;
}
if (zval_is_scalar<T>(zv2)) {
zval_set_scalar(retval, v1 / zval_get_scalar<T>(zv2));
return true;
}
return false;
}
zend_always_inline
static bool pow(zval *zv1, zval *zv2, zval *retval)
{
auto v1 = zval_get_scalar<native_t>(zv1);
if (zval_is_scalar<native_t>(zv2)) {
zval_set_scalar(retval, std::pow(v1, zval_get_scalar<native_t>(zv2)));
return true;
}
if (zval_is_scalar<T>(zv2)) {
zval_set_scalar(retval, std::pow(v1, zval_get_scalar<T>(zv2)));
return true;
}
return false;
}
zend_always_inline
static bool conj(zval *zv, zval *unused, zval *retval)
{
zval_set_scalar(retval, std::conj(zval_get_scalar<native_t>(zv)));
return true;
}
};
}
#endif // !PHP_ARMA_COMPLEX_HH

View File

@ -4,7 +4,6 @@
// @Author CismonX
//
#include "php_arma.hh"
#include "constants.hh"
#include <tuple>
@ -52,5 +51,8 @@ namespace php_arma
std::tuple("APPEND", hdf5_opts::append),
std::tuple("REPLACE", hdf5_opts::replace)
);
const_declare(features_ce = abstract_class_register<features_php_name>(),
std::tuple("OPERATORS", features::operators)
);
}
}

View File

@ -7,7 +7,7 @@
#ifndef PHP_ARMA_CONSTANTS_HH
#define PHP_ARMA_CONSTANTS_HH
#include <php.h>
#include "php_arma.hh"
namespace php_arma
{
@ -49,17 +49,28 @@ namespace php_arma
static constexpr auto replace = 1u << 2;
};
struct features
{
#ifdef PHP_ARMA_OPERATORS
static constexpr auto operators = true;
#else
static constexpr auto operators = false;
#endif // PHP_ARMA_OPERATORS
};
void constants_init();
constexpr const char fill_php_name[] = "Fill";
constexpr const char file_type_php_name[] = "FileType";
constexpr const char sort_direction_php_name[] = "SortDirection";
constexpr const char hdf5_opts_php_name[] = "HDF5Opts";
constexpr const char features_php_name[] = "Features";
inline zend_class_entry *fill_ce;
inline zend_class_entry *file_type_ce;
inline zend_class_entry *sort_direction_ce;
inline zend_class_entry *hdf5_opts_ce;
inline zend_class_entry *features_ce;
}
#endif //!PHP_ARMA_CONSTANTS_HH

View File

@ -5,10 +5,9 @@
//
#include "php_arma.hh"
#include "operators.hh"
#include "complex.hh"
#include <zend_compile.h>
#if PHP_VERSION_ID >= 70300
// See https://github.com/php/php-src/blob/PHP-7.3/UPGRADING.INTERNALS#L61 section 3.1.b.
#define EX_CONSTANT(op) RT_CONSTANT(EX(opline), op)
@ -19,18 +18,18 @@ namespace php_arma
zend_always_inline
zval *get_zval_ptr_undef(
zend_uchar op_type,
znode_op op,
const znode_op *op,
zend_free_op *free_op,
zend_execute_data *execute_data
) {
switch (op_type) {
case IS_TMP_VAR:
case IS_VAR:
return *free_op = EX_VAR(op.var);
return *free_op = EX_VAR(op->var);
case IS_CONST:
return EX_CONSTANT(op);
return EX_CONSTANT(*op);
case IS_CV:
return EX_VAR(op.var);
return EX_VAR(op->var);
default:
return nullptr;
}
@ -55,11 +54,15 @@ namespace php_arma
const zend_op *opline = EX(opline);
zend_free_op free_op1 = nullptr;
zend_free_op free_op2 = nullptr;
zval *op1 = get_zval_ptr_undef(opline->op1_type, opline->op1, &free_op1, execute_data);
zval *op2 = get_zval_ptr_undef(opline->op2_type, opline->op2, &free_op2, execute_data);
zval *op1 = get_zval_ptr_undef(opline->op1_type, &opline->op1, &free_op1, execute_data);
zval *op2 = get_zval_ptr_undef(opline->op2_type, &opline->op2, &free_op2, execute_data);
ZVAL_DEREF(op1);
ZVAL_DEREF(op2);
if (op1) {
ZVAL_DEREF(op1);
}
if (op2) {
ZVAL_DEREF(op2);
}
if (UNEXPECTED(opcode_is_greater(opline))) {
std::swap(op1, op2);
@ -83,7 +86,7 @@ namespace php_arma
int add_handler(zend_execute_data *execute_data)
{
return op_handler(execute_data, [] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1)) != IS_OBJECT) {
if (UNEXPECTED(Z_TYPE_P(zv1) != IS_OBJECT)) {
return false;
}
@ -97,8 +100,8 @@ namespace php_arma
int add_assign_handler(zend_execute_data *execute_data)
{
return op_handler(execute_data, [] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1)) != IS_OBJECT) {
return op_handler(execute_data, [execute_data] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1) != IS_OBJECT)) {
return false;
}
@ -113,7 +116,7 @@ namespace php_arma
int sub_handler(zend_execute_data *execute_data)
{
return op_handler(execute_data, [] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1)) != IS_OBJECT) {
if (UNEXPECTED(Z_TYPE_P(zv1) != IS_OBJECT)) {
return false;
}
@ -127,8 +130,8 @@ namespace php_arma
int sub_assign_handler(zend_execute_data *execute_data)
{
return op_handler(execute_data, [] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1)) != IS_OBJECT) {
return op_handler(execute_data, [execute_data] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1) != IS_OBJECT)) {
return false;
}
@ -143,7 +146,7 @@ namespace php_arma
int mul_handler(zend_execute_data *execute_data)
{
return op_handler(execute_data, [] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1)) != IS_OBJECT) {
if (UNEXPECTED(Z_TYPE_P(zv1) != IS_OBJECT)) {
return false;
}
@ -157,8 +160,8 @@ namespace php_arma
int mul_assign_handler(zend_execute_data *execute_data)
{
return op_handler(execute_data, [] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1)) != IS_OBJECT) {
return op_handler(execute_data, [execute_data] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1) != IS_OBJECT)) {
return false;
}
@ -173,7 +176,7 @@ namespace php_arma
int div_handler(zend_execute_data *execute_data)
{
return op_handler(execute_data, [] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1)) != IS_OBJECT) {
if (UNEXPECTED(Z_TYPE_P(zv1) != IS_OBJECT)) {
return false;
}
@ -187,8 +190,8 @@ namespace php_arma
int div_assign_handler(zend_execute_data *execute_data)
{
return op_handler(execute_data, [] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1)) != IS_OBJECT) {
return op_handler(execute_data, [execute_data] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1) != IS_OBJECT)) {
return false;
}
@ -203,7 +206,7 @@ namespace php_arma
int pow_handler(zend_execute_data *execute_data)
{
return op_handler(execute_data, [] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1)) != IS_OBJECT) {
if (UNEXPECTED(Z_TYPE_P(zv1) != IS_OBJECT)) {
return false;
}
@ -215,10 +218,25 @@ namespace php_arma
});
}
int pow_assign_handler(zend_execute_data *execute_data)
{
return op_handler(execute_data, [execute_data] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1) != IS_OBJECT)) {
return false;
}
PHP_ARMA_OPERATOR_BEGIN(Z_OBJCE_P(zv1), complex_ce)
PHP_ARMA_COMPLEX_OPERATOR_ASSIGN(double, pow)
PHP_ARMA_OPERATOR_END();
return false;
});
}
int bw_not_handler(zend_execute_data *execute_data)
{
return op_handler(execute_data, [] (auto zv1, auto zv2, auto retval) {
if (UNEXPECTED(Z_TYPE_P(zv1)) != IS_OBJECT) {
if (UNEXPECTED(Z_TYPE_P(zv1) != IS_OBJECT)) {
return false;
}
@ -230,7 +248,7 @@ namespace php_arma
});
}
void operator_init()
void operators_init()
{
zend_set_user_opcode_handler(ZEND_ADD, add_handler);
zend_set_user_opcode_handler(ZEND_ASSIGN_ADD, add_assign_handler);
@ -241,6 +259,7 @@ namespace php_arma
zend_set_user_opcode_handler(ZEND_DIV, div_handler);
zend_set_user_opcode_handler(ZEND_ASSIGN_DIV, div_assign_handler);
zend_set_user_opcode_handler(ZEND_POW, pow_handler);
zend_set_user_opcode_handler(ZEND_ASSIGN_POW, pow_assign_handler);
zend_set_user_opcode_handler(ZEND_BW_NOT, bw_not_handler);
}
}

View File

@ -9,7 +9,7 @@
namespace php_arma
{
void operator_init();
void operators_init();
}
#endif // !PHP_ARMA_OPERATOR_HH

View File

@ -10,9 +10,9 @@
#include "base.hh"
#include "mapval.hh"
#ifdef PHP_ARMA_OPERATOR
#include "operator.hh"
#endif // PHP_ARMA_OPERATOR
#ifdef PHP_ARMA_OPERATORS
#include "operators.hh"
#endif // PHP_ARMA_OPERATORS
#include <ext/standard/info.h>
@ -23,9 +23,9 @@ PHP_MINIT_FUNCTION(arma)
php_arma::base_init();
php_arma::mapval_init();
#ifdef PHP_ARMA_OPERATOR
php_arma::operator_init();
#endif // PHP_ARMA_OPERATOR
#ifdef PHP_ARMA_OPERATORS
php_arma::operators_init();
#endif // PHP_ARMA_OPERATORS
return SUCCESS;
}

View File

@ -65,6 +65,22 @@ extern zend_module_entry arma_module_entry;
static constexpr const char val[] = name; \
}
#ifdef PHP_ARMA_OPERATORS
/// Helper macros for handling operator overloading.
#define PHP_ARMA_OPERATOR_BEGIN(ce, parent_ce) \
{ \
auto _ce = ce; \
if (instanceof_function(_ce, parent_ce)) {
#define PHP_ARMA_OPERATOR_END() \
return false; \
} \
}
#endif // PHP_ARMA_OPERATORS
namespace php_arma
{
/// Helpers for compile-time string concatenation for better module startup performance.

View File

@ -1 +1,18 @@
<?php
namespace Arma;
/**
* Support status of option features.
*
* Values of constants in this class is dependent.
*
* @package Arma
*/
abstract class Features
{
/**
* Whether operator overloading is supported.
*/
const OPERATORS = true;
}

View File

@ -1,7 +1,10 @@
--TEST--
Test for constants.
--SKIPIF--
<?php require_once 'includes/loaded.php'; ?>
<?php
require_once 'includes/loaded.php';
is_php_arma_loaded();
?>
--FILE--
<?php

View File

@ -1,7 +1,10 @@
--TEST--
Test for `Arma\Complex`.
--SKIPIF--
<?php require_once 'includes/loaded.php'; ?>
<?php
require_once 'includes/loaded.php';
is_php_arma_loaded();
?>
--FILE--
<?php

View File

@ -1 +1,38 @@
--TEST--
Test for `Arma\Complex`.
--SKIPIF--
<?php
require_once 'includes/loaded.php';
require_once 'includes/supports.php';
if (is_php_arma_loaded()) {
supports_operator_overloading();
}
?>
--FILE--
<?php
require_once 'includes/assert.php';
$foo = new Arma\CxDouble(1.2, 1.7);
$bar = new Arma\CxDouble(2.3, 2.6);
batch_assert('operator overloading of Arma\\Complex',
[$foo->add($bar), $foo + $bar],
[$foo->sub($bar), $foo - $bar],
[$foo->mul($bar), $foo * $bar],
[$foo->div($bar), $foo / $bar],
[$foo->pow($bar), $foo ** $bar],
[$foo->conj(), ~$foo]
);
$foo1 = $foo->add($bar);
$bar1 = $bar->pow($foo1);
$foo2 = $foo1->sub($bar1);
$bar2 = $bar1->div($foo2);
$foo3 = $foo2->mul($bar2);
$foo *= $bar /= $foo -= $bar **= $foo += $bar;
batch_assert('operator overloading of Arma\\Complex', [$foo3, $foo]);
?>
--EXPECT--

View File

@ -1,5 +1,14 @@
<?php
if (!extension_loaded('arma')) {
echo 'skip armadillo extension is not loaded.';
/**
* Check whether the php-armadillo extension is loaded.
*
* @return bool
*/
function is_php_arma_loaded() {
if (!extension_loaded('arma')) {
echo 'skip armadillo extension is not loaded.';
return false;
}
return true;
}

View File

@ -1 +1,14 @@
<?php
/**
* Check whether operator overloading is supported.
*
* @return bool
*/
function supports_operator_overloading() {
if (!Arma\Features::OPERATORS) {
echo 'skip operator overloading is not supported.';
return false;
}
return true;
}