pull/1/head
韩天峰 8 months ago
parent 6ac8494173
commit 72b7ebdfae
  1. 10
      run-tests.php
  2. 31
      src/Php/Reflection.php
  3. 109
      src/Php/Translator.php
  4. 147
      tests/aot/arithmetic_operators.phpt
  5. 158
      tests/aot/assignment_operators.phpt
  6. 177
      tests/aot/comparison_operators.phpt
  7. 82
      tests/aot/control_structures.phpt
  8. 136
      tests/aot/logical_operators.phpt

@ -149,7 +149,7 @@ function main(): void
$end_time, $environment,
$exts_skipped, $exts_tested, $exts_to_test, $failed_tests_file,
$ignored_by_ext, $ini_overwrites, $colorize,
$log_format, $no_clean, $no_file_cache,
$log_format, $no_clean, $no_aot, $no_file_cache,
$pass_options, $php, $php_cgi, $preload,
$result_tests_file, $slow_min_ms, $start_time,
$temp_source, $temp_target, $test_cnt,
@ -491,6 +491,9 @@ function main(): void
case '--no-clean':
$no_clean = true;
break;
case '--no-aot':
$no_aot = true;
break;
case '--color':
$colorize = true;
break;
@ -2426,8 +2429,11 @@ TEST $file
$orig_cmd = $cmd;
global $no_aot;
if (!$no_aot) {
$bin_file = compile_php_file($test_file);
$cmd = './' . $bin_file . ' ' . $args . $cmdRedirect;
}
if ($valgrind) {
$env['USE_ZEND_ALLOC'] = '0';
@ -2622,12 +2628,14 @@ COMMAND $cmd
if (!$cfg['keep']['php'] && !$leaked) {
@unlink($test_file);
@unlink($preload_filename);
if (!$no_aot) {
// 刪除 AOT 编译器生成的 .cc 和 .o 文件
$file = substr($test_file, 0, strlen($test_file) - 4);
@unlink($file . '.cc');
@unlink($file . '.cc.o');
@unlink('./' . $bin_file);
}
}
@unlink($tmp_post);
if (!$leaked && !$failed_headers) {

@ -0,0 +1,31 @@
<?php
namespace PhpAot\Php;
use ReflectionFunction;
class Reflection
{
private static array $functions = [];
static function getFunction(string $fn)
{
if (!isset(self::$functions[$fn])) {
self::$functions[$fn] = new ReflectionFunction($fn);
}
return self::$functions[$fn];
}
static function getFunctionReturnType(string $fn): ?string
{
$func = self::getFunction($fn);
$returnType = $func->getReturnType();
if (!$returnType) {
return null;
}
if ($returnType instanceof \ReflectionUnionType) {
return null;
}
return $returnType->getName();
}
}

@ -265,9 +265,20 @@ class Translator extends \PhpAot\Core\Translator
return $node->getType();
}
public function getZendType(string $type): string
private function getVarType(string $name): string
{
return $this->zendTypeMap[$type] ?? 'zval *';
if ($this->hasLocalVar($name)) {
return $this->localVars[$name];
}
if ($this->hasLocalVar($name)) {
return $this->globalVars[$name];
}
return self::TYPE_VAR;
}
public function getTypeFromZendType(string $type): string
{
return $this->zendTypeMap[$type] ?? self::TYPE_VAR;
}
public function isInternalFunction(string $name): bool
@ -300,7 +311,7 @@ class Translator extends \PhpAot\Core\Translator
$this->functionDef = new FunctionDef();
$this->functionDef->name = $name;
if ($v->returnType) {
$this->functionDef->returnType = $this->getZendType($this->parseIdentifier($v->returnType));
$this->functionDef->returnType = $this->getTypeFromZendType($this->parseIdentifier($v->returnType));
} else {
$this->functionDef->returnType = 'void';
}
@ -770,13 +781,7 @@ class Translator extends \PhpAot\Core\Translator
private function detectVarType($var): string
{
$name = $this->parseIdentifier($var);
if ($this->hasLocalVar($name)) {
return $this->localVars[$name];
}
if ($this->hasLocalVar($name)) {
return $this->globalVars[$name];
}
return self::TYPE_VAR;
return $this->getVarType($name);
}
private function detectExprType($expr): string
@ -823,7 +828,7 @@ class Translator extends \PhpAot\Core\Translator
if ($this->isInternalFunction($name)) {
return $this->internalFunctions[$name]->returnType;
}
break;
return $this->detectFuncCallReturnType($expr);
case 'Expr_New':
return self::TYPE_OBJECT;
case 'Expr_Assign':
@ -1029,7 +1034,7 @@ class Translator extends \PhpAot\Core\Translator
private function parseBinaryOpSmaller(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '<');
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '<'));
}
private function parsePreInc(mixed $expr): string
@ -1080,7 +1085,7 @@ class Translator extends \PhpAot\Core\Translator
private function parseAssignOpMul(mixed $expr): string
{
$var = $this->parseIdentifier($expr->var);
return $var . ' *= ' . $this->parseIdentifier($expr->expr);
return $var . ' *= ' . $this->convertVarType($var, $this->parseExpr($expr->expr));
}
private function parseAssignOpDiv(mixed $expr): string
@ -1108,6 +1113,18 @@ class Translator extends \PhpAot\Core\Translator
if ($name === 'strlen' and $expr->args[0]->value->getType() === 'Expr_Variable') {
return '((php::Int)' . $this->parseIdentifier($expr->args[0]->value) . '.length())';
}
if (count($expr->args) == 1) {
switch ($name) {
case 'intval':
return $this->convertIntExpr($this->parseExpr($expr->args[0]->value));
case 'floatval':
return $this->convertFloatExpr($this->parseExpr($expr->args[0]->value));
case 'boolval':
return $this->convertBoolExpr($this->parseExpr($expr->args[0]->value));
default:
break;
}
}
if (empty($expr->args)) {
return 'php::call(' . $fn . ')';
} else {
@ -1163,13 +1180,13 @@ class Translator extends \PhpAot\Core\Translator
private function parseBinaryOpGreater(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '>');
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '>'));
}
private function parseAssignOpMod(mixed $expr): string
{
$var = $this->parseIdentifier($expr->var);
return $var . ' % ' . $this->parseIdentifier($expr->expr);
return $var . ' %= ' . $this->parseIdentifier($expr->expr);
}
private function parseBinaryOpPow(mixed $expr): string
@ -1177,7 +1194,7 @@ class Translator extends \PhpAot\Core\Translator
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
return 'pow(' . $left . ', ' . $right . ')';
return 'php::pow(' . $left . ', ' . $right . ')';
}
private function parsePreDec(mixed $expr): string
@ -1240,27 +1257,30 @@ class Translator extends \PhpAot\Core\Translator
private function parseBinaryOpEqual(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '==');
return 'php::equals(' . $this->parseExpr($expr->left) . ', ' . $this->parseExpr($expr->right) . ')';
}
private function parseBinaryOpNotEqual(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '!=');
return '!php::equals(' . $this->parseExpr($expr->left) . ', ' . $this->parseExpr($expr->right) . ')';
}
/**
* 逻辑比较的运算,必须返回 bool 类型
*/
private function parseBinaryOpLogicalAnd(Node $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '&&');
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '&&'));
}
private function parseBinaryOpLogicalOr(Node $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '||');
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '||'));
}
private function parseBinaryOpLogicalXor(Node $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '^');
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '^'));
}
private function parseBooleanNot(Node $expr): string
@ -1285,37 +1305,52 @@ class Translator extends \PhpAot\Core\Translator
private function convertIntExpr(string $expr): string
{
if (!str_starts_with($expr, 'php::to_int(')) {
return 'php::to_int(' . $expr . ')';
}
return $expr;
}
private function convertFloatExpr(string $expr): string
{
if (!str_starts_with($expr, 'php::to_float(')) {
return 'php::to_float(' . $expr . ')';
}
return $expr;
}
private function convertStringExpr(string $expr): string
{
if (!str_starts_with($expr, 'php::to_string(')) {
return 'php::to_string(' . $expr . ')';
}
return $expr;
}
private function convertArrayExpr(string $expr): string
{
if (!str_starts_with($expr, 'php::to_array(')) {
return 'php::to_array(' . $expr . ')';
}
return $expr;
}
private function convertBoolExpr(string $expr): string
{
if (!str_starts_with($expr, 'php::to_bool(')) {
return 'php::to_bool(' . $expr . ')';
}
return $expr;
}
private function parseBinaryOpSmallerOrEqual(Node $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '<=');
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '<='));
}
private function parseBinaryOpGreaterOrEqual(Node $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '>=');
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '>='));
}
private function parsePrint(Node $expr): string
@ -1794,4 +1829,32 @@ class Translator extends \PhpAot\Core\Translator
{
return 'php::to_float(' . $this->parseIdentifier($expr->expr) . ')';
}
private function detectFuncCallReturnType($expr): string
{
$name = $expr->name;
$returnType = Reflection::getFunctionReturnType($name);
if ($returnType) {
return $this->getTypeFromZendType($returnType);
} else {
return self::TYPE_VAR;
}
}
private function convertVarType($var, $expr): string
{
if ($this->hasVar($var)) {
$type = $this->getVarType($var);
if ($type === self::TYPE_FLOAT) {
return $this->convertFloatExpr($expr);
}
if ($type === self::TYPE_INT) {
return $this->convertIntExpr($expr);
}
if ($type === self::TYPE_BOOL) {
return $this->convertBoolExpr($expr);
}
}
return $expr;
}
}

@ -0,0 +1,147 @@
--TEST--
Arithmetic Operators
--FILE--
<?php
// Test basic arithmetic operators
$a = 10;
$b = 3;
// Addition
$add = $a + $b;
var_dump($add);
// Subtraction
$sub = $a - $b;
var_dump($sub);
// Multiplication
$mul = $a * $b;
var_dump($mul);
// Division
$div = $a / floatval($b);
var_dump($div);
// Modulus
$mod = $a % $b;
var_dump($mod);
// Exponentiation (PHP 5.6+)
$pow = $a ** $b;
var_dump($pow);
// Test with floats
$x = 10.5;
$y = 3.2;
$float_add = $x + $y;
var_dump($float_add);
echo "float_div:\n==========================\n";
$float_div = $x / $y;
var_dump($float_div);
// Test increment/decrement operators
$c = 5;
var_dump($c); // 5
$c++; // post-increment
var_dump($c); // 6
++$c; // pre-increment
var_dump($c); // 7
$c--; // post-decrement
var_dump($c); // 6
--$c; // pre-decrement
var_dump($c); // 5
// Test compound assignment operators
$d = 20;
$d += 5;
var_dump($d); // 25
echo "self plus:\n==========================\n";
$float_div = $x / $y;
$d -= 3;
var_dump($d); // 22
$d *= 2;
var_dump($d); // 44
echo "assign div:\n==========================\n";
$d /= 4;
var_dump($d); // 11
$d %= 5;
var_dump($d); // 1
// Test operator precedence
$result1 = 2 + 3 * 4; // 2 + (3 * 4) = 14
var_dump($result1);
$result2 = (2 + 3) * 4; // (2 + 3) * 4 = 20
var_dump($result2);
$result3 = 10 - 3 + 2; // (10 - 3) + 2 = 9
var_dump($result3);
$result4 = 2 ** 3 ** 2; // 2 ** (3 ** 2) = 2 ** 9 = 512
var_dump($result4);
// Test with negative numbers
$neg_a = -10;
$neg_b = 3;
$neg_result = $neg_a % $neg_b;
var_dump($neg_result);
echo "neg float div:\n==========================\n";
$neg_result2 = $neg_a / (float)$neg_b;
var_dump($neg_result2);
// Test division by zero behavior (should not crash in this test)
$zero_div = 10 / 2; // Not dividing by zero, just testing normal division
var_dump($zero_div);
// Test zero modulus behavior (should not crash in this test)
$zero_mod = 10 % 2;
var_dump($zero_mod);
?>
--EXPECT--
int(13)
int(7)
int(30)
float(3.3333333333333335)
int(1)
int(1000)
float(13.7)
float_div:
==========================
float(3.28125)
int(5)
int(6)
int(7)
int(6)
int(5)
int(25)
self plus:
==========================
int(22)
int(44)
assign div:
==========================
int(11)
int(1)
int(14)
int(20)
int(9)
int(512)
int(-1)
neg float div:
==========================
float(-3.3333333333333335)
int(5)
int(0)

@ -0,0 +1,158 @@
--TEST--
Assignment Operators
--FILE--
<?php
// Test assignment operators
$a = 10;
var_dump($a);
// Addition assignment
$a += 5;
var_dump($a);
// Subtraction assignment
$a -= 3;
var_dump($a);
// Multiplication assignment
$a *= 2;
var_dump($a);
// Division assignment
$a /= 4;
var_dump($a);
// Modulus assignment
$a = 17;
$a %= 5;
var_dump($a);
// Concatenation assignment
$str = "Hello";
var_dump($str);
$str .= " World";
var_dump($str);
// Test with different data types
$num = 100;
var_dump($num);
$num /= 4;
var_dump($num);
$num_float = floatval($num);
$num_float *= 1.5;
var_dump($num_float);
// Test compound operations with expressions
$x = 5;
$x += 3 * 2; // $x = $x + (3 * 2) = 5 + 6 = 11
var_dump($x);
$y = 20;
$y -= 4 + 1; // $y = $y - (4 + 1) = 20 - 5 = 15
var_dump($y);
$z = 6;
$z *= 2 ** 3; // $z = $z * (2 ** 3) = 6 * 8 = 48
var_dump($z);
// Test assignment with function results
$numbers = [1, 2, 3, 4, 5];
$sum = 0;
foreach ($numbers as $num) {
$sum += $num;
}
var_dump($sum);
// Test multiple assignments
$a = $b = $c = 10;
var_dump($a);
var_dump($b);
var_dump($c);
$a += 5;
$b *= 2;
$c -= 3;
var_dump($a);
var_dump($b);
var_dump($c);
// Test assignment operators with arrays
$arr = [1, 2, 3];
var_dump($arr);
// Append to array using assignment
$arr[] = 4;
var_dump($arr);
// String concatenation with variable
$greeting = "Hello";
$name = "John";
$greeting .= ", " . $name . "!";
var_dump($greeting);
// Test assignment in conditional context
$value = 5;
$result = $value *= 2;
var_dump($value);
var_dump($result);
// Test increment and decrement in assignments
$counter = 10;
$pre_inc = ++$counter; // counter becomes 11, pre_inc gets 11
var_dump($counter);
var_dump($pre_inc);
$post_inc = $counter++; // post_inc gets 11, counter becomes 12
var_dump($counter);
var_dump($post_inc);
?>
--EXPECT--
int(10)
int(15)
int(12)
int(24)
int(6)
int(2)
string(5) "Hello"
string(11) "Hello World"
int(100)
int(25)
float(37.5)
int(11)
int(15)
int(48)
int(15)
int(10)
int(10)
int(10)
int(15)
int(20)
int(7)
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(4) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(4)
}
string(12) "Hello, John!"
int(10)
int(10)
int(11)
int(11)
int(12)
int(11)

@ -0,0 +1,177 @@
--TEST--
Comparison Operators
--FILE--
<?php
// Test comparison operators
$a = 5;
$b = 10;
$c = 5;
// Equal (==)
var_dump($a == $c); // true
var_dump($a == $b); // false
// Identical (===)
var_dump($a === $c); // true
var_dump($a === "5"); // false
// Not equal (!=, <>)
var_dump($a != $b); // true
var_dump($a <> $c); // false
// Not identical (!==)
var_dump($a !== "5"); // true
var_dump($a !== $c); // false
// Greater than
var_dump($b > $a); // true
var_dump($a > $c); // false
// Less than
var_dump($a < $b); // true
var_dump($a < $c); // false
// Greater than or equal
var_dump($a >= $c); // true
var_dump($b >= $a); // true
var_dump($a >= $b); // false
// Less than or equal
var_dump($a <= $c); // true
var_dump($a <= $b); // true
var_dump($b <= $a); // false
// Test with different data types
$str_num = "10";
$int_num = 10;
var_dump($str_num == $int_num); // true (loose comparison)
var_dump($str_num === $int_num); // false (strict comparison)
// Test spaceship operator (PHP 7+)
$spaceship1 = 5 <=> 10; // -1
var_dump($spaceship1);
$spaceship2 = 10 <=> 10; // 0
var_dump($spaceship2);
$spaceship3 = 15 <=> 10; // 1
var_dump($spaceship3);
// Test comparisons with strings
$str1 = "apple";
$str2 = "banana";
$str3 = "apple";
var_dump($str1 < $str2); // true (lexicographic comparison)
var_dump($str1 == $str3); // true
var_dump($str1 > $str3); // false
// Test comparisons with floats
$float1 = 1.5;
$float2 = 1.7;
$float3 = 1.5;
var_dump($float1 < $float2); // true
var_dump($float1 == $float3); // true
var_dump($float1 > $float2); // false
// Test boolean comparisons
$bool1 = true;
$bool2 = false;
$int1 = 1;
$int0 = 0;
var_dump($bool1 == $int1); // true
var_dump($bool2 == $int0); // true
var_dump($bool1 === $int1); // false (different types)
// Test in conditional contexts
if (5 > 3) {
$result1 = "5 is greater than 3";
}
var_dump($result1);
if (2 < 1) {
$result2 = "This won't execute";
} else {
$result2 = "2 is not less than 1";
}
var_dump($result2);
// Test complex comparisons
$x = 15;
$y = 20;
$z = 15;
$complex1 = ($x < $y) && ($x == $z);
var_dump($complex1);
$complex2 = ($x > $y) || ($x == $z);
var_dump($complex2);
$complex3 = !($x > $y);
var_dump($complex3);
// Test null comparisons
$null_val = null;
$zero_val = 0;
$empty_str = "";
var_dump($null_val == $zero_val); // true
var_dump($null_val === $zero_val); // false
echo '$zero_val == $empty_str:' . PHP_EOL;
var_dump($zero_val == $empty_str); // true
var_dump($zero_val === $empty_str); // false
$num_a = 999;
$num_b = 999;
var_dump('int==int', $num_a == $num_b);
var_dump('int!=int', $num_a != $num_b);
?>
--EXPECT--
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(false)
bool(true)
bool(false)
int(-1)
int(0)
int(1)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(false)
string(19) "5 is greater than 3"
string(20) "2 is not less than 1"
bool(true)
bool(true)
bool(true)
bool(true)
bool(false)
$zero_val == $empty_str:
bool(false)
bool(false)
string(8) "int==int"
bool(true)
string(8) "int!=int"
bool(false)

@ -0,0 +1,82 @@
--TEST--
Control Structures - if/else, for/while, switch
--FILE--
<?php
// Test if/else structures
$a = 10;
$b = 20;
if ($a > $b) {
$result = "a is greater";
} elseif ($a < $b) {
$result = "b is greater";
} else {
$result = "a and b are equal";
}
var_dump($result);
// Test for loop
$sum = 0;
for ($i = 1; $i <= 5; $i++) {
$sum += $i;
}
var_dump($sum);
// Test while loop
$counter = 0;
$while_sum = 0;
while ($counter < 5) {
$counter++;
$while_sum += $counter;
}
var_dump($while_sum);
// Test do-while loop
$do_counter = 0;
$do_sum = 0;
do {
$do_counter++;
$do_sum += $do_counter;
} while ($do_counter < 5);
var_dump($do_sum);
// Test switch statement
$number = 2;
switch ($number) {
case 1:
$switch_result = "one";
break;
case 2:
$switch_result = "two";
break;
case 3:
$switch_result = "three";
break;
default:
$switch_result = "other";
break;
}
var_dump($switch_result);
// Test nested conditions
$x = 5;
$y = 10;
if ($x > 0) {
if ($y > $x) {
$nested_result = "y is greater than x";
} else {
$nested_result = "x is greater than or equal to y";
}
} else {
$nested_result = "x is not positive";
}
var_dump($nested_result);
?>
--EXPECT--
string(12) "b is greater"
int(15)
int(15)
int(15)
string(3) "two"
string(19) "y is greater than x"

@ -0,0 +1,136 @@
--TEST--
Logical Operators
--FILE--
<?php
// Test logical operators
$a = true;
$b = false;
$x = 5;
$y = 10;
// AND operator (&&)
var_dump($a && $a); // true
var_dump($a && $b); // false
var_dump(($x > 0) && ($y > 0)); // true
var_dump(($x > 0) && ($y < 0)); // false
// OR operator (||)
var_dump($a || $b); // true
var_dump($b || $b); // false
var_dump(($x > 10) || ($y > 5)); // true
var_dump(($x > 10) || ($y < 5)); // false
// NOT operator (!)
var_dump(!$a); // false
var_dump(!$b); // true
var_dump(!($x > 0)); // false
var_dump(!($x > 10)); // true
// XOR operator (XOR)
var_dump('$a xor $b', $a xor $b); // true
var_dump($a xor $a); // false
var_dump($b xor $b); // false
var_dump(($x > 0) xor ($y < 0)); // true
// Alternative operators
var_dump($a and $b); // false (lower precedence)
var_dump($a or $b); // true (lower precedence)
var_dump(!$b); // true (lower precedence)
// Complex logical expressions
$age = 25;
$has_license = true;
$has_car = false;
$can_drive = ($age >= 18) && $has_license;
var_dump($can_drive);
$can_travel = $can_drive || $has_car;
var_dump($can_travel);
$should_buy_car = !$has_car && $can_drive;
var_dump($should_buy_car);
// Test with numeric values (0 is false, non-zero is true)
$num1 = 0;
$num2 = 5;
$num3 = -3;
var_dump($num1 && $num2); // false
var_dump($num2 && $num3); // true
var_dump($num1 || $num2); // true
var_dump(!$num1); // true
var_dump(!$num2); // false
// Test with strings (empty string is false, non-empty is true)
$str1 = "";
$str2 = "hello";
var_dump($str1 || $str2); // true
var_dump(!$str1); // true
// Test precedence: && has higher precedence than ||
$result3 = true || false && false;
var_dump($result3); // true (true || (false && false))
$result4 = (true || false) && false;
var_dump($result4); // false
// Test with null and other falsy values
$null_val = null;
$falsy1 = 0;
$falsy2 = "";
var_dump(!$null_val); // true
var_dump($falsy1 || $falsy2); // false
$message = "";
if ($x > 0 && $y > 0) {
$message = "Both x and y are positive";
}
var_dump($message);
if ($x < 0 || $y < 0) {
$message2 = "Either x or y is negative";
} else {
$message2 = "Neither x nor y is negative";
}
var_dump($message2);
?>
--EXPECT--
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(false)
bool(true)
bool(false)
bool(true)
string(9) "$a xor $b"
bool(true)
bool(false)
bool(false)
bool(true)
bool(false)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(true)
bool(false)
bool(true)
bool(false)
string(25) "Both x and y are positive"
string(27) "Neither x nor y is negative"
Loading…
Cancel
Save