Files
Phraseanet/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryParser.php
2014-12-03 17:23:00 +01:00

109 lines
3.1 KiB
PHP

<?php
namespace Alchemy\Phrasea\SearchEngine\Elastic\Search;
use Alchemy\Phrasea\SearchEngine\Elastic\AST;
use Alchemy\Phrasea\SearchEngine\Elastic\Exception\QueryException;
use Hoa\Compiler\Exception\Exception as CompilerException;
use Hoa\Compiler\Llk\Parser;
use Hoa\Compiler\Llk\TreeNode;
use Hoa\Compiler\Visitor\Dump as DumpVisitor;
use Hoa\Visitor\Visit;
class QueryParser
{
private $parser;
private static $leftAssociativeOperators = array(
NodeTypes::AND_EXPR,
NodeTypes::OR_EXPR,
NodeTypes::EXCEPT_EXPR
);
public function __construct(Parser $parser)
{
$this->parser = $parser;
}
/**
* Creates a Query object from a string
*/
public function parse($string, $postprocessing = true)
{
return $this->visitString($string, new QueryVisitor(), $postprocessing);
}
public function dump($string, $postprocessing = true)
{
return $this->visitString($string, new DumpVisitor(), $postprocessing);
}
private function visitString($string, Visit $visitor, $postprocessing = true)
{
try {
$ast = $this->parser->parse($string);
} catch (CompilerException $e) {
throw new QueryException('Provided query is not valid', 0, $e);
}
if ($postprocessing) {
$this->fixOperatorAssociativity($ast);
}
return $visitor->visit($ast);
}
/**
* Walks the tree to restore left-associativity of some operators
*
* @param TreeNode $root AST root node
*/
private function fixOperatorAssociativity(TreeNode &$root)
{
switch ($root->getChildrenNumber()) {
case 0:
// Leaf nodes can't be rotated, and have no childs
return;
case 2:
// We only want to rotate tree contained in the left associative
// subset of operators
$rootType = $root->getId();
if (!in_array($rootType, self::$leftAssociativeOperators)) {
break;
}
// Do not break operator precedence
$pivot = $root->getChild(1);
if ($pivot->getId() !== $rootType) {
break;
}
$this->leftRotateTree($root);
break;
}
// Recursively fix tree branches
$children = $root->getChildren();
foreach ($children as $index => $_) {
$this->fixOperatorAssociativity($children[$index]);
}
$root->setChildren($children);
}
private function leftRotateTree(TreeNode &$root)
{
// Pivot = Root.Left
$pivot = $root->getChild(1);
// Root.Right = Pivot.Left
$children = $root->getChildren();
$children[1] = $pivot->getChild(0); // Pivot, rotation side
$root->setChildren($children);
// Pivot.Left = Root
$children = $pivot->getChildren();
$children[0] = $root;
$pivot->setChildren($children);
// Root = Pivot
$root = $pivot;
}
}