diff --git a/psalm.xml b/psalm.xml index 72d84aa..a6bc102 100644 --- a/psalm.xml +++ b/psalm.xml @@ -68,5 +68,8 @@ + + + diff --git a/src/AbstractLexer.php b/src/AbstractLexer.php index ab4caab..167891a 100644 --- a/src/AbstractLexer.php +++ b/src/AbstractLexer.php @@ -20,7 +20,7 @@ * Base class for writing simple lexers, i.e. for creating small DSLs. * * @template T of UnitEnum|string|int - * @template V of string|int + * @template V of string|int|float|bool */ abstract class AbstractLexer { diff --git a/src/Token.php b/src/Token.php index b6df694..95b84dc 100644 --- a/src/Token.php +++ b/src/Token.php @@ -10,7 +10,7 @@ /** * @template T of UnitEnum|string|int - * @template V of string|int + * @template V of string|int|float|bool */ final class Token { @@ -20,7 +20,7 @@ final class Token * @readonly * @var V */ - public string|int $value; + public string|int|float|bool $value; /** * The type of the token (identifier, numeric, string, input parameter, none) @@ -41,7 +41,7 @@ final class Token * @param V $value * @param T|null $type */ - public function __construct(string|int $value, $type, int $position) + public function __construct(string|int|float|bool $value, $type, int $position) { $this->value = $value; $this->type = $type; diff --git a/tests/AbstractLexerTest.php b/tests/AbstractLexerTest.php index ea88754..1204267 100644 --- a/tests/AbstractLexerTest.php +++ b/tests/AbstractLexerTest.php @@ -4,6 +4,7 @@ namespace Doctrine\Tests\Common\Lexer; +use Doctrine\Common\Lexer\AbstractLexer; use Doctrine\Common\Lexer\Token; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -11,6 +12,8 @@ use function array_map; use function assert; use function count; +use function is_int; +use function is_numeric; use function setlocale; use const LC_ALL; @@ -279,4 +282,60 @@ public function testMarkerAnnotationLocaleTr(): void self::assertEquals('@', $mutableLexer->token->value); self::assertEquals('ODM\Id', $mutableLexer->lookahead->value); } + + public function testCanTokenizeFloatValue(): void + { + $lexer = new /** @template-extends AbstractLexer */ class () extends AbstractLexer { + final public const T_NONE = 1; + final public const T_INTEGER = 2; + final public const T_FLOAT = 4; + final public const T_BOOL = 8; + + protected function getType(string|int|float|bool &$value): int + { + if ($value === 'y') { + $value = true; + + return self::T_BOOL; + } + + if (is_numeric($value)) { + $value += 0; + + if (is_int($value)) { + return self::T_INTEGER; + } + + return self::T_FLOAT; + } + + return self::T_NONE; + } + + /** {@inheritDoc} */ + protected function getCatchablePatterns(): array + { + return [ + '(?:[0-9]+)(?:[\.][0-9]+)?(?:e[+-]?[0-9]+)?', + 'y', + ]; + } + + /** {@inheritDoc} */ + protected function getNonCatchablePatterns(): array + { + return ['\s+']; + } + }; + + $lexer->setInput('123.456'); + $token = $lexer->peek(); + assert($token !== null); + self::assertSame(123.456, $token->value, 'expect a real float, not a numerical string'); + + $lexer->setInput('y'); + $token = $lexer->peek(); + assert($token !== null); + self::assertTrue($token->value, 'expect a real bool, not a numerical string'); + } }