/*
   +----------------------------------------------------------------------+
   | Copyright © The PHP Group and Contributors.                          |
   +----------------------------------------------------------------------+
   | This source file is subject to the Modified BSD License that is      |
   | bundled with this package in the file LICENSE, and is available      |
   | through the World Wide Web at <https://www.php.net/license/>.        |
   |                                                                      |
   | SPDX-License-Identifier: BSD-3-Clause                                |
   +----------------------------------------------------------------------+
   | Author: Rasmus Lerdorf <rasmus@php.net>                              |
   +----------------------------------------------------------------------+
*/

#include "php.h"

/* {{{ Returns the type of the variable */
PHP_FUNCTION(gettype)
{
	zval *arg;
	zend_string *type;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(arg)
	ZEND_PARSE_PARAMETERS_END();

	type = zend_zval_get_legacy_type(arg);
	if (EXPECTED(type)) {
		RETURN_INTERNED_STR(type);
	} else {
		RETURN_STRING("unknown type");
	}
}
/* }}} */

/* {{{ Returns the type of the variable resolving class names */
PHP_FUNCTION(get_debug_type)
{
	zval *arg;
	const char *name;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(arg)
	ZEND_PARSE_PARAMETERS_END();

	switch (Z_TYPE_P(arg)) {
		case IS_NULL:
			RETURN_INTERNED_STR(ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE));
		case IS_FALSE:
		case IS_TRUE:
			RETURN_INTERNED_STR(ZSTR_KNOWN(ZEND_STR_BOOL));
		case IS_LONG:
			RETURN_INTERNED_STR(ZSTR_KNOWN(ZEND_STR_INT));
		case IS_DOUBLE:
			RETURN_INTERNED_STR(ZSTR_KNOWN(ZEND_STR_FLOAT));
		case IS_STRING:
			RETURN_INTERNED_STR(ZSTR_KNOWN(ZEND_STR_STRING));
		case IS_ARRAY:
			RETURN_INTERNED_STR(ZSTR_KNOWN(ZEND_STR_ARRAY));
		case IS_OBJECT:
			if (Z_OBJ_P(arg)->ce->ce_flags & ZEND_ACC_ANON_CLASS) {
				name = ZSTR_VAL(Z_OBJ_P(arg)->ce->name);
				RETURN_NEW_STR(zend_string_init(name, strlen(name), 0));
			} else {
				RETURN_STR_COPY(Z_OBJ_P(arg)->ce->name);
			}
		case IS_RESOURCE:
			name = zend_rsrc_list_get_rsrc_type(Z_RES_P(arg));
			if (name) {
				RETURN_NEW_STR(zend_strpprintf(0, "resource (%s)", name));
			} else {
				RETURN_INTERNED_STR(ZSTR_KNOWN(ZEND_STR_CLOSED_RESOURCE));
			}
		default:
			RETURN_INTERNED_STR(ZSTR_KNOWN(ZEND_STR_UNKNOWN));
	}
}
/* }}} */


/* {{{ Set the type of the variable */
PHP_FUNCTION(settype)
{
	zval *var;
	zend_string *type;
	zval tmp, *ptr;

	ZEND_PARSE_PARAMETERS_START(2, 2)
		Z_PARAM_ZVAL(var)
		Z_PARAM_STR(type)
	ZEND_PARSE_PARAMETERS_END();

	ZEND_ASSERT(Z_ISREF_P(var));
	if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(var)))) {
		ZVAL_COPY(&tmp, Z_REFVAL_P(var));
		ptr = &tmp;
	} else {
		ptr = Z_REFVAL_P(var);
	}
	if (zend_string_equals_ci(type, ZSTR_KNOWN(ZEND_STR_INTEGER))) {
		convert_to_long(ptr);
	} else if (zend_string_equals_ci(type, ZSTR_KNOWN(ZEND_STR_INT))) {
		convert_to_long(ptr);
	} else if (zend_string_equals_ci(type, ZSTR_KNOWN(ZEND_STR_FLOAT))) {
		convert_to_double(ptr);
	} else if (zend_string_equals_ci(type, ZSTR_KNOWN(ZEND_STR_DOUBLE))) {
		convert_to_double(ptr);
	} else if (zend_string_equals_ci(type, ZSTR_KNOWN(ZEND_STR_STRING))) {
		convert_to_string(ptr);
	} else if (zend_string_equals_ci(type, ZSTR_KNOWN(ZEND_STR_ARRAY))) {
		convert_to_array(ptr);
	} else if (zend_string_equals_ci(type, ZSTR_KNOWN(ZEND_STR_OBJECT))) {
		convert_to_object(ptr);
	} else if (zend_string_equals_ci(type, ZSTR_KNOWN(ZEND_STR_BOOL))) {
		convert_to_boolean(ptr);
	} else if (zend_string_equals_ci(type, ZSTR_KNOWN(ZEND_STR_BOOLEAN))) {
		convert_to_boolean(ptr);
	} else if (zend_string_equals_ci(type, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE))) {
		convert_to_null(ptr);
	} else {
		if (ptr == &tmp) {
			zval_ptr_dtor(&tmp);
		}
		if (zend_string_equals_ci(type, ZSTR_KNOWN(ZEND_STR_RESOURCE))) {
			zend_value_error("Cannot convert to resource type");
		} else {
			zend_argument_value_error(2, "must be a valid type");
		}
		RETURN_THROWS();
	}

	if (ptr == &tmp) {
		zend_try_assign_typed_ref(Z_REF_P(var), &tmp);
	}
	RETVAL_TRUE;
}
/* }}} */

/* {{{ Get the integer value of a variable using the optional base for the conversion */
PHP_FUNCTION(intval)
{
	zval *num;
	zend_long base = 10;

	ZEND_PARSE_PARAMETERS_START(1, 2)
		Z_PARAM_ZVAL(num)
		Z_PARAM_OPTIONAL
		Z_PARAM_LONG(base)
	ZEND_PARSE_PARAMETERS_END();

	if (Z_TYPE_P(num) != IS_STRING || base == 10) {
		RETURN_LONG(zval_get_long(num));
	}


	if (base == 0 || base == 2) {
		char *strval = Z_STRVAL_P(num);
		size_t strlen = Z_STRLEN_P(num);

		while (isspace((unsigned char)*strval) && strlen) {
			strval++;
			strlen--;
		}

		/* Length of 3+ covers "0b#" and "-0b" (which results in 0) */
		if (strlen > 2) {
			int offset = 0;
			if (strval[0] == '-' || strval[0] == '+') {
				offset = 1;
			}

			if (strval[offset] == '0' && (strval[offset + 1] == 'b' || strval[offset + 1] == 'B')) {
				if (strval[0] != '-') {
					/* Either "+0b", or "0b" */
					RETURN_LONG(ZEND_STRTOL(strval + 2 + offset, NULL, 2));
				}

				char *tmpval;
				strlen -= 2; /* Removing "0b" */
				tmpval = emalloc(strlen + 1);

				/* Place the unary symbol at pos 0 */
				tmpval[0] = '-';

				/* Copy the data from after "0b" to the end of the buffer */
				memcpy(tmpval + offset, strval + offset + 2, strlen - offset);
				tmpval[strlen] = 0;

				RETVAL_LONG(ZEND_STRTOL(tmpval, NULL, 2));
				efree(tmpval);
				return;
			}
		}
	}

	RETVAL_LONG(ZEND_STRTOL(Z_STRVAL_P(num), NULL, base));
}
/* }}} */

/* {{{ Get the float value of a variable */
PHP_FUNCTION(floatval)
{
	zval *num;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(num)
	ZEND_PARSE_PARAMETERS_END();

	RETURN_DOUBLE(zval_get_double(num));
}
/* }}} */

/* {{{ Get the boolean value of a variable */
PHP_FUNCTION(boolval)
{
	zval *value;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(value)
	ZEND_PARSE_PARAMETERS_END();

	RETURN_BOOL(zend_is_true(value));
}
/* }}} */

/* {{{ Get the string value of a variable */
PHP_FUNCTION(strval)
{
	zval *value;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(value)
	ZEND_PARSE_PARAMETERS_END();

	RETVAL_STR(zval_get_string(value));
}
/* }}} */

static inline void php_is_type(INTERNAL_FUNCTION_PARAMETERS, int type)
{
	zval *arg;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(arg)
	ZEND_PARSE_PARAMETERS_END();

	if (Z_TYPE_P(arg) == type) {
		if (type == IS_RESOURCE) {
			const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(arg));
			if (!type_name) {
				RETURN_FALSE;
			}
		}
		RETURN_TRUE;
	} else {
		RETURN_FALSE;
	}
}


/* {{{ Returns true if variable is null
   Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
PHP_FUNCTION(is_null)
{
	php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_NULL);
}
/* }}} */

/* {{{ Returns true if variable is a resource
   Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
PHP_FUNCTION(is_resource)
{
	php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_RESOURCE);
}
/* }}} */

/* {{{ Returns true if variable is a boolean
   Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
PHP_FUNCTION(is_bool)
{
	zval *arg;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(arg)
	ZEND_PARSE_PARAMETERS_END();

	RETURN_BOOL(Z_TYPE_P(arg) == IS_FALSE || Z_TYPE_P(arg) == IS_TRUE);
}
/* }}} */

/* {{{ Returns true if variable is an integer
   Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
PHP_FUNCTION(is_int)
{
	php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_LONG);
}
/* }}} */

/* {{{ Returns true if variable is float point
   Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
PHP_FUNCTION(is_float)
{
	php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_DOUBLE);
}
/* }}} */

/* {{{ Returns true if variable is a string
   Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
PHP_FUNCTION(is_string)
{
	php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_STRING);
}
/* }}} */

/* {{{ Returns true if variable is an array
   Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
PHP_FUNCTION(is_array)
{
	php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_ARRAY);
}
/* }}} */

/* {{{ Returns true if $array is an array whose keys are all numeric, sequential, and start at 0 */
PHP_FUNCTION(array_is_list)
{
	HashTable *array;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ARRAY_HT(array)
	ZEND_PARSE_PARAMETERS_END();

	RETURN_BOOL(zend_array_is_list(array));
}
/* }}} */

/* {{{ Returns true if variable is an object
   Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
PHP_FUNCTION(is_object)
{
	php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_OBJECT);
}
/* }}} */

static zend_always_inline void _zend_is_numeric(zval *return_value, zval *arg)
{
	switch (Z_TYPE_P(arg)) {
		case IS_LONG:
		case IS_DOUBLE:
			RETURN_TRUE;

		case IS_STRING:
			if (is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), NULL, NULL, 0)) {
				RETURN_TRUE;
			} else {
				RETURN_FALSE;
			}
			break;

		default:
			RETURN_FALSE;
	}
}

/* {{{ Returns true if value is a number or a numeric string */
PHP_FUNCTION(is_numeric)
{
	zval *arg;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(arg)
	ZEND_PARSE_PARAMETERS_END();

	_zend_is_numeric(return_value, arg);
}
/* }}} */

ZEND_FRAMELESS_FUNCTION(is_numeric, 1)
{
	zval *arg;

	Z_FLF_PARAM_ZVAL(1, arg);

	_zend_is_numeric(return_value, arg);
}

/* {{{ Returns true if value is a scalar */
PHP_FUNCTION(is_scalar)
{
	zval *arg;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(arg)
	ZEND_PARSE_PARAMETERS_END();

	switch (Z_TYPE_P(arg)) {
		case IS_FALSE:
		case IS_TRUE:
		case IS_DOUBLE:
		case IS_LONG:
		case IS_STRING:
			RETURN_TRUE;

		default:
			RETURN_FALSE;
	}
}
/* }}} */

/* {{{ Returns true if var is callable. */
PHP_FUNCTION(is_callable)
{
	zval *var, *callable_name = NULL;
	zend_string *name;
	bool retval;
	bool syntax_only = 0;
	int check_flags = 0;

	ZEND_PARSE_PARAMETERS_START(1, 3)
		Z_PARAM_ZVAL(var)
		Z_PARAM_OPTIONAL
		Z_PARAM_BOOL(syntax_only)
		Z_PARAM_ZVAL(callable_name)
	ZEND_PARSE_PARAMETERS_END();

	if (syntax_only) {
		check_flags |= IS_CALLABLE_CHECK_SYNTAX_ONLY;
	}
	if (ZEND_NUM_ARGS() > 2) {
		retval = zend_is_callable_ex(var, NULL, check_flags, &name, NULL, NULL);
		ZEND_TRY_ASSIGN_REF_STR(callable_name, name);
	} else {
		retval = zend_is_callable_ex(var, NULL, check_flags, NULL, NULL, NULL);
	}

	RETURN_BOOL(retval);
}
/* }}} */

/* {{{ Returns true if var is iterable (array or instance of Traversable). */
PHP_FUNCTION(is_iterable)
{
	zval *var;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(var)
	ZEND_PARSE_PARAMETERS_END();

	RETURN_BOOL(zend_is_iterable(var));
}
/* }}} */

/* {{{ Returns true if var is countable (array or instance of Countable). */
PHP_FUNCTION(is_countable)
{
	zval *var;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(var)
	ZEND_PARSE_PARAMETERS_END();

	RETURN_BOOL(zend_is_countable(var));
}
/* }}} */
