From ffe0f9d2a9ddfdc9a4aa7a65c8777f9b302a25fe Mon Sep 17 00:00:00 2001 From: Kelly Norton Date: Tue, 22 Oct 2024 10:08:42 -0400 Subject: [PATCH] Added DotRenderer for debugging of AutomataMatcher --- bin/render-state-machine | 21 +++++++++++++++ src/AutomataMatcher.php | 30 +++++++++++++++++++-- src/AutomataMatcher/DotRenderer.php | 41 +++++++++++++++++++++++++++++ src/AutomataMatcher/State.php | 35 +++++++++++++++++++++++- 4 files changed, 124 insertions(+), 3 deletions(-) create mode 100755 bin/render-state-machine create mode 100644 src/AutomataMatcher/DotRenderer.php diff --git a/bin/render-state-machine b/bin/render-state-machine new file mode 100755 index 0000000..ab398dd --- /dev/null +++ b/bin/render-state-machine @@ -0,0 +1,21 @@ +#!/usr/bin/env php +\n", $argv[0]); + exit(1); +} + +$matcher = AutomataMatcher::build( + Owners::fromFile($argv[1])->getRules() +); + +printf("%s\n", DotRenderer::render($matcher)); \ No newline at end of file diff --git a/src/AutomataMatcher.php b/src/AutomataMatcher.php index 3d185f0..66a7066 100644 --- a/src/AutomataMatcher.php +++ b/src/AutomataMatcher.php @@ -51,8 +51,6 @@ private static function parsePattern(Pattern $pattern): array $tokens = []; $pattern = $pattern->toString(); - $original = $pattern; - if (!str_starts_with($pattern, '/')) { $pattern = '**/' . $pattern; } @@ -131,4 +129,32 @@ public function asJson(): array { return $this->start->jsonSerialize(); } + + /** + * @return array{nodes: array, edges: array{from: string, to: string, label: string}[]} + */ + public function getDebugInfo(): array + { + $patterns = []; + foreach ($this->rules as $rule) { + foreach (self::parsePattern($rule->getPattern()) as $token) { + $patterns[$token->getRegex()] = $token->getPattern(); + } + } + + $nodes = []; + $edges = []; + $this->start->getDebugInfo($nodes, $edges); + $edges = array_map( + function (array $edge) use ($patterns): array { + ['from' => $from, 'to' => $to, 'label' => $label] = $edge; + if ($label !== '**') { + $label = $patterns[$label] ?? '??'; + } + return ['from' => $from, 'to' => $to, 'label' => $label]; + }, + $edges + ); + return ['nodes' => $nodes, 'edges' => $edges]; + } } \ No newline at end of file diff --git a/src/AutomataMatcher/DotRenderer.php b/src/AutomataMatcher/DotRenderer.php new file mode 100644 index 0000000..9570810 --- /dev/null +++ b/src/AutomataMatcher/DotRenderer.php @@ -0,0 +1,41 @@ + $nodes, 'edges' => $edges] = $matcher->getDebugInfo(); + + $start = array_key_first($nodes); + + $lines = [ + 'node [shape = circle; label = "";];', + "{$start} [peripheries = 2;];", + ]; + foreach ($nodes as $id => $priority) { + $label = $priority === -1 ? '' : $priority; + $lines[] = "{$id} [label=\"{$label}\";];"; + } + + foreach ($edges as ['from' => $from, 'to' => $to, 'label' => $label]) { + $lines[] = "{$from} -> {$to} [label=\"{$label}\"];"; + } + + return implode( + PHP_EOL, + [ + 'digraph G {', + ...$lines, + '}', + ] + ); + } +} \ No newline at end of file diff --git a/src/AutomataMatcher/State.php b/src/AutomataMatcher/State.php index c95339c..0c9c8a7 100644 --- a/src/AutomataMatcher/State.php +++ b/src/AutomataMatcher/State.php @@ -2,11 +2,14 @@ namespace Kellegous\CodeOwners\AutomataMatcher; -use http\Exception\InvalidArgumentException; +use InvalidArgumentException; use JsonSerializable; final class State implements JsonSerializable { + /** + * @var int + */ private int $priority = -1; /** @@ -19,6 +22,9 @@ final class State implements JsonSerializable */ private bool $isRecursive; + /** + * @param bool $isRecursive + */ public function __construct(bool $isRecursive = false) { $this->isRecursive = $isRecursive; @@ -99,4 +105,31 @@ public function jsonSerialize(): array '**' => $this->isRecursive, ]; } + + /** + * @param array $nodes + * @param array{from:int, to: int, label:string}[] $edges + * @return void + */ + public function getDebugInfo( + array &$nodes, + array &$edges + ): void { + $nodes[spl_object_id($this)] = $this->priority; + foreach ($this->edges as $regex => $state) { + $edges[] = [ + 'from' => spl_object_id($this), + 'to' => spl_object_id($state), + 'label' => $regex, + ]; + $state->getDebugInfo($nodes, $edges); + } + if ($this->isRecursive) { + $edges[] = [ + 'from' => spl_object_id($this), + 'to' => spl_object_id($this), + 'label' => '**', + ]; + } + } } \ No newline at end of file