diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index b4000d6ada..9780b2c69a 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -304,35 +304,25 @@ private function createGroupType(TreeNode $group, bool $maybeConstant, string $p return TypeCombinator::union(...$types); } - $isNonEmpty = TrinaryLogic::createMaybe(); - $isNonFalsy = TrinaryLogic::createMaybe(); - $isNumeric = TrinaryLogic::createMaybe(); - $inOptionalQuantification = false; - $onlyLiterals = []; - - $this->walkGroupAst( + $walkResult = $this->walkGroupAst( $group, false, - $isNonEmpty, - $isNonFalsy, - $isNumeric, - $inOptionalQuantification, - $onlyLiterals, false, $patternModifiers, + RegexGroupWalkResult::createEmpty(), ); - if ($maybeConstant && $onlyLiterals !== null && $onlyLiterals !== []) { + if ($maybeConstant && $walkResult->getOnlyLiterals() !== null && $walkResult->getOnlyLiterals() !== []) { $result = []; - foreach ($onlyLiterals as $literal) { + foreach ($walkResult->getOnlyLiterals() as $literal) { $result[] = new ConstantStringType($literal); } return TypeCombinator::union(...$result); } - if ($isNumeric->yes()) { - if ($isNonFalsy->yes()) { + if ($walkResult->isNumeric()->yes()) { + if ($walkResult->isNonFalsy()->yes()) { return new IntersectionType([ new StringType(), new AccessoryNumericStringType(), @@ -341,13 +331,13 @@ private function createGroupType(TreeNode $group, bool $maybeConstant, string $p } $result = new IntersectionType([new StringType(), new AccessoryNumericStringType()]); - if (!$isNonEmpty->yes()) { + if (!$walkResult->isNonEmpty()->yes()) { return TypeCombinator::union(new ConstantStringType(''), $result); } return $result; - } elseif ($isNonFalsy->yes()) { + } elseif ($walkResult->isNonFalsy()->yes()) { return new IntersectionType([new StringType(), new AccessoryNonFalsyStringType()]); - } elseif ($isNonEmpty->yes()) { + } elseif ($walkResult->isNonEmpty()->yes()) { return new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]); } @@ -376,20 +366,13 @@ private function getRootAlternation(TreeNode $group): ?TreeNode return null; } - /** - * @param array|null $onlyLiterals - */ private function walkGroupAst( TreeNode $ast, bool $inAlternation, - TrinaryLogic &$isNonEmpty, - TrinaryLogic &$isNonFalsy, - TrinaryLogic &$isNumeric, - bool &$inOptionalQuantification, - ?array &$onlyLiterals, bool $inClass, string $patternModifiers, - ): void + RegexGroupWalkResult $walkResult, + ): RegexGroupWalkResult { $children = $ast->getChildren(); @@ -411,61 +394,65 @@ private function walkGroupAst( } // a single token non-falsy on its own - $isNonFalsy = TrinaryLogic::createYes(); + $walkResult = $walkResult->nonFalsy(TrinaryLogic::createYes()); break; } if ($meaningfulTokens > 0) { - $isNonEmpty = TrinaryLogic::createYes(); + $walkResult = $walkResult->nonEmpty(TrinaryLogic::createYes()); // two non-empty tokens concatenated results in a non-falsy string if ($meaningfulTokens > 1 && !$inAlternation) { - $isNonFalsy = TrinaryLogic::createYes(); + $walkResult = $walkResult->nonFalsy(TrinaryLogic::createYes()); } } } elseif ($ast->getId() === '#quantification') { [$min] = $this->getQuantificationRange($ast); if ($min === 0) { - $inOptionalQuantification = true; + $walkResult = $walkResult->inOptionalQuantification(true); } if ($min >= 1) { - $isNonEmpty = TrinaryLogic::createYes(); - $inOptionalQuantification = false; + $walkResult = $walkResult + ->nonEmpty(TrinaryLogic::createYes()) + ->inOptionalQuantification(false); } if ($min >= 2 && !$inAlternation) { - $isNonFalsy = TrinaryLogic::createYes(); + $walkResult = $walkResult->nonFalsy(TrinaryLogic::createYes()); } - $onlyLiterals = null; - } elseif ($ast->getId() === '#class' && $onlyLiterals !== null) { + $walkResult = $walkResult->onlyLiterals(null); + } elseif ($ast->getId() === '#class' && $walkResult->getOnlyLiterals() !== null) { $inClass = true; $newLiterals = []; foreach ($children as $child) { - $oldLiterals = $onlyLiterals; + $oldLiterals = $walkResult->getOnlyLiterals(); $this->getLiteralValue($child, $oldLiterals, true, $patternModifiers, true); foreach ($oldLiterals ?? [] as $oldLiteral) { $newLiterals[] = $oldLiteral; } } - $onlyLiterals = $newLiterals; + $walkResult = $walkResult->onlyLiterals($newLiterals); } elseif ($ast->getId() === 'token') { + $onlyLiterals = $walkResult->getOnlyLiterals(); $literalValue = $this->getLiteralValue($ast, $onlyLiterals, !$inClass, $patternModifiers, false); + $walkResult = $walkResult->onlyLiterals($onlyLiterals); + if ($literalValue !== null) { if (Strings::match($literalValue, '/^\d+$/') === null) { - $isNumeric = TrinaryLogic::createNo(); - } elseif ($isNumeric->maybe()) { - $isNumeric = TrinaryLogic::createYes(); + $walkResult = $walkResult->numeric(TrinaryLogic::createNo()); + } elseif ($walkResult->isNumeric()->maybe()) { + $walkResult = $walkResult->numeric(TrinaryLogic::createYes()); } - if (!$inOptionalQuantification && $literalValue !== '') { - $isNonEmpty = TrinaryLogic::createYes(); + if (!$walkResult->isInOptionalQuantification() && $literalValue !== '') { + $walkResult = $walkResult->nonEmpty(TrinaryLogic::createYes()); } } } elseif (!in_array($ast->getId(), ['#capturing', '#namedcapturing', '#alternation'], true)) { - $onlyLiterals = null; + $walkResult = $walkResult->onlyLiterals(null); } if ($ast->getId() === '#alternation') { @@ -476,22 +463,20 @@ private function walkGroupAst( // doable but really silly compared to just \d so we can safely assume the string is not numeric // for negative classes if ($ast->getId() === '#negativeclass') { - $isNumeric = TrinaryLogic::createNo(); + $walkResult = $walkResult->numeric(TrinaryLogic::createNo()); } foreach ($children as $child) { - $this->walkGroupAst( + $walkResult = $this->walkGroupAst( $child, $inAlternation, - $isNonEmpty, - $isNonFalsy, - $isNumeric, - $inOptionalQuantification, - $onlyLiterals, $inClass, $patternModifiers, + $walkResult, ); } + + return $walkResult; } private function isMaybeEmptyNode(TreeNode $node, string $patternModifiers, bool &$isNonFalsy): bool diff --git a/src/Type/Regex/RegexGroupWalkResult.php b/src/Type/Regex/RegexGroupWalkResult.php new file mode 100644 index 0000000000..65e7fd1691 --- /dev/null +++ b/src/Type/Regex/RegexGroupWalkResult.php @@ -0,0 +1,121 @@ +|null $onlyLiterals + */ + public function __construct( + private bool $inOptionalQuantification, + private ?array $onlyLiterals, + private TrinaryLogic $isNonEmpty, + private TrinaryLogic $isNonFalsy, + private TrinaryLogic $isNumeric, + ) + { + } + + public static function createEmpty(): self + { + return new self( + false, + [], + TrinaryLogic::createMaybe(), + TrinaryLogic::createMaybe(), + TrinaryLogic::createMaybe(), + ); + } + + public function inOptionalQuantification(bool $inOptionalQuantification): self + { + return new self( + $inOptionalQuantification, + $this->onlyLiterals, + $this->isNonEmpty, + $this->isNonFalsy, + $this->isNumeric, + ); + } + + /** + * @param array|null $onlyLiterals + */ + public function onlyLiterals(?array $onlyLiterals): self + { + return new self( + $this->inOptionalQuantification, + $onlyLiterals, + $this->isNonEmpty, + $this->isNonFalsy, + $this->isNumeric, + ); + } + + public function nonEmpty(TrinaryLogic $nonEmpty): self + { + return new self( + $this->inOptionalQuantification, + $this->onlyLiterals, + $nonEmpty, + $this->isNonFalsy, + $this->isNumeric, + ); + } + + public function nonFalsy(TrinaryLogic $nonFalsy): self + { + return new self( + $this->inOptionalQuantification, + $this->onlyLiterals, + $this->isNonEmpty, + $nonFalsy, + $this->isNumeric, + ); + } + + public function numeric(TrinaryLogic $numeric): self + { + return new self( + $this->inOptionalQuantification, + $this->onlyLiterals, + $this->isNonEmpty, + $this->isNonFalsy, + $numeric, + ); + } + + public function isInOptionalQuantification(): bool + { + return $this->inOptionalQuantification; + } + + /** + * @return array|null + */ + public function getOnlyLiterals(): ?array + { + return $this->onlyLiterals; + } + + public function isNonEmpty(): TrinaryLogic + { + return $this->isNonEmpty; + } + + public function isNonFalsy(): TrinaryLogic + { + return $this->isNonFalsy; + } + + public function isNumeric(): TrinaryLogic + { + return $this->isNumeric; + } + +}