Skip to content

Commit aa97b61

Browse files
committed
Refactors regex highlighting for different outputs
Introduces an abstract `HighlighterVisitor` to centralize common logic for regex syntax highlighting. This commit streamlines the highlighting process by providing a base class for output-specific formatters (e.g., CLI and HTML). Removes duplicate code and simplifies the creation of new highlighters. Adds a generic `highlight()` method to automatically detect and apply the appropriate highlighter based on the environment (CLI or HTML).
1 parent 6d97ed7 commit aa97b61

File tree

5 files changed

+392
-278
lines changed

5 files changed

+392
-278
lines changed

src/NodeVisitor/ConsoleHighlighterVisitor.php

Lines changed: 15 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -17,44 +17,20 @@
1717

1818
/**
1919
* Highlights regex syntax for console output using ANSI escape codes.
20-
*
21-
* @extends AbstractNodeVisitor<string>
2220
*/
23-
final class ConsoleHighlighterVisitor extends AbstractNodeVisitor
21+
final class ConsoleHighlighterVisitor extends HighlighterVisitor
2422
{
25-
private const RESET = "\033[0m";
23+
private const string RESET = "\033[0m";
2624

27-
private const COLORS = [
25+
private const array COLORS = [
2826
'meta' => "\033[1;34m", // Bold Blue
2927
'quantifier' => "\033[1;33m", // Bold Yellow
3028
'type' => "\033[0;32m", // Green
3129
'anchor' => "\033[0;35m", // Magenta
3230
'literal' => '', // Default
3331
];
3432

35-
public function visitRegex(Node\RegexNode $node): string
36-
{
37-
return $node->pattern->accept($this);
38-
}
39-
40-
public function visitAlternation(Node\AlternationNode $node): string
41-
{
42-
$parts = [];
43-
foreach ($node->alternatives as $alt) {
44-
$parts[] = $alt->accept($this);
45-
}
46-
return implode(self::COLORS['meta'] . '|' . self::RESET, $parts);
47-
}
48-
49-
public function visitSequence(Node\SequenceNode $node): string
50-
{
51-
$parts = [];
52-
foreach ($node->children as $child) {
53-
$parts[] = $child->accept($this);
54-
}
55-
return implode('', $parts);
56-
}
57-
33+
#[\Override]
5834
public function visitGroup(Node\GroupNode $node): string
5935
{
6036
$inner = $node->child->accept($this);
@@ -68,174 +44,30 @@ public function visitGroup(Node\GroupNode $node): string
6844
Node\GroupType::T_GROUP_NAMED => "?<{$node->name}>",
6945
default => '',
7046
};
71-
$opening = self::COLORS['meta'] . '(' . $prefix . self::RESET;
72-
$closing = self::COLORS['meta'] . ')' . self::RESET;
73-
return $opening . $inner . $closing;
74-
}
75-
76-
public function visitQuantifier(Node\QuantifierNode $node): string
77-
{
78-
$inner = $node->node->accept($this);
79-
$quant = $node->quantifier;
80-
if ($node->type === Node\QuantifierType::T_LAZY) {
81-
$quant .= '?';
82-
} elseif ($node->type === Node\QuantifierType::T_POSSESSIVE) {
83-
$quant .= '+';
84-
}
85-
return $inner . self::COLORS['quantifier'] . $quant . self::RESET;
86-
}
87-
88-
public function visitLiteral(Node\LiteralNode $node): string
89-
{
90-
return self::COLORS['literal'] . $node->value . self::RESET;
91-
}
92-
93-
public function visitCharType(Node\CharTypeNode $node): string
94-
{
95-
return self::COLORS['type'] . '\\' . $node->value . self::RESET;
96-
}
97-
98-
public function visitDot(Node\DotNode $node): string
99-
{
100-
return self::COLORS['meta'] . '.' . self::RESET;
101-
}
102-
103-
public function visitAnchor(Node\AnchorNode $node): string
104-
{
105-
return self::COLORS['anchor'] . $node->value . self::RESET;
106-
}
107-
108-
public function visitAssertion(Node\AssertionNode $node): string
109-
{
110-
return self::COLORS['type'] . '\\' . $node->value . self::RESET;
111-
}
112-
113-
public function visitCharClass(Node\CharClassNode $node): string
114-
{
115-
$parts = $node->expression instanceof Node\AlternationNode
116-
? $node->expression->alternatives
117-
: [$node->expression];
118-
$inner = '';
119-
foreach ($parts as $part) {
120-
$inner .= $part->accept($this);
121-
}
122-
$neg = $node->isNegated ? '^' : '';
123-
return self::COLORS['meta'] . '[' . $neg . self::RESET . $inner . self::COLORS['meta'] . ']' . self::RESET;
124-
}
125-
126-
public function visitRange(Node\RangeNode $node): string
127-
{
128-
$start = $node->start->accept($this);
129-
$end = $node->end->accept($this);
130-
return $start . self::COLORS['meta'] . '-' . self::RESET . $end;
131-
}
132-
133-
// Implement other visit methods similarly, defaulting to literal or meta
134-
public function visitBackref(Node\BackrefNode $node): string
135-
{
136-
return self::COLORS['type'] . '\\' . $node->ref . self::RESET;
137-
}
138-
139-
public function visitUnicode(Node\UnicodeNode $node): string
140-
{
141-
return self::COLORS['type'] . '\\x' . $node->code . self::RESET;
142-
}
143-
144-
public function visitUnicodeNamed(Node\UnicodeNamedNode $node): string
145-
{
146-
return self::COLORS['type'] . '\\N{' . $node->name . '}' . self::RESET;
147-
}
148-
149-
public function visitUnicodeProp(Node\UnicodePropNode $node): string
150-
{
151-
$prop = $node->prop;
152-
if (strlen($prop) > 1 || str_starts_with($prop, '^')) {
153-
$prop = '{' . $prop . '}';
154-
}
155-
return self::COLORS['type'] . '\\p' . $prop . self::RESET;
156-
}
157-
158-
public function visitOctal(Node\OctalNode $node): string
159-
{
160-
return self::COLORS['type'] . '\\o{' . $node->code . '}' . self::RESET;
161-
}
47+
$opening = self::COLORS['meta'].'('.$prefix.self::RESET;
48+
$closing = self::COLORS['meta'].')'.self::RESET;
16249

163-
public function visitOctalLegacy(Node\OctalLegacyNode $node): string
164-
{
165-
return self::COLORS['type'] . '\\' . $node->code . self::RESET;
166-
}
167-
168-
public function visitPosixClass(Node\PosixClassNode $node): string
169-
{
170-
return self::COLORS['type'] . '[:' . $node->class . ':]' . self::RESET;
171-
}
172-
173-
public function visitComment(Node\CommentNode $node): string
174-
{
175-
return self::COLORS['meta'] . '(?#...' . ')' . self::RESET;
50+
return $opening.$inner.$closing;
17651
}
17752

53+
#[\Override]
17854
public function visitConditional(Node\ConditionalNode $node): string
17955
{
18056
$condition = $node->condition->accept($this);
18157
$yes = $node->yes->accept($this);
18258
$no = $node->no->accept($this);
183-
$noPart = $no ? self::COLORS['meta'] . '|' . self::RESET . $no : '';
184-
return self::COLORS['meta'] . '(?(' . self::RESET . $condition . self::COLORS['meta'] . ')' . self::RESET . $yes . $noPart . self::COLORS['meta'] . ')' . self::RESET;
185-
}
186-
187-
public function visitSubroutine(Node\SubroutineNode $node): string
188-
{
189-
return self::COLORS['type'] . '(?' . $node->reference . ')' . self::RESET;
190-
}
191-
192-
public function visitPcreVerb(Node\PcreVerbNode $node): string
193-
{
194-
return self::COLORS['meta'] . '(*' . $node->verb . ')' . self::RESET;
195-
}
196-
197-
public function visitDefine(Node\DefineNode $node): string
198-
{
199-
$inner = $node->content->accept($this);
200-
return self::COLORS['meta'] . '(?(DEFINE)' . self::RESET . $inner . self::COLORS['meta'] . ')' . self::RESET;
201-
}
202-
203-
public function visitLimitMatch(Node\LimitMatchNode $node): string
204-
{
205-
return self::COLORS['meta'] . '(*LIMIT_MATCH=' . $node->limit . ')' . self::RESET;
206-
}
59+
$noPart = $no ? self::COLORS['meta'].'|'.self::RESET.$no : '';
20760

208-
public function visitCallout(Node\CalloutNode $node): string
209-
{
210-
$content = $node->isStringIdentifier ? '"' . (string)$node->identifier . '"' : (string)$node->identifier;
211-
return self::COLORS['meta'] . '(?C' . $content . ')' . self::RESET;
212-
}
213-
214-
public function visitScriptRun(Node\ScriptRunNode $node): string
215-
{
216-
return self::COLORS['meta'] . '(*script_run:' . $node->script . ')' . self::RESET;
217-
}
218-
219-
public function visitVersionCondition(Node\VersionConditionNode $node): string
220-
{
221-
return self::COLORS['meta'] . '(?(VERSION>=' . $node->version . ')' . self::RESET;
222-
}
223-
224-
public function visitKeep(Node\KeepNode $node): string
225-
{
226-
return self::COLORS['type'] . '\\K' . self::RESET;
61+
return self::COLORS['meta'].'(?('.self::RESET.$condition.self::COLORS['meta'].')'.self::RESET.$yes.$noPart.self::COLORS['meta'].')'.self::RESET;
22762
}
22863

229-
public function visitControlChar(Node\ControlCharNode $node): string
64+
protected function wrap(string $content, string $type): string
23065
{
231-
return self::COLORS['type'] . '\\c' . $node->char . self::RESET;
66+
return self::COLORS[$type].$content.self::RESET;
23267
}
23368

234-
public function visitClassOperation(Node\ClassOperationNode $node): string
69+
protected function escape(string $string): string
23570
{
236-
$left = $node->left->accept($this);
237-
$right = $node->right->accept($this);
238-
$op = $node->type === Node\ClassOperationType::INTERSECTION ? '&&' : '--';
239-
return self::COLORS['meta'] . '[' . self::RESET . $left . self::COLORS['meta'] . $op . self::RESET . $right . self::COLORS['meta'] . ']' . self::RESET;
71+
return $string;
24072
}
241-
}
73+
}

0 commit comments

Comments
 (0)