mirror of
https://github.com/danog/Valinor.git
synced 2024-11-26 20:24:40 +01:00
misc: save type token symbols during lexing
This commit is contained in:
parent
d7dda9d261
commit
ad0f8fee17
@ -31,6 +31,8 @@ final class ArrayToken implements TraversingToken
|
||||
/** @var class-string<ArrayType|NonEmptyArrayType> */
|
||||
private string $arrayType;
|
||||
|
||||
private string $symbol;
|
||||
|
||||
private static self $array;
|
||||
|
||||
private static self $nonEmptyArray;
|
||||
@ -38,19 +40,20 @@ final class ArrayToken implements TraversingToken
|
||||
/**
|
||||
* @param class-string<ArrayType|NonEmptyArrayType> $arrayType
|
||||
*/
|
||||
private function __construct(string $arrayType)
|
||||
private function __construct(string $arrayType, string $symbol)
|
||||
{
|
||||
$this->arrayType = $arrayType;
|
||||
$this->symbol = $symbol;
|
||||
}
|
||||
|
||||
public static function array(): self
|
||||
{
|
||||
return self::$array ??= new self(ArrayType::class);
|
||||
return self::$array ??= new self(ArrayType::class, 'array');
|
||||
}
|
||||
|
||||
public static function nonEmptyArray(): self
|
||||
{
|
||||
return self::$nonEmptyArray ??= new self(NonEmptyArrayType::class);
|
||||
return self::$nonEmptyArray ??= new self(NonEmptyArrayType::class, 'non-empty-array');
|
||||
}
|
||||
|
||||
public function traverse(TokenStream $stream): Type
|
||||
@ -70,6 +73,11 @@ final class ArrayToken implements TraversingToken
|
||||
return ($this->arrayType)::native();
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return $this->symbol;
|
||||
}
|
||||
|
||||
private function arrayType(TokenStream $stream): CompositeTraversableType
|
||||
{
|
||||
// @PHP8.0 use `new ($this->arrayType)(...)`
|
||||
@ -130,9 +138,7 @@ final class ArrayToken implements TraversingToken
|
||||
$optional = false;
|
||||
|
||||
if ($stream->next() instanceof UnknownSymbolToken) {
|
||||
/** @var UnknownSymbolToken $token */
|
||||
$token = $stream->forward();
|
||||
$type = new StringValueType($token->symbol());
|
||||
$type = new StringValueType($stream->forward()->symbol());
|
||||
} else {
|
||||
$type = $stream->read();
|
||||
}
|
||||
|
@ -40,4 +40,9 @@ final class ClassNameToken implements TraversingToken
|
||||
{
|
||||
return $this->className;
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return $this->className;
|
||||
}
|
||||
}
|
||||
|
@ -38,4 +38,9 @@ final class ClassStringToken implements TraversingToken
|
||||
|
||||
return new ClassStringType($type);
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return 'class-string';
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,9 @@ use CuyZ\Valinor\Utility\IsSingleton;
|
||||
final class ClosingBracketToken implements Token
|
||||
{
|
||||
use IsSingleton;
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return '>';
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,9 @@ use CuyZ\Valinor\Utility\IsSingleton;
|
||||
final class ClosingCurlyBracketToken implements Token
|
||||
{
|
||||
use IsSingleton;
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return '}';
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,9 @@ use CuyZ\Valinor\Utility\IsSingleton;
|
||||
final class ClosingSquareBracketToken implements Token
|
||||
{
|
||||
use IsSingleton;
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return ']';
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,9 @@ use CuyZ\Valinor\Utility\IsSingleton;
|
||||
final class ColonToken implements Token
|
||||
{
|
||||
use IsSingleton;
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return ':';
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,9 @@ use CuyZ\Valinor\Utility\IsSingleton;
|
||||
final class CommaToken implements Token
|
||||
{
|
||||
use IsSingleton;
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return ',';
|
||||
}
|
||||
}
|
||||
|
@ -27,4 +27,9 @@ final class EnumNameToken implements TraversingToken
|
||||
{
|
||||
return new EnumType($this->enumName);
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return $this->enumName;
|
||||
}
|
||||
}
|
||||
|
@ -22,4 +22,9 @@ final class FloatValueToken implements TraversingToken
|
||||
{
|
||||
return new FloatValueType($this->value);
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return (string)$this->value;
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,11 @@ final class GenericClassNameToken implements TraversingToken
|
||||
: new ClassType($this->className, $generics);
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return $this->className;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, Type> $templates
|
||||
* @return Type[]
|
||||
|
@ -70,4 +70,9 @@ final class IntegerToken implements TraversingToken
|
||||
|
||||
return new IntegerRangeType($min->value(), $max->value());
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return 'int';
|
||||
}
|
||||
}
|
||||
|
@ -22,4 +22,9 @@ final class IntegerValueToken implements TraversingToken
|
||||
{
|
||||
return new IntegerValueType($this->value);
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return (string)$this->value;
|
||||
}
|
||||
}
|
||||
|
@ -39,4 +39,9 @@ final class IntersectionToken implements LeftTraversingToken
|
||||
|
||||
return new IntersectionType($type, $rightType);
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return '&';
|
||||
}
|
||||
}
|
||||
|
@ -56,4 +56,9 @@ final class IterableToken implements TraversingToken
|
||||
|
||||
return $iterableType;
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return 'iterable';
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ final class ListToken implements TraversingToken
|
||||
/** @var class-string<ListType|NonEmptyListType> */
|
||||
private string $listType;
|
||||
|
||||
private string $symbol;
|
||||
|
||||
private static self $list;
|
||||
|
||||
private static self $nonEmptyList;
|
||||
@ -23,19 +25,20 @@ final class ListToken implements TraversingToken
|
||||
/**
|
||||
* @param class-string<ListType|NonEmptyListType> $listType
|
||||
*/
|
||||
private function __construct(string $listType)
|
||||
private function __construct(string $listType, string $symbol)
|
||||
{
|
||||
$this->listType = $listType;
|
||||
$this->symbol = $symbol;
|
||||
}
|
||||
|
||||
public static function list(): self
|
||||
{
|
||||
return self::$list ??= new self(ListType::class);
|
||||
return self::$list ??= new self(ListType::class, 'list');
|
||||
}
|
||||
|
||||
public static function nonEmptyList(): self
|
||||
{
|
||||
return self::$nonEmptyList ??= new self(NonEmptyListType::class);
|
||||
return self::$nonEmptyList ??= new self(NonEmptyListType::class, 'non-empty-list');
|
||||
}
|
||||
|
||||
public function traverse(TokenStream $stream): Type
|
||||
@ -58,4 +61,9 @@ final class ListToken implements TraversingToken
|
||||
|
||||
return ($this->listType)::native();
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return $this->symbol;
|
||||
}
|
||||
}
|
||||
|
@ -29,23 +29,27 @@ final class NativeToken implements TraversingToken
|
||||
|
||||
private Type $type;
|
||||
|
||||
private function __construct(Type $type)
|
||||
private string $symbol;
|
||||
|
||||
private function __construct(Type $type, string $symbol)
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->symbol = $symbol;
|
||||
}
|
||||
|
||||
public static function accepts(string $symbol): bool
|
||||
{
|
||||
return (bool)self::type($symbol);
|
||||
return (bool)self::type(strtolower($symbol));
|
||||
}
|
||||
|
||||
public static function from(string $symbol): self
|
||||
{
|
||||
$symbol = strtolower($symbol);
|
||||
$type = self::type($symbol);
|
||||
|
||||
assert($type instanceof Type);
|
||||
|
||||
return self::$map[$symbol] ??= new self($type);
|
||||
return self::$map[$symbol] ??= new self($type, $symbol);
|
||||
}
|
||||
|
||||
public function traverse(TokenStream $stream): Type
|
||||
@ -53,10 +57,15 @@ final class NativeToken implements TraversingToken
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return $this->symbol;
|
||||
}
|
||||
|
||||
private static function type(string $symbol): ?Type
|
||||
{
|
||||
// @PHP8.0 match
|
||||
switch (strtolower($symbol)) {
|
||||
switch ($symbol) {
|
||||
case 'null':
|
||||
return NullType::get();
|
||||
case 'true':
|
||||
|
@ -19,4 +19,9 @@ final class NullableToken implements TraversingToken
|
||||
{
|
||||
return new UnionType(NullType::get(), $stream->read());
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,9 @@ use CuyZ\Valinor\Utility\IsSingleton;
|
||||
final class OpeningBracketToken implements Token
|
||||
{
|
||||
use IsSingleton;
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return '<';
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,9 @@ use CuyZ\Valinor\Utility\IsSingleton;
|
||||
final class OpeningCurlyBracketToken implements Token
|
||||
{
|
||||
use IsSingleton;
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return '{';
|
||||
}
|
||||
}
|
||||
|
@ -23,4 +23,9 @@ final class OpeningSquareBracketToken implements LeftTraversingToken
|
||||
|
||||
return ArrayType::simple($type);
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return '[';
|
||||
}
|
||||
}
|
||||
|
@ -32,4 +32,9 @@ final class StringValueToken implements TraversingToken
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return $this->type->toString();
|
||||
}
|
||||
}
|
||||
|
@ -7,4 +7,5 @@ namespace CuyZ\Valinor\Type\Parser\Lexer\Token;
|
||||
/** @internal */
|
||||
interface Token
|
||||
{
|
||||
public function symbol(): string;
|
||||
}
|
||||
|
@ -12,13 +12,21 @@ final class TypeToken implements TraversingToken
|
||||
{
|
||||
private Type $type;
|
||||
|
||||
public function __construct(Type $type)
|
||||
private string $symbol;
|
||||
|
||||
public function __construct(Type $type, string $symbol)
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->symbol = $symbol;
|
||||
}
|
||||
|
||||
public function traverse(TokenStream $stream): Type
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return $this->symbol;
|
||||
}
|
||||
}
|
||||
|
@ -23,4 +23,9 @@ final class UnionToken implements LeftTraversingToken
|
||||
|
||||
return new UnionType($type, $stream->read());
|
||||
}
|
||||
|
||||
public function symbol(): string
|
||||
{
|
||||
return '|';
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ final class TypeAliasLexer implements TypeLexer
|
||||
public function tokenize(string $symbol): Token
|
||||
{
|
||||
if (isset($this->aliases[$symbol])) {
|
||||
return new TypeToken($this->aliases[$symbol]);
|
||||
return new TypeToken($this->aliases[$symbol], $symbol);
|
||||
}
|
||||
|
||||
return $this->delegate->tokenize($symbol);
|
||||
|
@ -8,4 +8,8 @@ use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
|
||||
|
||||
final class FakeToken implements Token
|
||||
{
|
||||
public function symbol(): string
|
||||
{
|
||||
return 'fake-token';
|
||||
}
|
||||
}
|
||||
|
170
tests/Unit/Type/Parser/Lexer/NativeLexerTest.php
Normal file
170
tests/Unit/Type/Parser/Lexer/NativeLexerTest.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Type\Parser\Lexer;
|
||||
|
||||
use CuyZ\Valinor\Tests\Fixture\Enum\PureEnum;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\NativeLexer;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\ArrayToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\ClassNameToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\ClassStringToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\ClosingBracketToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\ClosingCurlyBracketToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\ClosingSquareBracketToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\ColonToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\CommaToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\EnumNameToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\FloatValueToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\IntegerToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\IntegerValueToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\IntersectionToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\IterableToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\ListToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\NativeToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\NullableToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\OpeningBracketToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\OpeningCurlyBracketToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\OpeningSquareBracketToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\StringValueToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\UnionToken;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\UnknownSymbolToken;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use stdClass;
|
||||
|
||||
final class NativeLexerTest extends TestCase
|
||||
{
|
||||
private NativeLexer $lexer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->lexer = new NativeLexer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider tokenized_type_is_correct_data_provider
|
||||
*
|
||||
* @param class-string<Token> $tokenClassName
|
||||
*/
|
||||
public function test_tokenized_type_is_correct(string $symbol, string $tokenClassName): void
|
||||
{
|
||||
$token = $this->lexer->tokenize($symbol);
|
||||
|
||||
self::assertInstanceOf($tokenClassName, $token);
|
||||
self::assertSame($symbol, $token->symbol());
|
||||
}
|
||||
|
||||
public function tokenized_type_is_correct_data_provider(): iterable
|
||||
{
|
||||
yield 'null' => [
|
||||
'symbol' => 'null',
|
||||
'token' => NativeToken::class,
|
||||
];
|
||||
yield 'union' => [
|
||||
'symbol' => '|',
|
||||
'token' => UnionToken::class,
|
||||
];
|
||||
yield 'intersection' => [
|
||||
'symbol' => '&',
|
||||
'token' => IntersectionToken::class,
|
||||
];
|
||||
yield 'opening bracket' => [
|
||||
'symbol' => '<',
|
||||
'token' => OpeningBracketToken::class,
|
||||
];
|
||||
yield 'closing bracket' => [
|
||||
'symbol' => '>',
|
||||
'token' => ClosingBracketToken::class,
|
||||
];
|
||||
yield 'opening square bracket' => [
|
||||
'symbol' => '[',
|
||||
'token' => OpeningSquareBracketToken::class,
|
||||
];
|
||||
yield 'closing square bracket' => [
|
||||
'symbol' => ']',
|
||||
'token' => ClosingSquareBracketToken::class,
|
||||
];
|
||||
yield 'opening curly bracket' => [
|
||||
'symbol' => '{',
|
||||
'token' => OpeningCurlyBracketToken::class,
|
||||
];
|
||||
yield 'closing curly bracket' => [
|
||||
'symbol' => '}',
|
||||
'token' => ClosingCurlyBracketToken::class,
|
||||
];
|
||||
yield 'colon' => [
|
||||
'symbol' => ':',
|
||||
'token' => ColonToken::class,
|
||||
];
|
||||
yield 'nullable' => [
|
||||
'symbol' => '?',
|
||||
'token' => NullableToken::class,
|
||||
];
|
||||
yield 'comma' => [
|
||||
'symbol' => ',',
|
||||
'token' => CommaToken::class,
|
||||
];
|
||||
yield 'int' => [
|
||||
'symbol' => 'int',
|
||||
'token' => IntegerToken::class,
|
||||
];
|
||||
yield 'array' => [
|
||||
'symbol' => 'array',
|
||||
'token' => ArrayToken::class,
|
||||
];
|
||||
yield 'non empty array' => [
|
||||
'symbol' => 'non-empty-array',
|
||||
'token' => ArrayToken::class,
|
||||
];
|
||||
yield 'list' => [
|
||||
'symbol' => 'list',
|
||||
'token' => ListToken::class,
|
||||
];
|
||||
yield 'non empty list' => [
|
||||
'symbol' => 'non-empty-list',
|
||||
'token' => ListToken::class,
|
||||
];
|
||||
yield 'iterable' => [
|
||||
'symbol' => 'iterable',
|
||||
'token' => IterableToken::class,
|
||||
];
|
||||
yield 'class-string' => [
|
||||
'symbol' => 'class-string',
|
||||
'token' => ClassStringToken::class,
|
||||
];
|
||||
yield 'single quote string value' => [
|
||||
'symbol' => "'foo'",
|
||||
'token' => StringValueToken::class,
|
||||
];
|
||||
yield 'double quote string value' => [
|
||||
'symbol' => '"foo"',
|
||||
'token' => StringValueToken::class,
|
||||
];
|
||||
yield 'integer value' => [
|
||||
'symbol' => '1337',
|
||||
'token' => IntegerValueToken::class,
|
||||
];
|
||||
yield 'float value' => [
|
||||
'symbol' => '1337.42',
|
||||
'token' => FloatValueToken::class,
|
||||
];
|
||||
yield 'class' => [
|
||||
'symbol' => stdClass::class,
|
||||
'token' => ClassNameToken::class,
|
||||
];
|
||||
yield 'unknown' => [
|
||||
'symbol' => 'unknown',
|
||||
'token' => UnknownSymbolToken::class,
|
||||
];
|
||||
|
||||
if (PHP_VERSION_ID >= 8_01_00) {
|
||||
yield 'enum' => [
|
||||
'symbol' => PureEnum::class,
|
||||
'token' => EnumNameToken::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Type\Parser\Lexer\Token;
|
||||
|
||||
use CuyZ\Valinor\Tests\Fake\Type\Parser\Factory\FakeTypeParserFactory;
|
||||
use CuyZ\Valinor\Tests\Fake\Type\Parser\Template\FakeTemplateParser;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\GenericClassNameToken;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use stdClass;
|
||||
|
||||
final class GenericClassNameTokenTest extends TestCase
|
||||
{
|
||||
public function test_symbol_is_correct(): void
|
||||
{
|
||||
$token = new GenericClassNameToken(stdClass::class, new FakeTypeParserFactory(), new FakeTemplateParser());
|
||||
|
||||
self::assertSame(stdClass::class, $token->symbol());
|
||||
}
|
||||
}
|
19
tests/Unit/Type/Parser/Lexer/Token/TypeTokenTest.php
Normal file
19
tests/Unit/Type/Parser/Lexer/Token/TypeTokenTest.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Type\Parser\Lexer\Token;
|
||||
|
||||
use CuyZ\Valinor\Tests\Fake\Type\FakeType;
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\TypeToken;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class TypeTokenTest extends TestCase
|
||||
{
|
||||
public function test_symbol_is_correct(): void
|
||||
{
|
||||
$token = new TypeToken(FakeType::permissive(), 'foo');
|
||||
|
||||
self::assertSame('foo', $token->symbol());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user