Basic unit tests for formula parsing, in preparation for work on fully supporting the Union Operator, and for providing support for Structured References

This commit is contained in:
MarkBaker 2022-04-14 21:21:08 +02:00
parent 709e2ae5ca
commit 483ef53855
2 changed files with 143 additions and 8 deletions

View File

@ -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) {

View File

@ -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',
// ],
];
}
}