Skip to content

Commit

Permalink
misc: replace regex-based type parser with character-based one
Browse files Browse the repository at this point in the history
This commit introduces a complete rewrite of the first layer of the type
parser. The previous one would use regex to split a raw type in tokens,
but that led to limitations — mostly concerning quoted strings — that
are now fixed.

Example of previous limitations, now solved:

```php
// Union of strings containing space chars
(new MapperBuilder())
    ->mapper()
    ->map(
        "'foo bar'|'baz fiz'",
        'baz fiz'
    );

// Shaped array with special chars in the key
(new MapperBuilder())
    ->mapper()
    ->map(
        "array{'some & key': string}",
        ['some & key' => 'value']
    );
```
  • Loading branch information
romm committed Aug 18, 2023
1 parent 1964d41 commit 0701371
Show file tree
Hide file tree
Showing 31 changed files with 888 additions and 818 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use CuyZ\Valinor\Definition\PropertyDefinition;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
use CuyZ\Valinor\Type\ClassType;
use CuyZ\Valinor\Type\GenericType;
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\AliasSpecification;
Expand All @@ -23,11 +24,11 @@
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\TypeParser;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\ClassType;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionMethod;
use ReflectionProperty;
use CuyZ\Valinor\Utility\Reflection\DocParser;

use function array_filter;
use function array_keys;
Expand Down Expand Up @@ -156,7 +157,7 @@ private function typeResolver(ClassType $type, string $targetClass): ReflectionT
private function localTypeAliases(ClassType $type): array
{
$reflection = Reflection::class($type->className());
$rawTypes = Reflection::localTypeAliases($reflection);
$rawTypes = DocParser::localTypeAliases($reflection);

$typeParser = $this->typeParser($type);

Expand All @@ -181,7 +182,7 @@ private function localTypeAliases(ClassType $type): array
private function importedTypeAliases(ClassType $type): array
{
$reflection = Reflection::class($type->className());
$importedTypesRaw = Reflection::importedTypeAliases($reflection);
$importedTypesRaw = DocParser::importedTypeAliases($reflection);

$typeParser = $this->typeParser($type);

Expand Down
28 changes: 21 additions & 7 deletions src/Definition/Repository/Reflection/ReflectionTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\MixedType;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\DocParser;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionFunctionAbstract;
use ReflectionParameter;
Expand All @@ -23,7 +24,7 @@ public function __construct(
private TypeParser $advancedParser
) {}

public function resolveType(\ReflectionProperty|\ReflectionParameter|\ReflectionFunctionAbstract $reflection): Type
public function resolveType(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection): Type
{
$nativeType = $this->nativeType($reflection);
$typeFromDocBlock = $this->typeFromDocBlock($reflection);
Expand Down Expand Up @@ -51,11 +52,24 @@ public function resolveType(\ReflectionProperty|\ReflectionParameter|\Reflection
return $typeFromDocBlock;
}

private function typeFromDocBlock(\ReflectionProperty|\ReflectionParameter|\ReflectionFunctionAbstract $reflection): ?Type
private function typeFromDocBlock(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection): ?Type
{
$type = $reflection instanceof ReflectionFunctionAbstract
? Reflection::docBlockReturnType($reflection)
: Reflection::docBlockType($reflection);
if ($reflection instanceof ReflectionFunctionAbstract) {
$type = DocParser::functionReturnType($reflection);
} elseif ($reflection instanceof ReflectionProperty) {
$type = DocParser::propertyType($reflection);
} else {
$type = null;

if ($reflection->isPromoted()) {
// @phpstan-ignore-next-line / parameter is promoted so class exists for sure
$type = DocParser::propertyType($reflection->getDeclaringClass()->getProperty($reflection->name));
}

if ($type === null) {
$type = DocParser::parameterType($reflection);
}
}

if ($type === null) {
return null;
Expand All @@ -64,7 +78,7 @@ private function typeFromDocBlock(\ReflectionProperty|\ReflectionParameter|\Refl
return $this->parseType($type, $reflection, $this->advancedParser);
}

private function nativeType(\ReflectionProperty|\ReflectionParameter|\ReflectionFunctionAbstract $reflection): ?Type
private function nativeType(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection): ?Type
{
$reflectionType = $reflection instanceof ReflectionFunctionAbstract
? $reflection->getReturnType()
Expand All @@ -83,7 +97,7 @@ private function nativeType(\ReflectionProperty|\ReflectionParameter|\Reflection
return $this->parseType($type, $reflection, $this->nativeParser);
}

private function parseType(string $raw, \ReflectionProperty|\ReflectionParameter|\ReflectionFunctionAbstract $reflection, TypeParser $parser): Type
private function parseType(string $raw, ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection, TypeParser $parser): Type
{
try {
return $parser->parse($raw);
Expand Down
8 changes: 1 addition & 7 deletions src/Library/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@
use CuyZ\Valinor\Type\ClassType;
use CuyZ\Valinor\Type\Parser\Factory\LexingTypeParserFactory;
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\Template\BasicTemplateParser;
use CuyZ\Valinor\Type\Parser\Template\TemplateParser;
use CuyZ\Valinor\Type\Parser\TypeParser;
use CuyZ\Valinor\Type\ScalarType;
use CuyZ\Valinor\Type\Types\ArrayType;
Expand Down Expand Up @@ -196,14 +194,10 @@ public function __construct(Settings $settings)

AttributesRepository::class => fn () => new NativeAttributesRepository(),

TypeParserFactory::class => fn () => new LexingTypeParserFactory(
$this->get(TemplateParser::class)
),
TypeParserFactory::class => fn () => new LexingTypeParserFactory(),

TypeParser::class => fn () => $this->get(TypeParserFactory::class)->get(),

TemplateParser::class => fn () => new BasicTemplateParser(),

RecursiveCacheWarmupService::class => fn () => new RecursiveCacheWarmupService(
$this->get(TypeParser::class),
$this->get(ObjectImplementations::class),
Expand Down
9 changes: 6 additions & 3 deletions src/Type/Parser/Exception/Template/DuplicatedTemplateName.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
use LogicException;

/** @internal */
final class DuplicatedTemplateName extends LogicException implements InvalidTemplate
final class DuplicatedTemplateName extends LogicException
{
public function __construct(string $template)
/**
* @param class-string $className
*/
public function __construct(string $className, string $template)
{
parent::__construct(
"The template `$template` was defined at least twice.",
"The template `$template` in class `$className` was defined at least twice.",
1604612898
);
}
Expand Down
7 changes: 4 additions & 3 deletions src/Type/Parser/Exception/Template/InvalidClassTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@

namespace CuyZ\Valinor\Type\Parser\Exception\Template;

use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use LogicException;

/** @internal */
final class InvalidClassTemplate extends LogicException implements InvalidTemplate
final class InvalidClassTemplate extends LogicException
{
/**
* @param class-string $className
*/
public function __construct(string $className, InvalidTemplate $exception)
public function __construct(string $className, string $template, InvalidType $exception)
{
parent::__construct(
"Template error for class `$className`: {$exception->getMessage()}",
"Invalid template `$template` for class `$className`: {$exception->getMessage()}",
1630092678,
$exception
);
Expand Down
10 changes: 0 additions & 10 deletions src/Type/Parser/Exception/Template/InvalidTemplate.php

This file was deleted.

21 changes: 0 additions & 21 deletions src/Type/Parser/Exception/Template/InvalidTemplateType.php

This file was deleted.

11 changes: 4 additions & 7 deletions src/Type/Parser/Factory/LexingTypeParserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,21 @@
use CuyZ\Valinor\Type\Parser\Lexer\AdvancedClassLexer;
use CuyZ\Valinor\Type\Parser\Lexer\NativeLexer;
use CuyZ\Valinor\Type\Parser\LexingParser;
use CuyZ\Valinor\Type\Parser\Template\TemplateParser;
use CuyZ\Valinor\Type\Parser\TypeParser;

/** @internal */
final class LexingTypeParserFactory implements TypeParserFactory
{
private TypeParser $nativeParser;

public function __construct(private TemplateParser $templateParser) {}

public function get(TypeParserSpecification ...$specifications): TypeParser
{
if (empty($specifications)) {
return $this->nativeParser ??= $this->nativeParser();
}

$lexer = new NativeLexer();
$lexer = new AdvancedClassLexer($lexer, $this, $this->templateParser);
$lexer = new AdvancedClassLexer($lexer, $this);

foreach ($specifications as $specification) {
$lexer = $specification->transform($lexer);
Expand All @@ -38,9 +35,9 @@ public function get(TypeParserSpecification ...$specifications): TypeParser
private function nativeParser(): TypeParser
{
$lexer = new NativeLexer();
$lexer = new AdvancedClassLexer($lexer, $this, $this->templateParser);
$lexer = new LexingParser($lexer);
$lexer = new AdvancedClassLexer($lexer, $this);
$parser = new LexingParser($lexer);

return new CachedParser($lexer);
return new CachedParser($parser);
}
}
31 changes: 0 additions & 31 deletions src/Type/Parser/LazyParser.php

This file was deleted.

4 changes: 1 addition & 3 deletions src/Type/Parser/Lexer/AdvancedClassLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,21 @@
use CuyZ\Valinor\Type\Parser\Lexer\Token\ClassNameToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\AdvancedClassNameToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
use CuyZ\Valinor\Type\Parser\Template\TemplateParser;

/** @internal */
final class AdvancedClassLexer implements TypeLexer
{
public function __construct(
private TypeLexer $delegate,
private TypeParserFactory $typeParserFactory,
private TemplateParser $templateParser
) {}

public function tokenize(string $symbol): Token
{
$token = $this->delegate->tokenize($symbol);

if ($token instanceof ClassNameToken) {
return new AdvancedClassNameToken($token, $this->typeParserFactory, $this->templateParser);
return new AdvancedClassNameToken($token, $this->typeParserFactory);
}

return $token;
Expand Down
Loading

0 comments on commit 0701371

Please sign in to comment.