Skip to content

Commit

Permalink
feat: introduce compiled normalizer cache
Browse files Browse the repository at this point in the history
  • Loading branch information
romm committed Mar 17, 2024
1 parent 37993b6 commit 064279c
Show file tree
Hide file tree
Showing 69 changed files with 3,003 additions and 289 deletions.
2 changes: 2 additions & 0 deletions src/Cache/FileSystemCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use CuyZ\Valinor\Definition\FunctionDefinition;
use CuyZ\Valinor\Definition\Repository\Cache\Compiler\ClassDefinitionCompiler;
use CuyZ\Valinor\Definition\Repository\Cache\Compiler\FunctionDefinitionCompiler;
use CuyZ\Valinor\Normalizer\Transformer\EvaluatedTransformer;
use Error;
use FilesystemIterator;
use Traversable;
Expand Down Expand Up @@ -193,6 +194,7 @@ private function compile(mixed $value): string
$code = match (true) {
$value instanceof ClassDefinition => $this->classDefinitionCompiler->compile($value),
$value instanceof FunctionDefinition => $this->functionDefinitionCompiler->compile($value),
$value instanceof EvaluatedTransformer => $value->code(),
default => var_export($value, true),
};

Expand Down
42 changes: 17 additions & 25 deletions src/Cache/KeySanitizerCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@
namespace CuyZ\Valinor\Cache;

use Closure;
use CuyZ\Valinor\Utility\Package;
use Psr\SimpleCache\CacheInterface;
use Traversable;

use function sha1;

/**
* @internal
*
Expand All @@ -19,24 +16,14 @@
*/
final class KeySanitizerCache implements WarmupCache
{
private static string $version;

/** @var Closure(string): string */
private Closure $sanitize;
private string $version;

public function __construct(
/** @var CacheInterface<EntryType> */
private CacheInterface $delegate
) {
// Two things:
// 1. We append the current version of the package to the cache key in
// order to avoid collisions between entries from different versions
// of the library.
// 2. The key is sha1'd so that it does not contain illegal characters.
// @see https://www.php-fig.org/psr/psr-16/#12-definitions
// @infection-ignore-all
$this->sanitize = static fn (string $key) => $key . sha1(self::$version ??= PHP_VERSION . '/' . Package::version());
}
private CacheInterface $delegate,
/** @var Closure(): string */
private Closure $sanitizeCallback,
) {}

public function warmup(): void
{
Expand All @@ -47,17 +34,17 @@ public function warmup(): void

public function get($key, $default = null): mixed
{
return $this->delegate->get(($this->sanitize)($key), $default);
return $this->delegate->get($this->sanitize($key), $default);
}

public function set($key, $value, $ttl = null): bool
{
return $this->delegate->set(($this->sanitize)($key), $value, $ttl);
return $this->delegate->set($this->sanitize($key), $value, $ttl);
}

public function delete($key): bool
{
return $this->delegate->delete(($this->sanitize)($key));
return $this->delegate->delete($this->sanitize($key));
}

public function clear(): bool
Expand All @@ -67,7 +54,7 @@ public function clear(): bool

public function has($key): bool
{
return $this->delegate->has(($this->sanitize)($key));
return $this->delegate->has($this->sanitize($key));
}

/**
Expand All @@ -76,7 +63,7 @@ public function has($key): bool
public function getMultiple($keys, $default = null): Traversable
{
foreach ($keys as $key) {
yield $key => $this->delegate->get(($this->sanitize)($key), $default);
yield $key => $this->delegate->get($this->sanitize($key), $default);
}
}

Expand All @@ -85,7 +72,7 @@ public function setMultiple($values, $ttl = null): bool
$versionedValues = [];

foreach ($values as $key => $value) {
$versionedValues[($this->sanitize)($key)] = $value;
$versionedValues[$this->sanitize($key)] = $value;
}

return $this->delegate->setMultiple($versionedValues, $ttl);
Expand All @@ -96,9 +83,14 @@ public function deleteMultiple($keys): bool
$transformedKeys = [];

foreach ($keys as $key) {
$transformedKeys[] = ($this->sanitize)($key);
$transformedKeys[] = $this->sanitize($key);
}

return $this->delegate->deleteMultiple($transformedKeys);
}

private function sanitize(string $key): string
{
return $key . $this->version ??= ($this->sanitizeCallback)();
}
}
61 changes: 61 additions & 0 deletions src/Compiler/Compiler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Compiler;

use function count;
use function str_repeat;
use function str_replace;

/** @internal */
final class Compiler
{
private string $code = '';

/** @var non-negative-int */
private int $indentation = 0;

public function compile(Node ...$nodes): self
{
$compiler = $this;

while ($current = array_shift($nodes)) {
$compiler = $current->compile($compiler);

if (count($nodes) > 0) {
$compiler = $compiler->write("\n");
}
}

return $compiler;
}

public function sub(): self
{
return new self();
}

public function write(string $code): self
{
$self = clone $this;
$self->code .= $code;

return $self;
}

public function indent(): self
{
$self = clone $this;
$self->indentation++;

return $self;
}

public function code(): string
{
$indent = str_repeat(' ', $this->indentation);

return $indent . str_replace("\n", "\n" . $indent, $this->code);
}
}
53 changes: 53 additions & 0 deletions src/Compiler/Library/NewAttributeNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Compiler\Library;

use CuyZ\Valinor\Compiler\Compiler;
use CuyZ\Valinor\Compiler\Node;
use CuyZ\Valinor\Definition\AttributeDefinition;

use function array_map;
use function serialize;

/** @internal */
final class NewAttributeNode extends Node
{
public function __construct(private AttributeDefinition $attribute) {}

public function compile(Compiler $compiler): Compiler
{
$argumentNodes = $this->argumentNode($this->attribute->arguments);

return $compiler->compile(
Node::newClass(
$this->attribute->class->name,
...$argumentNodes,
),
);
}

/**
* @param array<mixed> $arguments
* @return list<Node>
*/
private function argumentNode(array $arguments): array
{
return array_map(function (mixed $argument) {
if (is_object($argument)) {
return Node::functionCall(
name: 'unserialize',
arguments: [Node::value(serialize($argument))],
);
}

if (is_array($argument)) {
return Node::array($this->argumentNode($argument));
}

/** @var scalar $argument */
return Node::value($argument);
}, $arguments);
}
}
107 changes: 107 additions & 0 deletions src/Compiler/Library/TypeAcceptNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Compiler\Library;

use CuyZ\Valinor\Compiler\Compiler;
use CuyZ\Valinor\Compiler\Node;
use CuyZ\Valinor\Type\CompositeTraversableType;
use CuyZ\Valinor\Type\FixedType;
use CuyZ\Valinor\Type\ObjectType;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\EnumType;
use CuyZ\Valinor\Type\Types\MixedType;
use CuyZ\Valinor\Type\Types\NativeBooleanType;
use CuyZ\Valinor\Type\Types\NativeFloatType;
use CuyZ\Valinor\Type\Types\NativeIntegerType;
use CuyZ\Valinor\Type\Types\NativeStringType;
use CuyZ\Valinor\Type\Types\NegativeIntegerType;
use CuyZ\Valinor\Type\Types\NullType;
use CuyZ\Valinor\Type\Types\ShapedArrayElement;
use CuyZ\Valinor\Type\Types\ShapedArrayType;
use CuyZ\Valinor\Type\Types\UndefinedObjectType;
use CuyZ\Valinor\Type\Types\UnionType;
use LogicException;
use UnitEnum;

use function array_map;
use function implode;
use function var_export;

/** @internal */
final class TypeAcceptNode extends Node
{
public function __construct(private Type $type) {}

public function compile(Compiler $compiler): Compiler
{
return $this->compileType($compiler, $this->type);
}

private function compileType(Compiler $compiler, Type $type): Compiler
{
return match (true) {
$type instanceof CompositeTraversableType => $compiler->write('\is_iterable($value)'),
$type instanceof EnumType => $this->compileEnumType($compiler, $type),
$type instanceof FixedType => $compiler->write('$value === ' . var_export($type->value(), true)),
$type instanceof MixedType => $compiler->write('true'),
$type instanceof NativeBooleanType => $compiler->write('\is_bool($value)'),
$type instanceof NativeFloatType => $compiler->write('\is_float($value)'),
$type instanceof NativeIntegerType => $compiler->write('\is_int($value)'),
// @todo positive int
$type instanceof NativeStringType => $compiler->write('\is_string($value)'),
$type instanceof NegativeIntegerType => $compiler->write('\is_string($value) && $value < 0'),
$type instanceof NullType => $compiler->write('\is_null($value)'),
$type instanceof ObjectType => $compiler->write("\$value instanceof ('{$type->className()}')"), // @todo anonymous class contains absolute file path
$type instanceof ShapedArrayType => $this->compileShapedArrayType($compiler, $type),
$type instanceof UnionType => $this->compileUnionType($compiler, $type),
$type instanceof UndefinedObjectType => $compiler->write('\is_object($value)'),
default => throw new LogicException("Type `{$type->toString()}` cannot be compiled."),
};
}

private function compileEnumType(Compiler $compiler, EnumType $type): Compiler
{
$code = '$value instanceof ' . $type->className();

if ($type->cases() !== []) {
$code .= ' && (' . implode(
' || ',
array_map(
fn (UnitEnum $enum) => '$value === ' . $enum::class . '::' . $enum->name,
$type->cases()
)
) . ')';
}

return $compiler->write($code);
}

private function compileShapedArrayType(Compiler $compiler, ShapedArrayType $type): Compiler
{
// @todo test
$code = implode(
' && ',
array_map(
fn (ShapedArrayElement $element) => $this->compileType($compiler->sub(), $type)->code(),
$type->elements()
)
);

return $compiler->write($code);
}

private function compileUnionType(Compiler $compiler, UnionType $type): Compiler
{
$code = implode(
' || ',
array_map(
fn (Type $type) => $this->compileType($compiler->sub(), $type)->code(),
$type->types()
)
);

return $compiler->write($code);
}
}
33 changes: 33 additions & 0 deletions src/Compiler/Native/AggregateNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Compiler\Native;

use CuyZ\Valinor\Compiler\Compiler;
use CuyZ\Valinor\Compiler\Node;

/** @internal */
final class AggregateNode extends Node
{
/** @var array<Node> */
private array $nodes;

public function __construct(Node ...$nodes)
{
$this->nodes = $nodes;
}

public function compile(Compiler $compiler): Compiler
{
while ($current = array_shift($this->nodes)) {
$compiler = $current->compile($compiler);

if (count($this->nodes) > 0) {
$compiler = $compiler->write("\n");
}
}

return $compiler;
}
}
Loading

0 comments on commit 064279c

Please sign in to comment.