j84reginato/my-eval

View on GitHub
src/Lexing/Token.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

namespace MyEval\Lexing;

use function strlen;

/**
 * Class to handle tokens, i.e. discrete pieces of the input string that has specific meaning.
 */
class Token
{
    /**
     * Create a token with a given value and type, as well as an optional 'match' which is the actual character string
     * matching the token definition. Most of the time, $value and $match are the same, but in order to handle token
     * synonyms, they may be different.
     *
     * As an example illustrating the above, the natural logarithm can be denoted ln() as well as log(). In order to
     * standardize the token list, both inputs might generate a token with value 'log' and type TokenType::FunctionName,
     * but the match parameter will be the actual string matched, i.e. 'log' and 'ln', respectively, so that the token
     * knows its own length so that the rest of the input string will be handled correctly.
     *
     * @param string      $value Standardized value of Token.
     * @param int         $type  Token type, as defined by the TokenType class.
     * @param string|null $match Optional actual match in the input string.
     */
    public function __construct(
        public readonly string $value,
        public readonly int $type,
        private string|null $match = null
    ) {
        $this->match = $match ?: $value;
    }

    /**
     * Length of the input string matching the token.
     *
     * @return int Length of string matching the token.
     */
    public function length(): int
    {
        return strlen($this->match);
    }

    /**
     * Helper function, converting the Token to a printable string.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->value;
    }

    /**
     * Helper function, determining whether a pair of tokens can form an implicit multiplication.
     *
     * Mathematical shorthand writing often leaves out explicit multiplication symbols, writing "2x" instead of "2*x"
     * or "2 \cdot x". The parser accepts implicit multiplication if the first token is a nullary operator or a closing
     * parenthesis, and the second token is a nullary operator or an opening parenthesis. (Unless the first token is a
     * function name, and the second is an opening parenthesis.)
     *
     * @param Token|null $token1
     * @param Token|null $token2
     *
     * @return bool
     */
    public static function canFactorsInImplicitMultiplication(?Token $token1, ?Token $token2): bool
    {
        if (
            ($token1 === null || $token2 === null) ||
            ($token1->type === TokenType::FUNCTION_NAME && $token2->type === TokenType::OPEN_PARENTHESIS) ||
            (!static::checkToken1($token1) || !static::checkToken2($token2))
        ) {
            return false;
        }

        return true;
    }

    /**
     * Checks if token 1 is eligible for implicit multiplication.
     *
     * @param Token $token1
     *
     * @return bool
     */
    private static function checkToken1(Token $token1): bool
    {
        return (
            $token1->type === TokenType::NATURAL_NUMBER ||
            $token1->type === TokenType::INTEGER ||
            $token1->type === TokenType::REAL_NUMBER ||
            $token1->type === TokenType::CONSTANT ||
            $token1->type === TokenType::VARIABLE ||
            $token1->type === TokenType::FUNCTION_NAME ||
            $token1->type === TokenType::CLOSE_PARENTHESIS ||
            $token1->type === TokenType::FACTORIAL_OPERATOR ||
            $token1->type === TokenType::SEMI_FACTORIAL_OPERATOR
        );
    }

    /**
     * Checks if token 2 is eligible for implicit multiplication.
     *
     * @param Token $token2
     *
     * @return bool
     */
    private static function checkToken2(Token $token2): bool
    {
        return (
            $token2->type === TokenType::NATURAL_NUMBER ||
            $token2->type === TokenType::INTEGER ||
            $token2->type === TokenType::REAL_NUMBER ||
            $token2->type === TokenType::CONSTANT ||
            $token2->type === TokenType::VARIABLE ||
            $token2->type === TokenType::FUNCTION_NAME ||
            $token2->type === TokenType::OPEN_PARENTHESIS
        );
    }
}