Skip to content

Commit

Permalink
Add tuple generator (#71)
Browse files Browse the repository at this point in the history
* Add tuple generator

* Remove text comparing test for tuple class generator

* Arrange Tuple methods order in the old way

* Remove ReturnTypeWillChange in all files

* Remove unecessery types changes

* Add tests to fully cover Tuples

* Add tuples generating validator

* Change Tuple message

* Move basic concat method to Tuple abstract class

* Change size const to inline

* Change chema version for phpunit

* Fix phpstan problems

* Add generate tuples command to composer

* Refactor tuples generating command

* Add diff check to generator command

* Keep .tuple dir

* Keep .tuple dir

* Fix tuple1 formatting

* fixes after merge master

* lock phpstan

---------

Co-authored-by: Arkadiusz Kondas <[email protected]>
  • Loading branch information
mtk3d and akondas authored May 28, 2024
1 parent 9ec74be commit 75528b1
Show file tree
Hide file tree
Showing 36 changed files with 2,107 additions and 73 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ clover.xml
.idea
composer.lock
/.phpunit.cache/
.tuple/*
!.tuple/.gitkeep
1 change: 1 addition & 0 deletions .php-cs-fixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
$finder = PhpCsFixer\Finder::create()
->in(__DIR__.'/src')
->in(__DIR__.'/tests')
->in(__DIR__.'/generators')
;

$config = new PhpCsFixer\Config();
Expand Down
Empty file added .tuple/.gitkeep
Empty file.
43 changes: 43 additions & 0 deletions bin/generate-tuples
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

require __DIR__.'/../vendor/autoload.php';

use Munus\Generators\Tuple\TupleGeneratorConfiguration;
use Munus\Tuple;

const VALIDATE_OPT = 'validate';

$options = getopt('', [VALIDATE_OPT]);
$validateOnly = array_key_exists(VALIDATE_OPT, $options);

$tupleGenerator = TupleGeneratorConfiguration::getTupleGenerator();
$tupleGenerator->prepareTuples(Tuple::TUPLE_MAX_SIZE);

$anyDiffs = false;

foreach ($tupleGenerator->getPreparedTuplesNames() as $className) {
passthru(
sprintf('diff -u .tuple/%s.php src/Tuple/%s.php', $className, $className),
$foundDiff
);

if ($foundDiff) {
$anyDiffs = true;
}
}

if ($anyDiffs && $validateOnly) {
error_log('Differences between exising and generated Tuples found.');
error_log('Please regenerate tuples and try again.');
exit(1);
}

if (!$validateOnly) {
$tupleGenerator->commitPreparedTuples();
print_r('Tuples successfully generated.'.PHP_EOL);
}

exit(0);
13 changes: 11 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.27",
"nette/php-generator": "^4.0",
"phpunit/phpunit": "^9.6.13",
"phpstan/phpstan": "^1.11.0"
"phpstan/phpstan": "1.11.1"
},
"autoload": {
"psr-4": {
Expand All @@ -28,7 +29,8 @@
},
"autoload-dev": {
"psr-4": {
"Munus\\Tests\\": "tests/"
"Munus\\Tests\\": "tests/",
"Munus\\Generators\\": "generators/"
}
},

Expand All @@ -51,7 +53,14 @@
"coverage-html": [
"phpunit --coverage-html coverage"
],
"generate-tuples": [
"./bin/generate-tuples"
],
"validate-tuples": [
"./bin/generate-tuples --validate"
],
"tests": [
"@validate-tuples",
"@check-cs",
"@phpstan",
"@phpunit-coverage"
Expand Down
12 changes: 12 additions & 0 deletions generators/Tuple/ClassPersister.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Munus\Generators\Tuple;

interface ClassPersister
{
public function save(string $directory, string $className, string $content): void;

public function moveClass(string $fromDir, string $toDir, string $className): void;
}
23 changes: 23 additions & 0 deletions generators/Tuple/FilePutContentsClassPersister.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Munus\Generators\Tuple;

class FilePutContentsClassPersister implements ClassPersister
{
public function __construct(private string $sourcePath)
{
}

public function save(string $directory, string $className, string $content): void
{
$filePath = $this->sourcePath.$directory.'/'.$className.'.php';
file_put_contents($filePath, $content);
}

public function moveClass(string $fromDir, string $toDir, string $className): void
{
copy(sprintf('%s/%s.php', $fromDir, $className), sprintf('%s/%s.php', $toDir, $className));
}
}
76 changes: 76 additions & 0 deletions generators/Tuple/FragmentGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Munus\Generators\Tuple;

use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\PhpNamespace;

abstract class FragmentGenerator
{
private const TYPE_TEMPLATE = 'T%s';
private const VALUE_TEMPLATE = '$value%s';
private const CLASS_VALUE_TEMPLATE = '$this->value%s';
private const PARAMETER_NAMES_TEMPLATE = 'value%s';
protected const EMPTY_COMMENT_LINE = '';

abstract public function append(PhpNamespace $namespace, ClassType $class, int $tupleSize, int $maxTupleSize): void;

protected function isMaxSizeTuple(int $tupleSize, int $maxTupleSize): bool
{
return $tupleSize === $maxTupleSize;
}

protected function isTupleZero(int $tupleSize): bool
{
return 0 === $tupleSize;
}

/**
* @return string[]
*/
protected function types(int $tupleSize): array
{
return $this->listOfTemplate(self::TYPE_TEMPLATE, $tupleSize);
}

/**
* @return string[]
*/
protected function values(int $tupleSize): array
{
return $this->listOfTemplate(self::VALUE_TEMPLATE, $tupleSize);
}

/**
* @return string[]
*/
protected function classValues(int $tupleSize): array
{
return $this->listOfTemplate(self::CLASS_VALUE_TEMPLATE, $tupleSize);
}

/**
* @return string[]
*/
protected function parameterNames(int $tupleSize): array
{
return $this->listOfTemplate(self::PARAMETER_NAMES_TEMPLATE, $tupleSize);
}

/**
* @return string[]
*/
protected function listOfTemplate(string $template, int $tupleSize): array
{
if ($this->isTupleZero($tupleSize)) {
return [];
}

return array_map(
fn (int $n): string => sprintf($template, $n),
range(1, $tupleSize),
);
}
}
23 changes: 23 additions & 0 deletions generators/Tuple/FragmentGenerator/AppendMethodGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Munus\Generators\Tuple\FragmentGenerator;

class AppendMethodGenerator extends AppendPrependMethodAbstractGenerator
{
protected function methodName(): string
{
return 'append';
}

protected function listOfTypes(int $tupleSize): string
{
return join(', ', [...$this->types($tupleSize), 'T']);
}

protected function listOfValues(int $tupleSize): string
{
return join(', ', [...$this->classValues($tupleSize), '$value']);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Munus\Generators\Tuple\FragmentGenerator;

use Munus\Exception\UnsupportedOperationException;
use Munus\Generators\Tuple\FragmentGenerator;
use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\PhpNamespace;

abstract class AppendPrependMethodAbstractGenerator extends FragmentGenerator
{
public function append(PhpNamespace $namespace, ClassType $class, int $tupleSize, int $maxTupleSize): void
{
$resultTupleSize = $tupleSize + 1;

$method = $class->addMethod($this->methodName());
$method->addParameter('value');

if ($this->isMaxSizeTuple($tupleSize, $maxTupleSize)) {
$namespace->addUse(UnsupportedOperationException::class);
$method->setBody($this->getMaxTupleSizeExceptionThrowBody());

return;
}

$method->setReturnType($this->getMethodReturnType($namespace, $resultTupleSize));
$method->addComment('@template T');
$method->addComment(self::EMPTY_COMMENT_LINE);
$method->addComment('@param T $value');
$method->addComment(self::EMPTY_COMMENT_LINE);
$method->addComment($this->getReturnTypeComment($resultTupleSize, $tupleSize));
$method->setBody($this->getMethodBody($resultTupleSize, $tupleSize));
}

private function getMaxTupleSizeExceptionThrowBody(): string
{
return sprintf(
'throw new UnsupportedOperationException(\'Can\\\'t %s next value. This is biggest possible Tuple\');',
$this->methodName(),
);
}

private function getMethodReturnType(PhpNamespace $namespace, int $resultTupleSize): string
{
return sprintf('%s\Tuple%s', $namespace->getName(), $resultTupleSize);
}

private function getReturnTypeComment(int $resultTupleSize, int $tupleSize): string
{
return sprintf('@returns Tuple%s<%s>', $resultTupleSize, $this->listOfTypes($tupleSize));
}

private function getMethodBody(int $resultTupleSize, int $tupleSize): string
{
return sprintf('return new Tuple%s(%s);', $resultTupleSize, $this->listOfValues($tupleSize));
}

abstract protected function listOfValues(int $tupleSize): string;

abstract protected function listOfTypes(int $tupleSize): string;

abstract protected function methodName(): string;
}
19 changes: 19 additions & 0 deletions generators/Tuple/FragmentGenerator/ArityMethodGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Munus\Generators\Tuple\FragmentGenerator;

use Munus\Generators\Tuple\FragmentGenerator;
use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\PhpNamespace;

class ArityMethodGenerator extends FragmentGenerator
{
public function append(PhpNamespace $namespace, ClassType $class, int $tupleSize, int $maxTupleSize): void
{
$arity = $class->addMethod('arity');
$arity->setReturnType('int');
$arity->setBody('return ?;', [$tupleSize]);
}
}
53 changes: 53 additions & 0 deletions generators/Tuple/FragmentGenerator/ConcatMethodGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Munus\Generators\Tuple\FragmentGenerator;

use Munus\Generators\Tuple\FragmentGenerator;
use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\PhpNamespace;

class ConcatMethodGenerator extends FragmentGenerator
{
public function append(PhpNamespace $namespace, ClassType $class, int $tupleSize, int $maxTupleSize): void
{
$concatableSize = $maxTupleSize - $tupleSize;

foreach (range(0, $concatableSize) as $n) {
$this->generateConcatTupleNMethod($n, $tupleSize, $class);
}
}

private function generateConcatTupleNMethod(int $n, int $tupleSize, ClassType $class): void
{
$returnTupleSize = $tupleSize + $n;

$concatTupleN = $class->addMethod(sprintf('concatTuple%s', $n));
$concatTupleN->addParameter('tuple');

$types = $this->types($tupleSize);
$uTypes = $this->listOfTemplate('U%s', $n);
$bothTypes = [...$types, ...$uTypes];

$paramTupleGenerics = 0 == $n
? ''
: sprintf('<%s>', join(', ', $uTypes));
$returnTupleGenerics = 0 == $returnTupleSize
? ''
: sprintf('<%s>', join(', ', $bothTypes));

foreach ($uTypes as $uType) {
$concatTupleN->addComment(sprintf('@template %s', $uType));
}

if (0 !== count($uTypes)) {
$concatTupleN->addComment(self::EMPTY_COMMENT_LINE);
}

$concatTupleN->addComment(sprintf('@param Tuple%s%s $tuple', $n, $paramTupleGenerics));
$concatTupleN->addComment(self::EMPTY_COMMENT_LINE);
$concatTupleN->addComment(sprintf('@returns Tuple%s%s', $returnTupleSize, $returnTupleGenerics));
$concatTupleN->addBody('return $this->concat($tuple);');
}
}
22 changes: 22 additions & 0 deletions generators/Tuple/FragmentGenerator/ConstructorMethodGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Munus\Generators\Tuple\FragmentGenerator;

use Munus\Generators\Tuple\FragmentGenerator;
use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\PhpNamespace;

class ConstructorMethodGenerator extends FragmentGenerator
{
public function append(PhpNamespace $namespace, ClassType $class, int $tupleSize, int $maxTupleSize): void
{
$constructor = $class->addMethod('__construct');

foreach ($this->parameterNames($tupleSize) as $n => $parameterName) {
$constructor->addComment(sprintf('@param T%s $%s', $n + 1, $parameterName));
$constructor->addPromotedParameter($parameterName)->setPrivate();
}
}
}
Loading

0 comments on commit 75528b1

Please sign in to comment.