Merge pull request #2749 from PHPOffice/CalcEngine-Parser-Unit_Tests
Basic unit tests for formula parsing
This commit is contained in:
commit
4a28fd6eec
|
|
@ -108,7 +108,8 @@ class Calculation
|
|||
'+' => true, '-' => true, '*' => true, '/' => true,
|
||||
'^' => true, '&' => true, '%' => false, '~' => false,
|
||||
'>' => true, '<' => true, '=' => true, '>=' => true,
|
||||
'<=' => true, '<>' => true, '|' => true, ':' => true,
|
||||
'<=' => true, '<>' => true, '∩' => true, '∪' => true,
|
||||
':' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -120,7 +121,7 @@ class Calculation
|
|||
'+' => true, '-' => true, '*' => true, '/' => true,
|
||||
'^' => true, '&' => true, '>' => true, '<' => true,
|
||||
'=' => true, '>=' => true, '<=' => true, '<>' => true,
|
||||
'|' => true, ':' => true,
|
||||
'∩' => true, '∪' => true, ':' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -3872,7 +3873,7 @@ class Calculation
|
|||
'*' => 0, '/' => 0, // Multiplication and Division
|
||||
'+' => 0, '-' => 0, // Addition and Subtraction
|
||||
'&' => 0, // Concatenation
|
||||
'|' => 0, ':' => 0, // Intersect and Range
|
||||
'∪' => 0, '∩' => 0, ':' => 0, // Union, Intersect and Range
|
||||
'>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison
|
||||
];
|
||||
|
||||
|
|
@ -3884,8 +3885,9 @@ class Calculation
|
|||
// This list includes all valid operators, whether binary (including boolean) or unary (such as %)
|
||||
// Array key is the operator, the value is its precedence
|
||||
private static $operatorPrecedence = [
|
||||
':' => 8, // Range
|
||||
'|' => 7, // Intersect
|
||||
':' => 9, // Range
|
||||
'∩' => 8, // Intersect
|
||||
'∪' => 7, // Union
|
||||
'~' => 6, // Negation
|
||||
'%' => 5, // Percentage
|
||||
'^' => 4, // Exponentiation
|
||||
|
|
@ -3957,7 +3959,7 @@ class Calculation
|
|||
++$index;
|
||||
} elseif ($opCharacter == '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded?
|
||||
++$index; // Drop the redundant plus symbol
|
||||
} elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde or pipe, because they are legal
|
||||
} elseif ((($opCharacter == '~') || ($opCharacter == '∩') || ($opCharacter == '∪')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde, union or intersect because they are legal
|
||||
return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression
|
||||
} elseif ((isset(self::$operators[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack?
|
||||
while (
|
||||
|
|
@ -4338,7 +4340,7 @@ class Calculation
|
|||
) {
|
||||
$output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
|
||||
}
|
||||
$stack->push('Binary Operator', '|'); // Put an Intersect Operator on the stack
|
||||
$stack->push('Binary Operator', '∩'); // Put an Intersect Operator on the stack
|
||||
$expectingOperator = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -4638,7 +4640,7 @@ class Calculation
|
|||
}
|
||||
|
||||
break;
|
||||
case '|': // Intersect
|
||||
case '∩': // Intersect
|
||||
$rowIntersect = array_intersect_key($operand1, $operand2);
|
||||
$cellIntersect = $oCol = $oRow = [];
|
||||
foreach (array_keys($rowIntersect) as $row) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ParseFormulaTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider providerBinaryOperations
|
||||
*/
|
||||
public function testParseOperations(array $expectedStack, string $formula): void
|
||||
{
|
||||
$parser = Calculation::getInstance();
|
||||
$stack = $parser->parseFormula($formula);
|
||||
self::assertSame($expectedStack, $stack);
|
||||
}
|
||||
|
||||
public function providerBinaryOperations(): array
|
||||
{
|
||||
return [
|
||||
'Unary negative with Value' => [
|
||||
[
|
||||
['type' => 'Value', 'value' => 3, 'reference' => null],
|
||||
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||
],
|
||||
'=-3',
|
||||
],
|
||||
'Unary negative percentage with Value' => [
|
||||
[
|
||||
['type' => 'Value', 'value' => 3, 'reference' => null],
|
||||
['type' => 'Unary Operator', 'value' => '%', 'reference' => null],
|
||||
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||
],
|
||||
'=-3%',
|
||||
],
|
||||
'Binary minus with Values' => [
|
||||
[
|
||||
['type' => 'Value', 'value' => 3, 'reference' => null],
|
||||
['type' => 'Value', 'value' => 4, 'reference' => null],
|
||||
['type' => 'Binary Operator', 'value' => '-', 'reference' => null],
|
||||
],
|
||||
'=3-4',
|
||||
],
|
||||
'Unary negative with Cell Reference' => [
|
||||
[
|
||||
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
|
||||
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||
],
|
||||
'=-A1',
|
||||
],
|
||||
'Unary negative with FQ Cell Reference' => [
|
||||
[
|
||||
['type' => 'Cell Reference', 'value' => "'Sheet 1'!A1", 'reference' => "'Sheet 1'!A1"],
|
||||
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||
],
|
||||
"=-'Sheet 1'!A1",
|
||||
],
|
||||
'Unary negative percentage with Cell Reference' => [
|
||||
[
|
||||
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
|
||||
['type' => 'Unary Operator', 'value' => '%', 'reference' => null],
|
||||
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||
],
|
||||
'=-A1%',
|
||||
],
|
||||
'Unary negative with Defined Name' => [
|
||||
[
|
||||
['type' => 'Defined Name', 'value' => 'DEFINED_NAME', 'reference' => 'DEFINED_NAME'],
|
||||
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||
],
|
||||
'=-DEFINED_NAME',
|
||||
],
|
||||
'Unary negative percentage with Defined Name' => [
|
||||
[
|
||||
['type' => 'Defined Name', 'value' => 'DEFINED_NAME', 'reference' => 'DEFINED_NAME'],
|
||||
['type' => 'Unary Operator', 'value' => '%', 'reference' => null],
|
||||
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||
],
|
||||
'=-DEFINED_NAME%',
|
||||
],
|
||||
'Cell Range' => [
|
||||
[
|
||||
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
|
||||
['type' => 'Cell Reference', 'value' => 'C3', 'reference' => 'C3'],
|
||||
['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
|
||||
],
|
||||
'=A1:C3',
|
||||
],
|
||||
'Cell Range Intersection' => [
|
||||
[
|
||||
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
|
||||
['type' => 'Cell Reference', 'value' => 'C3', 'reference' => 'C3'],
|
||||
['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
|
||||
['type' => 'Cell Reference', 'value' => 'B2', 'reference' => 'B2'],
|
||||
['type' => 'Cell Reference', 'value' => 'D4', 'reference' => 'D4'],
|
||||
['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
|
||||
['type' => 'Binary Operator', 'value' => '∩', 'reference' => null],
|
||||
],
|
||||
'=A1:C3 B2:D4',
|
||||
],
|
||||
'Named Range Intersection' => [
|
||||
[
|
||||
['type' => 'Defined Name', 'value' => 'DEFINED_NAME_1', 'reference' => 'DEFINED_NAME_1'],
|
||||
['type' => 'Defined Name', 'value' => 'DEFINED_NAME_2', 'reference' => 'DEFINED_NAME_2'],
|
||||
['type' => 'Binary Operator', 'value' => '∩', 'reference' => null],
|
||||
],
|
||||
'=DEFINED_NAME_1 DEFINED_NAME_2',
|
||||
],
|
||||
// 'Cell Range Union' => [
|
||||
// [
|
||||
// ['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
|
||||
// ['type' => 'Cell Reference', 'value' => 'C3', 'reference' => 'C3'],
|
||||
// ['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
|
||||
// ['type' => 'Cell Reference', 'value' => 'B2', 'reference' => 'B2'],
|
||||
// ['type' => 'Cell Reference', 'value' => 'D4', 'reference' => 'D4'],
|
||||
// ['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
|
||||
// ['type' => 'Binary Operator', 'value' => '∪', 'reference' => null],
|
||||
// ],
|
||||
// '=A1:C3,B2:D4',
|
||||
// ],
|
||||
// 'Named Range Union' => [
|
||||
// [
|
||||
// ['type' => 'Defined Name', 'value' => 'DEFINED_NAME_1', 'reference' => 'DEFINED_NAME_1'],
|
||||
// ['type' => 'Defined Name', 'value' => 'DEFINED_NAME_2', 'reference' => 'DEFINED_NAME_2'],
|
||||
// ['type' => 'Binary Operator', 'value' => '∪', 'reference' => null],
|
||||
// ],
|
||||
// '=DEFINED_NAME_1,DEFINED_NAME_2',
|
||||
// ],
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue