Arquivos
hhvm/hphp/runtime/ext/ext_array.cpp
T
Paul Tarjan 7f4e3c39d6 implement SplFileObject
This diff started off with trying to actually build `SplFileObject` since it was just stubbed out. But then I had to implement everything that extended from it since C++ classes can't extend PHP classes. And then it ballooend into what you see here. I actually think this is better in the long run, so that's why I kept going down this road.

The only thing that doesn't work in pure PHP is `sscanf`. @mwilliams has a fix for that. We need variable args by reference.

I implemented `RecursiveIteratorIterator` in a similar way to our C++ code instead of copying Zend. It translated to PHP a bit nicer. We still don't support the `RecursiveTreeIterator`, but I havn't come accross a need for that yet. I changed the implementation to actually use the `getChildren()` methods instead of peaking inside the `RecursiveDirectoryIterator`.
2013-06-03 10:55:24 -07:00

1360 linhas
42 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/ext/ext_array.h"
#include "hphp/runtime/ext/ext_function.h"
#include "hphp/runtime/ext/ext_continuation.h"
#include "hphp/runtime/ext/ext_collections.h"
#include "hphp/runtime/base/util/request_local.h"
#include "hphp/runtime/base/zend/zend_collator.h"
#include "hphp/runtime/base/builtin_functions.h"
#include "hphp/runtime/base/sort_flags.h"
#include "hphp/runtime/vm/jit/translator.h"
#include "hphp/runtime/vm/jit/translator-inline.h"
#include "hphp/runtime/base/array/hphp_array.h"
#include "hphp/util/logger.h"
#define SORT_DESC 3
#define SORT_ASC 4
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
static StaticString s_count("count");
const int64_t k_UCOL_DEFAULT = UCOL_DEFAULT;
const int64_t k_UCOL_PRIMARY = UCOL_PRIMARY;
const int64_t k_UCOL_SECONDARY = UCOL_SECONDARY;
const int64_t k_UCOL_TERTIARY = UCOL_TERTIARY;
const int64_t k_UCOL_DEFAULT_STRENGTH = UCOL_DEFAULT_STRENGTH;
const int64_t k_UCOL_QUATERNARY = UCOL_QUATERNARY;
const int64_t k_UCOL_IDENTICAL = UCOL_IDENTICAL;
const int64_t k_UCOL_OFF = UCOL_OFF;
const int64_t k_UCOL_ON = UCOL_ON;
const int64_t k_UCOL_SHIFTED = UCOL_SHIFTED;
const int64_t k_UCOL_NON_IGNORABLE = UCOL_NON_IGNORABLE;
const int64_t k_UCOL_LOWER_FIRST = UCOL_LOWER_FIRST;
const int64_t k_UCOL_UPPER_FIRST = UCOL_UPPER_FIRST;
const int64_t k_UCOL_FRENCH_COLLATION = UCOL_FRENCH_COLLATION;
const int64_t k_UCOL_ALTERNATE_HANDLING = UCOL_ALTERNATE_HANDLING;
const int64_t k_UCOL_CASE_FIRST = UCOL_CASE_FIRST;
const int64_t k_UCOL_CASE_LEVEL = UCOL_CASE_LEVEL;
const int64_t k_UCOL_NORMALIZATION_MODE = UCOL_NORMALIZATION_MODE;
const int64_t k_UCOL_STRENGTH = UCOL_STRENGTH;
const int64_t k_UCOL_HIRAGANA_QUATERNARY_MODE = UCOL_HIRAGANA_QUATERNARY_MODE;
const int64_t k_UCOL_NUMERIC_COLLATION = UCOL_NUMERIC_COLLATION;
using HPHP::Transl::CallerFrame;
using HPHP::Transl::EagerCallerFrame;
#define getCheckedArrayRetType(input, fail, type) \
Variant::TypedValueAccessor tva_##input = input.getTypedAccessor(); \
if (UNLIKELY(Variant::GetAccessorType(tva_##input) != KindOfArray)) { \
throw_bad_array_exception(); \
return fail; \
} \
type arr_##input = Variant::GetAsArray(tva_##input);
#define getCheckedArrayRet(input, fail) \
getCheckedArrayRetType(input, fail, CArrRef)
#define getCheckedArray(input) getCheckedArrayRet(input, uninit_null())
Variant f_array_change_key_case(CVarRef input, bool upper /* = false */) {
getCheckedArrayRet(input, false);
return ArrayUtil::ChangeKeyCase(arr_input, !upper);
}
Variant f_array_chunk(CVarRef input, int size,
bool preserve_keys /* = false */) {
getCheckedArray(input);
return ArrayUtil::Chunk(arr_input, size, preserve_keys);
}
static inline bool array_column_coerce_key(Variant &key, const char *name) {
/* NULL has a special meaning for each field */
if (key.isNull()) {
return true;
}
/* Custom coercion rules for key types */
if (key.isInteger() || key.isDouble()) {
key = key.toInt64();
return true;
} else if (key.isString() || key.isObject()) {
key = key.toString();
return true;
} else {
raise_warning("The %s key should be either a string or an integer", name);
return false;
}
}
Variant f_array_column(CVarRef input, CVarRef val_key,
CVarRef idx_key /* = null_variant */) {
/* Be strict about array type */
getCheckedArrayRet(input, uninit_null());
Variant val = val_key, idx = idx_key;
if (!array_column_coerce_key(val, "column") ||
!array_column_coerce_key(idx, "index")) {
return false;
}
Array ret = Array::Create();
for(auto it = arr_input.begin(); !it.end(); it.next()) {
if (!it.second().isArray()) {
continue;
}
Array sub = it.second().toArray();
Variant elem;
if (val.isNull()) {
elem = sub;
} else if (sub.exists(val)) {
elem = sub[val];
} else {
// skip subarray without named element
continue;
}
if (idx.isNull() || !sub.exists(idx)) {
ret.append(elem);
} else if (sub[idx].isObject()) {
ret.set(sub[idx].toString(), elem);
} else {
ret.set(sub[idx], elem);
}
}
return ret;
}
Variant f_array_combine(CVarRef keys, CVarRef values) {
getCheckedArray(keys);
getCheckedArray(values);
return ArrayUtil::Combine(arr_keys, arr_values);
}
Variant f_array_count_values(CVarRef input) {
getCheckedArray(input);
return ArrayUtil::CountValues(arr_input);
}
Variant f_array_fill_keys(CVarRef keys, CVarRef value) {
getCheckedArray(keys);
return ArrayUtil::CreateArray(arr_keys, value);
}
Variant f_array_fill(int start_index, int num, CVarRef value) {
return ArrayUtil::CreateArray(start_index, num, value);
}
static bool filter_func(CVarRef value, const void *data) {
CallCtx* ctx = (CallCtx*)data;
Variant ret;
TypedValue args[1];
tvDup(value.asTypedValue(), args + 0);
g_vmContext->invokeFuncFew((TypedValue*)&ret, *ctx, 1, args);
return ret.toBoolean();
}
Variant f_array_filter(CVarRef input, CVarRef callback /* = null_variant */) {
getCheckedArray(input);
if (callback.isNull()) {
return ArrayUtil::Filter(arr_input);
}
CallCtx ctx;
EagerCallerFrame cf;
vm_decode_function(callback, cf(), false, ctx);
if (ctx.func == NULL) {
return uninit_null();
}
return ArrayUtil::Filter(arr_input, filter_func, &ctx);
}
Variant f_array_flip(CVarRef trans) {
getCheckedArrayRet(trans, false);
return ArrayUtil::Flip(arr_trans);
}
HOT_FUNC
bool f_array_key_exists(CVarRef key, CVarRef search) {
const ArrayData *ad;
Variant::TypedValueAccessor sacc = search.getTypedAccessor();
DataType saccType = Variant::GetAccessorType(sacc);
if (LIKELY(saccType == KindOfArray)) {
ad = Variant::GetArrayData(sacc);
} else if (saccType == KindOfObject) {
ObjectData* obj = Variant::GetObjectData(sacc);
if (obj->isCollection()) {
return collectionOffsetContains(obj, key);
}
return f_array_key_exists(key, toArray(search));
} else {
throw_bad_type_exception("array_key_exists expects an array or an object; "
"false returned.");
return false;
}
Variant::TypedValueAccessor kacc = key.getTypedAccessor();
switch (Variant::GetAccessorType(kacc)) {
case KindOfString:
case KindOfStaticString: {
int64_t n = 0;
StringData *sd = Variant::GetStringData(kacc);
if (sd->isStrictlyInteger(n)) {
return ad->exists(n);
}
return ad->exists(StrNR(sd));
}
case KindOfInt64:
return ad->exists(Variant::GetInt64(kacc));
case KindOfUninit:
case KindOfNull:
return ad->exists(empty_string);
default:
break;
}
raise_warning("Array key should be either a string or an integer");
return false;
}
bool f_key_exists(CVarRef key, CVarRef search) {
return f_array_key_exists(key, search);
}
Variant f_array_keys(CVarRef input, CVarRef search_value /* = null_variant */,
bool strict /* = false */) {
getCheckedArray(input);
return arr_input.keys(search_value, strict);
}
static Variant map_func(CArrRef params, const void *data) {
CallCtx* ctx = (CallCtx*)data;
if (ctx == NULL) {
if (params.size() == 1) {
return params[0];
}
return params;
}
Variant ret;
g_vmContext->invokeFunc((TypedValue*)&ret, *ctx, params);
return ret;
}
Variant f_array_map(int _argc, CVarRef callback, CVarRef arr1, CArrRef _argv /* = null_array */) {
Array inputs;
if (!arr1.isArray()) {
throw_bad_array_exception();
return uninit_null();
}
inputs.append(arr1);
if (!_argv.empty()) {
inputs = inputs.merge(_argv);
}
CallCtx ctx;
ctx.func = NULL;
if (!callback.isNull()) {
EagerCallerFrame cf;
vm_decode_function(callback, cf(), false, ctx);
}
if (ctx.func == NULL) {
return ArrayUtil::Map(inputs, map_func, NULL);
}
return ArrayUtil::Map(inputs, map_func, &ctx);
}
static void php_array_merge(Array &arr1, CArrRef arr2) {
arr1.merge(arr2);
}
static void php_array_merge_recursive(PointerSet &seen, bool check,
Array &arr1, CArrRef arr2) {
if (check) {
if (seen.find((void*)arr1.get()) != seen.end()) {
raise_warning("array_merge_recursive(): recursion detected");
return;
}
seen.insert((void*)arr1.get());
}
for (ArrayIter iter(arr2); iter; ++iter) {
Variant key(iter.first());
CVarRef value(iter.secondRef());
if (key.isNumeric()) {
arr1.appendWithRef(value);
} else if (arr1.exists(key, true)) {
// There is no need to do toKey() conversion, for a key that is already
// in the array.
Variant &v = arr1.lvalAt(key, AccessFlags::Key);
Array subarr1(v.toArray()->copy());
php_array_merge_recursive(seen, v.isReferenced(), subarr1,
value.toArray());
v.unset(); // avoid contamination of the value that was strongly bound
v = subarr1;
} else {
arr1.addLval(key, true).setWithRef(value);
}
}
if (check) {
seen.erase((void*)arr1.get());
}
}
Variant f_array_merge(int _argc, CVarRef array1,
CArrRef _argv /* = null_array */) {
getCheckedArray(array1);
Array ret = Array::Create();
php_array_merge(ret, arr_array1);
for (ArrayIter iter(_argv); iter; ++iter) {
Variant v = iter.second();
if (!v.isArray()) {
throw_bad_array_exception();
return uninit_null();
}
CArrRef arr_v = v.asCArrRef();
php_array_merge(ret, arr_v);
}
return ret;
}
Variant f_array_merge_recursive(int _argc, CVarRef array1,
CArrRef _argv /* = null_array */) {
getCheckedArray(array1);
Array ret = Array::Create();
PointerSet seen;
php_array_merge_recursive(seen, false, ret, arr_array1);
assert(seen.empty());
for (ArrayIter iter(_argv); iter; ++iter) {
Variant v = iter.second();
if (!v.isArray()) {
throw_bad_array_exception();
return uninit_null();
}
CArrRef arr_v = v.asCArrRef();
php_array_merge_recursive(seen, false, ret, arr_v);
assert(seen.empty());
}
return ret;
}
static void php_array_replace(Array &arr1, CArrRef arr2) {
for (ArrayIter iter(arr2); iter; ++iter) {
Variant key = iter.first();
CVarRef value = iter.secondRef();
arr1.lvalAt(key, AccessFlags::Key).setWithRef(value);
}
}
static void php_array_replace_recursive(PointerSet &seen, bool check,
Array &arr1, CArrRef arr2) {
if (check) {
if (seen.find((void*)arr1.get()) != seen.end()) {
raise_warning("array_replace_recursive(): recursion detected");
return;
}
seen.insert((void*)arr1.get());
}
for (ArrayIter iter(arr2); iter; ++iter) {
Variant key = iter.first();
CVarRef value = iter.secondRef();
if (arr1.exists(key, true) && value.isArray()) {
Variant &v = arr1.lvalAt(key, AccessFlags::Key);
if (v.isArray()) {
Array subarr1 = v.toArray();
const ArrNR& arr_value = value.toArrNR();
php_array_replace_recursive(seen, v.isReferenced(), subarr1,
arr_value);
v = subarr1;
} else {
arr1.set(key, value, true);
}
} else {
arr1.lvalAt(key, AccessFlags::Key).setWithRef(value);
}
}
if (check) {
seen.erase((void*)arr1.get());
}
}
Variant f_array_replace(int _argc, CVarRef array1,
CArrRef _argv /* = null_array */) {
getCheckedArray(array1);
Array ret = Array::Create();
php_array_replace(ret, arr_array1);
for (ArrayIter iter(_argv); iter; ++iter) {
CVarRef v = iter.secondRef();
getCheckedArray(v);
php_array_replace(ret, arr_v);
}
return ret;
}
Variant f_array_replace_recursive(int _argc, CVarRef array1,
CArrRef _argv /* = null_array */) {
getCheckedArray(array1);
Array ret = Array::Create();
PointerSet seen;
php_array_replace_recursive(seen, false, ret, arr_array1);
assert(seen.empty());
for (ArrayIter iter(_argv); iter; ++iter) {
CVarRef v = iter.secondRef();
getCheckedArray(v);
php_array_replace_recursive(seen, false, ret, arr_v);
assert(seen.empty());
}
return ret;
}
Variant f_array_pad(CVarRef input, int pad_size, CVarRef pad_value) {
getCheckedArray(input);
if (pad_size > 0) {
return ArrayUtil::Pad(arr_input, pad_value, pad_size, true);
}
return ArrayUtil::Pad(arr_input, pad_value, -pad_size, false);
}
Variant f_array_pop(VRefParam array) {
return array.pop();
}
Variant f_array_product(CVarRef array) {
getCheckedArray(array);
if (arr_array.empty()) {
return 0; // to be consistent with PHP
}
int64_t i;
double d;
if (ArrayUtil::Product(arr_array, &i, &d) == KindOfInt64) {
return i;
} else {
return d;
}
}
Variant f_array_push(int _argc, VRefParam array, CVarRef var, CArrRef _argv /* = null_array */) {
getCheckedArrayRetType(array, uninit_null(), Array &);
arr_array.append(var);
for (ArrayIter iter(_argv); iter; ++iter) {
arr_array.append(iter.second());
}
return arr_array.size();
}
Variant f_array_rand(CVarRef input, int num_req /* = 1 */) {
getCheckedArray(input);
return ArrayUtil::RandomKeys(arr_input, num_req);
}
static Variant reduce_func(CVarRef result, CVarRef operand, const void *data) {
CallCtx* ctx = (CallCtx*)data;
Variant ret;
TypedValue args[2];
tvDup(result.asTypedValue(), args + 0);
tvDup(operand.asTypedValue(), args + 1);
g_vmContext->invokeFuncFew((TypedValue*)&ret, *ctx, 2, args);
return ret;
}
Variant f_array_reduce(CVarRef input, CVarRef callback,
CVarRef initial /* = null_variant */) {
getCheckedArray(input);
CallCtx ctx;
CallerFrame cf;
vm_decode_function(callback, cf(), false, ctx);
if (ctx.func == NULL) {
return uninit_null();
}
return ArrayUtil::Reduce(arr_input, reduce_func, &ctx, initial);
}
Variant f_array_reverse(CVarRef array, bool preserve_keys /* = false */) {
getCheckedArray(array);
return ArrayUtil::Reverse(arr_array, preserve_keys);
}
Variant f_array_search(CVarRef needle, CVarRef haystack,
bool strict /* = false */) {
getCheckedArrayRet(haystack, false);
return arr_haystack.key(needle, strict);
}
Variant f_array_shift(VRefParam array) {
return array.dequeue();
}
Variant f_array_slice(CVarRef array, int offset,
CVarRef length /* = null_variant */,
bool preserve_keys /* = false */) {
getCheckedArray(array);
int64_t len = length.isNull() ? 0x7FFFFFFF : length.toInt64();
return ArrayUtil::Slice(arr_array, offset, len, preserve_keys);
}
Variant f_array_splice(VRefParam input, int offset,
CVarRef length /* = null_variant */,
CVarRef replacement /* = null_variant */) {
getCheckedArray(input);
Array ret(Array::Create());
int64_t len = length.isNull() ? 0x7FFFFFFF : length.toInt64();
input = ArrayUtil::Splice(arr_input, offset, len, replacement, &ret);
return ret;
}
Variant f_array_sum(CVarRef array) {
getCheckedArray(array);
int64_t i;
double d;
if (ArrayUtil::Sum(arr_array, &i, &d) == KindOfInt64) {
return i;
} else {
return d;
}
}
int64_t f_array_unshift(int _argc, VRefParam array, CVarRef var, CArrRef _argv /* = null_array */) {
if (array.toArray()->isVectorData()) {
if (!_argv.empty()) {
for (ssize_t pos = _argv->iter_end(); pos != ArrayData::invalid_index;
pos = _argv->iter_rewind(pos)) {
array.prepend(_argv->getValueRef(pos));
}
}
array.prepend(var);
} else {
{
Array newArray;
newArray.append(var);
if (!_argv.empty()) {
for (ssize_t pos = _argv->iter_begin();
pos != ArrayData::invalid_index;
pos = _argv->iter_advance(pos)) {
newArray.append(_argv->getValueRef(pos));
}
}
for (ArrayIter iter(array); iter; ++iter) {
Variant key(iter.first());
CVarRef value(iter.secondRef());
if (key.isInteger()) {
newArray.appendWithRef(value);
} else {
newArray.lvalAt(key, AccessFlags::Key).setWithRef(value);
}
}
array = newArray;
}
// Reset the array's internal pointer
if (array.is(KindOfArray)) {
array.array_iter_reset();
}
}
return array.toArray().size();
}
Variant f_array_values(CVarRef input) {
getCheckedArray(input);
return arr_input.values();
}
static void walk_func(VRefParam value, CVarRef key, CVarRef userdata,
const void *data) {
CallCtx* ctx = (CallCtx*)data;
Variant sink;
TypedValue args[3];
tvDup(value->asTypedValue(), args + 0);
tvDup(key.asTypedValue(), args + 1);
tvDup(userdata.asTypedValue(), args + 2);
g_vmContext->invokeFuncFew((TypedValue*)&sink, *ctx, 3, args);
}
bool f_array_walk_recursive(VRefParam input, CVarRef funcname,
CVarRef userdata /* = null_variant */) {
if (!input.isArray()) {
throw_bad_array_exception();
return false;
}
CallCtx ctx;
CallerFrame cf;
vm_decode_function(funcname, cf(), false, ctx);
if (ctx.func == NULL) {
return uninit_null();
}
PointerSet seen;
ArrayUtil::Walk(input, walk_func, &ctx, true, &seen, userdata);
return true;
}
bool f_array_walk(VRefParam input, CVarRef funcname,
CVarRef userdata /* = null_variant */) {
if (!input.isArray()) {
throw_bad_array_exception();
return false;
}
CallCtx ctx;
CallerFrame cf;
vm_decode_function(funcname, cf(), false, ctx);
if (ctx.func == NULL) {
return uninit_null();
}
ArrayUtil::Walk(input, walk_func, &ctx, false, NULL, userdata);
return true;
}
static void compact(VarEnv* v, Array &ret, CVarRef var) {
if (var.isArray()) {
for (ArrayIter iter(var.getArrayData()); iter; ++iter) {
compact(v, ret, iter.second());
}
} else {
String varname = var.toString();
if (!varname.empty() && v->lookup(varname.get()) != NULL) {
ret.set(varname, *reinterpret_cast<Variant*>(v->lookup(varname.get())));
}
}
}
Array f_compact(int _argc, CVarRef varname, CArrRef _argv /* = null_array */) {
Array ret = Array::Create();
VarEnv* v = g_vmContext->getVarEnv();
if (v) {
compact(v, ret, varname);
compact(v, ret, _argv);
}
return ret;
}
static int php_count_recursive(CArrRef array) {
long cnt = array.size();
for (ArrayIter iter(array); iter; ++iter) {
Variant value = iter.second();
if (value.isArray()) {
CArrRef arr_value = value.asCArrRef();
cnt += php_count_recursive(arr_value);
}
}
return cnt;
}
bool f_shuffle(VRefParam array) {
if (!array.isArray()) {
throw_bad_array_exception();
return false;
}
array = ArrayUtil::Shuffle(array);
return true;
}
int64_t f_count(CVarRef var, bool recursive /* = false */) {
switch (var.getType()) {
case KindOfUninit:
case KindOfNull:
return 0;
case KindOfObject:
{
Object obj = var.toObject();
if (obj.instanceof(SystemLib::s_CountableClass)) {
return obj->o_invoke_few_args(s_count, 0);
}
}
break;
case KindOfArray:
if (recursive) {
CArrRef arr_var = var.toCArrRef();
return php_count_recursive(arr_var);
}
return var.getArrayData()->size();
default:
break;
}
return 1;
}
int64_t f_sizeof(CVarRef var, bool recursive /* = false */) {
return f_count(var, recursive);
}
Variant f_each(VRefParam array) {
return array.array_iter_each();
}
Variant f_current(VRefParam array) {
return array.array_iter_current();
}
Variant f_next(VRefParam array) {
return array.array_iter_next();
}
Variant f_pos(VRefParam array) {
return array.array_iter_current();
}
Variant f_prev(VRefParam array) {
return array.array_iter_prev();
}
Variant f_reset(VRefParam array) {
return array.array_iter_reset();
}
Variant f_end(VRefParam array) {
return array.array_iter_end();
}
Variant f_key(VRefParam array) {
return array.array_iter_key();
}
bool f_in_array(CVarRef needle, CVarRef haystack, bool strict /* = false */) {
getCheckedArrayRet(haystack, false);
return arr_haystack.valueExists(needle, strict);
}
Variant f_range(CVarRef low, CVarRef high, CVarRef step /* = 1 */) {
bool is_step_double = false;
double dstep = 1.0;
if (step.isDouble()) {
dstep = step.toDouble();
is_step_double = true;
} else if (step.isString()) {
int64_t sn;
double sd;
DataType stype = step.toString()->isNumericWithVal(sn, sd, 0);
if (stype == KindOfDouble) {
is_step_double = true;
dstep = sd;
} else if (stype == KindOfInt64) {
dstep = (double)sn;
} else {
dstep = step.toDouble();
}
} else {
dstep = step.toDouble();
}
/* We only want positive step values. */
if (dstep < 0.0) dstep *= -1;
if (low.isString() && high.isString()) {
String slow = low.toString();
String shigh = high.toString();
if (slow.size() >= 1 && shigh.size() >=1) {
int64_t n1, n2;
double d1, d2;
DataType type1 = slow->isNumericWithVal(n1, d1, 0);
DataType type2 = shigh->isNumericWithVal(n2, d2, 0);
if (type1 == KindOfDouble || type2 == KindOfDouble || is_step_double) {
if (type1 != KindOfDouble) d1 = slow.toDouble();
if (type2 != KindOfDouble) d2 = shigh.toDouble();
return ArrayUtil::Range(d1, d2, dstep);
}
int64_t lstep = (int64_t) dstep;
if (type1 == KindOfInt64 || type2 == KindOfInt64) {
if (type1 != KindOfInt64) n1 = slow.toInt64();
if (type2 != KindOfInt64) n2 = shigh.toInt64();
return ArrayUtil::Range((double)n1, (double)n2, lstep);
}
return ArrayUtil::Range((unsigned char)slow.charAt(0),
(unsigned char)shigh.charAt(0), lstep);
}
}
if (low.is(KindOfDouble) || high.is(KindOfDouble) || is_step_double) {
return ArrayUtil::Range(low.toDouble(), high.toDouble(), dstep);
}
int64_t lstep = (int64_t) dstep;
return ArrayUtil::Range(low.toDouble(), high.toDouble(), lstep);
}
///////////////////////////////////////////////////////////////////////////////
// diff/intersect helpers
static int cmp_func(CVarRef v1, CVarRef v2, const void *data) {
Variant *callback = (Variant *)data;
return vm_call_user_func(*callback, CREATE_VECTOR2(v1, v2));
}
#define COMMA ,
#define diff_intersect_body(type,intersect_params,user_setup) \
getCheckedArray(array1); \
if (!arr_array1.size()) return arr_array1; \
user_setup \
Array ret = arr_array1.type(array2, intersect_params); \
if (ret.size()) { \
for (ArrayIter iter(_argv); iter; ++iter) { \
ret = ret.type(iter.second(), intersect_params); \
if (!ret.size()) break; \
} \
} \
return ret;
///////////////////////////////////////////////////////////////////////////////
// diff functions
Variant f_array_diff(int _argc, CVarRef array1, CVarRef array2,
CArrRef _argv /* = null_array */) {
diff_intersect_body(diff, false COMMA true,);
}
Variant f_array_udiff(int _argc, CVarRef array1, CVarRef array2,
CVarRef data_compare_func,
CArrRef _argv /* = null_array */) {
diff_intersect_body(diff, false COMMA true COMMA NULL COMMA NULL
COMMA cmp_func COMMA &func,
Variant func = data_compare_func;
Array extra = _argv;
if (!extra.empty()) {
extra.prepend(func);
func = extra.pop();
});
}
Variant f_array_diff_assoc(int _argc, CVarRef array1, CVarRef array2,
CArrRef _argv /* = null_array */) {
diff_intersect_body(diff, true COMMA true,);
}
Variant f_array_diff_uassoc(int _argc, CVarRef array1, CVarRef array2,
CVarRef key_compare_func,
CArrRef _argv /* = null_array */) {
diff_intersect_body(diff, true COMMA true COMMA cmp_func COMMA &func,
Variant func = key_compare_func;
Array extra = _argv;
if (!extra.empty()) {
extra.prepend(func);
func = extra.pop();
});
}
Variant f_array_udiff_assoc(int _argc, CVarRef array1, CVarRef array2,
CVarRef data_compare_func,
CArrRef _argv /* = null_array */) {
diff_intersect_body(diff, true COMMA true COMMA NULL COMMA NULL
COMMA cmp_func COMMA &func,
Variant func = data_compare_func;
Array extra = _argv;
if (!extra.empty()) {
extra.prepend(func);
func = extra.pop();
});
}
Variant f_array_udiff_uassoc(int _argc, CVarRef array1, CVarRef array2,
CVarRef data_compare_func,
CVarRef key_compare_func,
CArrRef _argv /* = null_array */) {
diff_intersect_body(diff, true COMMA true COMMA cmp_func COMMA &key_func
COMMA cmp_func COMMA &data_func,
Variant data_func = data_compare_func;
Variant key_func = key_compare_func;
Array extra = _argv;
if (!extra.empty()) {
extra.prepend(key_func);
extra.prepend(data_func);
key_func = extra.pop();
data_func = extra.pop();
});
}
Variant f_array_diff_key(int _argc, CVarRef array1, CVarRef array2,
CArrRef _argv /* = null_array */) {
diff_intersect_body(diff, true COMMA false,);
}
Variant f_array_diff_ukey(int _argc, CVarRef array1, CVarRef array2,
CVarRef key_compare_func,
CArrRef _argv /* = null_array */) {
diff_intersect_body(diff, true COMMA false COMMA cmp_func COMMA &func,
Variant func = key_compare_func;
Array extra = _argv;
if (!extra.empty()) {
extra.prepend(func);
func = extra.pop();
});
}
///////////////////////////////////////////////////////////////////////////////
// intersect functions
Variant f_array_intersect(int _argc, CVarRef array1, CVarRef array2,
CArrRef _argv /* = null_array */) {
diff_intersect_body(intersect, false COMMA true,);
}
Variant f_array_uintersect(int _argc, CVarRef array1, CVarRef array2,
CVarRef data_compare_func,
CArrRef _argv /* = null_array */) {
diff_intersect_body(intersect, false COMMA true COMMA NULL COMMA NULL
COMMA cmp_func COMMA &func,
Variant func = data_compare_func;
Array extra = _argv;
if (!extra.empty()) {
extra.prepend(func);
func = extra.pop();
});
}
Variant f_array_intersect_assoc(int _argc, CVarRef array1, CVarRef array2,
CArrRef _argv /* = null_array */) {
diff_intersect_body(intersect, true COMMA true,);
}
Variant f_array_intersect_uassoc(int _argc, CVarRef array1, CVarRef array2,
CVarRef key_compare_func,
CArrRef _argv /* = null_array */) {
diff_intersect_body(intersect, true COMMA true COMMA cmp_func COMMA &func,
Variant func = key_compare_func;
Array extra = _argv;
if (!extra.empty()) {
extra.prepend(func);
func = extra.pop();
});
}
Variant f_array_uintersect_assoc(int _argc, CVarRef array1, CVarRef array2,
CVarRef data_compare_func,
CArrRef _argv /* = null_array */) {
diff_intersect_body(intersect, true COMMA true COMMA NULL COMMA NULL
COMMA cmp_func COMMA &func,
Variant func = data_compare_func;
Array extra = _argv;
if (!extra.empty()) {
extra.prepend(func);
func = extra.pop();
});
}
Variant f_array_uintersect_uassoc(int _argc, CVarRef array1, CVarRef array2,
CVarRef data_compare_func,
CVarRef key_compare_func,
CArrRef _argv /* = null_array */) {
diff_intersect_body(intersect, true COMMA true COMMA cmp_func COMMA &key_func
COMMA cmp_func COMMA &data_func,
Variant data_func = data_compare_func;
Variant key_func = key_compare_func;
Array extra = _argv;
if (!extra.empty()) {
extra.prepend(key_func);
extra.prepend(data_func);
key_func = extra.pop();
data_func = extra.pop();
});
}
Variant f_array_intersect_key(int _argc, CVarRef array1, CVarRef array2, CArrRef _argv /* = null_array */) {
diff_intersect_body(intersect, true COMMA false,);
}
Variant f_array_intersect_ukey(int _argc, CVarRef array1, CVarRef array2,
CVarRef key_compare_func, CArrRef _argv /* = null_array */) {
diff_intersect_body(intersect, true COMMA false COMMA cmp_func COMMA &func,
Variant func = key_compare_func;
Array extra = _argv;
if (!extra.empty()) {
extra.prepend(func);
func = extra.pop();
});
}
///////////////////////////////////////////////////////////////////////////////
// sorting functions
class Collator : public RequestEventHandler {
public:
String getLocale() {
return m_locale;
}
intl_error &getErrorCodeRef() {
return m_errcode;
}
bool setLocale(CStrRef locale) {
if (m_locale.same(locale)) {
return true;
}
if (m_ucoll) {
ucol_close(m_ucoll);
m_ucoll = NULL;
}
m_errcode.clear();
m_ucoll = ucol_open(locale.data(), &(m_errcode.code));
if (m_ucoll == NULL) {
raise_warning("failed to load %s locale from icu data", locale.data());
return false;
}
if (U_FAILURE(m_errcode.code)) {
ucol_close(m_ucoll);
m_ucoll = NULL;
return false;
}
m_locale = locale;
return true;
}
UCollator *getCollator() {
return m_ucoll;
}
bool setAttribute(int64_t attr, int64_t val) {
if (!m_ucoll) {
Logger::Verbose("m_ucoll is NULL");
return false;
}
m_errcode.clear();
ucol_setAttribute(m_ucoll, (UColAttribute)attr,
(UColAttributeValue)val, &(m_errcode.code));
if (U_FAILURE(m_errcode.code)) {
Logger::Verbose("Error setting attribute value");
return false;
}
return true;
}
bool setStrength(int64_t strength) {
if (!m_ucoll) {
Logger::Verbose("m_ucoll is NULL");
return false;
}
ucol_setStrength(m_ucoll, (UCollationStrength)strength);
return true;
}
Variant getErrorCode() {
if (!m_ucoll) {
Logger::Verbose("m_ucoll is NULL");
return false;
}
return m_errcode.code;
}
virtual void requestInit() {
m_locale = String(uloc_getDefault(), CopyString);
m_errcode.clear();
m_ucoll = ucol_open(m_locale.data(), &(m_errcode.code));
assert(m_ucoll);
}
virtual void requestShutdown() {
m_locale.reset();
m_errcode.clear();
if (m_ucoll) {
ucol_close(m_ucoll);
m_ucoll = NULL;
}
}
private:
String m_locale;
UCollator *m_ucoll;
intl_error m_errcode;
};
IMPLEMENT_STATIC_REQUEST_LOCAL(Collator, s_collator);
static Array::PFUNC_CMP get_cmp_func(int sort_flags, bool ascending) {
switch (sort_flags) {
case SORT_NUMERIC:
return ascending ?
Array::SortNumericAscending : Array::SortNumericDescending;
case SORT_STRING:
return ascending ?
Array::SortStringAscending : Array::SortStringDescending;
case SORT_LOCALE_STRING:
return ascending ?
Array::SortLocaleStringAscending : Array::SortLocaleStringDescending;
case SORT_REGULAR:
default:
return ascending ?
Array::SortRegularAscending : Array::SortRegularDescending;
}
}
class ArraySortTmp {
public:
explicit ArraySortTmp(Array& arr) : m_arr(arr) {
m_ad = arr.get()->escalateForSort();
m_ad->incRefCount();
}
~ArraySortTmp() {
if (m_ad != m_arr.get()) {
m_arr = m_ad;
m_ad->decRefCount();
}
}
ArrayData* operator->() { return m_ad; }
private:
Array& m_arr;
ArrayData* m_ad;
};
static bool
php_sort(VRefParam array, int sort_flags, bool ascending, bool use_collator) {
if (array.isArray()) {
Array& arr_array = Variant::GetAsArray(array.getTypedAccessor());
if (use_collator && sort_flags != SORT_LOCALE_STRING) {
UCollator *coll = s_collator->getCollator();
if (coll) {
intl_error &errcode = s_collator->getErrorCodeRef();
return collator_sort(array, sort_flags, ascending,
coll, &errcode);
}
}
ArraySortTmp ast(arr_array);
ast->sort(sort_flags, ascending);
return true;
}
if (array.isObject()) {
ObjectData* obj = array.getObjectData();
if (obj->getCollectionType() == Collection::VectorType) {
c_Vector* vec = static_cast<c_Vector*>(obj);
vec->sort(sort_flags, ascending);
return true;
}
}
throw_bad_array_exception();
return false;
}
static bool
php_asort(VRefParam array, int sort_flags, bool ascending, bool use_collator) {
if (array.isArray()) {
Array& arr_array = Variant::GetAsArray(array.getTypedAccessor());
if (use_collator && sort_flags != SORT_LOCALE_STRING) {
UCollator *coll = s_collator->getCollator();
if (coll) {
intl_error &errcode = s_collator->getErrorCodeRef();
return collator_asort(array, sort_flags, ascending,
coll, &errcode);
}
}
ArraySortTmp ast(arr_array);
ast->asort(sort_flags, ascending);
return true;
}
if (array.isObject()) {
ObjectData* obj = array.getObjectData();
if (obj->getCollectionType() == Collection::StableMapType) {
c_StableMap* smp = static_cast<c_StableMap*>(obj);
smp->asort(sort_flags, ascending);
return true;
}
}
throw_bad_array_exception();
return false;
}
static bool
php_ksort(VRefParam array, int sort_flags, bool ascending) {
if (array.isArray()) {
Array& arr_array = Variant::GetAsArray(array.getTypedAccessor());
ArraySortTmp ast(arr_array);
ast->ksort(sort_flags, ascending);
return true;
}
if (array.isObject()) {
ObjectData* obj = array.getObjectData();
if (obj->getCollectionType() == Collection::StableMapType) {
c_StableMap* smp = static_cast<c_StableMap*>(obj);
smp->ksort(sort_flags, ascending);
return true;
}
}
throw_bad_array_exception();
return false;
}
bool f_sort(VRefParam array, int sort_flags /* = 0 */,
bool use_collator /* = false */) {
return php_sort(array, sort_flags, true, use_collator);
}
bool f_rsort(VRefParam array, int sort_flags /* = 0 */,
bool use_collator /* = false */) {
return php_sort(array, sort_flags, false, use_collator);
}
bool f_asort(VRefParam array, int sort_flags /* = 0 */,
bool use_collator /* = false */) {
return php_asort(array, sort_flags, true, use_collator);
}
bool f_arsort(VRefParam array, int sort_flags /* = 0 */,
bool use_collator /* = false */) {
return php_asort(array, sort_flags, false, use_collator);
}
bool f_ksort(VRefParam array, int sort_flags /* = 0 */) {
return php_ksort(array, sort_flags, true);
}
bool f_krsort(VRefParam array, int sort_flags /* = 0 */) {
return php_ksort(array, sort_flags, false);
}
// NOTE: PHP's implementation of natsort and natcasesort accepts ArrayAccess
// objects as well, which does not make much sense, and which is not supported
// here.
Variant f_natsort(VRefParam array) {
return php_asort(array, SORT_NATURAL, true, false);
}
Variant f_natcasesort(VRefParam array) {
return php_asort(array, SORT_NATURAL_CASE, true, false);
}
bool f_usort(VRefParam array, CVarRef cmp_function) {
if (array.isArray()) {
Array& arr_array = Variant::GetAsArray(array.getTypedAccessor());
ArraySortTmp ast(arr_array);
ast->usort(cmp_function);
return true;
}
if (array.isObject()) {
ObjectData* obj = array.getObjectData();
if (obj->getCollectionType() == Collection::VectorType) {
c_Vector* vec = static_cast<c_Vector*>(obj);
vec->usort(cmp_function);
return true;
}
}
throw_bad_array_exception();
return false;
}
bool f_uasort(VRefParam array, CVarRef cmp_function) {
if (array.isArray()) {
Array& arr_array = Variant::GetAsArray(array.getTypedAccessor());
ArraySortTmp ast(arr_array);
ast->uasort(cmp_function);
return true;
}
if (array.isObject()) {
ObjectData* obj = array.getObjectData();
if (obj->getCollectionType() == Collection::StableMapType) {
c_StableMap* smp = static_cast<c_StableMap*>(obj);
smp->uasort(cmp_function);
return true;
}
}
throw_bad_array_exception();
return false;
}
bool f_uksort(VRefParam array, CVarRef cmp_function) {
if (array.isArray()) {
Array& arr_array = Variant::GetAsArray(array.getTypedAccessor());
ArraySortTmp ast(arr_array);
ast->uksort(cmp_function);
return true;
}
if (array.isObject()) {
ObjectData* obj = array.getObjectData();
if (obj->getCollectionType() == Collection::StableMapType) {
c_StableMap* smp = static_cast<c_StableMap*>(obj);
smp->uksort(cmp_function);
return true;
}
}
throw_bad_array_exception();
return false;
}
bool f_array_multisort(int _argc, VRefParam ar1,
CArrRef _argv /* = null_array */) {
getCheckedArrayRet(ar1, false);
std::vector<Array::SortData> data;
std::vector<Array> arrays;
arrays.reserve(1 + _argv.size()); // so no resize would happen
Array::SortData sd;
sd.original = &ar1;
arrays.push_back(arr_ar1);
sd.array = &arrays.back();
sd.by_key = false;
int sort_flags = SORT_REGULAR;
bool ascending = true;
for (int i = 0; i < _argv.size(); i++) {
Variant *v = &((Array&)_argv).lvalAt(i);
Variant::TypedValueAccessor tva = v->getTypedAccessor();
if (Variant::GetAccessorType(tva) == KindOfArray) {
sd.cmp_func = get_cmp_func(sort_flags, ascending);
data.push_back(sd);
sort_flags = SORT_REGULAR;
ascending = true;
sd.original = v;
arrays.push_back(Variant::GetAsArray(tva));
sd.array = &arrays.back();
} else {
int n = v->toInt32();
if (n == SORT_ASC) {
ascending = true;
} else if (n == SORT_DESC) {
ascending = false;
} else {
sort_flags = n;
}
}
}
sd.cmp_func = get_cmp_func(sort_flags, ascending);
data.push_back(sd);
return Array::MultiSort(data, true);
}
Variant f_array_unique(CVarRef array, int sort_flags /* = 2 */) {
// NOTE, PHP array_unique accepts ArrayAccess objects as well,
// which is not supported here.
getCheckedArray(array);
switch (sort_flags) {
case SORT_STRING:
case SORT_LOCALE_STRING:
return ArrayUtil::StringUnique(arr_array);
case SORT_NUMERIC:
return ArrayUtil::NumericUnique(arr_array);
case SORT_REGULAR:
default:
return ArrayUtil::RegularSortUnique(arr_array);
}
}
String f_i18n_loc_get_default() {
return s_collator->getLocale();
}
bool f_i18n_loc_set_default(CStrRef locale) {
return s_collator->setLocale(locale);
}
bool f_i18n_loc_set_attribute(int64_t attr, int64_t val) {
return s_collator->setAttribute(attr, val);
}
bool f_i18n_loc_set_strength(int64_t strength) {
return s_collator->setStrength(strength);
}
Variant f_i18n_loc_get_error_code() {
return s_collator->getErrorCode();
}
Variant f_hphp_array_idx(CVarRef key, CVarRef search, CVarRef def) {
if (!key.isNull()) {
if (LIKELY(search.isArray())) {
ArrayData *arr = search.getArrayData();
VarNR index = key.toKey();
if (!index.isNull()) {
CVarRef ret = arr->get(index, false);
return (&ret != &null_variant) ? ret : def;
}
} else {
raise_error("hphp_array_idx: search must be an array");
}
}
return def;
}
///////////////////////////////////////////////////////////////////////////////
}