<commit_before><?php

namespace Gdbots\QueryParser;

use Gdbots\QueryParser\Node;

/**
 * A parser using the QueryLexer as input for tokens. The parser builds a
 * a query tree, representing the query expression delivered as input.
 * The parser returns a Query Tree if the parsing is successful or null if it failed.
 * The parser will try to parse the entire input string and delivers errors,
 * for each expression it could not parse.
 */
class QueryParser
{
    /**
     * @var QueryLexer
     */
    protected $scanner;

    /**
     * An array containing strings with an error message for every expression
     * that could not be parsed.
     *
     * @var array
     */
    protected $errors = [];

    /**
     * __construct
     */
    public function __construct()
    {
        $this->scanner = new QueryLexer();
    }

    /**
     * Returns scanner instance.
     *
     * @return QueryLexer
     */
    public function getLexer()
    {
        return $this->scanner;
    }

    /**
     * Makes the parser build an expression tree from the given input.
     *
     * @param string $input
     * @param bool   $ignoreOperator
     *
     * @return \Gdbots\QueryParser\Node\AbstractQueryItem|null
     */
    public function parse($input)
    {
        $this->scanner->readString($input);
        $this->errors = [];

        return $this->readAndExpressionList();
    }

    /**
     * @param string $input
     *
     * @return \Gdbots\QueryParser\Node\AbstractQueryItem|null
     */
    public function parseWithOperator($input)
    {
        $this->scanner->setIgnoreOperator(false);

        return $this->parse($input);
    }

    /**
     * Private function to add an error message to the errors array.
     *
     * @param string $message
     */
    protected function addError($message)
    {
        $this->errors[] = $message;
    }

    /**
     * Checks if the parser has any errors to deliver.
     *
     * @return bool
     */
    public function hasErrors()
    {
        return count($this->errors) > 0;
    }

    /**
     * Returns an array with error messages from the parser.
     *
     * @return array
     */
    public function getErrors()
    {
        return $this->errors;
    }

    /**
     * Makes the parser read a single term. This can be a word, text, or explicit term.
     *
     * @param int    $tokenType
     * @param string $term
     *
     * @return \Gdbots\QueryParser\Node\AbstractQueryItem|null
     */
    protected function readTerm($tokenType, $term)
    {
        if (!in_array($tokenType, [QueryLexer::T_FILTER])) {
            return $term;
        }

        if (in_array($tokenType, [QueryLexer::T_FILTER]) && $term->getTokenType() != QueryLexer::T_WORD) {
            $this->addError(sprintf(
                'Error: FILTER only support Word. Found: "%s"',
                $this->scanner->getTokenTypeText()
            ));

            return $term;
        }

        $value = null;
        $tokenTypeText = $this->scanner->getToken();

        switch ($this->scanner->next()) {
            case QueryLexer::T_WORD:
            case QueryLexer::T_DATE:
            case QueryLexer::T_NUMBER:
            case QueryLexer::T_URL:
                $value = new Node\Word($this->scanner->getToken(), $this->scanner->getTokenType());

                break;

            case QueryLexer::T_PHRASE:
                $value = new Node\Phrase($this->scanner->getToken());

                break;

            default:
                $this->addError(sprintf(
                    'Error: Expected Word, Phrase, or Url. Found: "%s"',
                    $this->scanner->getTokenTypeText()
                ));

                return null;
        }

        $this->scanner->next();

        switch ($this->scanner->getTokenType()) {
            case QueryLexer::T_RANGE:
                $this->scanner->next();

                $value = new Node\Range($value->getToken(), $this->scanner->getToken());

                $this->scanner->next();

                break;
        }

        return new Node\ExplicitTerm($term, $tokenType, $tokenTypeText, $value);
    }

    /**
     * Makes the parser read an expression. This can be:
     * - '(' Subexpression ')'
     * - '"' text '"'
     * - Word
     * - Word:Phrase
     * - Word:Word
     * - '-' Expression
     * - '+' Expression
     * - '#' Expression<commit_msg>Reduce readTerm cyclomatic complexity<commit_after><?php

namespace Gdbots\QueryParser;

use Gdbots\QueryParser\Node;

/**
 * A parser using the QueryLexer as input for tokens. The parser builds a
 * a query tree, representing the query expression delivered as input.
 * The parser returns a Query Tree if the parsing is successful or null if it failed.
 * The parser will try to parse the entire input string and delivers errors,
 * for each expression it could not parse.
 */
class QueryParser
{
    /**
     * @var QueryLexer
     */
    protected $scanner;

    /**
     * An array containing strings with an error message for every expression
     * that could not be parsed.
     *
     * @var array
     */
    protected $errors = [];

    /**
     * __construct
     */
    public function __construct()
    {
        $this->scanner = new QueryLexer();
    }

    /**
     * Returns scanner instance.
     *
     * @return QueryLexer
     */
    public function getLexer()
    {
        return $this->scanner;
    }

    /**
     * Makes the parser build an expression tree from the given input.
     *
     * @param string $input
     * @param bool   $ignoreOperator
     *
     * @return \Gdbots\QueryParser\Node\AbstractQueryItem|null
     */
    public function parse($input)
    {
        $this->scanner->readString($input);
        $this->errors = [];

        return $this->readAndExpressionList();
    }

    /**
     * @param string $input
     *
     * @return \Gdbots\QueryParser\Node\AbstractQueryItem|null
     */
    public function parseWithOperator($input)
    {
        $this->scanner->setIgnoreOperator(false);

        return $this->parse($input);
    }

    /**
     * Private function to add an error message to the errors array.
     *
     * @param string $message
     */
    protected function addError($message)
    {
        $this->errors[] = $message;
    }

    /**
     * Checks if the parser has any errors to deliver.
     *
     * @return bool
     */
    public function hasErrors()
    {
        return count($this->errors) > 0;
    }

    /**
     * Returns an array with error messages from the parser.
     *
     * @return array
     */
    public function getErrors()
    {
        return $this->errors;
    }

    /**
     * Makes the parser read a single term. This can be a word, text, or explicit term.
     *
     * @param int    $tokenType
     * @param string $term
     *
     * @return \Gdbots\QueryParser\Node\AbstractQueryItem|null
     */
    protected function readTerm($tokenType, $term)
    {
        if (!in_array($tokenType, [QueryLexer::T_FILTER])) {
            return $term;
        }

        if (in_array($tokenType, [QueryLexer::T_FILTER]) && $term->getTokenType() != QueryLexer::T_WORD) {
            $this->addError(sprintf(
                'Error: FILTER only support Word. Found: "%s"',
                $this->scanner->getTokenTypeText()
            ));

            return $term;
        }

        $value = null;
        $tokenTypeText = $this->scanner->getToken();

        $this->scanner->next();

        if (in_array(
            $this->scanner->getTokenType(),
            [
                QueryLexer::T_WORD,
                QueryLexer::T_DATE,
                QueryLexer::T_NUMBER,
                QueryLexer::T_URL
            ]
        )) {
            $value = new Node\Word($this->scanner->getToken(), $this->scanner->getTokenType());
        }

        if ($this->scanner->getTokenType() == QueryLexer::T_PHRASE) {
            $value = new Node\Phrase($this->scanner->getToken());
        }

        if (!$value) {
            $this->addError(sprintf(
                'Error: Expected Word, Phrase, or Url. Found: "%s"',
                $this->scanner->getTokenTypeText()
            ));

            return null;
        }

        $this->scanner->next();

        if ($this->scanner->getTokenType() == QueryLexer::T_RANGE) {
            $this->scanner->next();

            $value = new Node\Range($value->getToken(), $this->scanner->getToken());

            $this->scanner->next();
        }

        return new Node\ExplicitTerm($term, $tokenType, $tokenTypeText, $value);
    }

    /**
     * Makes the parser read an expression. This can be:
     * - '(' Subexpression ')'
     * - '"' text '"'
     * - Word
     * - Word:Phrase
     * - Word:Word
     * - '-' Expression
     * - '+' Expression
     * - '#' Expression
{'fraction_non_alphanumeric': 0.12765715584858475, 'fraction_numerical': 0.00022735023303398886, 'mean_word_length': 1.8426494345718902, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 89, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6395385', 'n_tokens_mistral': 2608, 'n_tokens_neox': 2493, 'n_words': 634}