Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added DNF type support #505

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/Aop/Pointcut/AndPointcut.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,20 @@
/**
* And constructor
*/
public function __construct(int $pointcutKind = null, Pointcut ...$pointcuts)
public function __construct(int $pointcutKind = null)
{
$args = func_get_args();

if (is_array($args[1])) {
$pointcuts = $args[1];
} else {
$pointcuts = array_slice($args, 1);
}

if (count(array_filter($pointcuts, static fn ($pointcut) => $pointcut instanceof Pointcut)) !== count($pointcuts)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space before opening parenthesis of function call prohibited

throw new \Exception('only pointcats allowed to be passed');
}

// If we don't have specified kind, it will be calculated as intersection then
if (!isset($pointcutKind)) {
$pointcutKind = -1;
Expand Down
38 changes: 35 additions & 3 deletions src/Aop/Pointcut/ClassInheritancePointcut.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
namespace Go\Aop\Pointcut;

use Go\Aop\Pointcut;
use Go\Aop\Pointcut\DNF\Parser\TokenizerParserInterface;
use Go\Aop\Pointcut\DNF\SemanticAnalyzerInterface;
use Go\Core\AspectKernel;
use Go\ParserReflection\ReflectionFileNamespace;
use ReflectionClass;
use ReflectionFunction;
Expand All @@ -25,11 +28,25 @@
*/
final readonly class ClassInheritancePointcut implements Pointcut
{
private const DNF_TOKENS = ['(', ')', '&', '|'];

private TokenizerParserInterface $tokenizerParser;
private SemanticAnalyzerInterface $semanticAnalyzer;

/**
* Inheritance class matcher constructor
*
* @param (string&class-string) $parentClassOrInterfaceName Parent class or interface name to match in hierarchy
*/
public function __construct(private string $parentClassOrInterfaceName) {}
public function __construct(private string $parentClassOrInterfaceName)
{
$this->tokenizerParser = AspectKernel::getInstance()->getContainer()->getService(
TokenizerParserInterface::class
);
$this->semanticAnalyzer = AspectKernel::getInstance()->getContainer()->getService(
SemanticAnalyzerInterface::class
);
}

public function matches(
ReflectionClass|ReflectionFileNamespace $context,
Expand All @@ -42,12 +59,27 @@ public function matches(
return false;
}

// Otherwise, we match only if given context is child of given previously class name (either interface or class)
return $context->isSubclassOf($this->parentClassOrInterfaceName) || in_array($this->parentClassOrInterfaceName, $context->getInterfaceNames());
if (!$this->isDNFType()) {
// Otherwise, we match only if given context is child of given previously class name (either interface or class)
return $context->isSubclassOf($this->parentClassOrInterfaceName) || in_array($this->parentClassOrInterfaceName, $context->getInterfaceNames());
}

return $this->checkDNFType($context);
}

public function getKind(): int
{
return self::KIND_CLASS;
}

private function isDNFType(): bool
{
return array_intersect(str_split($this->parentClassOrInterfaceName), self::DNF_TOKENS) !== [];
}

private function checkDNFType(ReflectionClass|ReflectionFileNamespace $context): bool
{
$ast = $this->tokenizerParser->parse($this->parentClassOrInterfaceName);
return $this->semanticAnalyzer->verifyTree($ast, $context);
}
}
14 changes: 14 additions & 0 deletions src/Aop/Pointcut/DNF/AST/Node.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of line character is invalid; expected \n but found \r\n


namespace Go\Aop\Pointcut\DNF\AST;

readonly class Node
{
public function __construct(
public NodeType $type,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line indented incorrectly; expected 4 spaces, found 8

public ?string $identifier = null,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line indented incorrectly; expected 4 spaces, found 8

public ?Node $left = null,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line indented incorrectly; expected 4 spaces, found 8

public ?Node $right = null,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line indented incorrectly; expected 4 spaces, found 8

) {
}
}
10 changes: 10 additions & 0 deletions src/Aop/Pointcut/DNF/AST/NodeType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of line character is invalid; expected \n but found \r\n


namespace Go\Aop\Pointcut\DNF\AST;

enum NodeType
{
case IDENTIFIER;
case AND;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected and but found AND

case OR;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected or but found OR

}
13 changes: 13 additions & 0 deletions src/Aop/Pointcut/DNF/Parser/Token.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of line character is invalid; expected \n but found \r\n


namespace Go\Aop\Pointcut\DNF\Parser;

enum Token
{
case EOF;
case IDENTIFIER;
case LPAREN;
case RPAREN;
case AND;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected and but found AND

case OR;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected or but found OR

}
74 changes: 74 additions & 0 deletions src/Aop/Pointcut/DNF/Parser/TokenCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of line character is invalid; expected \n but found \r\n


namespace Go\Aop\Pointcut\DNF\Parser;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There must be one blank line after the namespace declaration

class TokenCollection implements TokenCollectionInterface
{
/**
* @param \ArrayIterator<\PhpToken> $tokens
*/
public function __construct(
private readonly \ArrayIterator $tokens

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line indented incorrectly; expected 4 spaces, found 8

) {
}

/**
* @inheritDoc
*/
public function expect(Token $token): void
{
/** @var Token $nextToken */
[$nextToken] = $this->next();

if ($nextToken !== $token) {
throw new \Exception(sprintf('Expected %s, but got %s', $token->name, $nextToken->name));
}
}

/**
* @inheritDoc
*/
public function next(): array
{
if (! $this->tokens->valid()) {
return [Token::EOF, null];
}

$val = trim($this->tokens->current()->text);

if ($val === '') {
return $this->next();
}

$return = [$this->getToken($val), $val];
$this->tokens->next();

return $return;
}

/**
* @inheritDoc
*/
public function peek(int $i): array
{
$offset = $this->tokens->key() + $i;
if (! $this->tokens->offsetExists($offset)) {
return [Token::EOF, null];
}

$val = trim($this->tokens->offsetGet($offset)->text);

return [$this->getToken($val), $val];
}

private function getToken(string $val): Token
{
return match ($val) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space before opening parenthesis of function call prohibited

chr(26) => Token::EOF,
'(' => Token::LPAREN,
')' => Token::RPAREN,
'|' => Token::OR,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected or but found OR

'&' => Token::AND,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected and but found AND

default => Token::IDENTIFIER

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line indented incorrectly; expected 8 spaces, found 12

};
}
}
21 changes: 21 additions & 0 deletions src/Aop/Pointcut/DNF/Parser/TokenCollectionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of line character is invalid; expected \n but found \r\n


namespace Go\Aop\Pointcut\DNF\Parser;

interface TokenCollectionInterface
{
/**
* @throws \Exception
*/
public function expect(Token $token): void;

/**
* @return array{0: Token, 1: string|null}
*/
public function next(): array;

/**
* @return array{0: Token, 1: string|null}
*/
public function peek(int $i): array;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected 1 newline at end of file; 0 found

113 changes: 113 additions & 0 deletions src/Aop/Pointcut/DNF/Parser/TokenizerParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of line character is invalid; expected \n but found \r\n


namespace Go\Aop\Pointcut\DNF\Parser;

use Exception;
use Go\Aop\Pointcut\DNF\AST\Node;
use Go\Aop\Pointcut\DNF\AST\NodeType;
use PhpToken;

class TokenizerParser implements TokenizerParserInterface
{
public function parse(string $input): Node
{
return $this->parseExpression($this->tokenize($input));
}

private function tokenize(string $input): TokenCollection
{
$input = sprintf('<?php %s%s', $input, chr(/* EOF */26));
$tokens = new \ArrayIterator(PhpToken::tokenize($input));
$arrayIntersect = array_intersect(
(array)$tokens,
['string', 'bool', 'array', 'float', 'int', 'integer', 'null', 'resource']
);
if ($arrayIntersect !== []) {
throw new Exception(sprintf('Tokens [%s] not allowed', implode(', ', $arrayIntersect)));
}

$tokens = new TokenCollection($tokens);
//skip '<?php'
$tokens->next();

return $tokens;
}

private function parseSubExpression(
TokenCollection $tokens,
int $bindingPower,
bool $insideParenthesis = false
): ?Node {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There must be a single space between the closing parenthesis and the opening brace of a multi-line function declaration; found 0 spaces

[$token, $val] = $tokens->next();
switch ($token) {
case Token::LPAREN:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CASE statements must be defined using a colon

$left = $this->parseSubexpression($tokens, 0, true);
$tokens->expect(Token::RPAREN);
break;
case Token::IDENTIFIER:
$left = new Node(NodeType::IDENTIFIER, $val);
break;
default:
throw new Exception('Bad Token');
}

while (true) {
[$token] = $tokens->peek(0);

if ($token === Token::OR && $insideParenthesis) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected or but found OR

throw new \Exception('Only intersections allowed in the group');
}

switch ($token) {
case Token::OR:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected or but found OR

case Token::AND:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected and but found AND

[$leftBP, $rightBP] = $this->getBindingPower($token);
if ($leftBP < $bindingPower) {
break 2;
}
$tokens->next();
$right = $this->parseSubexpression($tokens, $rightBP, $insideParenthesis);
$left = $this->operatorNode($token, $left, $right);
break;
case Token::RPAREN:
case Token::EOF:
default:
break 2;
}
}

return $left;
}

private function parseExpression(TokenCollection $tokens): ?Node
{
$sub = $this->parseSubExpression($tokens, 0);
$tokens->expect(Token::EOF);

return $sub;
}

private function operatorNode(Token $type, Node $left, ?Node $right): Node
{
return match ($type) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space before opening parenthesis of function call prohibited

Token::OR => new Node(NodeType::OR, left: $left, right: $right),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected or but found OR

Token::AND => new Node(NodeType::AND, left: $left, right: $right),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected and but found AND

default => throw new Exception('invalid op')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line indented incorrectly; expected 8 spaces, found 12

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line indented incorrectly; expected 8 spaces, found 12

};
}

/**
* @param Token $type
*
* @return array{0: int, 1: int}
*/
private function getBindingPower(Token $type): array
{
return match ($type) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space before opening parenthesis of function call prohibited

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space before opening parenthesis of function call prohibited

Token::OR => [1, 2],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected or but found OR

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected or but found OR

Token::AND => [3, 4],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected and but found AND

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected and but found AND

default => throw new Exception('Invalid operator')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line indented incorrectly; expected 8 spaces, found 12

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line indented incorrectly; expected 8 spaces, found 12

};
}

}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Expected 1 newline at end of file; 0 found
  • The closing brace for the class must go on the next line after the body

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Expected 1 newline at end of file; 0 found
  • The closing brace for the class must go on the next line after the body

8 changes: 8 additions & 0 deletions src/Aop/Pointcut/DNF/Parser/TokenizerParserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of line character is invalid; expected \n but found \r\n

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of line character is invalid; expected \n but found \r\n


namespace Go\Aop\Pointcut\DNF\Parser;

interface TokenizerParserInterface
{
public function parse(string $input);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected 1 newline at end of file; 0 found

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected 1 newline at end of file; 0 found

44 changes: 44 additions & 0 deletions src/Aop/Pointcut/DNF/SemanticAnalyzer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of line character is invalid; expected \n but found \r\n

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of line character is invalid; expected \n but found \r\n


namespace Go\Aop\Pointcut\DNF;

use Go\Aop\Pointcut\DNF\AST\Node;
use Go\Aop\Pointcut\DNF\AST\NodeType;
use Go\ParserReflection\ReflectionFileNamespace;

class SemanticAnalyzer implements SemanticAnalyzerInterface
{
public function verifyTree(?Node $tree, \ReflectionClass|ReflectionFileNamespace $val)
{
if ($tree !== null && $tree->type === NodeType::IDENTIFIER && $tree->identifier !== null) {
$parentClasses = $this->getParentClasses($val);

return in_array($tree->identifier, $parentClasses, true)
|| $val->implementsInterface($tree->identifier);
}

if ($tree?->type === NodeType::AND) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected and but found AND

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected and but found AND

return $this->verifyTree($tree->left, $val) && $this->verifyTree($tree->right, $val);
}

if ($tree->type === NodeType::OR) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP keywords must be lowercase; expected or but found OR

return $this->verifyTree($tree->left, $val) || $this->verifyTree($tree->right, $val);
}
}

/**
* @param ReflectionFileNamespace|\ReflectionClass $val
*
* @return array
*/
public function getParentClasses(ReflectionFileNamespace|\ReflectionClass $val): array
{
$parentClasses = [];
while ($val->getParentClass()) {
$parentClasses[] = $val->getParentClass()->getName();
$val = $val->getParentClass();
}

return $parentClasses;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected 1 newline at end of file; 0 found

Loading