line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Regexp::Debugger; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
70867
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
34
|
|
4
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
45
|
|
5
|
1
|
|
|
1
|
|
7
|
eval "use feature 'evalbytes'"; # Experimental fix for Perl 5.16 |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
53
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.002006'; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
# Handle Perl 5.18's new-found caution... |
10
|
1
|
|
|
1
|
|
693
|
no if $] >= 5.018, warnings => "experimental::smartmatch"; |
|
1
|
|
|
|
|
14
|
|
|
1
|
|
|
|
|
5
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
# Give an accurate warning if used with an antique Perl... |
13
|
|
|
|
|
|
|
BEGIN { |
14
|
1
|
50
|
|
1
|
|
128
|
if ($] < 5.010001) { |
15
|
0
|
|
|
|
|
0
|
die sprintf "Regexp::Debugger requires Perl v5.10.1 or later (at %s line %s)\n", |
16
|
|
|
|
|
|
|
(caller 2)[1..2]; |
17
|
|
|
|
|
|
|
} |
18
|
|
|
|
|
|
|
} |
19
|
|
|
|
|
|
|
|
20
|
1
|
|
|
1
|
|
24
|
use 5.010001; # ...activate all the tasty 5.10 goodies |
|
1
|
|
|
|
|
3
|
|
21
|
|
|
|
|
|
|
|
22
|
1
|
|
|
1
|
|
4
|
use List::Util qw< min max first sum >; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
254
|
|
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
# Track configurable options lexically... |
25
|
|
|
|
|
|
|
my @config; |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
# Track debugging history in various formats... |
28
|
|
|
|
|
|
|
my %history_of; |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# Persistent information within debugger... |
31
|
|
|
|
|
|
|
my $prev_regex_pos; # ...track where we were previously in the regex |
32
|
|
|
|
|
|
|
my $start_str_pos; # ...track where we started matching in the string |
33
|
|
|
|
|
|
|
my $prev_str_pos; # ...track where we were previously in the string |
34
|
|
|
|
|
|
|
my $prev_match_was_null; # ...under /g was previous match a null match? |
35
|
|
|
|
|
|
|
my %capture; # ...track capture groups within regex |
36
|
|
|
|
|
|
|
my @pre_is_pending; # ...did we try something last event? |
37
|
|
|
|
|
|
|
my $interaction_quit; # ...did we get a quit request? |
38
|
|
|
|
|
|
|
my $interaction_mode; # ...step-by-step, jump to match, or continue? |
39
|
|
|
|
|
|
|
my $interaction_depth; # ...depth at which this interaction was initiated |
40
|
|
|
|
|
|
|
my $display_mode; # ...how is the match being visualized at present? |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# Bounds on speed of displaying states... |
43
|
|
|
|
|
|
|
my $MIN_SKIP_DURATION = 0.001; # ...1/1000 second |
44
|
|
|
|
|
|
|
my $MAX_SKIP_DURATION = 0.2; # ...2/10 second |
45
|
|
|
|
|
|
|
my $SKIP_ACCELERATION = 0.98; # ...increase by 2% each step |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
# Colours for heatmaps... |
48
|
|
|
|
|
|
|
my @DEF_HEAT_COLOUR = ( |
49
|
|
|
|
|
|
|
'white on_black', # 0-20 percentile |
50
|
|
|
|
|
|
|
'cyan on_blue', # 20-40 percentile |
51
|
|
|
|
|
|
|
'blue on_cyan', # 40-60 percentile |
52
|
|
|
|
|
|
|
'red on_yellow', # 60-80 percentile |
53
|
|
|
|
|
|
|
'yellow on_red', # 80-100 percentile |
54
|
|
|
|
|
|
|
); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# Colours for detailed regex descriptions... |
57
|
|
|
|
|
|
|
my %DESCRIPTION_COLOUR = ( |
58
|
|
|
|
|
|
|
desc_sep_col => 'blue on_black underline', |
59
|
|
|
|
|
|
|
desc_regex_col => 'white on_black', |
60
|
|
|
|
|
|
|
desc_text_col => 'cyan on_black', |
61
|
|
|
|
|
|
|
); |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# Colour for error messages... |
64
|
|
|
|
|
|
|
my $ERR_COL = 'red'; |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
# Default config which any explicit config modifies... |
68
|
|
|
|
|
|
|
my @SHOW_WS_OPTIONS = qw< compact visible original >; |
69
|
|
|
|
|
|
|
my %DEFAULT_CONFIG = ( |
70
|
|
|
|
|
|
|
# How debugging info is displayed initially... |
71
|
|
|
|
|
|
|
display_mode => 'visual', |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
# Colour scheme for debugging info... |
74
|
|
|
|
|
|
|
info_col => ' white on_black', |
75
|
|
|
|
|
|
|
try_col => 'bold magenta on_black', |
76
|
|
|
|
|
|
|
match_col => ' bold cyan on_black', |
77
|
|
|
|
|
|
|
fail_col => ' yellow on_red', |
78
|
|
|
|
|
|
|
ws_col => ' bold blue underline', |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# Colour scheme for regex descriptions... |
81
|
|
|
|
|
|
|
%DESCRIPTION_COLOUR, |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# Where debugging info is written to (undef --> STDOUT)... |
84
|
|
|
|
|
|
|
save_to_fh => undef, |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
# How whitespace is managed... |
87
|
|
|
|
|
|
|
show_ws => $SHOW_WS_OPTIONS[0], |
88
|
|
|
|
|
|
|
); |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
# The current config... |
91
|
|
|
|
|
|
|
my $lexical_config = \%DEFAULT_CONFIG; |
92
|
|
|
|
|
|
|
# Simulate print() and say() on appropriate filehandle... |
93
|
|
|
|
|
|
|
sub _print { |
94
|
0
|
0
|
|
0
|
|
0
|
if (!$lexical_config->{save_to_fh}) { |
95
|
1
|
|
|
1
|
|
15
|
no warnings 'utf8'; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
103
|
|
96
|
0
|
0
|
|
|
|
0
|
print map { defined($_) ? $_ : '' } @_; |
|
0
|
|
|
|
|
0
|
|
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub _say { |
101
|
0
|
0
|
|
0
|
|
0
|
if (!$lexical_config->{save_to_fh}) { |
102
|
1
|
|
|
1
|
|
7
|
no warnings 'utf8'; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
202
|
|
103
|
0
|
0
|
|
|
|
0
|
say map { defined($_) ? $_ : '' } @_; |
|
0
|
|
|
|
|
0
|
|
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
# How should matches be indicated??? |
108
|
|
|
|
|
|
|
my $MATCH_DRAG = ' '; |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# Will heatmaps be visible??? |
111
|
|
|
|
|
|
|
my $heatmaps_invisible; |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# Indent unit for hierarchical display... |
114
|
|
|
|
|
|
|
my $INDENT = q{ }; |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Simulate Term::ANSIColor badly (if necessary)... |
117
|
|
|
|
|
|
|
CHECK { |
118
|
|
|
|
|
|
|
my $can_color |
119
|
|
|
|
|
|
|
= ( $^O ne 'MSWin32' or eval { require Win32::Console::ANSI } ) |
120
|
1
|
|
33
|
1
|
|
1159
|
&& eval { require Term::ANSIColor }; |
121
|
|
|
|
|
|
|
|
122
|
1
|
50
|
|
|
|
8968
|
if ( !$can_color ) { |
123
|
0
|
|
|
|
|
0
|
*Term::ANSIColor::colored = sub { return shift }; |
|
0
|
|
|
|
|
0
|
|
124
|
0
|
|
|
|
|
0
|
$MATCH_DRAG = '_'; |
125
|
0
|
|
|
|
|
0
|
$heatmaps_invisible = 1; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# Load the module... |
130
|
|
|
|
|
|
|
sub import { |
131
|
1
|
|
|
1
|
|
7
|
use Carp; |
|
1
|
|
|
|
|
9
|
|
|
1
|
|
|
|
|
159
|
|
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# Don't need the module name... |
134
|
1
|
|
|
1
|
|
14
|
shift; |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
# Export re 'eval' semantics... |
137
|
1
|
|
|
|
|
3
|
$^H |= 0x00200000; |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
# Unpack the arguments... |
140
|
1
|
50
|
|
|
|
5
|
if (@_ % 2) { |
141
|
0
|
|
|
|
|
0
|
croak 'Odd number of configuration args after "use Regexp::Debugger"'; |
142
|
|
|
|
|
|
|
} |
143
|
1
|
|
|
|
|
3
|
my %arg = @_; |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# Creat a new lexically scoped config and remember its index... |
146
|
1
|
|
|
|
|
7
|
push @config, { %DEFAULT_CONFIG }; |
147
|
1
|
|
|
|
|
5
|
$^H{'Regexp::Debugger::lexical_scope'} = $#config; |
148
|
|
|
|
|
|
|
|
149
|
1
|
|
|
|
|
5
|
_load_config(\%arg); |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# Signal lexical scoping (active, unless something was exported)... |
152
|
1
|
|
|
|
|
5
|
$^H{'Regexp::Debugger::active'} = 1; |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
# Process any regexes in module's active lexical scope... |
155
|
1
|
|
|
1
|
|
1288
|
use overload; |
|
1
|
|
|
|
|
1001
|
|
|
1
|
|
|
|
|
12
|
|
156
|
|
|
|
|
|
|
overload::constant( |
157
|
|
|
|
|
|
|
qr => sub { |
158
|
0
|
|
|
0
|
|
0
|
my ($raw, $cooked, $type) = @_; |
159
|
|
|
|
|
|
|
|
160
|
0
|
|
0
|
|
|
0
|
my $hints = (caller 1)[10] // {}; |
161
|
0
|
|
|
|
|
0
|
my $lexical_scope = $hints->{'Regexp::Debugger::lexical_scope'}; |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
# In active scope and really a regex and interactivity possible... |
164
|
0
|
|
0
|
|
|
0
|
my $is_interactive = defined $arg{save_to} || -t *STDIN && -t *STDOUT; |
165
|
0
|
0
|
0
|
|
|
0
|
if (_module_is_active() && $type =~ /qq?/ && $is_interactive) { |
|
|
|
0
|
|
|
|
|
166
|
0
|
|
|
|
|
0
|
return bless {cooked=>$cooked, lexical_scope=>$lexical_scope}, 'Regexp::Debugger::Precursor'; |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
# Ignore everything else... |
169
|
|
|
|
|
|
|
else { |
170
|
0
|
|
|
|
|
0
|
return $cooked; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
} |
173
|
1
|
|
|
|
|
12
|
); |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
# Deactivate module's regex effect when it is "anti-imported" with 'no'... |
177
|
|
|
|
|
|
|
sub unimport { |
178
|
|
|
|
|
|
|
# Signal lexical (non-)scoping... |
179
|
0
|
|
|
0
|
|
0
|
$^H{'Regexp::Debugger::active'} = 0; |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# Encapsulate the hoopy user-defined pragma interface... |
183
|
|
|
|
|
|
|
sub _module_is_active { |
184
|
0
|
|
0
|
0
|
|
0
|
my $hints = (caller 1)[10] // return 0; |
185
|
0
|
|
|
|
|
0
|
return $hints->{'Regexp::Debugger::active'}; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
# Load ~/.rxrx config... |
189
|
|
|
|
|
|
|
sub _load_config { |
190
|
1
|
|
|
1
|
|
2
|
my $explicit_config_ref = shift(); |
191
|
1
|
|
|
|
|
1
|
my %config; |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
# Work out where to look... |
194
|
1
|
|
|
|
|
4
|
my $home_dir = $ENV{HOME}; |
195
|
1
|
50
|
33
|
|
|
3
|
if (!$home_dir && eval { require File::HomeDir } ) { |
|
0
|
|
|
|
|
0
|
|
196
|
0
|
|
|
|
|
0
|
$home_dir = File::HomeDir->my_home; |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
# Find config file... |
200
|
|
|
|
|
|
|
CONFIG_FILE: |
201
|
1
|
50
|
|
|
|
5
|
for my $config_file ( '.rxrx', ( $home_dir ? "$home_dir/.rxrx" : () ) ) { |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# Is this a readable config file??? |
204
|
2
|
50
|
|
|
|
73
|
open my $fh, '<', $config_file |
205
|
|
|
|
|
|
|
or next CONFIG_FILE; |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# Read and parse config file... |
208
|
|
|
|
|
|
|
CONFIG_LINE: |
209
|
0
|
|
|
|
|
0
|
for my $config_line (<$fh>) { |
210
|
0
|
0
|
|
|
|
0
|
if ($config_line =~ /^\s*(.*?)\s*[:=]\s*(.*?)\s*$/) { |
211
|
0
|
|
|
|
|
0
|
$config{$1} = $2; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
|
215
|
0
|
|
|
|
|
0
|
last CONFIG_FILE; |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# Make any explicit args override .rxrxrc config... |
219
|
1
|
|
|
|
|
5
|
%config = (display => 'visual', %config, %{$explicit_config_ref}); |
|
1
|
|
|
|
|
3
|
|
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
# Configure colour scheme for displays... |
222
|
1
|
|
|
|
|
13
|
for my $colour (grep /_col$/, keys %DEFAULT_CONFIG) { |
223
|
8
|
50
|
|
|
|
17
|
if (exists $config{$colour}) { |
224
|
0
|
|
|
|
|
0
|
$config[-1]{$colour} = $config{$colour} |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# Configure how whitespace is displayed... |
229
|
1
|
|
|
|
|
2
|
my $show_ws = $config{show_ws}; |
230
|
1
|
50
|
|
|
|
3
|
if (defined $show_ws) { |
231
|
0
|
0
|
|
|
|
0
|
if ($show_ws ~~ @SHOW_WS_OPTIONS) { |
232
|
0
|
|
|
|
|
0
|
$config[-1]{show_ws} = $show_ws; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
else { |
235
|
0
|
|
|
|
|
0
|
croak "Unknown 'show_ws' option: '$show_ws'"; |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# Configure heatmap colour scheme... |
240
|
|
|
|
|
|
|
my @heatmap_cols = |
241
|
0
|
|
|
|
|
0
|
map { $config{$_} } |
242
|
|
|
|
|
|
|
sort { |
243
|
|
|
|
|
|
|
# Sort numerically (if feasible), else alphabetically... |
244
|
0
|
0
|
|
|
|
0
|
my $a_key = $a =~ /(\d+)/ ? $1 : undef; |
245
|
0
|
0
|
|
|
|
0
|
my $b_key = $b =~ /(\d+)/ ? $1 : undef; |
246
|
0
|
0
|
0
|
|
|
0
|
defined $a_key && defined $b_key |
247
|
|
|
|
|
|
|
? $a_key <=> $b_key |
248
|
|
|
|
|
|
|
: $a cmp $b; |
249
|
|
|
|
|
|
|
} |
250
|
1
|
|
|
|
|
4
|
grep { /^heatmap/ } |
|
1
|
|
|
|
|
4
|
|
251
|
|
|
|
|
|
|
keys %config; |
252
|
|
|
|
|
|
|
|
253
|
1
|
50
|
|
|
|
5
|
if (!@heatmap_cols) { |
254
|
1
|
|
|
|
|
3
|
@heatmap_cols = @DEF_HEAT_COLOUR; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
|
257
|
1
|
|
|
|
|
3
|
$config[-1]{heatmap_col} = \@heatmap_cols; |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
# Configure initial display mode... |
261
|
1
|
|
|
|
|
2
|
my $display = $config{display}; |
262
|
1
|
50
|
|
|
|
3
|
if (defined $display) { |
263
|
|
|
|
|
|
|
$config[-1]{display_mode} |
264
|
1
|
0
|
|
|
|
9
|
= $display =~ m{^events}i ? 'events' |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
265
|
|
|
|
|
|
|
: $display =~ m{^heatmap}i ? 'heatmap' |
266
|
|
|
|
|
|
|
: $display =~ m{^visual}i ? 'visual' |
267
|
|
|
|
|
|
|
: $display =~ m{^JSON}i ? 'JSON' |
268
|
|
|
|
|
|
|
: croak "Unknown 'display' option: '$display'"; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# Configure destination of debugging info... |
272
|
1
|
|
|
|
|
3
|
my $save_to = $config{save_to}; |
273
|
1
|
50
|
|
|
|
4
|
if (defined $save_to) { |
274
|
1
|
|
|
1
|
|
817
|
use Scalar::Util qw< openhandle >; |
|
1
|
|
|
|
|
16
|
|
|
1
|
|
|
|
|
685
|
|
275
|
0
|
0
|
|
|
|
|
if (openhandle($save_to)) { |
|
|
0
|
|
|
|
|
|
276
|
0
|
|
|
|
|
|
$config[-1]{save_to_fh} = $save_to; |
277
|
|
|
|
|
|
|
} |
278
|
|
|
|
|
|
|
elsif (!ref $save_to) { |
279
|
0
|
|
|
|
|
|
my ($mode, $filename) = $save_to =~ m{ (>{0,2}) (.*) }x; |
280
|
0
|
0
|
0
|
|
|
|
open my $fh, $mode||'>', $filename |
281
|
|
|
|
|
|
|
or croak "Invalid 'save_to' option: '$save_to'\n($!)"; |
282
|
0
|
|
|
|
|
|
$config[-1]{save_to_fh} = $fh; |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
else { |
285
|
0
|
|
|
|
|
|
croak "Invalid 'save_to' option: ", ref($save_to); |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
# General memory for each state of each regex... |
292
|
|
|
|
|
|
|
# (structure is: $state{REGEX_NUM}{STATE_NUMBER}{ATTRIBUTE}) |
293
|
|
|
|
|
|
|
my %state; |
294
|
|
|
|
|
|
|
my $next_regex_ID = 0; |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
#=====[ COMPILE-TIME INTERIM REPRESENTATION OF REGEXES ]=================== |
298
|
|
|
|
|
|
|
{ |
299
|
|
|
|
|
|
|
package Regexp::Debugger::Precursor; |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
# Only translate precursors once... |
302
|
|
|
|
|
|
|
state %regex_cache; |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
use overload ( |
305
|
|
|
|
|
|
|
# Concatenation/interpolation just concatenates to the precursor... |
306
|
|
|
|
|
|
|
q{.} => sub { |
307
|
0
|
|
|
0
|
|
0
|
my ($x, $y, $reversed) = @_; |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
# Where are we from??? |
310
|
0
|
|
0
|
|
|
0
|
my $lexical_scope = $x->{lexical_scope} // 0; |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
# Reorder if necessary... |
313
|
0
|
0
|
|
|
|
0
|
if ($reversed) { ($y,$x) = ($x,$y); } |
|
0
|
|
|
|
|
0
|
|
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
# Unpack if objects... |
316
|
0
|
0
|
0
|
|
|
0
|
if (ref $x) { $x = eval{ $x->{cooked} } // $x } |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
317
|
0
|
0
|
0
|
|
|
0
|
if (ref $y) { $y = eval{ $y->{cooked} } // $y } |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
# Undo overeager \Q if necessary... |
320
|
0
|
0
|
|
|
|
0
|
if ($x =~ m{^\\\(\\\?\\\#R_d\\:(\d+)\\\)}) { $x = '\\Q' . $state{$1}{raw_regex} . '\\E' } |
|
0
|
0
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
|
321
|
0
|
|
|
|
|
0
|
elsif ($x =~ m{^\(\?\#R_D:(\d+)\)}) { $x = '\\U' . uc($state{$1}{raw_regex}) . '\\E' } |
322
|
0
|
|
|
|
|
0
|
elsif ($x =~ m{^\(\?\#r_d:(\d+)\)}) { $x = '\\L' . lc($state{$1}{raw_regex}) . '\\E' } |
323
|
0
|
0
|
|
|
|
0
|
if ($y =~ m{^\\\(\\\?\\\#R_d\\:(\d+)\\\)}) { $y = '\\Q' . $state{$1}{raw_regex} . '\\E' } |
|
0
|
0
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
|
324
|
0
|
|
|
|
|
0
|
elsif ($y =~ m{^\(\?\#R_D:(\d+)\)}) { $y = '\\U' . uc($state{$1}{raw_regex}) . '\\E' } |
325
|
0
|
|
|
|
|
0
|
elsif ($y =~ m{^\(\?\#r_d:(\d+)\)}) { $y = '\\L' . lc($state{$1}{raw_regex}) . '\\E' } |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
# Do the concatenation... |
328
|
0
|
|
0
|
|
|
0
|
$x .= $y//q{}; |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
# Rebless as a precursor object... |
331
|
0
|
|
|
|
|
0
|
return bless {cooked=>$x, lexical_scope=>$lexical_scope}, 'Regexp::Debugger::Precursor'; |
332
|
|
|
|
|
|
|
}, |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
# Using as a string (i.e. matching) preprocesses the precursor... |
335
|
|
|
|
|
|
|
q{""} => sub { |
336
|
0
|
|
|
0
|
|
0
|
my ($obj) = @_; |
337
|
|
|
|
|
|
|
|
338
|
1
|
|
|
1
|
|
9
|
use Scalar::Util qw< refaddr >; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
114
|
|
339
|
0
|
|
|
|
|
0
|
my $obj_id = refaddr($obj); |
340
|
0
|
0
|
|
|
|
0
|
return $regex_cache{$obj_id} if $regex_cache{$obj_id}; |
341
|
|
|
|
|
|
|
|
342
|
0
|
|
|
|
|
0
|
my ($cooked, $lexical_scope) = @{$obj}{'cooked', 'lexical_scope'}; |
|
0
|
|
|
|
|
0
|
|
343
|
0
|
|
|
|
|
0
|
my $x_flag = 1; |
344
|
1
|
|
|
1
|
|
8
|
use re 'eval'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
174
|
|
345
|
0
|
0
|
|
|
|
0
|
if (!eval { qr/$cooked/x}) { |
|
0
|
|
|
|
|
0
|
|
346
|
0
|
|
|
|
|
0
|
say 'redo'; |
347
|
0
|
|
|
|
|
0
|
$x_flag = 0; |
348
|
|
|
|
|
|
|
} |
349
|
0
|
0
|
|
|
|
0
|
if (!eval { qr/$cooked/}) { |
|
0
|
|
|
|
|
0
|
|
350
|
0
|
|
|
|
|
0
|
say 're-redo'; |
351
|
0
|
|
|
|
|
0
|
$x_flag = 1; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
0
|
|
|
|
|
0
|
return $regex_cache{$obj_id} |
355
|
|
|
|
|
|
|
= Regexp::Debugger::_build_debugging_regex( $cooked, $lexical_scope, $x_flag ); |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
}, |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
# Everything else, as usual... |
361
|
1
|
|
|
|
|
16
|
fallback => 1, |
362
|
1
|
|
|
1
|
|
7
|
); |
|
1
|
|
|
|
|
2
|
|
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
#=====[ Augment a regex with debugging statements ]================ |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
# Build code insertions for before and after elements in a regex... |
370
|
|
|
|
|
|
|
# (the final $^R ensure these extra code blocks are "transparent") |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
sub _build_event { |
374
|
0
|
|
|
0
|
|
|
my ($regex_ID, $event_ID, $event_desc_ref) = @_; |
375
|
0
|
|
0
|
|
|
|
$event_desc_ref->{quantifier} //= q{}; |
376
|
0
|
|
|
|
|
|
$state{$regex_ID}{$event_ID} = $event_desc_ref; |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
# Work around for bug in infinite-recursion checking in Perl 5.24 to 5.30... |
379
|
0
|
0
|
0
|
|
|
|
state $lookahead = $] <= 5.022 || $] >= 5.032 ? q{(?=)} : q{(?=[\d\D]?(?{1}))}; |
380
|
|
|
|
|
|
|
|
381
|
0
|
|
|
|
|
|
return qq{(?{Regexp::Debugger::_report_event($regex_ID, $event_ID, pos()); \$^R})$lookahead}; |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
sub _build_whitespace_event { |
385
|
0
|
|
|
0
|
|
|
my ($construct,$regex_ID, $event_ID, $event_desc_ref) = @_; |
386
|
0
|
|
0
|
|
|
|
$event_desc_ref->{quantifier} //= q{}; |
387
|
0
|
|
|
|
|
|
my %event_desc_copy = %{$event_desc_ref}; |
|
0
|
|
|
|
|
|
|
388
|
0
|
|
|
|
|
|
$state{$regex_ID}{$event_ID} = { %event_desc_copy, event_type => 'pre' }; |
389
|
0
|
|
|
|
|
|
$state{$regex_ID}{$event_ID+1} = { %event_desc_copy, event_type => 'post', msg => 'Matched' }; |
390
|
|
|
|
|
|
|
|
391
|
0
|
|
|
|
|
|
return qq{(?>(?{local \$Regexp::Debugger::prevpos=pos})$construct(?{ |
392
|
|
|
|
|
|
|
if (defined \$Regexp::Debugger::prevpos && \$Regexp::Debugger::prevpos < pos){ |
393
|
|
|
|
|
|
|
Regexp::Debugger::_report_event($regex_ID, $event_ID, \$Regexp::Debugger::prevpos); |
394
|
|
|
|
|
|
|
Regexp::Debugger::_report_event($regex_ID, $event_ID+1, pos()); |
395
|
|
|
|
|
|
|
}\$^R })|(?{ |
396
|
|
|
|
|
|
|
Regexp::Debugger::_report_event($regex_ID, $event_ID, pos()); |
397
|
|
|
|
|
|
|
\$^R |
398
|
|
|
|
|
|
|
})(?!))}; |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# Translate lookaround markers... |
403
|
|
|
|
|
|
|
my %LOOKTYPE = ( |
404
|
|
|
|
|
|
|
'(?=' => 'positive lookahead', |
405
|
|
|
|
|
|
|
'(?!' => 'negative lookahead', |
406
|
|
|
|
|
|
|
'(?<=' => 'positive lookbehind', |
407
|
|
|
|
|
|
|
'(? 'negative lookbehind', |
408
|
|
|
|
|
|
|
); |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
sub _build_debugging_regex { |
411
|
0
|
|
|
0
|
|
|
my ( $raw_regex, $lexical_scope, $x_flag ) = @_; |
412
|
0
|
|
0
|
|
|
|
$lexical_scope //= 0; |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
# Track whether the /x flag is active... |
415
|
0
|
0
|
|
|
|
|
our $if_x_flag = $x_flag ? q{} : q{(?!)}; |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
# How does this regexp show whitespace??? |
418
|
0
|
|
|
|
|
|
our $show_ws = $config[$lexical_scope]{show_ws}; |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# Build a clean, compacted version of the regex in this var... |
421
|
0
|
|
|
|
|
|
my $clean_regex = q{}; |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
# Track nested parentheticals so we can correctly mark each ')'... |
424
|
0
|
|
|
|
|
|
my @paren_stack = ( {} ); |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
# Give this regex a unique ID... |
427
|
0
|
|
|
|
|
|
my $regex_ID = $next_regex_ID++; |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
# Remember raw data in case of over-eager quotemeta'ing... |
430
|
0
|
|
|
|
|
|
$state{$regex_ID}{raw_regex} = $raw_regex; |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
# Remember location of regex... |
433
|
0
|
|
|
|
|
|
my ($filename, $end_line) = (caller 1)[1,2]; |
434
|
0
|
|
|
|
|
|
my $regex_lines = $raw_regex =~ tr/\n//; |
435
|
0
|
|
|
|
|
|
my $start_line = $end_line - $regex_lines; |
436
|
|
|
|
|
|
|
$state{$regex_ID}{location} |
437
|
0
|
0
|
|
|
|
|
= $start_line == $end_line ? qq{'$filename' line $start_line} |
438
|
|
|
|
|
|
|
: qq{'$filename' lines $start_line-$end_line}; |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# Track each inserted debugging statement... |
441
|
0
|
|
|
|
|
|
my $next_event_ID = 0; |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
# Track capture groups... |
444
|
0
|
|
|
|
|
|
my $next_capture_group = 0; |
445
|
0
|
|
|
|
|
|
my $max_capture_group = 0; |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
# Track named capture aliases... |
448
|
0
|
|
|
|
|
|
my @capture_names_for; |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
# Track \Q...\E |
451
|
0
|
|
|
|
|
|
my $in_quote = 0; |
452
|
0
|
|
|
|
|
|
my $shared_quote_pos; |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
# Describe construct... |
455
|
0
|
|
|
|
|
|
our $construct_desc; |
456
|
0
|
|
|
|
|
|
our $quantifier_desc; |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
# Check for likely problems in the regex... |
459
|
0
|
|
|
|
|
|
our @problems = (); |
460
|
0
|
|
|
|
|
|
()= $raw_regex =~ m{ |
461
|
|
|
|
|
|
|
( \( & [^\W\d]\w*+ \) ) |
462
|
0
|
|
|
|
|
|
(?{ push @problems, { line => 1 + substr($_,0,pos()-length($^N)) =~ tr/\n/\n/, |
463
|
|
|
|
|
|
|
desc => $^N, |
464
|
|
|
|
|
|
|
type => 'subpattern call', |
465
|
|
|
|
|
|
|
dym => "(?" . substr($^N,1) |
466
|
|
|
|
|
|
|
} |
467
|
|
|
|
|
|
|
}) |
468
|
|
|
|
|
|
|
| |
469
|
|
|
|
|
|
|
( \( [<'] [^\W\d]\w*+ [>'] (?= \s*+ [^\s)]++ ) ) |
470
|
0
|
|
|
|
|
|
(?{ push @problems, { line => 1 + substr($_,0,pos()-length($^N)) =~ tr/\n/\n/, |
471
|
|
|
|
|
|
|
desc => "$^N ... )", |
472
|
|
|
|
|
|
|
type => 'named capture or subpattern definition', |
473
|
|
|
|
|
|
|
dym => "(?" . substr($^N,1) . ' ... )' |
474
|
|
|
|
|
|
|
} |
475
|
|
|
|
|
|
|
}) |
476
|
|
|
|
|
|
|
}xmsgc; |
477
|
0
|
|
|
|
|
|
$state{$regex_ID}{regex_problems} = [@problems]; |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
# Translate each component... |
480
|
1
|
|
|
1
|
|
892
|
use re 'eval'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
759
|
|
481
|
0
|
|
|
|
|
|
$raw_regex =~ s{ |
482
|
|
|
|
|
|
|
# Set-up... |
483
|
0
|
|
|
|
|
|
(?{ $quantifier_desc = q{}; $construct_desc = q{}; }) |
|
0
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# Match the next construct... |
486
|
|
|
|
|
|
|
(? |
487
|
|
|
|
|
|
|
(? \A ) |
488
|
|
|
|
|
|
|
| |
489
|
|
|
|
|
|
|
(? \z ) |
490
|
|
|
|
|
|
|
| |
491
|
|
|
|
|
|
|
(? |
492
|
0
|
0
|
|
|
|
|
(??{!$Regexp::Debugger::in_quote ? q{} : q{(?!)} }) |
493
|
|
|
|
|
|
|
\\Q |
494
|
0
|
|
|
|
|
|
(?{$Regexp::Debugger::in_quote = 1}) |
495
|
|
|
|
|
|
|
) |
496
|
0
|
0
|
|
|
|
|
| (??{$Regexp::Debugger::in_quote ? q{} : q{(?!)} }) |
497
|
|
|
|
|
|
|
( |
498
|
|
|
|
|
|
|
(? \s++ ) |
499
|
|
|
|
|
|
|
| |
500
|
|
|
|
|
|
|
(? \\E ) |
501
|
0
|
|
|
|
|
|
(?{$Regexp::Debugger::in_quote = 0}) |
502
|
|
|
|
|
|
|
| |
503
|
|
|
|
|
|
|
(? \S ) |
504
|
|
|
|
|
|
|
) |
505
|
|
|
|
|
|
|
| |
506
|
|
|
|
|
|
|
(? |
507
|
0
|
|
|
|
|
|
\\U (?{$construct_desc = 'an auto-uppercased sequence'}) |
508
|
0
|
|
|
|
|
|
| \\L (?{$construct_desc = 'an auto-lowercased sequence'}) |
509
|
|
|
|
|
|
|
) |
510
|
|
|
|
|
|
|
| |
511
|
|
|
|
|
|
|
(? |
512
|
|
|
|
|
|
|
\\E |
513
|
|
|
|
|
|
|
) |
514
|
|
|
|
|
|
|
| |
515
|
0
|
|
|
|
|
|
(?{$quantifier_desc = '';}) |
516
|
|
|
|
|
|
|
(? [)] ) (? (?&QUANTIFIER) )? |
517
|
|
|
|
|
|
|
| |
518
|
|
|
|
|
|
|
(? |
519
|
0
|
|
|
|
|
|
(?(?{ $show_ws eq 'compact' }) |
520
|
|
|
|
|
|
|
(? |
521
|
|
|
|
|
|
|
( (?: \s | (?&COMMENT) )+ ) |
522
|
0
|
|
|
|
|
|
(?! (?&UNSPACED_QUANTIFIER) ) (?{ $quantifier_desc = q{} }) |
523
|
0
|
|
|
|
|
|
(?{$construct_desc = $^N}) |
524
|
|
|
|
|
|
|
| |
525
|
|
|
|
|
|
|
( \s ) |
526
|
0
|
|
|
|
|
|
(?{$construct_desc = $^N}) |
527
|
|
|
|
|
|
|
(? (?&UNSPACED_QUANTIFIER) ) |
528
|
|
|
|
|
|
|
) |
529
|
|
|
|
|
|
|
| |
530
|
|
|
|
|
|
|
(?!) |
531
|
|
|
|
|
|
|
) |
532
|
|
|
|
|
|
|
| |
533
|
0
|
|
|
|
|
|
(?(?{ $show_ws eq 'visible' }) |
534
|
|
|
|
|
|
|
(? |
535
|
|
|
|
|
|
|
( [^\S\n\t]+ ) |
536
|
0
|
|
|
|
|
|
(?! (?&UNSPACED_QUANTIFIER) ) (?{ $quantifier_desc = q{} }) |
537
|
0
|
|
|
|
|
|
(?{$construct_desc = $^N}) |
538
|
|
|
|
|
|
|
| |
539
|
|
|
|
|
|
|
( [^\S\n\t] ) |
540
|
0
|
|
|
|
|
|
(?{$construct_desc = $^N}) |
541
|
|
|
|
|
|
|
(? (?&UNSPACED_QUANTIFIER) ) |
542
|
|
|
|
|
|
|
) |
543
|
|
|
|
|
|
|
| |
544
|
|
|
|
|
|
|
(?!) |
545
|
|
|
|
|
|
|
) |
546
|
|
|
|
|
|
|
| |
547
|
0
|
|
|
|
|
|
(?(?{ $show_ws eq 'original'}) |
548
|
|
|
|
|
|
|
(? |
549
|
|
|
|
|
|
|
( [^\S\n\t] ) |
550
|
0
|
|
|
|
|
|
(?{$construct_desc = $^N}) |
551
|
|
|
|
|
|
|
(? (?&UNSPACED_QUANTIFIER) )? |
552
|
|
|
|
|
|
|
) |
553
|
|
|
|
|
|
|
| |
554
|
|
|
|
|
|
|
(?!) |
555
|
|
|
|
|
|
|
) |
556
|
|
|
|
|
|
|
| |
557
|
|
|
|
|
|
|
(? \n ) |
558
|
|
|
|
|
|
|
(? (?&UNSPACED_QUANTIFIER) )? |
559
|
0
|
|
|
|
|
|
(?{$construct_desc = 'a literal newline character'}) |
560
|
|
|
|
|
|
|
| |
561
|
|
|
|
|
|
|
(? \t ) |
562
|
|
|
|
|
|
|
(? (?&UNSPACED_QUANTIFIER) )? |
563
|
0
|
|
|
|
|
|
(?{$construct_desc = 'a literal tab character'}) |
564
|
|
|
|
|
|
|
) |
565
|
|
|
|
|
|
|
| |
566
|
|
|
|
|
|
|
(? |
567
|
|
|
|
|
|
|
[(][?][#] \s* (?i: BREAK ) \s* [)] |
568
|
|
|
|
|
|
|
) |
569
|
|
|
|
|
|
|
| |
570
|
|
|
|
|
|
|
(? |
571
|
|
|
|
|
|
|
(?&COMMENT) |
572
|
|
|
|
|
|
|
) |
573
|
|
|
|
|
|
|
| |
574
|
|
|
|
|
|
|
(? |
575
|
|
|
|
|
|
|
[(] [?] (?&MODIFIERS) [)] |
576
|
|
|
|
|
|
|
) |
577
|
|
|
|
|
|
|
| |
578
|
|
|
|
|
|
|
(? |
579
|
|
|
|
|
|
|
(?<_anchor> |
580
|
|
|
|
|
|
|
\^ |
581
|
0
|
|
|
|
|
|
(?{$construct_desc = 'at start of string (or line)'}) |
582
|
|
|
|
|
|
|
| |
583
|
|
|
|
|
|
|
\$ |
584
|
0
|
|
|
|
|
|
(?{$construct_desc = 'at end of string (or final newline)'}) |
585
|
|
|
|
|
|
|
| |
586
|
|
|
|
|
|
|
\\ (?: |
587
|
0
|
|
|
|
|
|
A (?{$construct_desc = 'at start of string'}) |
588
|
|
|
|
|
|
|
| |
589
|
0
|
|
|
|
|
|
B (?{$construct_desc = 'not at an identifier boundary'}) |
590
|
|
|
|
|
|
|
| |
591
|
0
|
|
|
|
|
|
b (?{$construct_desc = 'at an identifier boundary'}) |
592
|
|
|
|
|
|
|
| |
593
|
0
|
|
|
|
|
|
G (?{$construct_desc = 'at previous match position'}) |
594
|
|
|
|
|
|
|
| |
595
|
0
|
|
|
|
|
|
Z (?{$construct_desc = 'at end of string (or final newline)'}) |
596
|
|
|
|
|
|
|
| |
597
|
0
|
|
|
|
|
|
z (?{$construct_desc = 'at end of string'}) |
598
|
|
|
|
|
|
|
) |
599
|
|
|
|
|
|
|
) |
600
|
|
|
|
|
|
|
) |
601
|
|
|
|
|
|
|
| |
602
|
|
|
|
|
|
|
(? |
603
|
|
|
|
|
|
|
[(] [?][?] (?&CODEBLOCK) [)] |
604
|
|
|
|
|
|
|
) |
605
|
|
|
|
|
|
|
| |
606
|
|
|
|
|
|
|
(? |
607
|
|
|
|
|
|
|
[(] [?] (?&CODEBLOCK) [)] |
608
|
|
|
|
|
|
|
) |
609
|
|
|
|
|
|
|
| |
610
|
|
|
|
|
|
|
# Control verbs like (*PRUNE) and (*MARK:name)... |
611
|
|
|
|
|
|
|
(? |
612
|
|
|
|
|
|
|
\(\* [[:upper:]]*+ (?: : [^)]++ )? \) |
613
|
|
|
|
|
|
|
) |
614
|
|
|
|
|
|
|
| |
615
|
|
|
|
|
|
|
(? [(] [?] (?&MODIFIERS)? : ) |
616
|
|
|
|
|
|
|
| |
617
|
|
|
|
|
|
|
(? [(] [?] [<]?[=!] ) |
618
|
|
|
|
|
|
|
| |
619
|
|
|
|
|
|
|
(? [(] [?] [>] ) |
620
|
0
|
|
|
|
|
|
(?{$construct_desc = 'a non-backtracking group'}) |
621
|
|
|
|
|
|
|
| |
622
|
|
|
|
|
|
|
(? [(] [?] [|] ) |
623
|
|
|
|
|
|
|
| |
624
|
|
|
|
|
|
|
(? [(] (?! [?]) ) |
625
|
|
|
|
|
|
|
| |
626
|
|
|
|
|
|
|
(? [(] [?] [(] DEFINE [)] ) |
627
|
|
|
|
|
|
|
| |
628
|
|
|
|
|
|
|
(? |
629
|
|
|
|
|
|
|
[(] [?] [(] |
630
|
|
|
|
|
|
|
(? |
631
|
|
|
|
|
|
|
\d+ |
632
|
|
|
|
|
|
|
| R \d* |
633
|
|
|
|
|
|
|
| R& (?&IDENTIFIER) |
634
|
|
|
|
|
|
|
| < (?&IDENTIFIER) > |
635
|
|
|
|
|
|
|
| ' (?&IDENTIFIER) ' |
636
|
|
|
|
|
|
|
| [?] (?&CODEBLOCK) |
637
|
|
|
|
|
|
|
) |
638
|
|
|
|
|
|
|
[)] |
639
|
|
|
|
|
|
|
) |
640
|
|
|
|
|
|
|
| |
641
|
|
|
|
|
|
|
(? (? |
642
|
|
|
|
|
|
|
[(] [?] (?= [(] [?]
|
643
|
|
|
|
|
|
|
)) |
644
|
|
|
|
|
|
|
| |
645
|
|
|
|
|
|
|
(? |
646
|
|
|
|
|
|
|
[(] [?] P? < (? (?&IDENTIFIER) ) > |
647
|
|
|
|
|
|
|
| [(] [?] ' (? (?&IDENTIFIER) ) ' |
648
|
|
|
|
|
|
|
) |
649
|
|
|
|
|
|
|
| |
650
|
|
|
|
|
|
|
(?<_alternation> [|] ) |
651
|
|
|
|
|
|
|
| |
652
|
|
|
|
|
|
|
(? \\K ) |
653
|
|
|
|
|
|
|
| |
654
|
|
|
|
|
|
|
(? |
655
|
|
|
|
|
|
|
(?<_self_matching> \w{2,} ) (?! (?&QUANTIFIER) ) |
656
|
1
|
|
|
1
|
|
459
|
(?{$quantifier_desc = ''; $construct_desc = qq{a literal sequence ("$+{_self_matching}")}}) |
|
1
|
|
|
|
|
444
|
|
|
1
|
|
|
|
|
6602
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
| |
658
|
0
|
|
|
|
|
|
(?{$quantifier_desc = '';}) |
659
|
|
|
|
|
|
|
(?<_self_matching> (?&NONMETA) ) (? (?&QUANTIFIER) )? |
660
|
0
|
|
|
|
|
|
(?{$construct_desc = qq{a literal '$+{_self_matching}' character}}) |
661
|
|
|
|
|
|
|
| |
662
|
0
|
|
|
|
|
|
(?{$quantifier_desc = '';}) |
663
|
|
|
|
|
|
|
(?<_metacharacter> |
664
|
0
|
|
|
|
|
|
[.] (?{$construct_desc = 'any character (except newline)'}) |
665
|
|
|
|
|
|
|
| |
666
|
|
|
|
|
|
|
\\ |
667
|
0
|
|
|
|
|
|
(?: (0[0-7]++) (?{$construct_desc = "a literal '".chr(oct($^N))."' character"}) |
668
|
0
|
|
|
|
|
|
| (\d++) (?{$construct_desc = "what was captured in \$$^N"}) |
669
|
0
|
|
|
|
|
|
| a (?{$construct_desc = 'an alarm/bell character'}) |
670
|
0
|
|
|
|
|
|
| c ([A-Z]) (?{$construct_desc = "a CTRL-$^N character"}) |
671
|
0
|
|
|
|
|
|
| C (?{$construct_desc = 'a C-language octet'}) |
672
|
0
|
|
|
|
|
|
| d (?{$construct_desc = 'a digit'}) |
673
|
0
|
|
|
|
|
|
| D (?{$construct_desc = 'a non-digit'}) |
674
|
0
|
|
|
|
|
|
| e (?{$construct_desc = 'an escape character'}) |
675
|
0
|
|
|
|
|
|
| f (?{$construct_desc = 'a form-feed character'}) |
676
|
0
|
|
|
|
|
|
| g (\d+) (?{$construct_desc = "what was captured in \$$^N"}) |
677
|
0
|
0
|
|
|
|
|
| g - (\d+) (?{$construct_desc = $^N == 1 ? "what was captured by the nearest preceding capture group" |
678
|
|
|
|
|
|
|
: "what was captured $^N capture groups back" }) |
679
|
0
|
|
|
|
|
|
| g \{ (\d+) \} (?{$construct_desc = "what was captured in \$$^N"}) |
680
|
0
|
0
|
|
|
|
|
| g \{ - (\d+) \} (?{$construct_desc = $^N == 1 ? "what was captured by the nearest preceding capture group" |
681
|
|
|
|
|
|
|
: "what was captured $^N capture groups back" }) |
682
|
0
|
|
|
|
|
|
| g \{ (\w++) \} (?{$construct_desc = "what the named capture <$^N> matched"}) |
683
|
0
|
|
|
|
|
|
| h (?{$construct_desc = 'a horizontal whitespace character'}) |
684
|
0
|
|
|
|
|
|
| H (?{$construct_desc = 'a non-horizontal-whitespace character'}) |
685
|
0
|
|
|
|
|
|
| k \< (\w++) \> (?{$construct_desc = "what the named capture <$^N> matched"}) |
686
|
0
|
|
|
|
|
|
| n (?{$construct_desc = 'a newline character'}) |
687
|
0
|
|
|
|
|
|
| N \{ ([^\}]++) \} (?{$construct_desc = "a single \L$^N\E character"}) |
688
|
0
|
|
|
|
|
|
| N (?{$construct_desc = 'a non-newline character'}) |
689
|
0
|
|
|
|
|
|
| p (\w++) (?{$construct_desc = "a character matching the Unicode property: $^N"}) |
690
|
0
|
|
|
|
|
|
| P (\w++) (?{$construct_desc = "a character not matching the Unicode property: $^N"}) |
691
|
0
|
|
|
|
|
|
| P \{ ([^\}]++) \} (?{$construct_desc = "a character not matching the Unicode property: $^N"}) |
692
|
0
|
|
|
|
|
|
| p \{ ([^\}]++) \} (?{$construct_desc = "a character matching the Unicode property: $^N"}) |
693
|
0
|
|
|
|
|
|
| r (?{$construct_desc = 'a return character'}) |
694
|
0
|
|
|
|
|
|
| R (?{$construct_desc = 'an end-of-line sequence'}) |
695
|
0
|
|
|
|
|
|
| S (?{$construct_desc = 'a non-whitespace character'}) |
696
|
0
|
|
|
|
|
|
| s (?{$construct_desc = 'a whitespace character'}) |
697
|
0
|
|
|
|
|
|
| t (?{$construct_desc = 'a tab character'}) |
698
|
0
|
|
|
|
|
|
| V (?{$construct_desc = 'a non-vertical-whitespace character'}) |
699
|
0
|
|
|
|
|
|
| v (?{$construct_desc = 'a vertical whitespace character'}) |
700
|
0
|
|
|
|
|
|
| w (?{$construct_desc = 'an identifier character'}) |
701
|
0
|
|
|
|
|
|
| W (?{$construct_desc = 'an non-identifier character'}) |
702
|
0
|
|
|
|
|
|
| x ([0-9A-Za-z]++) (?{$construct_desc = "a literal '".chr(oct('0x'.$^N))."' character"}) |
703
|
0
|
|
|
|
|
|
| x \{ ([0-9A-Za-z ]++) \} (?{$construct_desc = "a literal '".chr(oct('0x'.$^N))."' character"}) |
704
|
0
|
|
|
|
|
|
| X (?{$construct_desc = 'a Unicode grapheme cluster'}) |
705
|
0
|
|
|
|
|
|
| (.) (?{$construct_desc = "a literal '$^N' character"}) |
706
|
|
|
|
|
|
|
) |
707
|
|
|
|
|
|
|
| |
708
|
|
|
|
|
|
|
[(][?] P = (\w++) [)] # PCRE version of \k |
709
|
0
|
|
|
|
|
|
(?{$construct_desc = "what the named capture <$^N> matched"}) |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
) (? (?&QUANTIFIER) )? |
712
|
|
|
|
|
|
|
| |
713
|
0
|
|
|
|
|
|
(?{$quantifier_desc = '';}) |
714
|
|
|
|
|
|
|
(?<_charset> (?&CHARSET) ) (? (?&QUANTIFIER) )? |
715
|
0
|
0
|
|
|
|
|
(?{$construct_desc = substr($+{_charset},0,2) eq '[^' |
716
|
|
|
|
|
|
|
? 'any character not listed' |
717
|
|
|
|
|
|
|
: 'any of the listed characters' |
718
|
|
|
|
|
|
|
}) |
719
|
|
|
|
|
|
|
| |
720
|
0
|
|
|
|
|
|
(?{$quantifier_desc = '';}) |
721
|
|
|
|
|
|
|
(?<_named_subpattern_call> |
722
|
|
|
|
|
|
|
[(][?] |
723
|
|
|
|
|
|
|
(?: |
724
|
0
|
|
|
|
|
|
[&] ((?&IDENTIFIER)) (?{$construct_desc = "a call to the subpattern named <$^N>"}) |
725
|
0
|
|
|
|
|
|
| P> ((?&IDENTIFIER)) (?{$construct_desc = "a call to the subpattern named <$^N>"}) |
726
|
0
|
|
|
|
|
|
| [+]? (\d++) (?{$construct_desc = 'a call to subpattern number $^N'}) |
727
|
0
|
0
|
|
|
|
|
| [-] (\d++) (?{$construct_desc = $^N == 1 ? "a call to the nearest preceding subpattern" |
728
|
|
|
|
|
|
|
: "a call to the subpattern $^N back" }) |
729
|
0
|
|
|
|
|
|
| R (?{$construct_desc = 'a recursive call to the current regex'}) |
730
|
|
|
|
|
|
|
) |
731
|
|
|
|
|
|
|
[)] |
732
|
|
|
|
|
|
|
) |
733
|
|
|
|
|
|
|
(? (?&QUANTIFIER) )? |
734
|
|
|
|
|
|
|
) |
735
|
|
|
|
|
|
|
| |
736
|
|
|
|
|
|
|
(? \\. | . ) |
737
|
|
|
|
|
|
|
) |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
(?(DEFINE) |
740
|
|
|
|
|
|
|
# Miscellaneous useful pattern fragments... |
741
|
|
|
|
|
|
|
(? [(][?][#] (?! \s* BREAK \s* ) .*? [)] |
742
|
|
|
|
|
|
|
| (??{$if_x_flag}) \# [^\n]* (?= \n | \z ) |
743
|
|
|
|
|
|
|
) |
744
|
|
|
|
|
|
|
(? \[ \^?+ \]?+ (?: \[:\w+:\] | \\. | [^]\\] )*+ \] ) |
745
|
|
|
|
|
|
|
(? [^\W\d]\w* ) |
746
|
|
|
|
|
|
|
(? \{ (?: (?&CODEBLOCK) | . )*? \} ) |
747
|
|
|
|
|
|
|
(? [adlupimsx]+ (?: - [imsx]+ )? |
748
|
|
|
|
|
|
|
| - [imsx]+ |
749
|
|
|
|
|
|
|
| \^ [alupimsx]+ |
750
|
|
|
|
|
|
|
) |
751
|
|
|
|
|
|
|
(? \s* (?&UNSPACED_QUANTIFIER) ) |
752
|
|
|
|
|
|
|
(? |
753
|
0
|
|
|
|
|
|
[*][+] (?{ $quantifier_desc = 'zero-or-more times (without backtracking)' }) |
754
|
0
|
|
|
|
|
|
| [*][?] (?{ $quantifier_desc = 'zero-or-more times (as few as possible)' }) |
755
|
0
|
|
|
|
|
|
| [*] (?{ $quantifier_desc = 'zero-or-more times (as many as possible)' }) |
756
|
0
|
|
|
|
|
|
| [+][+] (?{ $quantifier_desc = 'one-or-more times (without backtracking)' }) |
757
|
0
|
|
|
|
|
|
| [+][?] (?{ $quantifier_desc = 'one-or-more times (as few as possible)' }) |
758
|
0
|
|
|
|
|
|
| [+] (?{ $quantifier_desc = 'one-or-more times (as many as possible)' }) |
759
|
0
|
|
|
|
|
|
| [?][+] (?{ $quantifier_desc = 'one-or-zero times (without backtracking)' }) |
760
|
0
|
|
|
|
|
|
| [?][?] (?{ $quantifier_desc = 'zero-or-one times (as few as possible)' }) |
761
|
0
|
|
|
|
|
|
| [?] (?{ $quantifier_desc = 'one-or-zero times (as many as possible)' }) |
762
|
0
|
|
|
|
|
|
| {\d+,?\d*}[+] (?{ $quantifier_desc = 'the specified number of times (without backtracking)' }) |
763
|
0
|
|
|
|
|
|
| {\d+,?\d*}[?] (?{ $quantifier_desc = 'the specified number of times (as few as possible)' }) |
764
|
0
|
|
|
|
|
|
| {\d+,?\d*} (?{ $quantifier_desc = 'the specified number of times (as many as possible)' }) |
765
|
|
|
|
|
|
|
) |
766
|
|
|
|
|
|
|
(? [\w~`!%&=:;"'<>,/-] | (?! (??{$if_x_flag}) ) \# ) |
767
|
|
|
|
|
|
|
) |
768
|
|
|
|
|
|
|
}{ |
769
|
|
|
|
|
|
|
# Which event is this??? |
770
|
0
|
|
|
|
|
|
my $event_ID = $next_event_ID++; |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
# What are we debugging??? |
773
|
0
|
|
|
|
|
|
my $construct = $+{construct}; |
774
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
# How deep in parens??? |
776
|
0
|
|
|
|
|
|
my $depth = scalar(@paren_stack); |
777
|
0
|
|
|
|
|
|
my $indent = $INDENT x $depth; |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
# All events get this standard information... |
780
|
|
|
|
|
|
|
my %std_info = ( |
781
|
0
|
|
|
0
|
|
|
construct_type => (first { /^_/ } keys %+), |
782
|
|
|
|
|
|
|
construct => $construct, |
783
|
|
|
|
|
|
|
regex_pos => length($clean_regex), |
784
|
0
|
|
0
|
|
|
|
quantifier => $+{quantifier} // q{}, |
785
|
|
|
|
|
|
|
depth => $depth, |
786
|
|
|
|
|
|
|
indent => $indent, |
787
|
|
|
|
|
|
|
); |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
# Record the construct for display... |
790
|
|
|
|
|
|
|
$clean_regex .= |
791
|
|
|
|
|
|
|
exists $+{newline_char} ? ($std_info{construct} = q{\n} . $std_info{quantifier}) |
792
|
|
|
|
|
|
|
: exists $+{tab_char} ? ($std_info{construct} = q{\t} . $std_info{quantifier}) |
793
|
|
|
|
|
|
|
: exists $+{whitespace_chars} ? ($std_info{construct} = q{ } . $std_info{quantifier}) |
794
|
0
|
0
|
|
|
|
|
: $construct |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
795
|
|
|
|
|
|
|
; |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
# Determine and remember the necessary translation... |
798
|
0
|
|
|
|
|
|
my $translation = do { |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
# Beginning and end of regex... |
801
|
0
|
0
|
|
|
|
|
if (exists $+{start}) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
802
|
|
|
|
|
|
|
# Prime paren-tracking stack... |
803
|
0
|
|
|
|
|
|
push @paren_stack, {}; |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
# Insert an event to report (re-)starting... |
806
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID => { |
807
|
|
|
|
|
|
|
%std_info, |
808
|
|
|
|
|
|
|
construct_type => '_START', |
809
|
|
|
|
|
|
|
event_type => 'pre', |
810
|
|
|
|
|
|
|
depth => 1, |
811
|
|
|
|
|
|
|
lexical_scope => $lexical_scope, |
812
|
|
|
|
|
|
|
}) |
813
|
|
|
|
|
|
|
. '(?:'; |
814
|
|
|
|
|
|
|
} |
815
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
# At end of regex (if we get here, we matched)... |
817
|
|
|
|
|
|
|
elsif (exists $+{end}) { |
818
|
|
|
|
|
|
|
# Insert a final event to report successful match... |
819
|
|
|
|
|
|
|
')' |
820
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
821
|
|
|
|
|
|
|
%std_info, |
822
|
|
|
|
|
|
|
construct_type => '_END', |
823
|
|
|
|
|
|
|
event_type => 'post', |
824
|
|
|
|
|
|
|
depth => 1, |
825
|
0
|
|
|
0
|
|
|
msg => sub { my $steps = @{$history_of{visual}}; |
|
0
|
|
|
|
|
|
|
826
|
0
|
0
|
|
|
|
|
$steps .= ' step' . ($steps != 1 ? 's' : ''); |
827
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
# Was this a second null match??? |
829
|
0
|
|
|
|
|
|
my $match_was_null = (pos == $start_str_pos); |
830
|
0
|
0
|
0
|
|
|
|
if ($match_was_null && $prev_match_was_null) { |
831
|
0
|
|
|
|
|
|
return "Regex matched in $steps but failed to advance within string"; |
832
|
|
|
|
|
|
|
} |
833
|
|
|
|
|
|
|
else { |
834
|
0
|
|
|
|
|
|
$prev_match_was_null = $match_was_null; |
835
|
0
|
|
|
|
|
|
return "Regex matched in $steps"; |
836
|
|
|
|
|
|
|
} |
837
|
|
|
|
|
|
|
}, |
838
|
|
|
|
|
|
|
}) |
839
|
|
|
|
|
|
|
. '|' |
840
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID+1 => { |
841
|
|
|
|
|
|
|
%std_info, |
842
|
|
|
|
|
|
|
construct_type => '_END', |
843
|
|
|
|
|
|
|
event_type => 'post', |
844
|
|
|
|
|
|
|
regex_failed => 1, |
845
|
|
|
|
|
|
|
depth => 1, |
846
|
0
|
|
0
|
0
|
|
|
msg => sub { my $steps = @{$history_of{visual}//[]}; |
|
0
|
|
|
|
|
|
|
847
|
0
|
0
|
|
|
|
|
"Regex failed to match" |
|
|
0
|
|
|
|
|
|
848
|
|
|
|
|
|
|
. ($steps ? " after $steps step" . ($steps != 1 ? 's' : '') |
849
|
|
|
|
|
|
|
: ' (unable to advance within string)'); |
850
|
|
|
|
|
|
|
}, |
851
|
|
|
|
|
|
|
}) |
852
|
0
|
|
|
|
|
|
. '(?!)'; |
853
|
|
|
|
|
|
|
} |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
# Alternatives marked by a |... |
856
|
|
|
|
|
|
|
elsif (exists $+{_alternation}) { |
857
|
|
|
|
|
|
|
# Reset capture numbers if in reset group... |
858
|
0
|
0
|
|
|
|
|
if (my $reset = $paren_stack[-1]{is_branch_reset}) { |
859
|
0
|
|
|
|
|
|
$next_capture_group = $reset-1; |
860
|
|
|
|
|
|
|
} |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
# We need two events, so add an extra one... |
863
|
0
|
|
|
|
|
|
$event_ID = $next_event_ID++; |
864
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
# Insert events to indicate which side of the | we're trying now... |
866
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID-1 => { |
867
|
|
|
|
|
|
|
%std_info, |
868
|
|
|
|
|
|
|
event_type => 'end', |
869
|
|
|
|
|
|
|
msg => 'End of successful alternative', |
870
|
|
|
|
|
|
|
desc => 'Or...', |
871
|
|
|
|
|
|
|
indent => $INDENT x ($depth-1), |
872
|
|
|
|
|
|
|
}) |
873
|
|
|
|
|
|
|
. $construct |
874
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
875
|
|
|
|
|
|
|
%std_info, |
876
|
|
|
|
|
|
|
event_type => 'start', |
877
|
|
|
|
|
|
|
msg => 'Trying next alternative', |
878
|
|
|
|
|
|
|
}) |
879
|
|
|
|
|
|
|
} |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
# Whitespace has to be treated specially (because it may or may not be significant... |
882
|
|
|
|
|
|
|
elsif (exists $+{whitespace}) { |
883
|
|
|
|
|
|
|
# The two events communicate privately via this variable... |
884
|
0
|
|
|
|
|
|
my $shared_str_pos; |
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
# Two events required, so add an extra ID... |
887
|
0
|
|
|
|
|
|
$next_event_ID++; |
888
|
|
|
|
|
|
|
|
889
|
0
|
0
|
|
|
|
|
$construct_desc = join q{}, map { $_ eq "\n" ? '\n' |
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
890
|
|
|
|
|
|
|
: $_ eq "\t" ? '\t' |
891
|
|
|
|
|
|
|
: $_ eq " " ? '\N{SPACE}' |
892
|
|
|
|
|
|
|
: $_ |
893
|
|
|
|
|
|
|
} split '', $construct_desc; |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
# Insert the appropriate events... |
896
|
0
|
|
|
|
|
|
_build_whitespace_event($construct, $regex_ID, $event_ID => { |
897
|
|
|
|
|
|
|
%std_info, |
898
|
|
|
|
|
|
|
matchable => 1, |
899
|
|
|
|
|
|
|
msg => "Trying literal whitespace ('$construct_desc') $quantifier_desc", |
900
|
|
|
|
|
|
|
shared_str_pos => \$shared_str_pos, |
901
|
|
|
|
|
|
|
}) |
902
|
|
|
|
|
|
|
} |
903
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
# \L and \U start case-shifted sequences... |
905
|
|
|
|
|
|
|
elsif (exists $+{case_start}) { |
906
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID => { |
907
|
|
|
|
|
|
|
%std_info, |
908
|
|
|
|
|
|
|
event_type => 'pre', |
909
|
|
|
|
|
|
|
msg => "Starting $construct_desc", |
910
|
|
|
|
|
|
|
desc => 'The start of ' . $construct_desc, |
911
|
|
|
|
|
|
|
}) |
912
|
|
|
|
|
|
|
} |
913
|
|
|
|
|
|
|
|
914
|
|
|
|
|
|
|
elsif (exists $+{case_end}) { |
915
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID => { |
916
|
|
|
|
|
|
|
%std_info, |
917
|
|
|
|
|
|
|
event_type => 'pre', |
918
|
|
|
|
|
|
|
msg => 'End of autocasing', |
919
|
|
|
|
|
|
|
desc => 'The end of autocasing', |
920
|
|
|
|
|
|
|
}) |
921
|
|
|
|
|
|
|
} |
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
# \Q starts a quoted sequence... |
924
|
|
|
|
|
|
|
elsif (exists $+{quote_start}) { |
925
|
|
|
|
|
|
|
# Set up communication channel between \Q and \E... |
926
|
0
|
|
|
|
|
|
my $shared_pos; |
927
|
0
|
|
|
|
|
|
$shared_quote_pos = \$shared_pos; |
928
|
|
|
|
|
|
|
|
929
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID => { |
930
|
|
|
|
|
|
|
%std_info, |
931
|
|
|
|
|
|
|
event_type => 'pre', |
932
|
|
|
|
|
|
|
msg => 'Starting quoted sequence', |
933
|
|
|
|
|
|
|
desc => 'The start of a quoted sequence', |
934
|
|
|
|
|
|
|
shared_str_pos => $shared_quote_pos, |
935
|
|
|
|
|
|
|
}) |
936
|
|
|
|
|
|
|
} |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
# \E ends a quoted sequence... |
939
|
|
|
|
|
|
|
elsif (exists $+{quote_end}) { |
940
|
|
|
|
|
|
|
# Retrieve communication channel between \Q and \E... |
941
|
0
|
|
|
|
|
|
my $shared_pos = $shared_quote_pos; |
942
|
0
|
|
|
|
|
|
$shared_quote_pos = undef; |
943
|
|
|
|
|
|
|
|
944
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID => { |
945
|
|
|
|
|
|
|
%std_info, |
946
|
|
|
|
|
|
|
event_type => 'post', |
947
|
|
|
|
|
|
|
msg => 'End of quoted sequence', |
948
|
|
|
|
|
|
|
desc => 'The end of a quoted sequence', |
949
|
|
|
|
|
|
|
shared_str_pos => $shared_pos, |
950
|
|
|
|
|
|
|
}) |
951
|
|
|
|
|
|
|
} |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
# Quoted subsequences... |
955
|
|
|
|
|
|
|
elsif (exists $+{quote_space}) { |
956
|
|
|
|
|
|
|
# The two events communicate privately via this variable... |
957
|
0
|
|
|
|
|
|
my $shared_str_pos; |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
# Two events, so add an extra ID... |
960
|
0
|
|
|
|
|
|
$event_ID = $next_event_ID++; |
961
|
|
|
|
|
|
|
|
962
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID-1 => { |
963
|
|
|
|
|
|
|
%std_info, |
964
|
|
|
|
|
|
|
matchable => 1, |
965
|
|
|
|
|
|
|
event_type => 'pre', |
966
|
|
|
|
|
|
|
msg => 'Trying autoquoted literal whitespace', |
967
|
|
|
|
|
|
|
shared_str_pos => \$shared_str_pos, |
968
|
|
|
|
|
|
|
}) |
969
|
|
|
|
|
|
|
. quotemeta($construct) |
970
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
971
|
|
|
|
|
|
|
%std_info, |
972
|
|
|
|
|
|
|
matchable => 1, |
973
|
|
|
|
|
|
|
event_type => 'post', |
974
|
|
|
|
|
|
|
msg => 'Matched autoquoted literal whitespace', |
975
|
|
|
|
|
|
|
shared_str_pos => \$shared_str_pos, |
976
|
|
|
|
|
|
|
}) |
977
|
|
|
|
|
|
|
} |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
elsif (exists $+{quote_nonspace}) { |
980
|
|
|
|
|
|
|
# The two events communicate privately via this variable... |
981
|
0
|
|
|
|
|
|
my $shared_str_pos; |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
# Two events, so add an extra ID... |
984
|
0
|
|
|
|
|
|
$event_ID = $next_event_ID++; |
985
|
|
|
|
|
|
|
|
986
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID-1 => { |
987
|
|
|
|
|
|
|
%std_info, |
988
|
|
|
|
|
|
|
matchable => 1, |
989
|
|
|
|
|
|
|
event_type => 'pre', |
990
|
|
|
|
|
|
|
msg => 'Trying an autoquoted literal character', |
991
|
|
|
|
|
|
|
desc => 'Match an autoquoted literal character', |
992
|
|
|
|
|
|
|
shared_str_pos => \$shared_str_pos, |
993
|
|
|
|
|
|
|
}) |
994
|
|
|
|
|
|
|
. quotemeta($construct) |
995
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
996
|
|
|
|
|
|
|
%std_info, |
997
|
|
|
|
|
|
|
matchable => 1, |
998
|
|
|
|
|
|
|
event_type => 'post', |
999
|
|
|
|
|
|
|
msg => 'Matched a literal character', |
1000
|
|
|
|
|
|
|
shared_str_pos => \$shared_str_pos, |
1001
|
|
|
|
|
|
|
}) |
1002
|
|
|
|
|
|
|
} |
1003
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
# Atoms are any elements that match and emit debugging info before and after matching... |
1005
|
|
|
|
|
|
|
elsif (exists $+{atom}) { |
1006
|
|
|
|
|
|
|
# The two events communicate privately via this variable... |
1007
|
0
|
|
|
|
|
|
my $shared_str_pos; |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
# Track depth of subpattern calls... |
1010
|
0
|
|
|
|
|
|
my $is_subpattern_call = exists $+{_named_subpattern_call}; |
1011
|
0
|
0
|
|
|
|
|
my $subpattern_call_prefix |
1012
|
|
|
|
|
|
|
= $is_subpattern_call |
1013
|
|
|
|
|
|
|
? q{(?{local $Regexp::Debugger::subpattern_depth = $Regexp::Debugger::subpattern_depth + 1})} |
1014
|
|
|
|
|
|
|
: q{}; |
1015
|
0
|
0
|
|
|
|
|
my $subpattern_call_suffix |
1016
|
|
|
|
|
|
|
= $is_subpattern_call |
1017
|
|
|
|
|
|
|
? q{(?{local $Regexp::Debugger::subpattern_depth = $Regexp::Debugger::subpattern_depth - 1})} |
1018
|
|
|
|
|
|
|
: q{}; |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
# Two events, so add an extra ID... |
1021
|
0
|
|
|
|
|
|
$event_ID = $next_event_ID++; |
1022
|
0
|
0
|
|
|
|
|
_build_event($regex_ID, $event_ID-1 => { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
%std_info, |
1024
|
|
|
|
|
|
|
matchable => 1, |
1025
|
|
|
|
|
|
|
event_type => 'pre', |
1026
|
|
|
|
|
|
|
msg => "Trying $construct_desc" . (length($quantifier_desc) ? ", $quantifier_desc" : q{}), |
1027
|
|
|
|
|
|
|
desc => "Match $construct_desc" . (length($quantifier_desc) ? ", $quantifier_desc" : q{}), |
1028
|
|
|
|
|
|
|
shared_str_pos => \$shared_str_pos, |
1029
|
|
|
|
|
|
|
}) |
1030
|
|
|
|
|
|
|
. $subpattern_call_prefix |
1031
|
|
|
|
|
|
|
. $construct |
1032
|
|
|
|
|
|
|
. $subpattern_call_suffix |
1033
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1034
|
|
|
|
|
|
|
%std_info, |
1035
|
|
|
|
|
|
|
matchable => 1, |
1036
|
|
|
|
|
|
|
event_type => 'post', |
1037
|
|
|
|
|
|
|
msg => 'Matched' |
1038
|
|
|
|
|
|
|
. ($is_subpattern_call ? " (discarding subpattern's captures)": q{}), |
1039
|
|
|
|
|
|
|
shared_str_pos => \$shared_str_pos, |
1040
|
|
|
|
|
|
|
}) |
1041
|
|
|
|
|
|
|
} |
1042
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
# Code blocks (?{...})... |
1044
|
|
|
|
|
|
|
elsif (exists $+{code_block}) { |
1045
|
|
|
|
|
|
|
# Add an event beforehand to indicate execution of the block... |
1046
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID => { |
1047
|
|
|
|
|
|
|
%std_info, |
1048
|
|
|
|
|
|
|
matchable => 0, |
1049
|
|
|
|
|
|
|
event_type => 'action', |
1050
|
|
|
|
|
|
|
msg => 'Executing code block', |
1051
|
|
|
|
|
|
|
desc => 'Execute a block of code', |
1052
|
|
|
|
|
|
|
}) |
1053
|
|
|
|
|
|
|
. $construct |
1054
|
|
|
|
|
|
|
} |
1055
|
|
|
|
|
|
|
|
1056
|
|
|
|
|
|
|
# Code blocks that generate dynamic patterns (??{...})... |
1057
|
|
|
|
|
|
|
elsif (exists $+{matchable_code_block}) { |
1058
|
|
|
|
|
|
|
# These events communicate privately via this variable... |
1059
|
0
|
|
|
|
|
|
my $shared_str_pos; |
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
# Modify construct to generate but not match... |
1062
|
0
|
|
|
|
|
|
substr($construct, 1, 1) = q{}; |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
# Inserting three events, so add an extra two IDs... |
1065
|
0
|
|
|
|
|
|
$event_ID = ($next_event_ID+=3); |
1066
|
|
|
|
|
|
|
# First event pair reports executing the block... |
1067
|
|
|
|
|
|
|
_build_event($regex_ID, $event_ID-4 => { |
1068
|
|
|
|
|
|
|
%std_info, |
1069
|
|
|
|
|
|
|
matchable => 0, |
1070
|
|
|
|
|
|
|
event_type => 'action', |
1071
|
|
|
|
|
|
|
msg => 'Executing code block of postponed subpattern', |
1072
|
|
|
|
|
|
|
desc => "Execute a code block, then match the block's final value", |
1073
|
|
|
|
|
|
|
}) |
1074
|
|
|
|
|
|
|
. $construct |
1075
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID-3 => { |
1076
|
|
|
|
|
|
|
%std_info, |
1077
|
|
|
|
|
|
|
matchable => 0, |
1078
|
|
|
|
|
|
|
event_type => 'action', |
1079
|
0
|
|
|
0
|
|
|
msg => sub { "Code block returned: '$^R'" }, |
1080
|
|
|
|
|
|
|
}) |
1081
|
|
|
|
|
|
|
# Second event pair reports match of subpattern the block returned... |
1082
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID-2 => { |
1083
|
|
|
|
|
|
|
%std_info, |
1084
|
|
|
|
|
|
|
matchable => 1, |
1085
|
|
|
|
|
|
|
event_type => 'pre', |
1086
|
0
|
|
|
0
|
|
|
msg => sub{ "Trying: qr{$^R}" }, |
1087
|
0
|
|
|
|
|
|
shared_str_pos => \$shared_str_pos, |
1088
|
|
|
|
|
|
|
}) |
1089
|
|
|
|
|
|
|
. '(??{ $^R })' |
1090
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID-1 => { |
1091
|
|
|
|
|
|
|
%std_info, |
1092
|
|
|
|
|
|
|
matchable => 1, |
1093
|
|
|
|
|
|
|
event_type => 'post', |
1094
|
|
|
|
|
|
|
msg => 'Matched', |
1095
|
|
|
|
|
|
|
shared_str_pos => \$shared_str_pos, |
1096
|
|
|
|
|
|
|
}) |
1097
|
|
|
|
|
|
|
} |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
# Keep marker... |
1100
|
|
|
|
|
|
|
elsif (exists $+{keep_marker}) { |
1101
|
|
|
|
|
|
|
# Insert events reporting testing the assertion, and if the test succeeds... |
1102
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID => { |
1103
|
|
|
|
|
|
|
%std_info, |
1104
|
|
|
|
|
|
|
matchable => 0, |
1105
|
|
|
|
|
|
|
event_type => 'action', |
1106
|
|
|
|
|
|
|
msg => "Forgetting everything matched to this point", |
1107
|
|
|
|
|
|
|
desc => 'Pretend the final match starts here', |
1108
|
|
|
|
|
|
|
}) |
1109
|
|
|
|
|
|
|
. $construct |
1110
|
|
|
|
|
|
|
. '(?{ local $Regexp::Grammars::match_start_pos = pos() })' |
1111
|
|
|
|
|
|
|
} |
1112
|
|
|
|
|
|
|
|
1113
|
|
|
|
|
|
|
# Zero-width assertions... |
1114
|
|
|
|
|
|
|
elsif (exists $+{zero_width}) { |
1115
|
|
|
|
|
|
|
# Two events, so add an extra ID... |
1116
|
0
|
|
|
|
|
|
$event_ID = $next_event_ID++; |
1117
|
|
|
|
|
|
|
|
1118
|
|
|
|
|
|
|
# Insert events reporting testing the assertion, and if the test succeeds... |
1119
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID-1 => { |
1120
|
|
|
|
|
|
|
%std_info, |
1121
|
|
|
|
|
|
|
matchable => 1, |
1122
|
|
|
|
|
|
|
event_type => 'pre', |
1123
|
|
|
|
|
|
|
msg => "Testing if $construct_desc", |
1124
|
|
|
|
|
|
|
desc => "Match only if $construct_desc", |
1125
|
|
|
|
|
|
|
}) |
1126
|
|
|
|
|
|
|
. $construct |
1127
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1128
|
|
|
|
|
|
|
%std_info, |
1129
|
|
|
|
|
|
|
matchable => 1, |
1130
|
|
|
|
|
|
|
event_type => 'post', |
1131
|
|
|
|
|
|
|
msg => 'Assertion satisfied', |
1132
|
|
|
|
|
|
|
}) |
1133
|
|
|
|
|
|
|
} |
1134
|
|
|
|
|
|
|
|
1135
|
|
|
|
|
|
|
# Control verbs: (*PRUNE) (*SKIP) (*FAIL) etc... |
1136
|
|
|
|
|
|
|
elsif (exists $+{control}) { |
1137
|
|
|
|
|
|
|
# Two events, so add an extra ID... |
1138
|
0
|
|
|
|
|
|
$event_ID = $next_event_ID++; |
1139
|
|
|
|
|
|
|
|
1140
|
|
|
|
|
|
|
# Insert events to report both the attempt and its success... |
1141
|
0
|
|
|
|
|
|
_build_event($regex_ID, $event_ID-1 => { |
1142
|
|
|
|
|
|
|
%std_info, |
1143
|
|
|
|
|
|
|
matchable => 1, |
1144
|
|
|
|
|
|
|
event_type => 'pre', |
1145
|
|
|
|
|
|
|
msg => 'Executing a control', |
1146
|
|
|
|
|
|
|
desc => 'Execute a backtracking control', |
1147
|
|
|
|
|
|
|
}) |
1148
|
|
|
|
|
|
|
. $construct |
1149
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1150
|
|
|
|
|
|
|
%std_info, |
1151
|
|
|
|
|
|
|
matchable => 1, |
1152
|
|
|
|
|
|
|
event_type => 'post', |
1153
|
|
|
|
|
|
|
msg => 'Control succeeded', |
1154
|
|
|
|
|
|
|
}) |
1155
|
|
|
|
|
|
|
} |
1156
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
# Start of DEFINE block... |
1158
|
|
|
|
|
|
|
elsif (exists $+{define_block}) { |
1159
|
|
|
|
|
|
|
# It's an unbalanced opening paren, so remember it on the stack... |
1160
|
0
|
|
|
|
|
|
push @paren_stack, { |
1161
|
|
|
|
|
|
|
is_capture => 0, |
1162
|
|
|
|
|
|
|
construct_type => '_DEFINE_block', |
1163
|
|
|
|
|
|
|
is_definition => 1, |
1164
|
|
|
|
|
|
|
}; |
1165
|
|
|
|
|
|
|
|
1166
|
|
|
|
|
|
|
# Insert and event to report skipping the entire block... |
1167
|
|
|
|
|
|
|
_build_event($regex_ID, $event_ID => { |
1168
|
|
|
|
|
|
|
%std_info, |
1169
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
|
0
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
matchable => 0, |
1171
|
|
|
|
|
|
|
event_type => 'pre', |
1172
|
|
|
|
|
|
|
msg => 'Skipping definitions', |
1173
|
|
|
|
|
|
|
desc => 'The start of a definition block (skipped during matching)', |
1174
|
|
|
|
|
|
|
}) |
1175
|
|
|
|
|
|
|
. $construct . '(?:' |
1176
|
|
|
|
|
|
|
} |
1177
|
|
|
|
|
|
|
|
1178
|
|
|
|
|
|
|
# Modifier set: (?is-mx) etc... |
1179
|
|
|
|
|
|
|
elsif (exists $+{modifier_set}) { |
1180
|
|
|
|
|
|
|
# Insert an event to report the change of active modifiers... |
1181
|
|
|
|
|
|
|
_build_event($regex_ID, $event_ID => { |
1182
|
|
|
|
|
|
|
%std_info, |
1183
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
|
0
|
|
|
|
|
|
|
1184
|
|
|
|
|
|
|
matchable => 0, |
1185
|
|
|
|
|
|
|
event_type => 'compile', |
1186
|
|
|
|
|
|
|
msg => 'Changing modifiers', |
1187
|
|
|
|
|
|
|
desc => 'Change current modifiers', |
1188
|
|
|
|
|
|
|
}) |
1189
|
|
|
|
|
|
|
. $construct |
1190
|
|
|
|
|
|
|
} |
1191
|
|
|
|
|
|
|
|
1192
|
|
|
|
|
|
|
# Conditional parens: (?(COND) X | Y )... |
1193
|
|
|
|
|
|
|
elsif (exists $+{conditional_paren}) { |
1194
|
|
|
|
|
|
|
# It's an unbalanced opening paren, so remember it on the stack... |
1195
|
|
|
|
|
|
|
push @paren_stack, { |
1196
|
|
|
|
|
|
|
is_capture => 0, |
1197
|
|
|
|
|
|
|
is_conditional => 1, |
1198
|
|
|
|
|
|
|
is_pending => exists $+{pending_condition}, # ...expecting a lookahead? |
1199
|
0
|
|
|
|
|
|
construct_type => '_conditional_group', |
1200
|
|
|
|
|
|
|
}; |
1201
|
|
|
|
|
|
|
|
1202
|
|
|
|
|
|
|
# Insert an event to report the test... |
1203
|
|
|
|
|
|
|
'(?:' |
1204
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1205
|
|
|
|
|
|
|
%std_info, |
1206
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
|
0
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
event_type => 'pre', |
1208
|
|
|
|
|
|
|
msg => 'Testing condition', |
1209
|
|
|
|
|
|
|
desc => 'The start of a conditional block', |
1210
|
|
|
|
|
|
|
}) |
1211
|
|
|
|
|
|
|
. $construct; |
1212
|
|
|
|
|
|
|
} |
1213
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
# Branch-reset parens... |
1215
|
|
|
|
|
|
|
elsif (exists $+{branch_reset_paren}) { |
1216
|
|
|
|
|
|
|
# It's an unbalanced opening paren, so remember it on the stack... |
1217
|
0
|
|
|
|
|
|
push @paren_stack, { |
1218
|
|
|
|
|
|
|
is_capture => 0, |
1219
|
|
|
|
|
|
|
is_branch_reset => $next_capture_group+1, |
1220
|
|
|
|
|
|
|
construct_type => '_branch_reset_group', |
1221
|
|
|
|
|
|
|
}; |
1222
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
# Insert an event to report the start of branch-reseting... |
1224
|
|
|
|
|
|
|
'(?:' |
1225
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1226
|
|
|
|
|
|
|
%std_info, |
1227
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
|
0
|
|
|
|
|
|
|
1228
|
|
|
|
|
|
|
event_type => 'pre', |
1229
|
|
|
|
|
|
|
msg => 'Starting branch-resetting group', |
1230
|
|
|
|
|
|
|
desc => 'The start of a branch-resetting group', |
1231
|
|
|
|
|
|
|
}) |
1232
|
|
|
|
|
|
|
. $construct; |
1233
|
|
|
|
|
|
|
} |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
# Non-capturing parens... |
1236
|
|
|
|
|
|
|
elsif (exists $+{noncapturing_paren}) { |
1237
|
|
|
|
|
|
|
# Do the non-capturing parens have embedded modifiers??? |
1238
|
0
|
0
|
|
|
|
|
my $addendum = length($construct) > 3 ? ', changing modifiers' : q{}; |
1239
|
|
|
|
|
|
|
|
1240
|
|
|
|
|
|
|
# Update for (?x: or (?-x:... |
1241
|
0
|
|
|
|
|
|
my $old_if_x_flag = $if_x_flag; |
1242
|
0
|
|
|
|
|
|
my $neg = index($construct, '-'); |
1243
|
0
|
0
|
|
|
|
|
if ($neg >= 0) { |
1244
|
0
|
|
|
|
|
|
my $x = index($construct, 'x'); |
1245
|
0
|
0
|
|
|
|
|
if ($x >= 0) { |
1246
|
0
|
0
|
|
|
|
|
if ($x < $neg) { |
1247
|
0
|
|
|
|
|
|
$if_x_flag = ''; |
1248
|
|
|
|
|
|
|
} |
1249
|
|
|
|
|
|
|
else { |
1250
|
0
|
|
|
|
|
|
$if_x_flag = '(?!)'; |
1251
|
|
|
|
|
|
|
} |
1252
|
|
|
|
|
|
|
} |
1253
|
|
|
|
|
|
|
} |
1254
|
|
|
|
|
|
|
|
1255
|
|
|
|
|
|
|
# It's an unbalanced opening paren, so remember it on the stack... |
1256
|
0
|
|
|
|
|
|
push @paren_stack, { |
1257
|
|
|
|
|
|
|
is_capture => 0, |
1258
|
|
|
|
|
|
|
construct_type => '_noncapture_group', |
1259
|
|
|
|
|
|
|
reinstate_x_flag => $old_if_x_flag, |
1260
|
|
|
|
|
|
|
}; |
1261
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
# Insert an event to report the start of a non-capturing group... |
1263
|
|
|
|
|
|
|
'(?:' |
1264
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1265
|
|
|
|
|
|
|
%std_info, |
1266
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
|
0
|
|
|
|
|
|
|
1267
|
|
|
|
|
|
|
event_type => 'pre', |
1268
|
|
|
|
|
|
|
msg => 'Starting non-capturing group' . $addendum, |
1269
|
|
|
|
|
|
|
desc => 'The start of a non-capturing group', |
1270
|
|
|
|
|
|
|
}) |
1271
|
|
|
|
|
|
|
. $construct; |
1272
|
|
|
|
|
|
|
} |
1273
|
|
|
|
|
|
|
|
1274
|
|
|
|
|
|
|
# Non-backtracking parens... |
1275
|
|
|
|
|
|
|
elsif (exists $+{non_backtracking_paren}) { |
1276
|
|
|
|
|
|
|
# It's an unbalanced opening paren, so remember it on the stack... |
1277
|
0
|
|
|
|
|
|
push @paren_stack, { |
1278
|
|
|
|
|
|
|
is_capture => 0, |
1279
|
|
|
|
|
|
|
is_nonbacktrack => 1, |
1280
|
|
|
|
|
|
|
construct_type => '_nonbacktracking_group', |
1281
|
|
|
|
|
|
|
}; |
1282
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
# Insert an event to report the start of a non-backtracking group... |
1284
|
|
|
|
|
|
|
'(?:' |
1285
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1286
|
|
|
|
|
|
|
%std_info, |
1287
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
|
0
|
|
|
|
|
|
|
1288
|
|
|
|
|
|
|
event_type => 'pre', |
1289
|
|
|
|
|
|
|
msg => 'Starting non-backtracking group', |
1290
|
|
|
|
|
|
|
desc => 'The start of a non-backtracking group', |
1291
|
|
|
|
|
|
|
}) |
1292
|
|
|
|
|
|
|
. '(?>'; |
1293
|
|
|
|
|
|
|
} |
1294
|
|
|
|
|
|
|
|
1295
|
|
|
|
|
|
|
# Positive lookahead/lookbehind parens... |
1296
|
|
|
|
|
|
|
elsif (exists $+{lookaround_paren}) { |
1297
|
|
|
|
|
|
|
# It's an unbalanced opening paren, so remember it on the stack... |
1298
|
|
|
|
|
|
|
push @paren_stack, { |
1299
|
|
|
|
|
|
|
is_capture => 0, |
1300
|
0
|
|
|
|
|
|
is_lookaround => $LOOKTYPE{$construct}, |
1301
|
|
|
|
|
|
|
construct_type => '_lookaround', |
1302
|
|
|
|
|
|
|
}; |
1303
|
|
|
|
|
|
|
|
1304
|
|
|
|
|
|
|
# Is this lookaround the test of a (?(COND) X | Y) conditional??? |
1305
|
0
|
0
|
0
|
|
|
|
if ($paren_stack[-2]{is_conditional} && $paren_stack[-2]{is_pending}) { |
1306
|
|
|
|
|
|
|
# If so, the test is no longer pending... |
1307
|
0
|
|
|
|
|
|
delete $paren_stack[-2]{is_pending}; |
1308
|
|
|
|
|
|
|
|
1309
|
|
|
|
|
|
|
# Insert an event to report the test... |
1310
|
|
|
|
|
|
|
$construct |
1311
|
|
|
|
|
|
|
. '(?:' |
1312
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1313
|
|
|
|
|
|
|
%std_info, |
1314
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
1315
|
|
|
|
|
|
|
event_type => 'pre', |
1316
|
|
|
|
|
|
|
msg => 'Testing for ' . $LOOKTYPE{$construct}, |
1317
|
0
|
|
|
|
|
|
desc => 'Match ' . lc $LOOKTYPE{$construct}, |
1318
|
|
|
|
|
|
|
}); |
1319
|
|
|
|
|
|
|
} |
1320
|
|
|
|
|
|
|
else { |
1321
|
|
|
|
|
|
|
# Otherwise, insert an event to report the start of the lookaround... |
1322
|
|
|
|
|
|
|
'(?:' |
1323
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1324
|
|
|
|
|
|
|
%std_info, |
1325
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
1326
|
|
|
|
|
|
|
event_type => 'pre', |
1327
|
|
|
|
|
|
|
msg => 'Starting ' . $LOOKTYPE{$construct}, |
1328
|
0
|
|
|
|
|
|
desc => 'Match ' . $LOOKTYPE{$construct}, |
1329
|
|
|
|
|
|
|
}) |
1330
|
|
|
|
|
|
|
. $construct; |
1331
|
|
|
|
|
|
|
} |
1332
|
|
|
|
|
|
|
} |
1333
|
|
|
|
|
|
|
|
1334
|
|
|
|
|
|
|
# Capturing parens... |
1335
|
|
|
|
|
|
|
elsif (exists $+{capturing_paren}) { |
1336
|
|
|
|
|
|
|
# The events communicate privately via this variable... |
1337
|
0
|
|
|
|
|
|
my $shared_str_pos; |
1338
|
|
|
|
|
|
|
|
1339
|
|
|
|
|
|
|
# Get the corresponding capture group number... |
1340
|
0
|
|
|
|
|
|
$next_capture_group++; |
1341
|
|
|
|
|
|
|
|
1342
|
|
|
|
|
|
|
# Track the maximum group number (for after branch resets)... |
1343
|
0
|
|
|
|
|
|
$max_capture_group = max($max_capture_group, $next_capture_group); |
1344
|
|
|
|
|
|
|
|
1345
|
|
|
|
|
|
|
# It's an unbalanced opening paren, so remember it on the stack... |
1346
|
0
|
|
|
|
|
|
push @paren_stack, { |
1347
|
|
|
|
|
|
|
is_capture => 1, |
1348
|
|
|
|
|
|
|
construct_type => '_capture_group', |
1349
|
|
|
|
|
|
|
capture_name => '$'.$next_capture_group, |
1350
|
|
|
|
|
|
|
shared_str_pos => \$shared_str_pos, |
1351
|
|
|
|
|
|
|
}; |
1352
|
|
|
|
|
|
|
|
1353
|
|
|
|
|
|
|
# Insert an event to report the start of capturing... |
1354
|
|
|
|
|
|
|
'(' |
1355
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1356
|
|
|
|
|
|
|
%std_info, |
1357
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
|
0
|
|
|
|
|
|
|
1358
|
|
|
|
|
|
|
event_type => 'pre', |
1359
|
|
|
|
|
|
|
msg => 'Capture to $'.$next_capture_group, |
1360
|
|
|
|
|
|
|
desc => "The start of a capturing block (\$$next_capture_group)", |
1361
|
|
|
|
|
|
|
}) |
1362
|
|
|
|
|
|
|
. '(?:'; |
1363
|
|
|
|
|
|
|
} |
1364
|
|
|
|
|
|
|
|
1365
|
|
|
|
|
|
|
# Named capturing parens... |
1366
|
|
|
|
|
|
|
elsif (exists $+{named_capturing_paren}) { |
1367
|
|
|
|
|
|
|
# The events communicate privately via this variable... |
1368
|
0
|
|
|
|
|
|
my $shared_str_pos; |
1369
|
|
|
|
|
|
|
|
1370
|
|
|
|
|
|
|
# Named capture groups are also numbered, so get the number... |
1371
|
0
|
|
|
|
|
|
$next_capture_group++; |
1372
|
|
|
|
|
|
|
|
1373
|
|
|
|
|
|
|
# Track the maximum group number (for after branch resets)... |
1374
|
0
|
|
|
|
|
|
$max_capture_group = max($max_capture_group, $next_capture_group); |
1375
|
|
|
|
|
|
|
|
1376
|
|
|
|
|
|
|
# If this creates a new numbered capture, remember the number... |
1377
|
0
|
0
|
0
|
|
|
|
if (!@{$capture_names_for[$next_capture_group]//[]}) { |
|
0
|
|
|
|
|
|
|
1378
|
0
|
|
|
|
|
|
push @{$capture_names_for[$next_capture_group]}, '$'.$next_capture_group; |
|
0
|
|
|
|
|
|
|
1379
|
|
|
|
|
|
|
} |
1380
|
|
|
|
|
|
|
|
1381
|
|
|
|
|
|
|
# Add this name to the list of aliases for the same numbered capture... |
1382
|
|
|
|
|
|
|
# (Needed because named captures in two reset branches may alias |
1383
|
|
|
|
|
|
|
# to the same underlying numbered capture variable. See perlre) |
1384
|
0
|
|
|
|
|
|
push @{$capture_names_for[$next_capture_group]}, '$+{'.$+{capture_name}.'}'; |
|
0
|
|
|
|
|
|
|
1385
|
|
|
|
|
|
|
|
1386
|
|
|
|
|
|
|
# It's an unbalanced opening paren, so remember it on the stack... |
1387
|
0
|
|
|
|
|
|
push @paren_stack, { |
1388
|
|
|
|
|
|
|
is_capture => 1, |
1389
|
|
|
|
|
|
|
construct_type => '_capture_group', |
1390
|
|
|
|
|
|
|
capture_name => $capture_names_for[$next_capture_group], |
1391
|
|
|
|
|
|
|
shared_str_pos => \$shared_str_pos, |
1392
|
|
|
|
|
|
|
}; |
1393
|
|
|
|
|
|
|
|
1394
|
|
|
|
|
|
|
# Insert an event to report the start of the named capture... |
1395
|
|
|
|
|
|
|
$construct |
1396
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1397
|
|
|
|
|
|
|
%std_info, |
1398
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
|
0
|
|
|
|
|
|
|
1399
|
|
|
|
|
|
|
event_type => 'pre', |
1400
|
|
|
|
|
|
|
msg => $capture_names_for[$next_capture_group], |
1401
|
|
|
|
|
|
|
desc => "The start of a named capturing block (also \$$next_capture_group)", |
1402
|
|
|
|
|
|
|
}) |
1403
|
|
|
|
|
|
|
. '(?:'; |
1404
|
|
|
|
|
|
|
} |
1405
|
|
|
|
|
|
|
|
1406
|
|
|
|
|
|
|
# Closing parens have to be deciphered... |
1407
|
|
|
|
|
|
|
elsif (exists $+{closing_paren}) { |
1408
|
|
|
|
|
|
|
# The top of the paren stack tells us what kind of group we're closing... |
1409
|
0
|
|
0
|
|
|
|
my $paren_data = pop(@paren_stack) // { type=>'unmatched closing )' }; |
1410
|
|
|
|
|
|
|
|
1411
|
|
|
|
|
|
|
# Update the next capture group number, if after a branch reset group... |
1412
|
0
|
0
|
|
|
|
|
if ($paren_data->{is_branch_reset}) { |
1413
|
0
|
|
|
|
|
|
$next_capture_group = $max_capture_group; |
1414
|
|
|
|
|
|
|
} |
1415
|
|
|
|
|
|
|
|
1416
|
|
|
|
|
|
|
# Generate an appropriate message for the type of group being closed... |
1417
|
|
|
|
|
|
|
my $msg = $paren_data->{is_capture} && ref $paren_data->{capture_name} |
1418
|
|
|
|
|
|
|
? $paren_data->{capture_name} |
1419
|
|
|
|
|
|
|
: $paren_data->{is_capture} ? 'End of ' . $paren_data->{capture_name} |
1420
|
|
|
|
|
|
|
: $paren_data->{is_definition} ? 'End of definition block' |
1421
|
|
|
|
|
|
|
: $paren_data->{is_branch_reset} ? 'End of branch-resetting group' |
1422
|
|
|
|
|
|
|
: $paren_data->{is_lookaround} ? 'End of ' . $paren_data->{is_lookaround} |
1423
|
|
|
|
|
|
|
: $paren_data->{is_conditional} ? 'End of conditional group' |
1424
|
0
|
0
|
0
|
|
|
|
: $paren_data->{is_nonbacktrack} ? 'End of non-backtracking group' |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1425
|
|
|
|
|
|
|
: 'End of non-capturing group' |
1426
|
|
|
|
|
|
|
; |
1427
|
|
|
|
|
|
|
|
1428
|
0
|
0
|
|
|
|
|
if (length($std_info{quantifier})) { |
1429
|
0
|
|
|
|
|
|
$msg .= " (matching $quantifier_desc)"; |
1430
|
|
|
|
|
|
|
} |
1431
|
|
|
|
|
|
|
|
1432
|
|
|
|
|
|
|
# Reinstate previous /x status (if necessary)... |
1433
|
0
|
0
|
|
|
|
|
if (exists $paren_data->{reinstate_x_flag}) { |
1434
|
0
|
|
|
|
|
|
$if_x_flag = $paren_data->{reinstate_x_flag}; |
1435
|
|
|
|
|
|
|
} |
1436
|
|
|
|
|
|
|
|
1437
|
|
|
|
|
|
|
# Two events, so add an extra ID... |
1438
|
0
|
|
|
|
|
|
$event_ID = $next_event_ID++; |
1439
|
|
|
|
|
|
|
|
1440
|
|
|
|
|
|
|
# Append an event reporting the completion of the group... |
1441
|
|
|
|
|
|
|
')' |
1442
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID-1 => { |
1443
|
|
|
|
|
|
|
%std_info, |
1444
|
0
|
|
|
|
|
|
%{$paren_data}, |
1445
|
|
|
|
|
|
|
event_type => 'post', |
1446
|
|
|
|
|
|
|
msg => $msg, |
1447
|
|
|
|
|
|
|
desc => ( ref $msg ? 'The end of the named capturing block' : 'The e' . substr($msg,1) ), |
1448
|
|
|
|
|
|
|
depth => $depth - 1, |
1449
|
|
|
|
|
|
|
indent => $INDENT x ($depth - 1), |
1450
|
|
|
|
|
|
|
}) |
1451
|
|
|
|
|
|
|
. ($paren_data->{is_nonbacktrack} |
1452
|
|
|
|
|
|
|
? '|' |
1453
|
|
|
|
|
|
|
. _build_event($regex_ID, $event_ID => { |
1454
|
|
|
|
|
|
|
%std_info, |
1455
|
0
|
|
|
|
|
|
%{$paren_data}, |
1456
|
|
|
|
|
|
|
event_type => 'failed_nonbacktracking', |
1457
|
|
|
|
|
|
|
msg => 'non-backtracking group', |
1458
|
|
|
|
|
|
|
depth => $depth - 1, |
1459
|
|
|
|
|
|
|
indent => $INDENT x ($depth - 1), |
1460
|
|
|
|
|
|
|
}) |
1461
|
|
|
|
|
|
|
. q{(?!)} |
1462
|
|
|
|
|
|
|
: q{} |
1463
|
|
|
|
|
|
|
) |
1464
|
|
|
|
|
|
|
. ')' |
1465
|
0
|
0
|
|
|
|
|
. $std_info{quantifier}; |
|
|
0
|
|
|
|
|
|
1466
|
|
|
|
|
|
|
} |
1467
|
|
|
|
|
|
|
|
1468
|
|
|
|
|
|
|
# Skip comments... |
1469
|
|
|
|
|
|
|
elsif (exists $+{break_comment}) { |
1470
|
|
|
|
|
|
|
# Insert an event reporting that the break comment is being skipped... |
1471
|
|
|
|
|
|
|
_build_event($regex_ID, $event_ID => { |
1472
|
|
|
|
|
|
|
%std_info, |
1473
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
|
0
|
|
|
|
|
|
|
1474
|
|
|
|
|
|
|
matchable => 0, |
1475
|
|
|
|
|
|
|
event_type => 'break', |
1476
|
|
|
|
|
|
|
msg => 'Breaking at (and skipping) comment', |
1477
|
|
|
|
|
|
|
desc => 'Ignore this comment (but Regexp::Debugger will break here)', |
1478
|
|
|
|
|
|
|
}) |
1479
|
|
|
|
|
|
|
} |
1480
|
|
|
|
|
|
|
|
1481
|
|
|
|
|
|
|
# Skip comments... |
1482
|
|
|
|
|
|
|
elsif (exists $+{comment}) { |
1483
|
|
|
|
|
|
|
# Insert an event reporting that the comment is being skipped... |
1484
|
|
|
|
|
|
|
_build_event($regex_ID, $event_ID => { |
1485
|
|
|
|
|
|
|
%std_info, |
1486
|
0
|
|
|
|
|
|
%{$paren_stack[-1]}, |
|
0
|
|
|
|
|
|
|
1487
|
|
|
|
|
|
|
matchable => 0, |
1488
|
|
|
|
|
|
|
event_type => 'skip', |
1489
|
|
|
|
|
|
|
msg => 'Skipping comment', |
1490
|
|
|
|
|
|
|
desc => 'Ignore this comment', |
1491
|
|
|
|
|
|
|
}) |
1492
|
|
|
|
|
|
|
} |
1493
|
|
|
|
|
|
|
|
1494
|
|
|
|
|
|
|
# Ignore (but preserve) anything else... |
1495
|
|
|
|
|
|
|
else { |
1496
|
0
|
|
|
|
|
|
$construct; |
1497
|
|
|
|
|
|
|
} |
1498
|
|
|
|
|
|
|
}; |
1499
|
|
|
|
|
|
|
}exmsg; |
1500
|
|
|
|
|
|
|
|
1501
|
|
|
|
|
|
|
# Remember the regex... |
1502
|
0
|
|
|
|
|
|
$state{$regex_ID}{regex_src} = $clean_regex; |
1503
|
|
|
|
|
|
|
|
1504
|
|
|
|
|
|
|
# Add a preface to reset state variables in the event handler... |
1505
|
0
|
|
|
|
|
|
$raw_regex = '(?>\A(?{Regexp::Debugger::_reset_debugger_state()})(?!)' |
1506
|
|
|
|
|
|
|
. '|\G(?{Regexp::Debugger::_reset_debugger_state_rematch()})(?!))' |
1507
|
|
|
|
|
|
|
. "|(?:$raw_regex)"; |
1508
|
|
|
|
|
|
|
|
1509
|
|
|
|
|
|
|
# say "(?#R_d:$regex_ID)".$raw_regex; |
1510
|
0
|
|
|
|
|
|
return "(?#R_d:$regex_ID)".$raw_regex; |
1511
|
|
|
|
|
|
|
} |
1512
|
|
|
|
|
|
|
|
1513
|
|
|
|
|
|
|
#====[ Dispatch in-regex events ]================================ |
1514
|
|
|
|
|
|
|
|
1515
|
|
|
|
|
|
|
# How big the display window is... |
1516
|
|
|
|
|
|
|
my $MAX_WIDTH = 80; |
1517
|
|
|
|
|
|
|
my $MAX_HEIGHT = 60; |
1518
|
|
|
|
|
|
|
|
1519
|
|
|
|
|
|
|
# What to print so as to "clear" the screen... |
1520
|
|
|
|
|
|
|
my $CLEAR_SCREEN = "\n" x $MAX_HEIGHT; |
1521
|
|
|
|
|
|
|
|
1522
|
|
|
|
|
|
|
# How wide is each column in event mode... |
1523
|
|
|
|
|
|
|
my $EVENT_COL_WIDTH = 15; |
1524
|
|
|
|
|
|
|
|
1525
|
|
|
|
|
|
|
|
1526
|
|
|
|
|
|
|
sub _record_event { |
1527
|
0
|
|
|
0
|
|
|
my ($data_mode, $event_desc) = @_; |
1528
|
|
|
|
|
|
|
|
1529
|
|
|
|
|
|
|
# Accumulate history... |
1530
|
|
|
|
|
|
|
my $history_to_date |
1531
|
0
|
0
|
0
|
|
|
|
= @{$history_of{$data_mode}//[]} ? $history_of{$data_mode}[-1]{display} : q{}; |
|
0
|
|
|
|
|
|
|
1532
|
|
|
|
|
|
|
|
1533
|
|
|
|
|
|
|
# Remember, always.... |
1534
|
0
|
|
|
|
|
|
push @{$history_of{$data_mode}}, { |
|
0
|
|
|
|
|
|
|
1535
|
|
|
|
|
|
|
display => $history_to_date . $event_desc . "\n" |
1536
|
|
|
|
|
|
|
}; |
1537
|
|
|
|
|
|
|
} |
1538
|
|
|
|
|
|
|
|
1539
|
|
|
|
|
|
|
sub _show_if_active { |
1540
|
0
|
|
|
0
|
|
|
my ($data_mode, $display_mode, $event_desc) = @_; |
1541
|
|
|
|
|
|
|
|
1542
|
|
|
|
|
|
|
# Show, if appropriate... |
1543
|
0
|
0
|
|
|
|
|
if ($display_mode eq $data_mode) { |
1544
|
0
|
0
|
0
|
|
|
|
if (!$lexical_config->{save_to_fh} || $data_mode ne 'JSON') { |
1545
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
1546
|
0
|
|
|
|
|
|
_say $history_of{$data_mode}[-1]{display}; |
1547
|
|
|
|
|
|
|
} |
1548
|
|
|
|
|
|
|
} |
1549
|
|
|
|
|
|
|
} |
1550
|
|
|
|
|
|
|
|
1551
|
0
|
|
|
0
|
|
|
sub _show_JSON { _show_if_active('JSON', @_) } |
1552
|
0
|
|
|
0
|
|
|
sub _show_event { _show_if_active('events', @_) } |
1553
|
|
|
|
|
|
|
|
1554
|
|
|
|
|
|
|
|
1555
|
|
|
|
|
|
|
# Add a new animation "frame"... |
1556
|
|
|
|
|
|
|
sub _new_visualize { |
1557
|
0
|
|
|
0
|
|
|
our $subpattern_depth; |
1558
|
0
|
|
|
|
|
|
my ($data_mode) = @_; |
1559
|
0
|
|
|
|
|
|
push @{$history_of{$data_mode}}, { display=>q{}, is_match => 0, depth => $subpattern_depth }; |
|
0
|
|
|
|
|
|
|
1560
|
|
|
|
|
|
|
} |
1561
|
|
|
|
|
|
|
|
1562
|
|
|
|
|
|
|
# Output the args and also add them to the current animation "frame" |
1563
|
|
|
|
|
|
|
sub _visualize { |
1564
|
0
|
|
|
0
|
|
|
my ($data_mode, @output) = @_; |
1565
|
0
|
|
|
|
|
|
state $NO_MATCH = 0; |
1566
|
0
|
|
|
|
|
|
state $NO_FAIL = 0; |
1567
|
0
|
|
|
|
|
|
_visualize_matchfail($data_mode, $NO_MATCH, $NO_FAIL, @output); |
1568
|
|
|
|
|
|
|
} |
1569
|
|
|
|
|
|
|
|
1570
|
|
|
|
|
|
|
sub _visualize_matchfail { |
1571
|
0
|
|
|
0
|
|
|
my ($data_mode, $is_match, $is_fail, @output) = @_; |
1572
|
0
|
|
|
|
|
|
my $output = join q{}, grep {defined} @output; |
|
0
|
|
|
|
|
|
|
1573
|
|
|
|
|
|
|
|
1574
|
0
|
0
|
|
|
|
|
$history_of{$data_mode}[-1]{is_fail} = 1 if $is_fail; |
1575
|
0
|
0
|
|
|
|
|
$history_of{$data_mode}[-1]{is_match} = 1 if $is_match; |
1576
|
0
|
|
|
|
|
|
$history_of{$data_mode}[-1]{display} .= $output . "\n"; |
1577
|
|
|
|
|
|
|
} |
1578
|
|
|
|
|
|
|
|
1579
|
|
|
|
|
|
|
# Show previous animation frames... |
1580
|
|
|
|
|
|
|
sub _revisualize { |
1581
|
0
|
|
|
0
|
|
|
my ($regex_ID, $input, $step) = @_; |
1582
|
|
|
|
|
|
|
|
1583
|
|
|
|
|
|
|
# Start at the previous step unless otherwise specified... |
1584
|
0
|
|
0
|
|
|
|
$step //= max(0, @{$history_of{$display_mode}}-2); |
|
0
|
|
|
|
|
|
|
1585
|
|
|
|
|
|
|
|
1586
|
|
|
|
|
|
|
STEP: |
1587
|
0
|
|
|
|
|
|
while (1) { |
1588
|
|
|
|
|
|
|
# Did we fall out of available history??? |
1589
|
0
|
0
|
|
|
|
|
last STEP if $step >= @{$history_of{$display_mode}}; |
|
0
|
|
|
|
|
|
|
1590
|
|
|
|
|
|
|
|
1591
|
|
|
|
|
|
|
# A terminates the process... |
1592
|
0
|
0
|
0
|
|
|
|
if ($input eq "\cC") { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1593
|
0
|
|
|
|
|
|
kill 9, $$; |
1594
|
|
|
|
|
|
|
} |
1595
|
|
|
|
|
|
|
|
1596
|
|
|
|
|
|
|
# An 'x' exits the process... |
1597
|
|
|
|
|
|
|
elsif ($input eq 'x') { |
1598
|
0
|
|
|
|
|
|
exit(0); |
1599
|
|
|
|
|
|
|
} |
1600
|
|
|
|
|
|
|
|
1601
|
|
|
|
|
|
|
# A redraws the screen at the current step... |
1602
|
|
|
|
|
|
|
elsif ($input eq "\cL") { |
1603
|
|
|
|
|
|
|
# Do nothing else |
1604
|
|
|
|
|
|
|
} |
1605
|
|
|
|
|
|
|
|
1606
|
|
|
|
|
|
|
# Step back (if possible)... |
1607
|
|
|
|
|
|
|
elsif ($input eq '-') { |
1608
|
0
|
|
|
|
|
|
$step = max(0, $step-1); |
1609
|
|
|
|
|
|
|
} |
1610
|
|
|
|
|
|
|
|
1611
|
|
|
|
|
|
|
# Display explanation of regex... |
1612
|
|
|
|
|
|
|
elsif ($input eq 'd') { |
1613
|
0
|
|
|
|
|
|
_show_regex_description($regex_ID); |
1614
|
|
|
|
|
|
|
} |
1615
|
|
|
|
|
|
|
|
1616
|
|
|
|
|
|
|
# Help! |
1617
|
|
|
|
|
|
|
elsif ($input eq '?') { |
1618
|
0
|
|
|
|
|
|
_show_help(); |
1619
|
|
|
|
|
|
|
} |
1620
|
|
|
|
|
|
|
|
1621
|
|
|
|
|
|
|
# Swap to requested mode... |
1622
|
|
|
|
|
|
|
elsif ($input eq 'v') { |
1623
|
0
|
|
|
|
|
|
$display_mode = 'visual'; |
1624
|
|
|
|
|
|
|
} |
1625
|
|
|
|
|
|
|
|
1626
|
|
|
|
|
|
|
elsif ($input eq 'h') { |
1627
|
|
|
|
|
|
|
# Can we use heatmap mode? |
1628
|
0
|
0
|
|
|
|
|
if ($heatmaps_invisible) { |
1629
|
0
|
|
|
|
|
|
say 'Cannot show heatmaps (Term::ANSIColor unavailable)'; |
1630
|
0
|
|
|
|
|
|
say "Try 'H' instead"; |
1631
|
0
|
|
|
|
|
|
$input = '?'; |
1632
|
|
|
|
|
|
|
} |
1633
|
|
|
|
|
|
|
# If heatmaps available, check for misuse of 'h' instead of '?'... |
1634
|
|
|
|
|
|
|
else { |
1635
|
0
|
|
|
|
|
|
my $prompt_help = $display_mode eq 'heatmap'; |
1636
|
0
|
|
|
|
|
|
$display_mode = 'heatmap'; |
1637
|
0
|
0
|
|
|
|
|
if ($prompt_help) { |
1638
|
0
|
|
|
|
|
|
say "(Type '?' for help)"; |
1639
|
|
|
|
|
|
|
} |
1640
|
|
|
|
|
|
|
} |
1641
|
|
|
|
|
|
|
} |
1642
|
|
|
|
|
|
|
|
1643
|
|
|
|
|
|
|
elsif ($input eq 'e') { |
1644
|
0
|
|
|
|
|
|
$display_mode = 'events'; |
1645
|
|
|
|
|
|
|
# say _info_colourer( |
1646
|
|
|
|
|
|
|
# qq{\n\n[Events of regex at $state{$regex_ID}{location}]} |
1647
|
|
|
|
|
|
|
# . qq{ [step: $step]} |
1648
|
|
|
|
|
|
|
# ); |
1649
|
|
|
|
|
|
|
} |
1650
|
|
|
|
|
|
|
|
1651
|
|
|
|
|
|
|
elsif ($input eq 'j') { |
1652
|
0
|
|
|
|
|
|
$display_mode = 'JSON'; |
1653
|
|
|
|
|
|
|
# say _info_colourer( |
1654
|
|
|
|
|
|
|
# qq{\n\n[JSON data of regex at $state{$regex_ID}{location}]} |
1655
|
|
|
|
|
|
|
# . qq{ [step: $step]} |
1656
|
|
|
|
|
|
|
# ); |
1657
|
|
|
|
|
|
|
} |
1658
|
|
|
|
|
|
|
|
1659
|
|
|
|
|
|
|
# Quit entirely... |
1660
|
|
|
|
|
|
|
elsif ($input eq 'q' || $input eq "\cD") { |
1661
|
0
|
|
|
|
|
|
last STEP; |
1662
|
|
|
|
|
|
|
} |
1663
|
|
|
|
|
|
|
|
1664
|
|
|
|
|
|
|
# Take a snapshot... |
1665
|
0
|
|
|
|
|
|
elsif ($input eq 'V') { _save_snapshot('full_visual', $step); } |
1666
|
0
|
|
|
|
|
|
elsif ($input eq 'H') { _save_snapshot('full_heatmap', $step); } |
1667
|
0
|
|
|
|
|
|
elsif ($input eq 'E') { _save_snapshot('events', $step); } |
1668
|
0
|
|
|
|
|
|
elsif ($input eq 'J') { _save_snapshot('JSON', $step); } |
1669
|
0
|
|
|
|
|
|
elsif ($input eq 'D') { _show_regex_description($regex_ID,'save'); } |
1670
|
|
|
|
|
|
|
|
1671
|
|
|
|
|
|
|
# Step forward until end... |
1672
|
|
|
|
|
|
|
elsif ($input eq 'c') { |
1673
|
0
|
|
|
|
|
|
my $skip_duration = $MAX_SKIP_DURATION; |
1674
|
|
|
|
|
|
|
|
1675
|
0
|
|
|
|
|
|
while (1) { |
1676
|
0
|
|
|
|
|
|
$step++; |
1677
|
0
|
0
|
|
|
|
|
last STEP if $step >= @{$history_of{$display_mode}}-1; |
|
0
|
|
|
|
|
|
|
1678
|
|
|
|
|
|
|
|
1679
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
1680
|
0
|
|
|
|
|
|
_print $history_of{$display_mode}[$step]{display}; |
1681
|
0
|
|
|
|
|
|
_pause($skip_duration); |
1682
|
0
|
|
|
|
|
|
$skip_duration = max($MIN_SKIP_DURATION, $skip_duration * $SKIP_ACCELERATION); |
1683
|
|
|
|
|
|
|
} |
1684
|
|
|
|
|
|
|
} |
1685
|
|
|
|
|
|
|
|
1686
|
|
|
|
|
|
|
elsif ($input eq 'C') { |
1687
|
0
|
|
|
|
|
|
$interaction_depth = $history_of{$display_mode}[$step]{depth}; |
1688
|
0
|
|
|
|
|
|
my $skip_duration = $MAX_SKIP_DURATION; |
1689
|
|
|
|
|
|
|
|
1690
|
0
|
|
|
|
|
|
while (1) { |
1691
|
0
|
|
|
|
|
|
$step++; |
1692
|
0
|
0
|
|
|
|
|
last STEP if $step >= @{$history_of{$display_mode}}-1; |
|
0
|
|
|
|
|
|
|
1693
|
|
|
|
|
|
|
|
1694
|
0
|
|
|
|
|
|
my $event = $history_of{$display_mode}[$step]; |
1695
|
0
|
|
0
|
|
|
|
my $depth = $event->{depth} // 0; |
1696
|
|
|
|
|
|
|
|
1697
|
0
|
0
|
|
|
|
|
if ($depth <= $interaction_depth) { |
1698
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
1699
|
0
|
|
|
|
|
|
_print $event->{display}; |
1700
|
0
|
|
|
|
|
|
_pause($skip_duration); |
1701
|
0
|
|
|
|
|
|
$skip_duration = max($MIN_SKIP_DURATION, $skip_duration * $SKIP_ACCELERATION); |
1702
|
|
|
|
|
|
|
} |
1703
|
|
|
|
|
|
|
} |
1704
|
|
|
|
|
|
|
} |
1705
|
|
|
|
|
|
|
|
1706
|
|
|
|
|
|
|
|
1707
|
|
|
|
|
|
|
# Step forward to match... |
1708
|
|
|
|
|
|
|
elsif ($input eq 'm') { |
1709
|
0
|
|
|
|
|
|
my $skip_duration = $MAX_SKIP_DURATION; |
1710
|
|
|
|
|
|
|
|
1711
|
|
|
|
|
|
|
SEARCH: |
1712
|
0
|
|
|
|
|
|
while (1) { |
1713
|
0
|
|
|
|
|
|
$step++; |
1714
|
0
|
0
|
|
|
|
|
last STEP if $step >= @{$history_of{$display_mode}}-1; |
|
0
|
|
|
|
|
|
|
1715
|
0
|
0
|
|
|
|
|
last SEARCH if $history_of{$display_mode}[$step]{is_match}; |
1716
|
|
|
|
|
|
|
|
1717
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
1718
|
0
|
|
|
|
|
|
_print $history_of{$display_mode}[$step]{display}; |
1719
|
0
|
|
|
|
|
|
_pause($skip_duration); |
1720
|
0
|
|
|
|
|
|
$skip_duration = max($MIN_SKIP_DURATION, $skip_duration * $SKIP_ACCELERATION); |
1721
|
|
|
|
|
|
|
} |
1722
|
|
|
|
|
|
|
} |
1723
|
|
|
|
|
|
|
|
1724
|
|
|
|
|
|
|
elsif ($input eq 'M') { |
1725
|
0
|
|
|
|
|
|
$interaction_depth = $history_of{$display_mode}[$step]{depth}; |
1726
|
0
|
|
|
|
|
|
my $skip_duration = $MAX_SKIP_DURATION; |
1727
|
|
|
|
|
|
|
|
1728
|
|
|
|
|
|
|
SEARCH: |
1729
|
0
|
|
|
|
|
|
while (1) { |
1730
|
0
|
|
|
|
|
|
$step++; |
1731
|
0
|
0
|
|
|
|
|
last STEP if $step >= @{$history_of{$display_mode}}-1; |
|
0
|
|
|
|
|
|
|
1732
|
|
|
|
|
|
|
|
1733
|
0
|
|
|
|
|
|
my $event = $history_of{$display_mode}[$step]; |
1734
|
0
|
|
0
|
|
|
|
my $depth = $event->{depth} // 0; |
1735
|
0
|
0
|
0
|
|
|
|
last SEARCH if $event->{is_match} && $depth <= $interaction_depth; |
1736
|
|
|
|
|
|
|
|
1737
|
0
|
0
|
|
|
|
|
if ($depth <= $interaction_depth) { |
1738
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
1739
|
0
|
|
|
|
|
|
_print $event->{display}; |
1740
|
0
|
|
|
|
|
|
_pause($skip_duration); |
1741
|
0
|
|
|
|
|
|
$skip_duration = max($MIN_SKIP_DURATION, $skip_duration * $SKIP_ACCELERATION); |
1742
|
|
|
|
|
|
|
} |
1743
|
|
|
|
|
|
|
} |
1744
|
|
|
|
|
|
|
} |
1745
|
|
|
|
|
|
|
|
1746
|
|
|
|
|
|
|
# Step forward to fail... |
1747
|
|
|
|
|
|
|
elsif ($input eq 'f') { |
1748
|
0
|
|
|
|
|
|
$interaction_depth = $history_of{$display_mode}[$step]{depth}; |
1749
|
0
|
|
|
|
|
|
my $skip_duration = $MAX_SKIP_DURATION; |
1750
|
|
|
|
|
|
|
|
1751
|
|
|
|
|
|
|
SEARCH: |
1752
|
0
|
|
|
|
|
|
while (1) { |
1753
|
0
|
|
|
|
|
|
$step++; |
1754
|
0
|
0
|
|
|
|
|
last STEP if $step >= @{$history_of{$display_mode}}-1; |
|
0
|
|
|
|
|
|
|
1755
|
0
|
0
|
|
|
|
|
last SEARCH if $history_of{$display_mode}[$step]{is_fail}; |
1756
|
|
|
|
|
|
|
|
1757
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
1758
|
0
|
|
|
|
|
|
_print $history_of{$display_mode}[$step]{display}; |
1759
|
0
|
|
|
|
|
|
_pause($skip_duration); |
1760
|
0
|
|
|
|
|
|
$skip_duration = max($MIN_SKIP_DURATION, $skip_duration * $SKIP_ACCELERATION); |
1761
|
|
|
|
|
|
|
} |
1762
|
|
|
|
|
|
|
} |
1763
|
|
|
|
|
|
|
|
1764
|
|
|
|
|
|
|
elsif ($input eq 'F') { |
1765
|
0
|
|
|
|
|
|
$interaction_depth = $history_of{$display_mode}[$step]{depth}; |
1766
|
0
|
|
|
|
|
|
my $skip_duration = $MAX_SKIP_DURATION; |
1767
|
|
|
|
|
|
|
|
1768
|
|
|
|
|
|
|
SEARCH: |
1769
|
0
|
|
|
|
|
|
while (1) { |
1770
|
0
|
|
|
|
|
|
$step++; |
1771
|
0
|
0
|
|
|
|
|
last STEP if $step >= @{$history_of{$display_mode}}-1; |
|
0
|
|
|
|
|
|
|
1772
|
|
|
|
|
|
|
|
1773
|
0
|
|
|
|
|
|
my $event = $history_of{$display_mode}[$step]; |
1774
|
0
|
|
0
|
|
|
|
my $depth = $event->{depth} // 0; |
1775
|
0
|
0
|
0
|
|
|
|
last SEARCH if $event->{is_fail} && $depth <= $interaction_depth; |
1776
|
|
|
|
|
|
|
|
1777
|
0
|
0
|
|
|
|
|
if ($depth <= $interaction_depth) { |
1778
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
1779
|
0
|
|
|
|
|
|
_print $event->{display}; |
1780
|
0
|
|
|
|
|
|
_pause($skip_duration); |
1781
|
0
|
|
|
|
|
|
$skip_duration = max($MIN_SKIP_DURATION, $skip_duration * $SKIP_ACCELERATION); |
1782
|
|
|
|
|
|
|
} |
1783
|
|
|
|
|
|
|
} |
1784
|
|
|
|
|
|
|
} |
1785
|
|
|
|
|
|
|
|
1786
|
|
|
|
|
|
|
# Return from current subpattern... |
1787
|
|
|
|
|
|
|
elsif ($input eq 'r') { |
1788
|
0
|
|
|
|
|
|
$interaction_depth = $history_of{$display_mode}[$step]{depth}; |
1789
|
0
|
|
|
|
|
|
my $skip_duration = $MAX_SKIP_DURATION; |
1790
|
|
|
|
|
|
|
|
1791
|
|
|
|
|
|
|
SEARCH: |
1792
|
0
|
|
|
|
|
|
while (1) { |
1793
|
0
|
|
|
|
|
|
$step++; |
1794
|
0
|
0
|
|
|
|
|
last STEP if $step >= @{$history_of{$display_mode}}-1; |
|
0
|
|
|
|
|
|
|
1795
|
0
|
0
|
|
|
|
|
last SEARCH if $history_of{$display_mode}[$step]{depth} < $interaction_depth; |
1796
|
|
|
|
|
|
|
} |
1797
|
|
|
|
|
|
|
} |
1798
|
|
|
|
|
|
|
|
1799
|
|
|
|
|
|
|
# Step forward, skipping subpatterns... |
1800
|
|
|
|
|
|
|
elsif ($input eq 'n') { |
1801
|
0
|
|
|
|
|
|
$interaction_depth = $history_of{$display_mode}[$step]{depth}; |
1802
|
0
|
|
|
|
|
|
$step++; |
1803
|
0
|
0
|
|
|
|
|
last STEP if $step >= @{$history_of{$display_mode}}-1; |
|
0
|
|
|
|
|
|
|
1804
|
0
|
|
|
|
|
|
while ($history_of{$display_mode}[$step]{depth} > $interaction_depth) { |
1805
|
0
|
0
|
|
|
|
|
last STEP if $step >= @{$history_of{$display_mode}}-1; |
|
0
|
|
|
|
|
|
|
1806
|
0
|
|
|
|
|
|
$step++; |
1807
|
|
|
|
|
|
|
} |
1808
|
|
|
|
|
|
|
} |
1809
|
|
|
|
|
|
|
|
1810
|
|
|
|
|
|
|
# Step back, skipping subpatterns... |
1811
|
|
|
|
|
|
|
elsif ($input eq 'p') { |
1812
|
0
|
|
|
|
|
|
$interaction_depth = $history_of{$display_mode}[$step+1]{depth}; |
1813
|
0
|
|
|
|
|
|
$step = max(0, $step-1); |
1814
|
0
|
|
|
|
|
|
until ($history_of{$display_mode}[$step]{depth} <= $interaction_depth) { |
1815
|
0
|
|
|
|
|
|
$step = max(0, $step-1); |
1816
|
|
|
|
|
|
|
} |
1817
|
|
|
|
|
|
|
} |
1818
|
|
|
|
|
|
|
|
1819
|
|
|
|
|
|
|
# Step all the way back, skipping subpatterns... |
1820
|
|
|
|
|
|
|
elsif ($input eq 'R') { |
1821
|
0
|
|
|
|
|
|
$interaction_depth = $history_of{$display_mode}[0]{depth}; |
1822
|
0
|
|
|
|
|
|
$step = 0; |
1823
|
|
|
|
|
|
|
} |
1824
|
|
|
|
|
|
|
|
1825
|
|
|
|
|
|
|
|
1826
|
|
|
|
|
|
|
# Otherwise just step forward... |
1827
|
|
|
|
|
|
|
else { |
1828
|
0
|
|
|
|
|
|
$step++; |
1829
|
|
|
|
|
|
|
} |
1830
|
|
|
|
|
|
|
|
1831
|
|
|
|
|
|
|
# Clear display and show the requested step... |
1832
|
0
|
0
|
|
|
|
|
if ($input ne '?') { |
1833
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
1834
|
0
|
|
|
|
|
|
_print $history_of{$display_mode}[$step]{display}; |
1835
|
0
|
0
|
0
|
|
|
|
if ($display_mode eq 'events' || $display_mode eq 'JSON') { |
1836
|
0
|
0
|
|
|
|
|
if (!$lexical_config->{save_to_fh}) { |
1837
|
0
|
|
|
|
|
|
say _info_colourer( |
1838
|
|
|
|
|
|
|
qq{\n\n[\u$display_mode of regex at $state{$regex_ID}{location}]} |
1839
|
|
|
|
|
|
|
. qq{ [step: $step]} |
1840
|
|
|
|
|
|
|
); |
1841
|
|
|
|
|
|
|
} |
1842
|
|
|
|
|
|
|
} |
1843
|
|
|
|
|
|
|
} |
1844
|
|
|
|
|
|
|
|
1845
|
|
|
|
|
|
|
# Next input (but use starting cmd if one given)... |
1846
|
0
|
|
|
|
|
|
$input = _interact(); |
1847
|
|
|
|
|
|
|
|
1848
|
|
|
|
|
|
|
} |
1849
|
|
|
|
|
|
|
|
1850
|
|
|
|
|
|
|
# Update the screen... |
1851
|
0
|
0
|
|
|
|
|
if (defined $history_of{$display_mode}[$step]{display}) { |
1852
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
1853
|
0
|
|
|
|
|
|
_print $history_of{$display_mode}[$step]{display}; |
1854
|
|
|
|
|
|
|
} |
1855
|
|
|
|
|
|
|
|
1856
|
|
|
|
|
|
|
# Return final command... |
1857
|
0
|
|
|
|
|
|
return ($input, $step); |
1858
|
|
|
|
|
|
|
} |
1859
|
|
|
|
|
|
|
|
1860
|
|
|
|
|
|
|
sub _build_visualization { |
1861
|
|
|
|
|
|
|
# Unpack all the info needed... |
1862
|
0
|
|
|
0
|
|
|
my ($data_mode, $named_args_ref) = @_; |
1863
|
|
|
|
|
|
|
|
1864
|
|
|
|
|
|
|
my ($regex_ID, $regex_src, $regex_pos, $construct_len, |
1865
|
|
|
|
|
|
|
$str_src, $str_pos, |
1866
|
|
|
|
|
|
|
$is_match, $is_fail, $is_trying, $is_capture, |
1867
|
|
|
|
|
|
|
$backtrack, $forward_step, $nested_because, |
1868
|
|
|
|
|
|
|
$msg, $colourer, $no_window, $step) |
1869
|
0
|
|
|
|
|
|
= @{$named_args_ref}{qw( |
|
0
|
|
|
|
|
|
|
1870
|
|
|
|
|
|
|
regex_ID regex_src regex_pos construct_len |
1871
|
|
|
|
|
|
|
str_src str_pos |
1872
|
|
|
|
|
|
|
is_match is_fail is_trying is_capture |
1873
|
|
|
|
|
|
|
backtrack forward_step nested_because |
1874
|
|
|
|
|
|
|
msg colourer no_window step |
1875
|
|
|
|
|
|
|
)}; |
1876
|
|
|
|
|
|
|
|
1877
|
|
|
|
|
|
|
# Clear screen... |
1878
|
0
|
|
|
|
|
|
_new_visualize($data_mode); |
1879
|
0
|
0
|
|
|
|
|
if (!$no_window) { |
1880
|
0
|
|
|
|
|
|
_visualize $data_mode, q{} for 1..$MAX_HEIGHT; |
1881
|
|
|
|
|
|
|
} |
1882
|
|
|
|
|
|
|
|
1883
|
|
|
|
|
|
|
# Remember originals... |
1884
|
0
|
|
|
|
|
|
my $raw_str_src = $str_src; |
1885
|
0
|
|
|
|
|
|
my $raw_regex_src = $regex_src; |
1886
|
|
|
|
|
|
|
|
1887
|
|
|
|
|
|
|
# Unwindowed displays show the title first... |
1888
|
0
|
0
|
|
|
|
|
if ($no_window) { |
1889
|
0
|
|
|
|
|
|
_visualize $data_mode, |
1890
|
|
|
|
|
|
|
_info_colourer( |
1891
|
|
|
|
|
|
|
qq{\n[\u$data_mode of regex at $state{$regex_ID}{location}]\n\n} |
1892
|
|
|
|
|
|
|
. qq{ [step: $step]} |
1893
|
|
|
|
|
|
|
); |
1894
|
|
|
|
|
|
|
} |
1895
|
|
|
|
|
|
|
|
1896
|
|
|
|
|
|
|
# Visualize capture vars, if available... |
1897
|
0
|
|
|
|
|
|
my $max_name_width = 1 + max map {length} 0, keys %capture; |
|
0
|
|
|
|
|
|
|
1898
|
|
|
|
|
|
|
CAPVAR: |
1899
|
1
|
|
|
1
|
|
9
|
for my $name (do{ no warnings 'numeric'; sort { substr($a,1) <=> substr($b,1) } keys %capture}) { |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2182
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
1900
|
|
|
|
|
|
|
# Remove any captures that are invalidated by backtracking... |
1901
|
0
|
0
|
|
|
|
|
if ($capture{$name}{start_pos} > $regex_pos) { |
1902
|
0
|
|
|
|
|
|
delete @{$capture{$name}}{'from','to'}; |
|
0
|
|
|
|
|
|
|
1903
|
|
|
|
|
|
|
} |
1904
|
|
|
|
|
|
|
|
1905
|
|
|
|
|
|
|
# Clean up and visualize each remaining variable... |
1906
|
0
|
|
0
|
|
|
|
my $start = $capture{$name}{from} // next CAPVAR; |
1907
|
0
|
|
0
|
|
|
|
my $end = $capture{$name}{to} // next CAPVAR; |
1908
|
0
|
|
|
|
|
|
my $cap_str = _quote_ws(substr($_,$start,$end-$start)); |
1909
|
|
|
|
|
|
|
|
1910
|
|
|
|
|
|
|
# Truncate captured value to maximum width by removing middle... |
1911
|
0
|
|
|
|
|
|
my $cap_len = length($cap_str); |
1912
|
0
|
0
|
|
|
|
|
if ($cap_len > $MAX_WIDTH) { |
1913
|
0
|
|
|
|
|
|
my $middle = $MAX_WIDTH/2 - 2; |
1914
|
0
|
|
|
|
|
|
substr($cap_str, $middle, -$middle, '....'); |
1915
|
|
|
|
|
|
|
} |
1916
|
|
|
|
|
|
|
|
1917
|
|
|
|
|
|
|
# Display capture var and value... |
1918
|
0
|
|
|
|
|
|
_visualize $data_mode, |
1919
|
|
|
|
|
|
|
_info_colourer(sprintf qq{%*s = '%s'}, $max_name_width, $name, $cap_str); |
1920
|
|
|
|
|
|
|
} |
1921
|
|
|
|
|
|
|
|
1922
|
|
|
|
|
|
|
# Visualize special var, if used in regex... |
1923
|
0
|
|
|
|
|
|
_visualize $data_mode, q{}; |
1924
|
0
|
0
|
0
|
|
|
|
if (index($raw_regex_src, '$^N') >= 0 && defined $^N) { |
1925
|
0
|
|
|
|
|
|
my $special_val = $^N; |
1926
|
|
|
|
|
|
|
|
1927
|
|
|
|
|
|
|
# Truncate captured value to maximum width by removing middle... |
1928
|
0
|
|
|
|
|
|
my $cap_len = length($special_val); |
1929
|
0
|
0
|
|
|
|
|
if ($cap_len > $MAX_WIDTH) { |
1930
|
0
|
|
|
|
|
|
my $middle = $MAX_WIDTH/2 - 2; |
1931
|
0
|
|
|
|
|
|
substr($special_val, $middle, -$middle, '....'); |
1932
|
|
|
|
|
|
|
} |
1933
|
|
|
|
|
|
|
|
1934
|
|
|
|
|
|
|
# Display capture var and value... |
1935
|
0
|
|
|
|
|
|
_visualize $data_mode, |
1936
|
|
|
|
|
|
|
_info_colourer(sprintf qq{%*s = '%s'}, $max_name_width, '$^N', $special_val); |
1937
|
|
|
|
|
|
|
} |
1938
|
|
|
|
|
|
|
|
1939
|
|
|
|
|
|
|
# Leave a gap... |
1940
|
0
|
|
|
|
|
|
_visualize $data_mode, q{} for 1..2; |
1941
|
|
|
|
|
|
|
|
1942
|
|
|
|
|
|
|
# Show matching... |
1943
|
0
|
|
|
|
|
|
_visualize_matchfail $data_mode, $is_match, $is_fail; |
1944
|
|
|
|
|
|
|
|
1945
|
|
|
|
|
|
|
# Reconfigure regex within visible window... |
1946
|
|
|
|
|
|
|
($regex_src, $regex_pos) |
1947
|
|
|
|
|
|
|
= _make_window( |
1948
|
|
|
|
|
|
|
text => $regex_src, |
1949
|
|
|
|
|
|
|
pos => $regex_pos, |
1950
|
0
|
0
|
|
|
|
|
heat => substr($data_mode, -7) eq 'heatmap' ? $history_of{match_heatmap} : [], |
1951
|
|
|
|
|
|
|
ws_colour => substr($data_mode, -7) eq 'heatmap', |
1952
|
|
|
|
|
|
|
no_window => $no_window, |
1953
|
|
|
|
|
|
|
); |
1954
|
|
|
|
|
|
|
|
1955
|
|
|
|
|
|
|
# How wide is the display??? |
1956
|
0
|
0
|
|
|
|
|
my $display_width |
1957
|
|
|
|
|
|
|
= $no_window ? $regex_pos |
1958
|
|
|
|
|
|
|
: max(0,min($regex_pos, $MAX_WIDTH - length($msg))); |
1959
|
|
|
|
|
|
|
|
1960
|
|
|
|
|
|
|
# Draw the regex with a message and a positional marker... |
1961
|
0
|
0
|
|
|
|
|
if ($data_mode ne 'full_heatmap') { |
1962
|
0
|
|
|
|
|
|
_visualize $data_mode, q{ }, q{ } x $display_width, $colourer->($msg); |
1963
|
0
|
|
|
|
|
|
_visualize $data_mode, q{ }, q{ } x $regex_pos , $colourer->('|'); |
1964
|
0
|
|
0
|
|
|
|
_visualize $data_mode, q{ }, q{ } x $regex_pos , $colourer->('V') x ($construct_len || 1); |
1965
|
|
|
|
|
|
|
} |
1966
|
|
|
|
|
|
|
else { |
1967
|
0
|
|
|
|
|
|
_visualize $data_mode, q{ }, q{ } x $regex_pos , _info_colourer('|'); |
1968
|
0
|
|
0
|
|
|
|
_visualize $data_mode, q{ }, q{ } x $regex_pos , _info_colourer('V' x ($construct_len || 1) ); |
1969
|
|
|
|
|
|
|
} |
1970
|
|
|
|
|
|
|
|
1971
|
|
|
|
|
|
|
# Draw regex itself... |
1972
|
0
|
|
|
|
|
|
_visualize $data_mode, q{/}, $regex_src, q{/}; |
1973
|
|
|
|
|
|
|
|
1974
|
|
|
|
|
|
|
# Leave a gap... |
1975
|
0
|
|
|
|
|
|
_visualize $data_mode, q{ } for 1..2; |
1976
|
|
|
|
|
|
|
|
1977
|
|
|
|
|
|
|
# Create marker for any match or capture within string... |
1978
|
0
|
|
|
|
|
|
$forward_step = min($forward_step, $MAX_WIDTH); |
1979
|
0
|
0
|
0
|
|
|
|
my $last_match_marker |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1980
|
|
|
|
|
|
|
= (q{ } x ($str_pos - max(0,$forward_step))) |
1981
|
|
|
|
|
|
|
. ( $nested_because eq 'failed' ? q{} |
1982
|
|
|
|
|
|
|
: $is_capture && $forward_step == 1 ? 'V' |
1983
|
|
|
|
|
|
|
: $is_capture && $forward_step > 1 ? '\\' . ('_' x ($forward_step-2)) . '/' |
1984
|
|
|
|
|
|
|
: '^' x $forward_step |
1985
|
|
|
|
|
|
|
) |
1986
|
|
|
|
|
|
|
; |
1987
|
|
|
|
|
|
|
|
1988
|
|
|
|
|
|
|
|
1989
|
|
|
|
|
|
|
# Reconfigure string within visible window... |
1990
|
0
|
|
|
|
|
|
my $match_start; |
1991
|
|
|
|
|
|
|
($str_src, $str_pos, $match_start, $last_match_marker) |
1992
|
|
|
|
|
|
|
= _make_window( |
1993
|
|
|
|
|
|
|
text => $str_src, |
1994
|
|
|
|
|
|
|
pos => $str_pos, |
1995
|
|
|
|
|
|
|
start => $Regexp::Grammars::match_start_pos, |
1996
|
0
|
0
|
|
|
|
|
heat => substr($data_mode, -7) eq 'heatmap' ? $history_of{string_heatmap} : [], |
1997
|
|
|
|
|
|
|
ws_colour => substr($data_mode, -7) eq 'heatmap', |
1998
|
|
|
|
|
|
|
marker => $last_match_marker, |
1999
|
|
|
|
|
|
|
no_window => $no_window, |
2000
|
|
|
|
|
|
|
); |
2001
|
|
|
|
|
|
|
|
2002
|
|
|
|
|
|
|
# Trim match start position... |
2003
|
0
|
0
|
|
|
|
|
if ($match_start > $str_pos) { |
2004
|
0
|
|
|
|
|
|
$match_start = $str_pos; |
2005
|
|
|
|
|
|
|
} |
2006
|
|
|
|
|
|
|
|
2007
|
|
|
|
|
|
|
# Colour match marker... |
2008
|
|
|
|
|
|
|
$last_match_marker |
2009
|
0
|
0
|
|
|
|
|
= substr($last_match_marker,0,1) eq '^' ? _match_colourer($last_match_marker, 'reverse') |
2010
|
|
|
|
|
|
|
: _info_colourer($last_match_marker); |
2011
|
|
|
|
|
|
|
|
2012
|
|
|
|
|
|
|
# Draw the string with a positional marker... |
2013
|
0
|
|
|
|
|
|
_visualize $data_mode, |
2014
|
|
|
|
|
|
|
q{ }, _info_colourer( substr(q{ } x $str_pos . '|' . $backtrack, 0, $MAX_WIDTH-2) ); |
2015
|
0
|
|
|
|
|
|
_visualize $data_mode, |
2016
|
|
|
|
|
|
|
q{ }, q{ } x $match_start, _match_colourer($MATCH_DRAG x ($str_pos-$match_start)), _info_colourer('V'); |
2017
|
0
|
0
|
|
|
|
|
$str_src = # Heatmap is already coloured... |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2018
|
|
|
|
|
|
|
substr($data_mode, -7) eq 'heatmap' ? |
2019
|
|
|
|
|
|
|
$str_src |
2020
|
|
|
|
|
|
|
|
2021
|
|
|
|
|
|
|
# On failure, fail-colour to current position... |
2022
|
|
|
|
|
|
|
: $nested_because eq 'failed' ? |
2023
|
|
|
|
|
|
|
_fail_colourer( substr($str_src, 0, $str_pos), 'ws' ) |
2024
|
|
|
|
|
|
|
. _ws_colourer( substr($str_src, $str_pos) ) |
2025
|
|
|
|
|
|
|
|
2026
|
|
|
|
|
|
|
# When trying, try-colour current position |
2027
|
|
|
|
|
|
|
: $is_trying ? |
2028
|
|
|
|
|
|
|
_fail_colourer( substr($str_src, 0, $match_start), 'ws' ) |
2029
|
|
|
|
|
|
|
. _match_colourer( substr($str_src, $match_start, $str_pos-$match_start), 'underline', 'ws' ) |
2030
|
|
|
|
|
|
|
. _try_colourer( substr($str_src, $str_pos, 1), 'underline bold', 'ws' ) |
2031
|
|
|
|
|
|
|
. _ws_colourer( substr($str_src, min(length($str_src),$str_pos+1)) ) |
2032
|
|
|
|
|
|
|
|
2033
|
|
|
|
|
|
|
: # Otherwise, report pre-failure and current match... |
2034
|
|
|
|
|
|
|
_fail_colourer( substr($str_src, 0, $match_start), 'ws' ) |
2035
|
|
|
|
|
|
|
. _match_colourer( substr($str_src, $match_start, $str_pos-$match_start), 'underline', 'ws' ) |
2036
|
|
|
|
|
|
|
. _ws_colourer( substr($str_src, $str_pos) ); |
2037
|
|
|
|
|
|
|
|
2038
|
0
|
|
|
|
|
|
_visualize $data_mode, q{'}, $str_src, q{'}; # String itself |
2039
|
|
|
|
|
|
|
|
2040
|
|
|
|
|
|
|
# Draw a marker for any match or capture within the string... |
2041
|
0
|
|
|
|
|
|
_visualize $data_mode, q{ }, $last_match_marker; |
2042
|
|
|
|
|
|
|
|
2043
|
|
|
|
|
|
|
# Windowed displays show the title last... |
2044
|
0
|
0
|
|
|
|
|
if (!$no_window) { |
2045
|
0
|
|
|
|
|
|
_visualize $data_mode, |
2046
|
|
|
|
|
|
|
_info_colourer( |
2047
|
|
|
|
|
|
|
qq{\n[\u$data_mode of regex at $state{$regex_ID}{location}]} |
2048
|
|
|
|
|
|
|
. qq{ [step: $step]} |
2049
|
|
|
|
|
|
|
); |
2050
|
|
|
|
|
|
|
} |
2051
|
|
|
|
|
|
|
|
2052
|
|
|
|
|
|
|
# Special case: full heatmaps are reported as a table too... |
2053
|
0
|
0
|
|
|
|
|
if ( $data_mode eq 'full_heatmap' ) { |
2054
|
|
|
|
|
|
|
# Tabulate regex... |
2055
|
0
|
|
|
|
|
|
_visualize $data_mode, _info_colourer("\n\nHeatmap for regex:\n"); |
2056
|
0
|
|
|
|
|
|
_visualize $data_mode, _build_tabulated_heatmap($raw_regex_src, $history_of{match_heatmap}); |
2057
|
|
|
|
|
|
|
|
2058
|
|
|
|
|
|
|
# Tabulate string... |
2059
|
0
|
|
|
|
|
|
_visualize $data_mode, _info_colourer("\n\nHeatmap for string:\n"); |
2060
|
0
|
|
|
|
|
|
_visualize $data_mode, _build_tabulated_heatmap($raw_str_src, $history_of{string_heatmap}); |
2061
|
|
|
|
|
|
|
} |
2062
|
|
|
|
|
|
|
} |
2063
|
|
|
|
|
|
|
|
2064
|
|
|
|
|
|
|
# Convert a heatmapped string to a table... |
2065
|
|
|
|
|
|
|
my $TABLE_STR_WIDTH = 15; |
2066
|
|
|
|
|
|
|
sub _build_tabulated_heatmap { |
2067
|
0
|
|
|
0
|
|
|
my ($str, $heatmap_ref) = @_; |
2068
|
|
|
|
|
|
|
|
2069
|
|
|
|
|
|
|
# Normalized data... |
2070
|
0
|
|
0
|
|
|
|
my $max_heat = max(1, map { $_ // 0 } @{$heatmap_ref}); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
2071
|
0
|
|
0
|
|
|
|
my @heat = map { ($_//0) / $max_heat } @{$heatmap_ref}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
2072
|
0
|
|
|
|
|
|
my $count_size = length($max_heat); |
2073
|
|
|
|
|
|
|
|
2074
|
|
|
|
|
|
|
# Determine colours to be used... |
2075
|
0
|
|
|
|
|
|
my @HEAT_COLOUR = @{$lexical_config->{heatmap_col}}; |
|
0
|
|
|
|
|
|
|
2076
|
|
|
|
|
|
|
|
2077
|
|
|
|
|
|
|
# Accumulate graph |
2078
|
0
|
|
|
|
|
|
my @graph; |
2079
|
0
|
|
|
|
|
|
for my $index (0..length($str)-1) { |
2080
|
|
|
|
|
|
|
|
2081
|
|
|
|
|
|
|
# Locate next char and its heat value... |
2082
|
0
|
|
|
|
|
|
my $char = substr($str, $index, 1); |
2083
|
0
|
|
0
|
|
|
|
my $abs_heat = $heatmap_ref->[$index] // 0; |
2084
|
0
|
0
|
|
|
|
|
my $display_char = $char eq "\n" ? '\n' |
|
|
0
|
|
|
|
|
|
2085
|
|
|
|
|
|
|
: $char eq "\t" ? '\t' |
2086
|
|
|
|
|
|
|
: $char; |
2087
|
|
|
|
|
|
|
|
2088
|
|
|
|
|
|
|
# Graph it... |
2089
|
0
|
0
|
0
|
|
|
|
if (@graph && length($graph[-1]{text} . $display_char) < $TABLE_STR_WIDTH && $graph[-1]{heat} == $abs_heat) { |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
2090
|
0
|
|
|
|
|
|
$graph[-1]{text} .= $display_char; |
2091
|
|
|
|
|
|
|
} |
2092
|
|
|
|
|
|
|
elsif ($char ne q{ } || $abs_heat != 0) { |
2093
|
0
|
|
0
|
|
|
|
my $rel_heat = $heat[$index] // 0; |
2094
|
0
|
|
|
|
|
|
push @graph, { |
2095
|
|
|
|
|
|
|
text => $display_char, |
2096
|
|
|
|
|
|
|
heat => $abs_heat, |
2097
|
|
|
|
|
|
|
rel_heat => $rel_heat, |
2098
|
|
|
|
|
|
|
bar => q{*} x (($MAX_WIDTH-$TABLE_STR_WIDTH) * $rel_heat), |
2099
|
|
|
|
|
|
|
}; |
2100
|
|
|
|
|
|
|
} |
2101
|
|
|
|
|
|
|
} |
2102
|
|
|
|
|
|
|
|
2103
|
|
|
|
|
|
|
# Draw table... |
2104
|
0
|
|
|
|
|
|
my $table; |
2105
|
0
|
|
|
|
|
|
for my $entry (@graph) { |
2106
|
0
|
|
|
|
|
|
my $colour_index = int( 0.5 + $#HEAT_COLOUR * $entry->{rel_heat} ); |
2107
|
|
|
|
|
|
|
$table .= |
2108
|
|
|
|
|
|
|
q{ } . |
2109
|
|
|
|
|
|
|
Term::ANSIColor::colored( |
2110
|
|
|
|
|
|
|
substr($entry->{text} . q{ } x $TABLE_STR_WIDTH, 0, $TABLE_STR_WIDTH) . |
2111
|
0
|
|
0
|
|
|
|
sprintf("| %-*s |%s\n", $count_size, $entry->{heat} || q{ }, $entry->{bar}), |
2112
|
|
|
|
|
|
|
$HEAT_COLOUR[$colour_index] |
2113
|
|
|
|
|
|
|
); |
2114
|
|
|
|
|
|
|
} |
2115
|
|
|
|
|
|
|
|
2116
|
0
|
|
|
|
|
|
return $table; |
2117
|
|
|
|
|
|
|
} |
2118
|
|
|
|
|
|
|
|
2119
|
|
|
|
|
|
|
# These need to be localized within regexes, so have to be package vars... |
2120
|
|
|
|
|
|
|
our $subpattern_depth; # ...how many levels down in named subpatterns? |
2121
|
|
|
|
|
|
|
|
2122
|
|
|
|
|
|
|
# Reset debugger variables at start of match... |
2123
|
|
|
|
|
|
|
sub _reset_debugger_state { |
2124
|
0
|
|
|
0
|
|
|
$prev_regex_pos = 0; # ...start of regex |
2125
|
0
|
|
|
|
|
|
$start_str_pos = 0; # ...starting point of match of string |
2126
|
0
|
|
|
|
|
|
$prev_str_pos = 0; # ...start of string |
2127
|
0
|
|
|
|
|
|
$prev_match_was_null = 0; # ...no previous match (to have been null) |
2128
|
0
|
|
|
|
|
|
@pre_is_pending = (); # ...no try is pending |
2129
|
0
|
|
|
|
|
|
$interaction_mode = 's'; # ...always start in step-by-step mode |
2130
|
0
|
|
|
|
|
|
$interaction_quit = 0; # ...reset quit command for each regex |
2131
|
0
|
|
|
|
|
|
$subpattern_depth = 0; # ...start at top level of named subcalls |
2132
|
|
|
|
|
|
|
|
2133
|
0
|
|
|
|
|
|
$Regexp::Grammars::match_start_pos = 0; # ...start matching at start of string |
2134
|
|
|
|
|
|
|
|
2135
|
|
|
|
|
|
|
# Also leave a gap in the event history and JSON representations... |
2136
|
0
|
|
|
|
|
|
_record_event 'events', q{}; |
2137
|
0
|
|
|
|
|
|
_record_event 'JSON', q{}; |
2138
|
0
|
|
|
|
|
|
_show_event $lexical_config->{display_mode}; |
2139
|
0
|
|
|
|
|
|
_show_JSON $lexical_config->{display_mode}, q{}; |
2140
|
|
|
|
|
|
|
} |
2141
|
|
|
|
|
|
|
|
2142
|
|
|
|
|
|
|
|
2143
|
|
|
|
|
|
|
# Reset some debugger variables at restart of match... |
2144
|
|
|
|
|
|
|
sub _reset_debugger_state_rematch { |
2145
|
0
|
|
|
0
|
|
|
$prev_regex_pos = 0; # ...start of regex |
2146
|
0
|
|
|
|
|
|
$start_str_pos = pos; # ...starting point of match of string |
2147
|
0
|
|
|
|
|
|
$prev_str_pos = pos; # ...point of rematch |
2148
|
0
|
|
|
|
|
|
@pre_is_pending = (); # ...no try is pending |
2149
|
0
|
|
|
|
|
|
$interaction_mode = 's'; # ...always start in step-by-step mode |
2150
|
0
|
|
|
|
|
|
$subpattern_depth = 0; # ...start at top level of named subcalls |
2151
|
|
|
|
|
|
|
|
2152
|
0
|
|
|
|
|
|
$Regexp::Grammars::match_start_pos = pos; # ...start matching at rematch point |
2153
|
|
|
|
|
|
|
|
2154
|
|
|
|
|
|
|
# Also leave a gap in the event history and JSON representations... |
2155
|
0
|
|
|
|
|
|
_record_event 'events', q{}; |
2156
|
0
|
|
|
|
|
|
_show_event $lexical_config->{display_mode}; |
2157
|
0
|
|
|
|
|
|
_record_event 'JSON', q{}; |
2158
|
0
|
|
|
|
|
|
_show_JSON $lexical_config->{display_mode}; |
2159
|
|
|
|
|
|
|
} |
2160
|
|
|
|
|
|
|
|
2161
|
|
|
|
|
|
|
|
2162
|
|
|
|
|
|
|
# Set up a JSON encoder... |
2163
|
|
|
|
|
|
|
my ($JSON_encoder, $JSON_decoder); |
2164
|
|
|
|
|
|
|
BEGIN { |
2165
|
|
|
|
|
|
|
($JSON_encoder, $JSON_decoder) = |
2166
|
1
|
|
|
|
|
227
|
eval{ require JSON::XS; } ? do { |
2167
|
0
|
|
|
|
|
0
|
my $json = JSON::XS->new->utf8(1)->pretty(1); |
2168
|
|
|
|
|
|
|
( |
2169
|
0
|
|
|
|
|
0
|
sub { return $json->encode(shift) }, |
2170
|
0
|
|
|
|
|
0
|
sub { return $json->decode(shift) }, |
2171
|
|
|
|
|
|
|
) |
2172
|
0
|
|
|
|
|
0
|
} |
2173
|
1
|
|
|
|
|
282
|
: eval{ require JSON; } ? do { |
2174
|
0
|
|
|
|
|
0
|
my $json = JSON->new->pretty(1); |
2175
|
|
|
|
|
|
|
( |
2176
|
0
|
|
|
|
|
0
|
sub { return $json->encode(shift) }, |
2177
|
0
|
|
|
|
|
0
|
sub { return $json->decode(shift) }, |
2178
|
|
|
|
|
|
|
) |
2179
|
0
|
|
|
|
|
0
|
} |
2180
|
1
|
|
|
|
|
16
|
: eval{ require 5.014; |
2181
|
1
|
|
|
|
|
166
|
require JSON::DWIW; } ? ( |
2182
|
0
|
|
|
|
|
0
|
sub { JSON::DWIW->to_json(shift, {pretty=>1}) }, |
2183
|
0
|
|
|
|
|
0
|
sub { JSON::DWIW->from_json(shift, {pretty=>1}) }, |
2184
|
|
|
|
|
|
|
) |
2185
|
1
|
|
|
|
|
680
|
: eval{ require JSON::Syck; } ? ( |
2186
|
|
|
|
|
|
|
\&JSON::Syck::Dump, |
2187
|
|
|
|
|
|
|
\&JSON::Syck::Load, |
2188
|
|
|
|
|
|
|
) |
2189
|
|
|
|
|
|
|
: ( |
2190
|
0
|
|
|
|
|
0
|
sub { '{}' }, |
2191
|
0
|
|
|
|
|
0
|
sub { {} }, |
2192
|
1
|
50
|
|
1
|
|
6
|
); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
2193
|
|
|
|
|
|
|
} |
2194
|
|
|
|
|
|
|
|
2195
|
|
|
|
|
|
|
# Report some activity within the regex match... |
2196
|
|
|
|
|
|
|
sub _report_event { |
2197
|
|
|
|
|
|
|
# Did the user quit the interactive debugger??? |
2198
|
0
|
0
|
|
0
|
|
|
return if $interaction_quit; |
2199
|
|
|
|
|
|
|
|
2200
|
|
|
|
|
|
|
# What are we matching (convert it to string if necessary).... |
2201
|
0
|
|
|
|
|
|
my $str_src = "$_"; |
2202
|
|
|
|
|
|
|
|
2203
|
|
|
|
|
|
|
# Which regex? Which event? Where in the string? Is this a recursive call? |
2204
|
0
|
|
|
|
|
|
my ($regex_ID, $event_ID, $str_pos, %opt) = @_; |
2205
|
0
|
|
0
|
|
|
|
my $nested_because = $opt{nested_because} // q{}; |
2206
|
0
|
|
|
|
|
|
my $non_interactive = $opt{non_iteractive}; |
2207
|
|
|
|
|
|
|
|
2208
|
|
|
|
|
|
|
# Locate state info for this event... |
2209
|
0
|
|
|
|
|
|
my $state_ref = $state{$regex_ID}; |
2210
|
0
|
|
|
|
|
|
my $event_ref = $state_ref->{$event_ID}; |
2211
|
|
|
|
|
|
|
|
2212
|
|
|
|
|
|
|
# Report any problems before reporting the event.... |
2213
|
0
|
0
|
|
|
|
|
if (@{ $state_ref->{regex_problems} }) { |
|
0
|
|
|
|
|
|
|
2214
|
0
|
|
|
|
|
|
for my $problem (@{$state_ref->{regex_problems}}) { |
|
0
|
|
|
|
|
|
|
2215
|
0
|
|
|
|
|
|
print { *STDERR} |
|
0
|
|
|
|
|
|
|
2216
|
|
|
|
|
|
|
"Possible typo in $problem->{type} at line $problem->{line} of regex:\n", |
2217
|
|
|
|
|
|
|
" Found: $problem->{desc}\n", |
2218
|
|
|
|
|
|
|
" Maybe: $problem->{dym}\n\n"; |
2219
|
|
|
|
|
|
|
} |
2220
|
0
|
|
|
|
|
|
print {*STDERR} "[Press any key to continue]"; |
|
0
|
|
|
|
|
|
|
2221
|
0
|
|
|
|
|
|
_interact(); |
2222
|
0
|
|
|
|
|
|
delete $state_ref->{regex_problems}; |
2223
|
|
|
|
|
|
|
} |
2224
|
|
|
|
|
|
|
|
2225
|
|
|
|
|
|
|
# Unpack the necessary info... |
2226
|
|
|
|
|
|
|
my ($matchable, $is_capture, $event_type, $construct, $depth) |
2227
|
0
|
|
|
|
|
|
= @{$event_ref}{qw< matchable is_capture event_type construct depth>}; |
|
0
|
|
|
|
|
|
|
2228
|
|
|
|
|
|
|
my ($construct_type, $quantifier, $regex_pos, $capture_name, $msg) |
2229
|
0
|
|
|
|
|
|
= @{$event_ref}{qw< construct_type quantifier regex_pos capture_name msg>}; |
|
0
|
|
|
|
|
|
|
2230
|
0
|
|
0
|
|
|
|
$construct_type //= q{}; |
2231
|
|
|
|
|
|
|
|
2232
|
|
|
|
|
|
|
# Reset display_mode, capture variables, and starting position on every restart... |
2233
|
0
|
0
|
|
|
|
|
if ($construct_type eq '_START') { |
2234
|
0
|
|
|
|
|
|
%capture = (); |
2235
|
0
|
|
|
|
|
|
$Regexp::Grammars::match_start_pos = pos(); |
2236
|
0
|
|
|
|
|
|
$lexical_config = $config[$event_ref->{lexical_scope}]; |
2237
|
|
|
|
|
|
|
|
2238
|
|
|
|
|
|
|
# Reset display mode only on start (i.e. not on restart)... |
2239
|
0
|
0
|
|
|
|
|
if ($str_pos == 0) { |
2240
|
0
|
|
|
|
|
|
$display_mode = $lexical_config->{display_mode}; |
2241
|
|
|
|
|
|
|
} |
2242
|
|
|
|
|
|
|
} |
2243
|
|
|
|
|
|
|
|
2244
|
|
|
|
|
|
|
# Ignore final failure messages, except at the very end... |
2245
|
0
|
0
|
|
|
|
|
if ($event_ref->{regex_failed}) { |
2246
|
0
|
0
|
0
|
|
|
|
return if ($str_pos//0) < length($str_src); |
2247
|
|
|
|
|
|
|
} |
2248
|
|
|
|
|
|
|
|
2249
|
|
|
|
|
|
|
# This variable allows us to query the start position of a submatch when at the end of the submatch... |
2250
|
0
|
|
|
|
|
|
my $shared_str_pos_ref = $event_ref->{shared_str_pos}; |
2251
|
|
|
|
|
|
|
|
2252
|
|
|
|
|
|
|
# Use the shared string pos on failure... |
2253
|
0
|
0
|
|
|
|
|
if ($nested_because eq 'failed') { |
2254
|
0
|
|
0
|
|
|
|
$str_pos = ${$shared_str_pos_ref // \$prev_str_pos} // $str_pos; |
|
0
|
|
0
|
|
|
|
|
2255
|
|
|
|
|
|
|
} |
2256
|
|
|
|
|
|
|
|
2257
|
|
|
|
|
|
|
# Flatten aliased capture name(s)... |
2258
|
0
|
0
|
|
|
|
|
if (ref $capture_name) { |
2259
|
0
|
|
|
|
|
|
$capture_name = join ' and ', @{$capture_name} |
|
0
|
|
|
|
|
|
|
2260
|
|
|
|
|
|
|
} |
2261
|
|
|
|
|
|
|
|
2262
|
|
|
|
|
|
|
# If we've matched, what did we match??? |
2263
|
0
|
|
|
|
|
|
my $forward_step = 0; # ... will eventually contain how far forward we stepped |
2264
|
0
|
0
|
0
|
|
|
|
if (($matchable || $is_capture) && $event_type eq 'post' && $construct ne '|') { |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
2265
|
0
|
0
|
|
|
|
|
$forward_step = $str_pos - ($shared_str_pos_ref ? ${$shared_str_pos_ref} : $str_pos); |
|
0
|
|
|
|
|
|
|
2266
|
|
|
|
|
|
|
} |
2267
|
|
|
|
|
|
|
|
2268
|
0
|
|
|
|
|
|
my $backtrack = q{}; # ...will store the arrow demonstrating the backtracking |
2269
|
|
|
|
|
|
|
|
2270
|
|
|
|
|
|
|
# Are we backtracking? |
2271
|
0
|
|
|
|
|
|
my $str_backtrack_len = min($EVENT_COL_WIDTH-1, $prev_str_pos-$str_pos); |
2272
|
0
|
|
|
|
|
|
my $regex_backtrack_len = min($EVENT_COL_WIDTH-1, $prev_regex_pos-$regex_pos); |
2273
|
1
|
|
|
1
|
|
8
|
my $event_str = '<' . do{ no warnings; '~' x $str_backtrack_len }; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
58
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
2274
|
1
|
|
|
1
|
|
7
|
my $event_regex = '<' . do{ no warnings; '~' x $regex_backtrack_len }; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
5824
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
2275
|
0
|
0
|
|
|
|
|
if ($nested_because ne 'failed') { |
2276
|
|
|
|
|
|
|
# Generate backtracking arrow... |
2277
|
0
|
0
|
0
|
|
|
|
if ($str_pos < ($prev_str_pos//0)) { |
|
|
0
|
0
|
|
|
|
|
2278
|
0
|
|
|
|
|
|
$backtrack = '<' . '~' x ($prev_str_pos-$str_pos-1); |
2279
|
|
|
|
|
|
|
} |
2280
|
|
|
|
|
|
|
elsif ($regex_pos < ($prev_regex_pos//0)) { |
2281
|
0
|
|
|
|
|
|
$backtrack = ' '; |
2282
|
|
|
|
|
|
|
} |
2283
|
|
|
|
|
|
|
|
2284
|
|
|
|
|
|
|
# Remember where we were... |
2285
|
0
|
|
|
|
|
|
$prev_str_pos = $str_pos; |
2286
|
0
|
|
|
|
|
|
$prev_regex_pos = $regex_pos; |
2287
|
|
|
|
|
|
|
} |
2288
|
|
|
|
|
|
|
|
2289
|
|
|
|
|
|
|
# Were there failed attempts pending??? |
2290
|
0
|
|
0
|
|
|
|
while (!$nested_because && @pre_is_pending && $pre_is_pending[-1][1] >= $subpattern_depth) { |
|
|
|
0
|
|
|
|
|
2291
|
0
|
|
0
|
|
|
|
my ($pending_event_ID, $pending_event_depth) = @{ pop(@pre_is_pending) // []}; |
|
0
|
|
|
|
|
|
|
2292
|
0
|
0
|
0
|
|
|
|
next if $event_type eq 'post' && $backtrack |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
2293
|
|
|
|
|
|
|
|| !defined $pending_event_ID |
2294
|
|
|
|
|
|
|
|| $pending_event_ID == $event_ID; |
2295
|
|
|
|
|
|
|
|
2296
|
0
|
|
|
|
|
|
local $subpattern_depth = $pending_event_depth; |
2297
|
0
|
|
|
|
|
|
_report_event($regex_ID, $pending_event_ID, undef, nested_because=>'failed'); |
2298
|
|
|
|
|
|
|
} |
2299
|
|
|
|
|
|
|
|
2300
|
|
|
|
|
|
|
# Get the source code of the regex... |
2301
|
0
|
|
|
|
|
|
my $regex_src = $state_ref->{regex_src}; |
2302
|
|
|
|
|
|
|
|
2303
|
|
|
|
|
|
|
# How long is this piece of the regex??? |
2304
|
0
|
|
|
|
|
|
my $construct_len = length $construct; |
2305
|
|
|
|
|
|
|
|
2306
|
|
|
|
|
|
|
# Build msg if it's dynamic... |
2307
|
0
|
0
|
|
|
|
|
if (ref($msg) eq 'CODE') { |
2308
|
0
|
|
|
|
|
|
$msg = $msg->(); |
2309
|
|
|
|
|
|
|
} |
2310
|
|
|
|
|
|
|
|
2311
|
|
|
|
|
|
|
# Construct status message (if necessary)... |
2312
|
|
|
|
|
|
|
$msg = $nested_because eq 'failed' ? q{Failed} |
2313
|
0
|
|
|
|
|
|
: $event_type eq 'pre' && ref $msg ? 'Capture to ' . join ' and ', @{$msg} |
2314
|
0
|
0
|
0
|
|
|
|
: $event_type eq 'post' && ref $msg ? 'End of ' . join ' and ', @{$msg} |
|
0
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2315
|
|
|
|
|
|
|
: defined $msg ? $msg |
2316
|
|
|
|
|
|
|
: pos && pos == $prev_str_pos && $construct_type eq '_START' ? q{Restarting regex match} |
2317
|
|
|
|
|
|
|
: $construct_type eq '_START' ? q{Starting regex match} |
2318
|
|
|
|
|
|
|
: q{} |
2319
|
|
|
|
|
|
|
; |
2320
|
|
|
|
|
|
|
|
2321
|
|
|
|
|
|
|
# Report back-tracking occurred (but not when returning from named subpatterns)... |
2322
|
0
|
0
|
|
|
|
|
if ($regex_backtrack_len > 0) { |
2323
|
0
|
0
|
0
|
|
|
|
$msg = $event_type eq 'failed_nonbacktracking' |
|
|
0
|
|
|
|
|
|
2324
|
|
|
|
|
|
|
? q{Back-tracking past } . lc($msg) . q{ without rematching} |
2325
|
|
|
|
|
|
|
: $construct_type ne '_named_subpattern_call' && index(lc($msg), 'failed') < 0 |
2326
|
|
|
|
|
|
|
? q{Back-tracked within regex and re} . lc($msg) |
2327
|
|
|
|
|
|
|
: $msg; |
2328
|
|
|
|
|
|
|
|
2329
|
0
|
|
|
|
|
|
my $re_idx = index($msg, 'and rere'); |
2330
|
0
|
0
|
|
|
|
|
if ($re_idx >= 0) { |
2331
|
0
|
|
|
|
|
|
substr($msg, $re_idx, 8, 'and re'); |
2332
|
|
|
|
|
|
|
} |
2333
|
0
|
|
|
|
|
|
$re_idx = index($msg, 'and reend'); |
2334
|
0
|
0
|
|
|
|
|
if ($re_idx >= 0) { |
2335
|
0
|
|
|
|
|
|
substr($msg, $re_idx, 9, 'and end'); |
2336
|
|
|
|
|
|
|
} |
2337
|
|
|
|
|
|
|
} |
2338
|
|
|
|
|
|
|
|
2339
|
|
|
|
|
|
|
# Track trying and matching... |
2340
|
0
|
|
0
|
|
|
|
my $is_match = index($msg, 'matched') >= 0 || index($msg, 'Matched') >= 0; |
2341
|
0
|
|
0
|
|
|
|
my $is_rematch = index($msg, 'rematched') >= 0 || index($msg, 'Rematched') >= 0; |
2342
|
0
|
|
0
|
|
|
|
my $is_trying = index($msg, 'trying') >= 0 || index($msg, 'Trying') >= 0; |
2343
|
0
|
|
0
|
|
|
|
my $is_skip = index($msg, 'skipping') >= 0 || index($msg, 'Skipping') >= 0; |
2344
|
0
|
|
0
|
|
|
|
my $is_fail = index($msg, 'failed') >= 0 || index($msg, 'Failed') >= 0; |
2345
|
|
|
|
|
|
|
|
2346
|
|
|
|
|
|
|
# Track string heatmap... |
2347
|
0
|
0
|
|
|
|
|
if ($forward_step) { |
|
|
0
|
|
|
|
|
|
2348
|
0
|
|
|
|
|
|
my @str_range = $str_pos-$forward_step+1 .. $str_pos-1; |
2349
|
0
|
|
|
|
|
|
$_++ for @{$history_of{string_heatmap}}[@str_range]; |
|
0
|
|
|
|
|
|
|
2350
|
|
|
|
|
|
|
} |
2351
|
|
|
|
|
|
|
elsif ($is_trying) { |
2352
|
0
|
|
|
|
|
|
$history_of{string_heatmap}[$str_pos]++; |
2353
|
|
|
|
|
|
|
} |
2354
|
|
|
|
|
|
|
|
2355
|
|
|
|
|
|
|
# Trace regex heatmap... |
2356
|
0
|
0
|
0
|
|
|
|
if ($is_rematch || !$is_match && !$is_fail && !$is_skip) { |
|
|
|
0
|
|
|
|
|
2357
|
0
|
|
|
|
|
|
my @regex_range = $regex_pos..$regex_pos+length($construct)-1; |
2358
|
0
|
|
|
|
|
|
$_++ for @{$history_of{match_heatmap}}[@regex_range]; |
|
0
|
|
|
|
|
|
|
2359
|
|
|
|
|
|
|
} |
2360
|
|
|
|
|
|
|
|
2361
|
|
|
|
|
|
|
# Track start and end positions for each capture... |
2362
|
0
|
0
|
|
|
|
|
if ($construct_type eq '_capture_group') { |
2363
|
0
|
0
|
|
|
|
|
if ($event_type eq 'pre') { |
|
|
0
|
|
|
|
|
|
2364
|
0
|
|
|
|
|
|
$capture{$capture_name}{from} = $str_pos; |
2365
|
0
|
|
|
|
|
|
$capture{$capture_name}{start_pos} = $regex_pos; |
2366
|
|
|
|
|
|
|
} |
2367
|
|
|
|
|
|
|
elsif ($event_type eq 'post') { |
2368
|
0
|
|
|
|
|
|
$capture{$capture_name}{to} = $str_pos; |
2369
|
|
|
|
|
|
|
} |
2370
|
|
|
|
|
|
|
} |
2371
|
|
|
|
|
|
|
|
2372
|
|
|
|
|
|
|
# Remember when a match/fail is pending... |
2373
|
0
|
|
0
|
|
|
|
my $is_pending = $matchable |
2374
|
|
|
|
|
|
|
&& $event_type eq 'pre' |
2375
|
|
|
|
|
|
|
# && $construct_type ne '_named_subpattern_call'; |
2376
|
|
|
|
|
|
|
; |
2377
|
0
|
0
|
|
|
|
|
if ($is_pending) { |
2378
|
|
|
|
|
|
|
# Pre- and post- events have adjacent IDs so add 1 to get post ID... |
2379
|
0
|
|
0
|
|
|
|
push @pre_is_pending, [$event_ID + 1, $subpattern_depth // 0]; |
2380
|
|
|
|
|
|
|
} |
2381
|
|
|
|
|
|
|
|
2382
|
|
|
|
|
|
|
# Send starting position to corresponding post- event... |
2383
|
0
|
0
|
0
|
|
|
|
if ($shared_str_pos_ref && $event_type eq 'pre' && $construct ne '|') { |
|
|
|
0
|
|
|
|
|
2384
|
0
|
|
|
|
|
|
${$shared_str_pos_ref} = $str_pos; |
|
0
|
|
|
|
|
|
|
2385
|
|
|
|
|
|
|
} |
2386
|
|
|
|
|
|
|
|
2387
|
|
|
|
|
|
|
# Compute indent for message (from paren depth + subcall depth)... |
2388
|
0
|
|
|
|
|
|
my $indent = $INDENT x ($event_ref->{depth} + $subpattern_depth); |
2389
|
|
|
|
|
|
|
|
2390
|
|
|
|
|
|
|
# Indicate any backtracking... |
2391
|
0
|
0
|
0
|
|
|
|
if (length($event_str) > 1 || length($event_regex) > 1) { |
2392
|
0
|
0
|
|
|
|
|
$event_str = q{} if length($event_str) == 1; |
2393
|
0
|
0
|
|
|
|
|
$event_regex = q{} if length($event_regex) == 1; |
2394
|
0
|
0
|
0
|
|
|
|
my $backtrack_msg |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2395
|
|
|
|
|
|
|
= $event_str && $event_regex ? 'Back-tracking in both regex and string' |
2396
|
|
|
|
|
|
|
: $event_str ? 'Back-tracking ' . $str_backtrack_len |
2397
|
|
|
|
|
|
|
. ' character' |
2398
|
|
|
|
|
|
|
. ($str_backtrack_len == 1 ? q{} : 's') |
2399
|
|
|
|
|
|
|
. ' in string' |
2400
|
|
|
|
|
|
|
: "Back-tracking in regex" |
2401
|
|
|
|
|
|
|
; |
2402
|
0
|
|
|
|
|
|
$backtrack_msg = _info_colourer($backtrack_msg); |
2403
|
0
|
|
|
|
|
|
$event_regex .= q{ } x ($EVENT_COL_WIDTH - length $event_regex); |
2404
|
0
|
|
|
|
|
|
$event_str .= q{ } x ($EVENT_COL_WIDTH - length $event_str); |
2405
|
0
|
|
|
|
|
|
_record_event 'events', |
2406
|
|
|
|
|
|
|
sprintf("%s | %s | %s", |
2407
|
|
|
|
|
|
|
_info_colourer($event_str), |
2408
|
|
|
|
|
|
|
_info_colourer($event_regex), |
2409
|
|
|
|
|
|
|
$indent . $backtrack_msg); |
2410
|
0
|
0
|
0
|
|
|
|
_show_event $display_mode |
2411
|
|
|
|
|
|
|
if index('nrFMC', $interaction_mode) < 0 || $subpattern_depth <= $interaction_depth; |
2412
|
|
|
|
|
|
|
} |
2413
|
|
|
|
|
|
|
|
2414
|
|
|
|
|
|
|
# Colour the message... |
2415
|
0
|
|
|
|
|
|
my $colourer = _colourer_for($msg); |
2416
|
|
|
|
|
|
|
|
2417
|
|
|
|
|
|
|
# Log (and perhaps display) event... |
2418
|
0
|
|
|
|
|
|
_record_event 'events', |
2419
|
|
|
|
|
|
|
sprintf("%-s | %-${EVENT_COL_WIDTH}s | %s", |
2420
|
|
|
|
|
|
|
_ws_colourer(substr($str_src . (q{ } x $EVENT_COL_WIDTH), $str_pos, $EVENT_COL_WIDTH)), |
2421
|
|
|
|
|
|
|
substr($regex_src, $regex_pos, $EVENT_COL_WIDTH), |
2422
|
|
|
|
|
|
|
$indent . $colourer->($msg)); |
2423
|
0
|
0
|
0
|
|
|
|
_show_event $display_mode |
2424
|
|
|
|
|
|
|
if index('nrFMC', $interaction_mode) < 0 || $subpattern_depth <= $interaction_depth; |
2425
|
|
|
|
|
|
|
|
2426
|
|
|
|
|
|
|
# Display event mode line, if appropriate... |
2427
|
0
|
0
|
0
|
|
|
|
if ($display_mode eq 'events' && !$lexical_config->{save_to_fh}) { |
2428
|
0
|
0
|
0
|
|
|
|
say _info_colourer( qq{\n[Events of regex at $state{$regex_ID}{location}]} ) |
2429
|
|
|
|
|
|
|
if index('nrFMC', $interaction_mode) < 0 || $subpattern_depth <= $interaction_depth; |
2430
|
|
|
|
|
|
|
} |
2431
|
|
|
|
|
|
|
|
2432
|
|
|
|
|
|
|
# Generate (and perhaps display) the JSON... |
2433
|
|
|
|
|
|
|
{ |
2434
|
|
|
|
|
|
|
# The data we're encoding... |
2435
|
0
|
|
|
|
|
|
my $data = { |
2436
|
|
|
|
|
|
|
regex_pos => $regex_pos, |
2437
|
|
|
|
|
|
|
str_pos => $str_pos, |
2438
|
0
|
|
|
|
|
|
event => { %{$event_ref}, msg => $msg }, |
|
0
|
|
|
|
|
|
|
2439
|
|
|
|
|
|
|
}; |
2440
|
|
|
|
|
|
|
|
2441
|
|
|
|
|
|
|
# But sanitize any procedural msg... |
2442
|
0
|
0
|
|
|
|
|
if (ref $data->{event}{msg} eq 'CODE') { |
2443
|
0
|
|
|
|
|
|
delete $data->{event}{msg}; |
2444
|
|
|
|
|
|
|
} |
2445
|
|
|
|
|
|
|
|
2446
|
|
|
|
|
|
|
# And sanitize any reference to internal communications channel... |
2447
|
0
|
|
|
|
|
|
my $starting_str_pos = delete $data->{event}{shared_str_pos}; |
2448
|
0
|
0
|
0
|
|
|
|
if (ref $starting_str_pos eq 'SCALAR' && ${$starting_str_pos} && ${$starting_str_pos} ne $str_pos) { |
|
0
|
|
0
|
|
|
|
|
|
0
|
|
|
|
|
|
|
2449
|
0
|
|
|
|
|
|
$data->{starting_str_pos} = ${$starting_str_pos}; |
|
0
|
|
|
|
|
|
|
2450
|
|
|
|
|
|
|
} |
2451
|
|
|
|
|
|
|
|
2452
|
0
|
|
|
|
|
|
my $json_rep = $JSON_encoder->($data); |
2453
|
|
|
|
|
|
|
|
2454
|
|
|
|
|
|
|
# Display opening delimiter at start... |
2455
|
0
|
0
|
0
|
|
|
|
if ($construct_type eq '_START' && $str_pos == 0) { |
2456
|
0
|
|
|
|
|
|
_record_event 'JSON', '['; |
2457
|
0
|
|
|
|
|
|
_show_JSON $display_mode; |
2458
|
|
|
|
|
|
|
} |
2459
|
|
|
|
|
|
|
|
2460
|
|
|
|
|
|
|
# Display event data (with comma, if needed)... |
2461
|
0
|
0
|
|
|
|
|
my $comma = $construct_type eq '_END' ? q{} : q{,}; |
2462
|
0
|
|
|
|
|
|
_record_event 'JSON', qq{ $json_rep$comma}; |
2463
|
0
|
|
|
|
|
|
_show_JSON $display_mode; |
2464
|
|
|
|
|
|
|
|
2465
|
|
|
|
|
|
|
# Display closing delimiter at end... |
2466
|
0
|
0
|
|
|
|
|
if ($construct_type eq '_END') { |
2467
|
0
|
|
|
|
|
|
_record_event 'JSON', ']'; |
2468
|
0
|
|
|
|
|
|
_show_JSON $display_mode; |
2469
|
|
|
|
|
|
|
} |
2470
|
|
|
|
|
|
|
|
2471
|
|
|
|
|
|
|
# Display mode line... |
2472
|
0
|
0
|
0
|
|
|
|
if ($display_mode eq 'JSON' && !$lexical_config->{save_to_fh}) { |
2473
|
0
|
0
|
0
|
|
|
|
say _info_colourer( qq{\n[JSON data of regex at $state{$regex_ID}{location}]} ) |
2474
|
|
|
|
|
|
|
if index('nrFMC', $interaction_mode) < 0 || $subpattern_depth <= $interaction_depth; |
2475
|
|
|
|
|
|
|
} |
2476
|
|
|
|
|
|
|
} |
2477
|
|
|
|
|
|
|
|
2478
|
|
|
|
|
|
|
# Build and display (if appropriate) the "2D" visualizations... |
2479
|
|
|
|
|
|
|
my %data = ( |
2480
|
|
|
|
|
|
|
regex_ID => $regex_ID, |
2481
|
|
|
|
|
|
|
regex_src => $regex_src, |
2482
|
|
|
|
|
|
|
regex_pos => $regex_pos, |
2483
|
|
|
|
|
|
|
construct_len => $construct_len, |
2484
|
|
|
|
|
|
|
str_src => $str_src, |
2485
|
|
|
|
|
|
|
str_pos => $str_pos, |
2486
|
|
|
|
|
|
|
is_match => $is_match, |
2487
|
|
|
|
|
|
|
is_fail => $is_fail, |
2488
|
|
|
|
|
|
|
is_trying => $is_trying, |
2489
|
|
|
|
|
|
|
is_capture => $is_capture, |
2490
|
|
|
|
|
|
|
backtrack => $backtrack, |
2491
|
|
|
|
|
|
|
forward_step => $forward_step, |
2492
|
|
|
|
|
|
|
nested_because => $nested_because, |
2493
|
|
|
|
|
|
|
msg => $msg, |
2494
|
|
|
|
|
|
|
colourer => $colourer, |
2495
|
0
|
0
|
|
|
|
|
step => scalar @{$history_of{visual}||[]}, |
|
0
|
|
|
|
|
|
|
2496
|
|
|
|
|
|
|
); |
2497
|
0
|
|
|
|
|
|
_build_visualization('visual', \%data); |
2498
|
0
|
|
|
|
|
|
_build_visualization('heatmap', \%data); |
2499
|
|
|
|
|
|
|
|
2500
|
0
|
|
|
|
|
|
$data{no_window} = 1; |
2501
|
0
|
|
|
|
|
|
_build_visualization('full_visual', \%data); |
2502
|
0
|
|
|
|
|
|
_build_visualization('full_heatmap', \%data); |
2503
|
|
|
|
|
|
|
|
2504
|
0
|
0
|
0
|
|
|
|
if ($display_mode eq 'visual' && (index('nrFMC', $interaction_mode) < 0 || $subpattern_depth <= $interaction_depth)) { |
|
|
|
0
|
|
|
|
|
2505
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
2506
|
0
|
|
|
|
|
|
_print $history_of{$display_mode}[-1]{display}; |
2507
|
|
|
|
|
|
|
} |
2508
|
|
|
|
|
|
|
|
2509
|
|
|
|
|
|
|
# Do any interaction... |
2510
|
0
|
|
|
|
|
|
my $input; |
2511
|
|
|
|
|
|
|
INPUT: |
2512
|
0
|
|
|
|
|
|
while (!$non_interactive) { |
2513
|
|
|
|
|
|
|
# Adaptive rate of display when skipping interactions... |
2514
|
0
|
|
|
|
|
|
state $skip_duration = $MAX_SKIP_DURATION; |
2515
|
0
|
|
|
|
|
|
$skip_duration = max($MIN_SKIP_DURATION, $skip_duration * $SKIP_ACCELERATION); |
2516
|
0
|
0
|
0
|
|
|
|
_pause($skip_duration) |
2517
|
|
|
|
|
|
|
if index('nrFMC', $interaction_mode) < 0 || $subpattern_depth <= $interaction_depth; |
2518
|
|
|
|
|
|
|
|
2519
|
|
|
|
|
|
|
# Skip interactions if current mode does not require them... |
2520
|
0
|
0
|
0
|
|
|
|
last INPUT if $event_type ne 'break' && ( |
|
|
|
0
|
|
|
|
|
2521
|
|
|
|
|
|
|
# Skip-to-match mode... |
2522
|
|
|
|
|
|
|
lc($interaction_mode) eq 'm' |
2523
|
|
|
|
|
|
|
&& (!$is_match || $interaction_mode eq 'M' && $subpattern_depth > $interaction_depth) |
2524
|
|
|
|
|
|
|
&& index($msg,'restarting regex match') < 0 |
2525
|
|
|
|
|
|
|
&& $construct_type ne '_END' |
2526
|
|
|
|
|
|
|
|| |
2527
|
|
|
|
|
|
|
# Skip-to-fail mode... |
2528
|
|
|
|
|
|
|
lc($interaction_mode) eq 'f' |
2529
|
|
|
|
|
|
|
&& (!$is_fail || $interaction_mode eq 'F' && $subpattern_depth > $interaction_depth) |
2530
|
|
|
|
|
|
|
&& index($msg,'restarting regex match') < 0 |
2531
|
|
|
|
|
|
|
&& $construct_type ne '_END' |
2532
|
|
|
|
|
|
|
|| |
2533
|
|
|
|
|
|
|
# Skip-to-return mode... |
2534
|
|
|
|
|
|
|
$interaction_mode eq 'r' |
2535
|
|
|
|
|
|
|
&& $subpattern_depth > 0 |
2536
|
|
|
|
|
|
|
&& $subpattern_depth > $interaction_depth |
2537
|
|
|
|
|
|
|
&& index($msg,'restarting regex match') < 0 |
2538
|
|
|
|
|
|
|
&& $construct_type ne '_END' |
2539
|
|
|
|
|
|
|
|| |
2540
|
|
|
|
|
|
|
# Skip-to-next mode... |
2541
|
|
|
|
|
|
|
$interaction_mode eq 'n' |
2542
|
|
|
|
|
|
|
&& $subpattern_depth > $interaction_depth |
2543
|
|
|
|
|
|
|
&& index($msg,'restarting regex match') < 0 |
2544
|
|
|
|
|
|
|
|| |
2545
|
|
|
|
|
|
|
# Skip-to-end mode... |
2546
|
|
|
|
|
|
|
lc($interaction_mode) eq 'c' |
2547
|
|
|
|
|
|
|
&& $construct_type ne '_END' |
2548
|
|
|
|
|
|
|
); |
2549
|
|
|
|
|
|
|
|
2550
|
|
|
|
|
|
|
# Reset adaptive skip rate on any interaction... |
2551
|
0
|
|
|
|
|
|
$skip_duration = $MAX_SKIP_DURATION; |
2552
|
|
|
|
|
|
|
|
2553
|
|
|
|
|
|
|
# Reset to step mode on a break... |
2554
|
0
|
0
|
|
|
|
|
if ($event_type eq 'break') { |
2555
|
0
|
|
|
|
|
|
$interaction_mode = 's'; |
2556
|
|
|
|
|
|
|
} |
2557
|
|
|
|
|
|
|
|
2558
|
|
|
|
|
|
|
# Do what, John??? |
2559
|
0
|
|
|
|
|
|
$input = _interact(); |
2560
|
|
|
|
|
|
|
|
2561
|
|
|
|
|
|
|
# A terminates the process... |
2562
|
0
|
0
|
0
|
|
|
|
if ($input eq "\cC") { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2563
|
0
|
|
|
|
|
|
kill 9, $$; |
2564
|
|
|
|
|
|
|
} |
2565
|
|
|
|
|
|
|
|
2566
|
|
|
|
|
|
|
# An 'x' exits the process... |
2567
|
|
|
|
|
|
|
elsif ($input eq 'x') { |
2568
|
0
|
|
|
|
|
|
exit(0); |
2569
|
|
|
|
|
|
|
} |
2570
|
|
|
|
|
|
|
|
2571
|
|
|
|
|
|
|
# A redraws the screen... |
2572
|
|
|
|
|
|
|
elsif ($input eq "\cL") { |
2573
|
0
|
|
|
|
|
|
_print $history_of{$display_mode}[-1]{display}; |
2574
|
0
|
0
|
0
|
|
|
|
if ($display_mode eq 'events' || $display_mode eq 'JSON') { |
2575
|
0
|
|
|
|
|
|
say _info_colourer( qq{\n\n[\u$display_mode of regex at $state{$regex_ID}{location}]} ); |
2576
|
|
|
|
|
|
|
} |
2577
|
0
|
|
|
|
|
|
next INPUT; |
2578
|
|
|
|
|
|
|
} |
2579
|
|
|
|
|
|
|
|
2580
|
|
|
|
|
|
|
# Display explanation of regex... |
2581
|
|
|
|
|
|
|
elsif ($input eq 'd') { |
2582
|
0
|
|
|
|
|
|
_show_regex_description($regex_ID); |
2583
|
0
|
|
|
|
|
|
next INPUT; |
2584
|
|
|
|
|
|
|
} |
2585
|
|
|
|
|
|
|
|
2586
|
|
|
|
|
|
|
# Help! |
2587
|
|
|
|
|
|
|
elsif ($input eq '?') { |
2588
|
0
|
|
|
|
|
|
_show_help(); |
2589
|
0
|
|
|
|
|
|
next INPUT; |
2590
|
|
|
|
|
|
|
} |
2591
|
|
|
|
|
|
|
|
2592
|
|
|
|
|
|
|
# Quit all debugging??? |
2593
|
|
|
|
|
|
|
elsif ($input eq 'q' || $input eq "\cD") { |
2594
|
0
|
|
|
|
|
|
$interaction_quit = 1; |
2595
|
0
|
|
|
|
|
|
last INPUT; |
2596
|
|
|
|
|
|
|
} |
2597
|
|
|
|
|
|
|
|
2598
|
|
|
|
|
|
|
# Step backwards... |
2599
|
|
|
|
|
|
|
elsif (index('-p', $input) >= 0) { |
2600
|
0
|
|
|
|
|
|
my $step; |
2601
|
0
|
|
|
|
|
|
($input, $step) = _revisualize($regex_ID, $input); |
2602
|
0
|
0
|
0
|
|
|
|
if ($input eq 'q' || $input eq "\cD") { |
|
|
0
|
|
|
|
|
|
2603
|
0
|
|
|
|
|
|
$interaction_quit = 1; |
2604
|
0
|
|
|
|
|
|
last INPUT; |
2605
|
|
|
|
|
|
|
} |
2606
|
|
|
|
|
|
|
elsif (index('smnrMfFcC', $input) >= 0) { |
2607
|
0
|
|
|
|
|
|
$interaction_mode = $input; |
2608
|
0
|
|
|
|
|
|
$subpattern_depth = $history_of{$display_mode}[$step-1]{depth}; |
2609
|
0
|
|
|
|
|
|
$is_match = $history_of{$display_mode}[$step-1]{is_match}; |
2610
|
0
|
|
|
|
|
|
$is_fail = $history_of{$display_mode}[$step-1]{is_fail}; |
2611
|
|
|
|
|
|
|
} |
2612
|
0
|
|
|
|
|
|
next INPUT; |
2613
|
|
|
|
|
|
|
} |
2614
|
|
|
|
|
|
|
|
2615
|
|
|
|
|
|
|
# Step all the way back to start... |
2616
|
|
|
|
|
|
|
elsif ($input eq 'R') { |
2617
|
0
|
|
|
|
|
|
my $step; |
2618
|
0
|
|
|
|
|
|
($input, $step) = _revisualize($regex_ID, $input, 0); |
2619
|
0
|
0
|
0
|
|
|
|
if ($input eq 'q' || $input eq "\cD") { |
|
|
0
|
|
|
|
|
|
2620
|
0
|
|
|
|
|
|
$interaction_quit = 1; |
2621
|
0
|
|
|
|
|
|
last INPUT; |
2622
|
|
|
|
|
|
|
} |
2623
|
|
|
|
|
|
|
elsif (index('smnrMfFcC', $input) >= 0) { |
2624
|
0
|
|
|
|
|
|
$interaction_mode = $input; |
2625
|
0
|
|
|
|
|
|
$subpattern_depth = $history_of{$display_mode}[$step-1]{depth}; |
2626
|
0
|
|
|
|
|
|
$is_match = $history_of{$display_mode}[$step-1]{is_match}; |
2627
|
0
|
|
|
|
|
|
$is_fail = $history_of{$display_mode}[$step-1]{is_fail}; |
2628
|
|
|
|
|
|
|
} |
2629
|
0
|
|
|
|
|
|
next INPUT; |
2630
|
|
|
|
|
|
|
} |
2631
|
|
|
|
|
|
|
|
2632
|
|
|
|
|
|
|
# Switch between visualizer/event/heatmap/JSON modes... |
2633
|
|
|
|
|
|
|
elsif ($input eq 'v') { |
2634
|
0
|
|
|
|
|
|
$display_mode = 'visual'; |
2635
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
2636
|
0
|
|
|
|
|
|
_print $history_of{'visual'}[-1]{display}; |
2637
|
0
|
|
|
|
|
|
next INPUT; |
2638
|
|
|
|
|
|
|
} |
2639
|
|
|
|
|
|
|
elsif ($input eq 'h') { |
2640
|
|
|
|
|
|
|
# Can we use heatmap mode? |
2641
|
0
|
0
|
|
|
|
|
if ($heatmaps_invisible) { |
2642
|
0
|
|
|
|
|
|
say 'Cannot show heatmaps (Term::ANSIColor unavailable)'; |
2643
|
0
|
|
|
|
|
|
say "Try 'H' instead"; |
2644
|
0
|
|
|
|
|
|
$input = '?'; |
2645
|
|
|
|
|
|
|
} |
2646
|
|
|
|
|
|
|
# If heatmaps available, check for misuse of 'h' instead of '?'... |
2647
|
|
|
|
|
|
|
else { |
2648
|
0
|
|
|
|
|
|
my $prompt_help = $display_mode eq 'heatmap'; |
2649
|
0
|
|
|
|
|
|
$display_mode = 'heatmap'; |
2650
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
2651
|
0
|
|
|
|
|
|
_print $history_of{'heatmap'}[-1]{display}; |
2652
|
0
|
0
|
|
|
|
|
if ($prompt_help) { |
2653
|
0
|
|
|
|
|
|
say "(Type '?' for help)"; |
2654
|
|
|
|
|
|
|
} |
2655
|
|
|
|
|
|
|
} |
2656
|
0
|
|
|
|
|
|
next INPUT; |
2657
|
|
|
|
|
|
|
} |
2658
|
|
|
|
|
|
|
elsif ($input eq 'e') { |
2659
|
0
|
|
|
|
|
|
$display_mode = 'events'; |
2660
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
2661
|
0
|
|
|
|
|
|
_print $history_of{'events'}[-1]{display}; |
2662
|
0
|
|
|
|
|
|
say _info_colourer( qq{\n\n[Events of regex at $state{$regex_ID}{location}]} ); |
2663
|
0
|
|
|
|
|
|
next INPUT; |
2664
|
|
|
|
|
|
|
} |
2665
|
|
|
|
|
|
|
elsif ($input eq 'j') { |
2666
|
0
|
|
|
|
|
|
$display_mode = 'JSON'; |
2667
|
0
|
|
|
|
|
|
_print $CLEAR_SCREEN; |
2668
|
0
|
|
|
|
|
|
_print $history_of{'JSON'}[-1]{display}; |
2669
|
0
|
|
|
|
|
|
say _info_colourer( qq{\n\n[JSON data of regex at $state{$regex_ID}{location}]} ); |
2670
|
0
|
|
|
|
|
|
next INPUT; |
2671
|
|
|
|
|
|
|
} |
2672
|
|
|
|
|
|
|
|
2673
|
|
|
|
|
|
|
# Take a snapshot... |
2674
|
0
|
|
|
|
|
|
elsif ($input eq 'V') { _save_snapshot('full_visual') ; next INPUT; } |
|
0
|
|
|
|
|
|
|
2675
|
0
|
|
|
|
|
|
elsif ($input eq 'H') { _save_snapshot('full_heatmap') ; next INPUT; } |
|
0
|
|
|
|
|
|
|
2676
|
0
|
|
|
|
|
|
elsif ($input eq 'E') { _save_snapshot('events') ; next INPUT; } |
|
0
|
|
|
|
|
|
|
2677
|
0
|
|
|
|
|
|
elsif ($input eq 'J') { _save_snapshot('JSON') ; next INPUT; } |
|
0
|
|
|
|
|
|
|
2678
|
0
|
|
|
|
|
|
elsif ($input eq 'D') { _show_regex_description($regex_ID,'save') ; next INPUT; } |
|
0
|
|
|
|
|
|
|
2679
|
|
|
|
|
|
|
|
2680
|
|
|
|
|
|
|
# Change of interaction mode??? |
2681
|
|
|
|
|
|
|
elsif (index('fFmMnscC', $input) >= 0) { |
2682
|
0
|
|
|
|
|
|
$interaction_mode = $input; |
2683
|
0
|
|
|
|
|
|
$interaction_depth = $subpattern_depth; |
2684
|
0
|
|
|
|
|
|
last INPUT; |
2685
|
|
|
|
|
|
|
} |
2686
|
|
|
|
|
|
|
elsif ($input eq 'r') { |
2687
|
0
|
|
|
|
|
|
$interaction_mode = $input; |
2688
|
0
|
|
|
|
|
|
$interaction_depth = $subpattern_depth - 1; |
2689
|
0
|
|
|
|
|
|
last INPUT; |
2690
|
|
|
|
|
|
|
} |
2691
|
|
|
|
|
|
|
|
2692
|
|
|
|
|
|
|
# Otherwise, move on... |
2693
|
|
|
|
|
|
|
else { |
2694
|
0
|
|
|
|
|
|
last INPUT; |
2695
|
|
|
|
|
|
|
} |
2696
|
|
|
|
|
|
|
} |
2697
|
|
|
|
|
|
|
|
2698
|
|
|
|
|
|
|
# At end of debugging, save data to file (if requested), and clean up... |
2699
|
0
|
0
|
|
|
|
|
if ($construct_type eq '_END') { |
2700
|
0
|
|
|
|
|
|
_save_to_fh($regex_ID, $str_src); |
2701
|
|
|
|
|
|
|
|
2702
|
0
|
|
|
|
|
|
%history_of = (); |
2703
|
0
|
|
|
|
|
|
$history_of{match_heatmap} = []; |
2704
|
0
|
|
|
|
|
|
$history_of{string_heatmap} = []; |
2705
|
|
|
|
|
|
|
} |
2706
|
|
|
|
|
|
|
|
2707
|
0
|
|
|
|
|
|
return $input; |
2708
|
|
|
|
|
|
|
} |
2709
|
|
|
|
|
|
|
|
2710
|
|
|
|
|
|
|
# Dump all history and config data to a stream... |
2711
|
|
|
|
|
|
|
sub _save_to_fh { |
2712
|
0
|
|
|
0
|
|
|
my ($regex_ID, $str_src) = @_; |
2713
|
|
|
|
|
|
|
|
2714
|
|
|
|
|
|
|
# No-op if not saving to file... |
2715
|
|
|
|
|
|
|
my $fh = delete $lexical_config->{save_to_fh} |
2716
|
0
|
0
|
|
|
|
|
or return; |
2717
|
|
|
|
|
|
|
|
2718
|
|
|
|
|
|
|
# Extract data to correct level... |
2719
|
0
|
|
|
|
|
|
my $match_heatmap = delete $history_of{match_heatmap}; |
2720
|
0
|
|
|
|
|
|
my $string_heatmap = delete $history_of{string_heatmap}; |
2721
|
0
|
|
|
|
|
|
my $location = $state{$regex_ID}{location}; |
2722
|
0
|
|
|
|
|
|
my $regex_display = $state{$regex_ID}{regex_src}; |
2723
|
0
|
|
|
|
|
|
my $regex_original = $state{$regex_ID}{raw_regex}; |
2724
|
|
|
|
|
|
|
|
2725
|
|
|
|
|
|
|
# Ensure print prints everything... |
2726
|
0
|
|
|
|
|
|
my $prev_select = select $fh; |
2727
|
0
|
|
|
|
|
|
local $|=1; |
2728
|
|
|
|
|
|
|
|
2729
|
|
|
|
|
|
|
# Encode and print... |
2730
|
0
|
|
|
|
|
|
print {$fh} $JSON_encoder->({ |
2731
|
|
|
|
|
|
|
regex_ID => $regex_ID, |
2732
|
|
|
|
|
|
|
regex_location => $location, |
2733
|
|
|
|
|
|
|
regex_original => $regex_original, |
2734
|
|
|
|
|
|
|
regex_display => $regex_display, |
2735
|
|
|
|
|
|
|
string_display => $str_src, |
2736
|
|
|
|
|
|
|
config => $lexical_config, |
2737
|
0
|
|
|
|
|
|
match_data => $JSON_decoder->($history_of{JSON}[-1]{display}), |
2738
|
|
|
|
|
|
|
match_heatmap => $match_heatmap, |
2739
|
|
|
|
|
|
|
string_heatmap => $string_heatmap, |
2740
|
|
|
|
|
|
|
visualization => \%history_of, |
2741
|
|
|
|
|
|
|
}), "\n"; |
2742
|
|
|
|
|
|
|
|
2743
|
|
|
|
|
|
|
# Restore filehandles... |
2744
|
0
|
|
|
|
|
|
select $prev_select; |
2745
|
0
|
|
|
|
|
|
$lexical_config->{save_to_fh} = $fh; |
2746
|
|
|
|
|
|
|
} |
2747
|
|
|
|
|
|
|
|
2748
|
|
|
|
|
|
|
sub _show_regex_description { |
2749
|
0
|
|
|
0
|
|
|
my ($regex_ID, $save) = @_; |
2750
|
|
|
|
|
|
|
|
2751
|
|
|
|
|
|
|
# How wide to display regex components... |
2752
|
0
|
|
|
|
|
|
my $MAX_DISPLAY = 20; |
2753
|
|
|
|
|
|
|
|
2754
|
|
|
|
|
|
|
# The info we're displaying... |
2755
|
0
|
|
|
|
|
|
my $info = $state{$regex_ID}; |
2756
|
|
|
|
|
|
|
|
2757
|
|
|
|
|
|
|
# Coloured separator... |
2758
|
|
|
|
|
|
|
my $separator = $save ? q{} |
2759
|
|
|
|
|
|
|
: Term::ANSIColor::colored( |
2760
|
|
|
|
|
|
|
q{ } x $MAX_WIDTH . "\n", |
2761
|
|
|
|
|
|
|
$lexical_config->{desc_sep_col} |
2762
|
0
|
0
|
|
|
|
|
); |
2763
|
|
|
|
|
|
|
|
2764
|
|
|
|
|
|
|
# Direct the output... |
2765
|
0
|
|
|
|
|
|
my $STDOUT; |
2766
|
0
|
0
|
|
|
|
|
if ($save) { |
2767
|
0
|
|
|
|
|
|
$STDOUT = _prompt_for_file('description'); |
2768
|
|
|
|
|
|
|
} |
2769
|
|
|
|
|
|
|
else { |
2770
|
0
|
|
0
|
|
|
|
my $pager = $ENV{PAGER} // 'more'; |
2771
|
0
|
0
|
|
|
|
|
if ($pager eq 'less') { |
2772
|
0
|
|
|
|
|
|
$pager .= ' -R'; |
2773
|
|
|
|
|
|
|
} |
2774
|
0
|
0
|
|
|
|
|
open $STDOUT, '|-', $pager or return; |
2775
|
|
|
|
|
|
|
} |
2776
|
|
|
|
|
|
|
|
2777
|
|
|
|
|
|
|
# Build the display... |
2778
|
0
|
|
|
|
|
|
say {$STDOUT} |
2779
|
|
|
|
|
|
|
$separator |
2780
|
|
|
|
|
|
|
. join q{}, |
2781
|
|
|
|
|
|
|
map { |
2782
|
0
|
|
|
|
|
|
my $indent = $info->{$_}{indent}; |
2783
|
0
|
|
|
|
|
|
my $construct = sprintf('%-*s', $MAX_DISPLAY, $indent . $info->{$_}{construct}); |
2784
|
0
|
|
|
|
|
|
my $desc = $indent . $info->{$_}{desc}; |
2785
|
|
|
|
|
|
|
|
2786
|
|
|
|
|
|
|
# Decorate according to destination... |
2787
|
0
|
0
|
|
|
|
|
if ($save) { |
2788
|
0
|
|
|
|
|
|
$desc = '#' . $desc |
2789
|
|
|
|
|
|
|
} |
2790
|
|
|
|
|
|
|
else { |
2791
|
0
|
|
|
|
|
|
$construct = Term::ANSIColor::colored($construct, $lexical_config->{desc_regex_col}); |
2792
|
0
|
|
|
|
|
|
$desc = Term::ANSIColor::colored($desc, $lexical_config->{desc_text_col}); |
2793
|
|
|
|
|
|
|
} |
2794
|
|
|
|
|
|
|
|
2795
|
|
|
|
|
|
|
# Format and return... |
2796
|
0
|
0
|
|
|
|
|
if (length($indent . $info->{$_}{construct}) > 20) { |
2797
|
0
|
|
|
|
|
|
$construct . "\n" |
2798
|
|
|
|
|
|
|
. q{ } x ($MAX_DISPLAY+2) . "$desc\n" |
2799
|
|
|
|
|
|
|
. $separator |
2800
|
|
|
|
|
|
|
} |
2801
|
|
|
|
|
|
|
else { |
2802
|
0
|
|
|
|
|
|
"$construct $desc\n" |
2803
|
|
|
|
|
|
|
. $separator |
2804
|
|
|
|
|
|
|
} |
2805
|
|
|
|
|
|
|
} |
2806
|
0
|
|
|
|
|
|
sort { $a <=> $b } |
2807
|
0
|
0
|
|
|
|
|
grep { /^\d+$/ && exists $info->{$_}{desc} } |
|
0
|
|
|
|
|
|
|
2808
|
|
|
|
|
|
|
keys %$info; |
2809
|
|
|
|
|
|
|
} |
2810
|
|
|
|
|
|
|
|
2811
|
|
|
|
|
|
|
sub _show_help { |
2812
|
0
|
|
|
0
|
|
|
say <<'END_HELP'; |
2813
|
|
|
|
|
|
|
________________________________________________/ Help \______ |
2814
|
|
|
|
|
|
|
|
2815
|
|
|
|
|
|
|
Motion: s : step forwards (and into named subpattern calls) |
2816
|
|
|
|
|
|
|
n : step forwards (but over named subpattern calls) |
2817
|
|
|
|
|
|
|
- : step backwards (and into named subpattern calls) |
2818
|
|
|
|
|
|
|
p : step backwards (but over named subpattern calls) |
2819
|
|
|
|
|
|
|
m : continue to next partial match |
2820
|
|
|
|
|
|
|
M : continue to next partial match in this named subpattern |
2821
|
|
|
|
|
|
|
f : continue to next partial failure |
2822
|
|
|
|
|
|
|
F : continue to next partial failure in this named subpattern |
2823
|
|
|
|
|
|
|
r : continue until this named subpattern returns |
2824
|
|
|
|
|
|
|
c : continue to end of full match |
2825
|
|
|
|
|
|
|
C : continue to end of full match (stepping over named subpatterns) |
2826
|
|
|
|
|
|
|
R : rewind to the start of the entire match |
2827
|
|
|
|
|
|
|
: repeat last motion |
2828
|
|
|
|
|
|
|
|
2829
|
|
|
|
|
|
|
Display: v : change to visualization |
2830
|
|
|
|
|
|
|
e : change to event log |
2831
|
|
|
|
|
|
|
h : change to heatmaps |
2832
|
|
|
|
|
|
|
j : change to JSON representation |
2833
|
|
|
|
|
|
|
d : describe the regex in detail |
2834
|
|
|
|
|
|
|
|
2835
|
|
|
|
|
|
|
Snapshot: V : take snapshot of current visualization |
2836
|
|
|
|
|
|
|
E : take snapshot of current event log |
2837
|
|
|
|
|
|
|
H : take snapshot of current heatmaps |
2838
|
|
|
|
|
|
|
J : take snapshot of current JSON representation |
2839
|
|
|
|
|
|
|
D : take snapshot of regex description |
2840
|
|
|
|
|
|
|
|
2841
|
|
|
|
|
|
|
Control: q : quit debugger and continue program |
2842
|
|
|
|
|
|
|
x : exit debugger and terminate program |
2843
|
|
|
|
|
|
|
|
2844
|
|
|
|
|
|
|
______________________________________________________________ |
2845
|
|
|
|
|
|
|
END_HELP |
2846
|
|
|
|
|
|
|
} |
2847
|
|
|
|
|
|
|
|
2848
|
|
|
|
|
|
|
# Take a snapshot of the current debugger state... |
2849
|
|
|
|
|
|
|
my @ERR_MODE = ( -timeout => 10, -style => $ERR_COL, -single); |
2850
|
|
|
|
|
|
|
|
2851
|
|
|
|
|
|
|
sub _prompt_for_file { |
2852
|
0
|
|
|
0
|
|
|
my ($data_mode) = @_; |
2853
|
|
|
|
|
|
|
|
2854
|
0
|
0
|
|
|
|
|
if (!eval { require Time::HiRes; }) { |
|
0
|
|
|
|
|
|
|
2855
|
0
|
|
|
0
|
|
|
*Time::HiRes::time = sub { time }; |
|
0
|
|
|
|
|
|
|
2856
|
|
|
|
|
|
|
} |
2857
|
|
|
|
|
|
|
|
2858
|
|
|
|
|
|
|
# Default target for save... |
2859
|
0
|
|
|
|
|
|
my $open_mode = '>'; |
2860
|
0
|
|
|
|
|
|
my $filename = 'rxrx_' . $data_mode . '_' . Time::HiRes::time(); |
2861
|
|
|
|
|
|
|
|
2862
|
|
|
|
|
|
|
# Request a filename... |
2863
|
0
|
|
|
|
|
|
print "Save $data_mode snapshot as: "; |
2864
|
0
|
|
|
|
|
|
my $input = _interact(); |
2865
|
|
|
|
|
|
|
|
2866
|
|
|
|
|
|
|
# Default to paged-to-screen... |
2867
|
0
|
0
|
|
|
|
|
if ($input eq "\n") { |
|
|
0
|
|
|
|
|
|
2868
|
0
|
|
|
|
|
|
say ''; |
2869
|
0
|
|
|
|
|
|
$open_mode = '|-'; |
2870
|
0
|
|
0
|
|
|
|
$filename = $ENV{PAGER} // 'more'; |
2871
|
0
|
0
|
|
|
|
|
if ($filename eq 'less') { |
2872
|
0
|
|
|
|
|
|
$filename .= ' -R'; |
2873
|
|
|
|
|
|
|
} |
2874
|
|
|
|
|
|
|
} |
2875
|
|
|
|
|
|
|
|
2876
|
|
|
|
|
|
|
# selects precomputed filename... |
2877
|
|
|
|
|
|
|
elsif ($input eq "\t") { |
2878
|
0
|
|
|
|
|
|
say $filename; |
2879
|
0
|
|
|
|
|
|
_pause(2); |
2880
|
|
|
|
|
|
|
} |
2881
|
|
|
|
|
|
|
|
2882
|
|
|
|
|
|
|
# Otherwise, use whatever they type... |
2883
|
|
|
|
|
|
|
else { |
2884
|
0
|
|
|
|
|
|
$filename = $input; |
2885
|
0
|
|
|
|
|
|
print $input; |
2886
|
0
|
|
|
|
|
|
$filename .= readline *STDIN; |
2887
|
0
|
|
|
|
|
|
chomp $filename; |
2888
|
|
|
|
|
|
|
} |
2889
|
|
|
|
|
|
|
|
2890
|
|
|
|
|
|
|
# Set up the output stream... |
2891
|
0
|
0
|
|
|
|
|
open my $fh, $open_mode, $filename or do { |
2892
|
0
|
|
|
|
|
|
say Term::ANSIColor::colored("Can't open $filename: $!", $ERR_COL); |
2893
|
0
|
|
|
|
|
|
say Term::ANSIColor::colored("(Hit any key to continue)", $ERR_COL); |
2894
|
0
|
|
|
|
|
|
_interact(); |
2895
|
0
|
|
|
|
|
|
return; |
2896
|
|
|
|
|
|
|
}; |
2897
|
|
|
|
|
|
|
|
2898
|
0
|
|
|
|
|
|
return $fh; |
2899
|
|
|
|
|
|
|
} |
2900
|
|
|
|
|
|
|
|
2901
|
|
|
|
|
|
|
sub _save_snapshot { |
2902
|
0
|
|
|
0
|
|
|
my ($data_mode, $step) = @_; |
2903
|
0
|
|
0
|
|
|
|
$step //= -1; |
2904
|
|
|
|
|
|
|
|
2905
|
|
|
|
|
|
|
# Open the save target... |
2906
|
0
|
|
|
|
|
|
my $fh = _prompt_for_file($data_mode); |
2907
|
|
|
|
|
|
|
|
2908
|
|
|
|
|
|
|
# Output current state (appropriately trimmed)... |
2909
|
0
|
|
|
|
|
|
my $state = $history_of{$data_mode}[$step]{display}; |
2910
|
0
|
|
|
|
|
|
while (substr($state, 0, 1) eq "\n") { |
2911
|
0
|
|
|
|
|
|
substr($state, 0, 1, q{}); |
2912
|
|
|
|
|
|
|
} |
2913
|
0
|
|
|
|
|
|
print {$fh} $state; |
|
0
|
|
|
|
|
|
|
2914
|
|
|
|
|
|
|
|
2915
|
|
|
|
|
|
|
# JSON output may be partial... |
2916
|
0
|
0
|
0
|
|
|
|
if ($data_mode eq 'JSON' && substr($state, -2) eq ",\n") { |
2917
|
0
|
|
|
|
|
|
print {$fh} " { MATCH_INCOMPLETE => 1 }\n]\n"; |
|
0
|
|
|
|
|
|
|
2918
|
|
|
|
|
|
|
} |
2919
|
|
|
|
|
|
|
|
2920
|
|
|
|
|
|
|
# Clean up... |
2921
|
0
|
|
|
|
|
|
close $fh; |
2922
|
|
|
|
|
|
|
|
2923
|
|
|
|
|
|
|
# Restore previous visuals... |
2924
|
0
|
|
|
|
|
|
_print $history_of{$display_mode}[-1]{display}; |
2925
|
|
|
|
|
|
|
|
2926
|
0
|
|
|
|
|
|
return; |
2927
|
|
|
|
|
|
|
} |
2928
|
|
|
|
|
|
|
|
2929
|
|
|
|
|
|
|
sub _build_heatmap { |
2930
|
0
|
|
|
0
|
|
|
my ($str, $count_ref) = @_; |
2931
|
|
|
|
|
|
|
|
2932
|
|
|
|
|
|
|
# Determine colours to be used... |
2933
|
0
|
|
|
|
|
|
my @HEAT_COLOUR = @{$lexical_config->{heatmap_col}}; |
|
0
|
|
|
|
|
|
|
2934
|
|
|
|
|
|
|
|
2935
|
|
|
|
|
|
|
# Normalize counts to match @HEAT_COLOUR entries... |
2936
|
0
|
|
0
|
|
|
|
my $max = max 1, map { $_ // 0 } @{$count_ref}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
2937
|
0
|
|
0
|
|
|
|
my @count = map { int( 0.5 + $#HEAT_COLOUR * ($_//0) / $max ) } @{$count_ref}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
2938
|
|
|
|
|
|
|
|
2939
|
|
|
|
|
|
|
# Colour each character... |
2940
|
0
|
|
|
|
|
|
my $heatmap = q{}; |
2941
|
0
|
|
|
|
|
|
for my $n (0..length($str)-1) { |
2942
|
0
|
|
0
|
|
|
|
my $heat = $HEAT_COLOUR[$count[$n] // 0]; |
2943
|
0
|
|
|
|
|
|
$heatmap .= _ws_colourer(substr($str,$n,1), $heat); |
2944
|
|
|
|
|
|
|
} |
2945
|
|
|
|
|
|
|
|
2946
|
0
|
|
|
|
|
|
return $heatmap; |
2947
|
|
|
|
|
|
|
} |
2948
|
|
|
|
|
|
|
|
2949
|
|
|
|
|
|
|
# Extract a window-into-string to fit it on screen... |
2950
|
|
|
|
|
|
|
sub _make_window { |
2951
|
0
|
|
|
0
|
|
|
my %arg = @_; |
2952
|
|
|
|
|
|
|
|
2953
|
0
|
|
0
|
|
|
|
my $src = $arg{text} // q{}; |
2954
|
0
|
|
0
|
|
|
|
my $pos = $arg{pos} // 0; |
2955
|
0
|
|
0
|
|
|
|
my $start_pos = $arg{start} // 0; |
2956
|
0
|
|
0
|
|
|
|
my @heatmap = @{ $arg{heat} // [] }; |
|
0
|
|
|
|
|
|
|
2957
|
0
|
|
|
|
|
|
my $ws_colour = $arg{ws_colour}; |
2958
|
0
|
|
|
|
|
|
my $window = !$arg{no_window}; |
2959
|
0
|
|
|
|
|
|
my $marker = $arg{marker}; |
2960
|
|
|
|
|
|
|
|
2961
|
|
|
|
|
|
|
# Extend heatmap and marker to length of text... |
2962
|
0
|
0
|
|
|
|
|
if (@heatmap) { |
2963
|
0
|
|
|
|
|
|
push @heatmap, (0) x (length($src) - @heatmap); |
2964
|
|
|
|
|
|
|
} |
2965
|
0
|
0
|
|
|
|
|
if ($marker) { |
2966
|
0
|
|
|
|
|
|
$marker .= q{ } x (length($src) - length($marker)); |
2967
|
|
|
|
|
|
|
} |
2968
|
|
|
|
|
|
|
|
2969
|
|
|
|
|
|
|
# Crop to window, if necessary... |
2970
|
0
|
0
|
|
|
|
|
if ($window) { |
2971
|
|
|
|
|
|
|
|
2972
|
|
|
|
|
|
|
# How big is the space we have to fill??? |
2973
|
0
|
|
|
|
|
|
my $window_width = $MAX_WIDTH - 2; # ...allow 2 chars for delimiters |
2974
|
0
|
|
|
|
|
|
my $mid_window = $MAX_WIDTH/2; |
2975
|
|
|
|
|
|
|
|
2976
|
|
|
|
|
|
|
# Only modify values if content longer than window... |
2977
|
0
|
0
|
|
|
|
|
if (length($src) > $window_width) { |
2978
|
|
|
|
|
|
|
# At the start of the string, chop off the end... |
2979
|
0
|
0
|
|
|
|
|
if ($pos <= $mid_window) { |
|
|
0
|
|
|
|
|
|
2980
|
0
|
0
|
|
|
|
|
if ($marker) { |
2981
|
0
|
|
|
|
|
|
$marker = substr($marker, 0, $window_width); |
2982
|
|
|
|
|
|
|
} |
2983
|
0
|
|
|
|
|
|
$src = substr($src, 0, $window_width); |
2984
|
0
|
|
|
|
|
|
substr($src,-3,3,q{...}); |
2985
|
|
|
|
|
|
|
} |
2986
|
|
|
|
|
|
|
# At the end of the string, chop off the start... |
2987
|
|
|
|
|
|
|
elsif (length($src) - $pos < $mid_window) { |
2988
|
0
|
|
|
|
|
|
$pos = $window_width - length($src) + $pos; |
2989
|
0
|
|
|
|
|
|
$start_pos = $window_width - length($src) + $start_pos; |
2990
|
0
|
0
|
|
|
|
|
if (@heatmap) { |
2991
|
0
|
|
|
|
|
|
@heatmap = @heatmap[length($src)-$window_width..$#heatmap]; |
2992
|
|
|
|
|
|
|
} |
2993
|
0
|
0
|
|
|
|
|
if ($marker) { |
2994
|
0
|
|
|
|
|
|
$marker = substr($marker, length($src)-$window_width, $window_width); |
2995
|
|
|
|
|
|
|
} |
2996
|
0
|
|
|
|
|
|
$src = substr($src, -$window_width); |
2997
|
0
|
|
|
|
|
|
substr($src,0,3,q{...}); |
2998
|
|
|
|
|
|
|
} |
2999
|
|
|
|
|
|
|
# In the middle of the string, centre the window on the position... |
3000
|
|
|
|
|
|
|
else { |
3001
|
0
|
|
|
|
|
|
$src = substr($src, $pos-$mid_window+1, $window_width); |
3002
|
0
|
0
|
|
|
|
|
if (@heatmap) { |
3003
|
0
|
|
|
|
|
|
@heatmap= splice(@heatmap, $pos-$mid_window+1, $window_width); |
3004
|
|
|
|
|
|
|
} |
3005
|
0
|
0
|
|
|
|
|
if ($marker) { |
3006
|
0
|
|
|
|
|
|
$marker = substr($marker, $pos-$mid_window+1, $window_width); |
3007
|
|
|
|
|
|
|
} |
3008
|
0
|
|
|
|
|
|
$start_pos -= $pos; |
3009
|
0
|
|
|
|
|
|
$pos = $window_width/2; |
3010
|
0
|
|
|
|
|
|
$start_pos += $pos; |
3011
|
0
|
|
|
|
|
|
substr($src,0,3,q{...}); |
3012
|
0
|
|
|
|
|
|
substr($src,-3,3,q{...}); |
3013
|
|
|
|
|
|
|
} |
3014
|
|
|
|
|
|
|
} |
3015
|
|
|
|
|
|
|
} |
3016
|
|
|
|
|
|
|
|
3017
|
|
|
|
|
|
|
# Convert to heatmap, if requested... |
3018
|
0
|
0
|
|
|
|
|
if (@heatmap) { |
|
|
0
|
|
|
|
|
|
3019
|
0
|
|
|
|
|
|
$src = _build_heatmap($src, \@heatmap); |
3020
|
|
|
|
|
|
|
} |
3021
|
|
|
|
|
|
|
elsif ($ws_colour) { |
3022
|
0
|
|
|
|
|
|
$src = _ws_colourer($src); |
3023
|
|
|
|
|
|
|
} |
3024
|
|
|
|
|
|
|
|
3025
|
|
|
|
|
|
|
# Trim trailing whitespace from marker... |
3026
|
0
|
|
0
|
|
|
|
while ($marker && substr($marker,-1) eq q{ }) { |
3027
|
0
|
|
|
|
|
|
substr($marker, -1) = q{}; |
3028
|
|
|
|
|
|
|
} |
3029
|
|
|
|
|
|
|
|
3030
|
0
|
|
|
|
|
|
return ($src, $pos, max($start_pos,0), $marker); |
3031
|
|
|
|
|
|
|
} |
3032
|
|
|
|
|
|
|
|
3033
|
|
|
|
|
|
|
# Colour message appropriately... |
3034
|
|
|
|
|
|
|
sub _fail_colourer { |
3035
|
0
|
|
|
0
|
|
|
my ($str, $ws_colouring) = @_; |
3036
|
0
|
0
|
|
|
|
|
my $colourer = $ws_colouring ? \&_ws_colourer : \&Term::ANSIColor::colored; |
3037
|
0
|
|
|
|
|
|
return $colourer->($str, $lexical_config->{fail_col}); |
3038
|
|
|
|
|
|
|
} |
3039
|
|
|
|
|
|
|
|
3040
|
|
|
|
|
|
|
sub _info_colourer { |
3041
|
0
|
|
|
0
|
|
|
my ($str, $ws_colouring) = @_; |
3042
|
0
|
0
|
|
|
|
|
my $colourer = $ws_colouring ? \&_ws_colourer : \&Term::ANSIColor::colored; |
3043
|
0
|
|
|
|
|
|
return $colourer->($str, $lexical_config->{info_col}); |
3044
|
|
|
|
|
|
|
} |
3045
|
|
|
|
|
|
|
|
3046
|
|
|
|
|
|
|
sub _try_colourer { |
3047
|
0
|
|
|
0
|
|
|
my ($str, $extras, $ws_colouring) = @_; |
3048
|
0
|
|
0
|
|
|
|
$extras //= q{}; |
3049
|
0
|
0
|
|
|
|
|
my $colourer = $ws_colouring ? \&_ws_colourer : \&Term::ANSIColor::colored; |
3050
|
0
|
|
|
|
|
|
return $colourer->($str, "$lexical_config->{try_col} $extras"); |
3051
|
|
|
|
|
|
|
} |
3052
|
|
|
|
|
|
|
|
3053
|
|
|
|
|
|
|
sub _match_colourer { |
3054
|
0
|
|
|
0
|
|
|
my ($str, $extras, $ws_colouring) = @_; |
3055
|
0
|
|
0
|
|
|
|
$extras //= q{}; |
3056
|
0
|
0
|
|
|
|
|
my $colourer = $ws_colouring ? \&_ws_colourer : \&Term::ANSIColor::colored; |
3057
|
0
|
|
|
|
|
|
return $colourer->($str, "$lexical_config->{match_col} $extras"); |
3058
|
|
|
|
|
|
|
} |
3059
|
|
|
|
|
|
|
|
3060
|
|
|
|
|
|
|
my %DISPLAY_FOR = ( |
3061
|
|
|
|
|
|
|
"\n" => 'n', |
3062
|
|
|
|
|
|
|
"\t" => 't', |
3063
|
|
|
|
|
|
|
"\r" => 'r', |
3064
|
|
|
|
|
|
|
"\f" => 'f', |
3065
|
|
|
|
|
|
|
"\b" => 'b', |
3066
|
|
|
|
|
|
|
"\a" => 'a', |
3067
|
|
|
|
|
|
|
"\e" => 'e', |
3068
|
|
|
|
|
|
|
"\0" => '0', |
3069
|
|
|
|
|
|
|
); |
3070
|
|
|
|
|
|
|
|
3071
|
|
|
|
|
|
|
sub _ws_colourer { |
3072
|
0
|
|
|
0
|
|
|
my ($str, $colour_scheme) = @_; |
3073
|
|
|
|
|
|
|
|
3074
|
|
|
|
|
|
|
# How to colour the text... |
3075
|
0
|
|
0
|
|
|
|
$colour_scheme //= 'clear'; |
3076
|
0
|
|
|
|
|
|
my $ws_colour_scheme = "$colour_scheme $lexical_config->{ws_col}"; |
3077
|
|
|
|
|
|
|
|
3078
|
|
|
|
|
|
|
# Accumulate the text... |
3079
|
0
|
|
|
|
|
|
my $coloured_str = q{}; |
3080
|
0
|
|
|
|
|
|
my $prefix = q{}; |
3081
|
|
|
|
|
|
|
|
3082
|
|
|
|
|
|
|
# Step through char-by-char... |
3083
|
|
|
|
|
|
|
CHAR: |
3084
|
0
|
|
|
|
|
|
for my $n (0..length($str)-1) { |
3085
|
0
|
|
|
|
|
|
my $char = substr($str, $n, 1); |
3086
|
|
|
|
|
|
|
|
3087
|
|
|
|
|
|
|
# If it's special, handle it... |
3088
|
0
|
|
|
|
|
|
for my $special_char (keys %DISPLAY_FOR) { |
3089
|
0
|
0
|
|
|
|
|
if ($char eq $special_char) { |
3090
|
0
|
0
|
|
|
|
|
if (length($prefix)) { |
3091
|
0
|
|
|
|
|
|
$coloured_str .= Term::ANSIColor::colored($prefix, $colour_scheme); |
3092
|
0
|
|
|
|
|
|
$prefix = q{}; |
3093
|
|
|
|
|
|
|
} |
3094
|
0
|
|
|
|
|
|
$coloured_str .= Term::ANSIColor::colored($DISPLAY_FOR{$special_char}, $ws_colour_scheme); |
3095
|
0
|
|
|
|
|
|
next CHAR; |
3096
|
|
|
|
|
|
|
} |
3097
|
|
|
|
|
|
|
} |
3098
|
|
|
|
|
|
|
|
3099
|
|
|
|
|
|
|
# Otherwise, accumulate it... |
3100
|
0
|
|
|
|
|
|
$prefix .= $char; |
3101
|
|
|
|
|
|
|
} |
3102
|
|
|
|
|
|
|
|
3103
|
|
|
|
|
|
|
# Clean up any remaining text... |
3104
|
0
|
0
|
|
|
|
|
if (length($prefix)) { |
3105
|
0
|
|
|
|
|
|
$coloured_str .= Term::ANSIColor::colored($prefix, $colour_scheme); |
3106
|
|
|
|
|
|
|
} |
3107
|
|
|
|
|
|
|
|
3108
|
0
|
|
|
|
|
|
return $coloured_str; |
3109
|
|
|
|
|
|
|
} |
3110
|
|
|
|
|
|
|
|
3111
|
|
|
|
|
|
|
sub _colourer_for { |
3112
|
0
|
|
|
0
|
|
|
my $msg = shift; |
3113
|
|
|
|
|
|
|
|
3114
|
0
|
0
|
0
|
|
|
|
if (index($msg,'forgetting') >= 0 || index($msg,'Forgetting') >= 0) { |
3115
|
0
|
|
|
|
|
|
return \&_info_colourer; |
3116
|
|
|
|
|
|
|
} |
3117
|
0
|
0
|
0
|
|
|
|
if (index($msg,'try') >= 0 || index($msg,'Try') >= 0) { |
3118
|
0
|
|
|
|
|
|
return \&_try_colourer; |
3119
|
|
|
|
|
|
|
} |
3120
|
0
|
0
|
0
|
|
|
|
if (index($msg,'failed') >= 0 || index($msg,'Failed') >= 0) { |
3121
|
0
|
|
|
|
|
|
return \&_fail_colourer; |
3122
|
|
|
|
|
|
|
} |
3123
|
0
|
0
|
0
|
|
|
|
if (index($msg,'matched') >= 0 || index($msg,'Matched') >= 0) { |
3124
|
0
|
|
|
|
|
|
return \&_match_colourer; |
3125
|
|
|
|
|
|
|
} |
3126
|
0
|
|
|
|
|
|
return \&_info_colourer; |
3127
|
|
|
|
|
|
|
} |
3128
|
|
|
|
|
|
|
|
3129
|
|
|
|
|
|
|
# Set up interaction as spiffily as possible... |
3130
|
|
|
|
|
|
|
|
3131
|
|
|
|
|
|
|
if (eval{ require Term::ReadKey }) { |
3132
|
|
|
|
|
|
|
*_interact = sub { |
3133
|
|
|
|
|
|
|
# No interactions when piping output to a filehandle... |
3134
|
|
|
|
|
|
|
return 'c' if $lexical_config->{save_to_fh}; |
3135
|
|
|
|
|
|
|
|
3136
|
|
|
|
|
|
|
# Otherwise grab a single key and return it... |
3137
|
|
|
|
|
|
|
Term::ReadKey::ReadMode('raw'); |
3138
|
|
|
|
|
|
|
my $input = Term::ReadKey::ReadKey(0); |
3139
|
|
|
|
|
|
|
Term::ReadKey::ReadMode('restore'); |
3140
|
|
|
|
|
|
|
return $input; |
3141
|
|
|
|
|
|
|
} |
3142
|
|
|
|
|
|
|
} |
3143
|
|
|
|
|
|
|
else { |
3144
|
|
|
|
|
|
|
*_interact = sub { |
3145
|
|
|
|
|
|
|
# No interactions when piping output to a filehandle... |
3146
|
0
|
0
|
|
0
|
|
|
return 'c' if $lexical_config->{save_to_fh}; |
3147
|
|
|
|
|
|
|
|
3148
|
|
|
|
|
|
|
# Otherwise return the first letter typed... |
3149
|
0
|
|
|
|
|
|
my $input = readline; |
3150
|
0
|
|
|
|
|
|
return substr($input, 0, 1); |
3151
|
|
|
|
|
|
|
} |
3152
|
|
|
|
|
|
|
} |
3153
|
|
|
|
|
|
|
|
3154
|
|
|
|
|
|
|
|
3155
|
|
|
|
|
|
|
#====[ REPL (a.k.a. rxrx) ]======================= |
3156
|
|
|
|
|
|
|
|
3157
|
|
|
|
|
|
|
# Deal with v5.16 weirdness... |
3158
|
|
|
|
|
|
|
BEGIN { |
3159
|
1
|
50
|
|
1
|
|
8
|
if ($] >= 5.016) { |
3160
|
1
|
|
|
|
|
7
|
require feature; |
3161
|
1
|
|
|
|
|
124
|
feature->import('evalbytes'); |
3162
|
1
|
|
|
|
|
796
|
*evaluate = \&CORE::evalbytes; |
3163
|
|
|
|
|
|
|
} |
3164
|
|
|
|
|
|
|
else { |
3165
|
0
|
|
|
|
|
0
|
*evaluate = sub{ eval shift }; |
|
0
|
|
|
|
|
0
|
|
3166
|
|
|
|
|
|
|
} |
3167
|
|
|
|
|
|
|
} |
3168
|
|
|
|
|
|
|
|
3169
|
|
|
|
|
|
|
my $FROM_START = 0; |
3170
|
|
|
|
|
|
|
|
3171
|
|
|
|
|
|
|
sub rxrx { |
3172
|
|
|
|
|
|
|
# Handle: rxrx |
3173
|
0
|
0
|
|
0
|
0
|
|
if (@_) { |
3174
|
0
|
|
|
|
|
|
local @ARGV = @_; |
3175
|
|
|
|
|
|
|
|
3176
|
|
|
|
|
|
|
# If file is a debugger dump, decode and step through it... |
3177
|
0
|
|
|
|
|
|
my $filetext = do { local $/; <> }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
3178
|
0
|
|
|
|
|
|
my $dumped_data = eval { $JSON_decoder->($filetext) }; |
|
0
|
|
|
|
|
|
|
3179
|
0
|
0
|
0
|
|
|
|
if (ref($dumped_data) eq 'HASH' && defined $dumped_data->{regex_ID} ) { |
3180
|
|
|
|
|
|
|
# Reconstruct internal state... |
3181
|
0
|
|
|
|
|
|
my $regex_ID = $dumped_data->{regex_ID}; |
3182
|
0
|
|
|
|
|
|
%history_of = %{ $dumped_data->{visualization} }; |
|
0
|
|
|
|
|
|
|
3183
|
0
|
|
|
|
|
|
$history_of{match_heatmap} = $dumped_data->{match_heatmap}; |
3184
|
0
|
|
|
|
|
|
$history_of{string_heatmap} = $dumped_data->{string_heatmap}; |
3185
|
0
|
|
|
|
|
|
$display_mode = $dumped_data->{config}{display_mode}; |
3186
|
0
|
|
|
|
|
|
$state{$regex_ID}{location} = $dumped_data->{regex_location}; |
3187
|
|
|
|
|
|
|
|
3188
|
|
|
|
|
|
|
# Display... |
3189
|
0
|
|
|
|
|
|
my $step = $FROM_START; |
3190
|
0
|
|
|
|
|
|
my $cmd; |
3191
|
0
|
|
|
|
|
|
while (1) { |
3192
|
0
|
|
|
|
|
|
($cmd, $step) = _revisualize($regex_ID, '-', $step); |
3193
|
0
|
0
|
|
|
|
|
last if lc($cmd) eq 'q'; |
3194
|
0
|
|
|
|
|
|
$step = min($step, @{$history_of{visual}}-1); |
|
0
|
|
|
|
|
|
|
3195
|
|
|
|
|
|
|
} |
3196
|
0
|
|
|
|
|
|
exit; |
3197
|
|
|
|
|
|
|
} |
3198
|
|
|
|
|
|
|
|
3199
|
|
|
|
|
|
|
# Otherwise, assume it's a perl source file and debug it... |
3200
|
|
|
|
|
|
|
else { |
3201
|
0
|
0
|
|
|
|
|
exec $^X, '-MRegexp::Debugger', @_ |
3202
|
|
|
|
|
|
|
or die "Couldn't invoke perl: $!"; |
3203
|
|
|
|
|
|
|
} |
3204
|
|
|
|
|
|
|
} |
3205
|
|
|
|
|
|
|
|
3206
|
|
|
|
|
|
|
# Otherwise, be interactive... |
3207
|
|
|
|
|
|
|
|
3208
|
|
|
|
|
|
|
# Track input history... |
3209
|
0
|
|
|
|
|
|
my $str_history = []; |
3210
|
0
|
|
|
|
|
|
my $regex_history = []; |
3211
|
|
|
|
|
|
|
|
3212
|
|
|
|
|
|
|
# Start with empty data... |
3213
|
0
|
|
|
|
|
|
my $input_regex = ''; |
3214
|
0
|
|
|
|
|
|
my $regex = ''; |
3215
|
0
|
|
|
|
|
|
my $regex_flags = ''; |
3216
|
0
|
|
|
|
|
|
my $string = ''; |
3217
|
|
|
|
|
|
|
|
3218
|
|
|
|
|
|
|
# And display it... |
3219
|
0
|
|
|
|
|
|
_display($string, $input_regex,q{}); |
3220
|
|
|
|
|
|
|
|
3221
|
|
|
|
|
|
|
INPUT: |
3222
|
0
|
|
|
|
|
|
while (1) { |
3223
|
0
|
|
|
|
|
|
my $input = _prompt('>'); |
3224
|
|
|
|
|
|
|
|
3225
|
|
|
|
|
|
|
# String history mode? |
3226
|
0
|
0
|
|
|
|
|
if ($input =~ /^['"]$/) { |
|
|
0
|
|
|
|
|
|
3227
|
0
|
|
|
|
|
|
$input = _rxrx_history($str_history); |
3228
|
|
|
|
|
|
|
} |
3229
|
|
|
|
|
|
|
|
3230
|
|
|
|
|
|
|
# Regex history mode? |
3231
|
|
|
|
|
|
|
elsif ($input eq '/') { |
3232
|
0
|
|
|
|
|
|
$input = _rxrx_history($regex_history); |
3233
|
|
|
|
|
|
|
} |
3234
|
|
|
|
|
|
|
|
3235
|
|
|
|
|
|
|
|
3236
|
|
|
|
|
|
|
# Are we updating the regex or string??? |
3237
|
0
|
0
|
|
|
|
|
if ($input =~ m{^ (? [+]\s*[/]|[/"']) (? .*?) (? \k (? [imsxlaud]*) )? \s* \z }x) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
3238
|
0
|
|
|
|
|
|
my ($cmd, $data, $endcmd, $flags) = @+{qw< cmd data endcmd flags >}; |
3239
|
|
|
|
|
|
|
|
3240
|
|
|
|
|
|
|
# Load the rest of the regex (if any)... |
3241
|
0
|
0
|
|
|
|
|
if ($cmd =~ m{[+]\s*[/]}xms) { |
3242
|
0
|
|
|
|
|
|
$cmd = '/'; |
3243
|
0
|
|
|
|
|
|
while (my $input = _prompt(' +')) { |
3244
|
0
|
0
|
|
|
|
|
last if $input eq q{}; |
3245
|
0
|
0
|
|
|
|
|
if ($input =~ m{\A (?.*) [/][imsxlaud]*\Z}xms) { |
3246
|
0
|
|
|
|
|
|
$data .= "\n$+{data}"; |
3247
|
0
|
|
|
|
|
|
last; |
3248
|
|
|
|
|
|
|
} |
3249
|
|
|
|
|
|
|
else { |
3250
|
0
|
|
|
|
|
|
$data .= "\n$input"; |
3251
|
|
|
|
|
|
|
} |
3252
|
|
|
|
|
|
|
} |
3253
|
|
|
|
|
|
|
} |
3254
|
|
|
|
|
|
|
|
3255
|
|
|
|
|
|
|
# Compile and save the new regex... |
3256
|
0
|
0
|
|
|
|
|
if ($cmd eq q{/}) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
3257
|
0
|
0
|
|
|
|
|
if ($data eq q{}) { |
3258
|
0
|
|
|
|
|
|
state $NULL_REGEX = eval q{use Regexp::Debugger; qr{(?#NULL)}; }; |
3259
|
0
|
|
|
|
|
|
$regex = $NULL_REGEX; |
3260
|
|
|
|
|
|
|
} |
3261
|
|
|
|
|
|
|
else { |
3262
|
0
|
|
|
|
|
|
$input_regex = $data; |
3263
|
0
|
|
0
|
|
|
|
$regex_flags = $flags // 'x'; |
3264
|
1
|
|
|
1
|
|
9
|
use re 'eval'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1201
|
|
3265
|
0
|
|
|
|
|
|
$regex = evaluate qq{\n# line 0 rxrx\nuse re 'eval'; use Regexp::Debugger; qr/$data/$regex_flags;}; |
3266
|
|
|
|
|
|
|
} |
3267
|
|
|
|
|
|
|
|
3268
|
|
|
|
|
|
|
# Report any errors... |
3269
|
0
|
0
|
|
|
|
|
if (!defined $regex) { |
3270
|
0
|
|
|
|
|
|
$input_regex = "Invalid regex:\n$@"; |
3271
|
0
|
|
|
|
|
|
say '>', eval qq{\n# line 0 rxrx\n qr/$data/$regex_flags;}; |
3272
|
|
|
|
|
|
|
} |
3273
|
|
|
|
|
|
|
else { # Remember it... |
3274
|
0
|
|
|
|
|
|
push @{$regex_history}, $input; |
|
0
|
|
|
|
|
|
|
3275
|
|
|
|
|
|
|
} |
3276
|
|
|
|
|
|
|
} |
3277
|
|
|
|
|
|
|
|
3278
|
|
|
|
|
|
|
# Otherwise compile the string (interpolated or not)... |
3279
|
|
|
|
|
|
|
elsif ($+{cmd} eq q{"}) { |
3280
|
0
|
|
|
|
|
|
$string = evaluate qq{"$+{data}"}; |
3281
|
|
|
|
|
|
|
|
3282
|
|
|
|
|
|
|
# Report any errors... |
3283
|
0
|
0
|
|
|
|
|
print "$@\n" if $@; |
3284
|
0
|
0
|
|
|
|
|
print "Invalid input\n" if !defined $string; |
3285
|
|
|
|
|
|
|
|
3286
|
|
|
|
|
|
|
# Remember it... |
3287
|
0
|
|
|
|
|
|
push @{$str_history}, $input; |
|
0
|
|
|
|
|
|
|
3288
|
|
|
|
|
|
|
} |
3289
|
|
|
|
|
|
|
elsif ($+{cmd} eq q{'}) { |
3290
|
0
|
|
|
|
|
|
$string = evaluate qq{'$+{data}'}; |
3291
|
|
|
|
|
|
|
|
3292
|
|
|
|
|
|
|
# Report any errors... |
3293
|
0
|
0
|
|
|
|
|
print "$@\n" if $@; |
3294
|
0
|
0
|
|
|
|
|
print "Invalid input\n" if !defined $string; |
3295
|
|
|
|
|
|
|
|
3296
|
|
|
|
|
|
|
# Remember it... |
3297
|
0
|
|
|
|
|
|
push @{$str_history}, $input; |
|
0
|
|
|
|
|
|
|
3298
|
|
|
|
|
|
|
} |
3299
|
|
|
|
|
|
|
} |
3300
|
|
|
|
|
|
|
|
3301
|
|
|
|
|
|
|
# Quit if quitting requested... |
3302
|
|
|
|
|
|
|
elsif ($input =~ /^ \s* [xXqQ]/x) { |
3303
|
0
|
|
|
|
|
|
say q{}; |
3304
|
0
|
|
|
|
|
|
last INPUT; |
3305
|
|
|
|
|
|
|
} |
3306
|
|
|
|
|
|
|
|
3307
|
|
|
|
|
|
|
# Help... |
3308
|
|
|
|
|
|
|
elsif ($input =~ /^ \s* [?hH]/x) { |
3309
|
0
|
|
|
|
|
|
print "\n" x 2; |
3310
|
0
|
|
|
|
|
|
say '____________________________________________/ Help \____'; |
3311
|
0
|
|
|
|
|
|
say ' '; |
3312
|
0
|
|
|
|
|
|
say ' / : Enter a pattern in a single line'; |
3313
|
0
|
|
|
|
|
|
say ' +/ : Enter first line of a multi-line pattern'; |
3314
|
0
|
|
|
|
|
|
say " ' : Enter a new literal string"; |
3315
|
0
|
|
|
|
|
|
say ' " : Enter a new double-quoted string'; |
3316
|
0
|
0
|
|
|
|
|
if (eval { require IO::Prompter }) { |
|
0
|
|
|
|
|
|
|
3317
|
0
|
|
|
|
|
|
say ''; |
3318
|
0
|
|
|
|
|
|
say 'CTRL-R : History completion - move backwards one input'; |
3319
|
0
|
|
|
|
|
|
say 'CTRL-N : History completion - move forwards one input'; |
3320
|
0
|
|
|
|
|
|
say ''; |
3321
|
0
|
|
|
|
|
|
say 'CTRL-B : Cursor motion - move back one character'; |
3322
|
0
|
|
|
|
|
|
say 'CTRL-F : Cursor motion - move forwards one character'; |
3323
|
0
|
|
|
|
|
|
say 'CTRL-A : Cursor motion - move to start of input'; |
3324
|
0
|
|
|
|
|
|
say 'CTRL-E : Cursor motion - move to end of input'; |
3325
|
|
|
|
|
|
|
} |
3326
|
0
|
|
|
|
|
|
say ''; |
3327
|
0
|
|
|
|
|
|
say ' m : Match current string against current pattern'; |
3328
|
0
|
|
|
|
|
|
say ''; |
3329
|
0
|
|
|
|
|
|
say ' g : Exhaustively match against current pattern'; |
3330
|
0
|
|
|
|
|
|
say ''; |
3331
|
0
|
|
|
|
|
|
say ' d : Deconstruct and explain the current regex'; |
3332
|
0
|
|
|
|
|
|
say ''; |
3333
|
0
|
|
|
|
|
|
say 'q or x : quit debugger and exit'; |
3334
|
0
|
|
|
|
|
|
next INPUT; |
3335
|
|
|
|
|
|
|
} |
3336
|
|
|
|
|
|
|
|
3337
|
|
|
|
|
|
|
# Visualize the match... |
3338
|
|
|
|
|
|
|
elsif ($input =~ /m/i) { |
3339
|
0
|
|
|
|
|
|
$string =~ $regex; |
3340
|
|
|
|
|
|
|
} |
3341
|
|
|
|
|
|
|
|
3342
|
|
|
|
|
|
|
# Visualize the matches... |
3343
|
|
|
|
|
|
|
elsif ($input =~ /g/i) { |
3344
|
0
|
|
|
|
|
|
() = $string =~ /$regex/g; |
3345
|
|
|
|
|
|
|
} |
3346
|
|
|
|
|
|
|
|
3347
|
|
|
|
|
|
|
# Explain the regex... |
3348
|
|
|
|
|
|
|
elsif ($input =~ /d/i) { |
3349
|
0
|
|
|
|
|
|
_show_regex_description($next_regex_ID-1); |
3350
|
0
|
|
|
|
|
|
next INPUT; |
3351
|
|
|
|
|
|
|
} |
3352
|
|
|
|
|
|
|
|
3353
|
|
|
|
|
|
|
# Redisplay the new regex and/or string... |
3354
|
0
|
0
|
0
|
|
|
|
if (defined $string && defined $input_regex) { |
3355
|
0
|
|
|
|
|
|
_display($string, $input_regex, $regex_flags); |
3356
|
|
|
|
|
|
|
} |
3357
|
|
|
|
|
|
|
} |
3358
|
|
|
|
|
|
|
} |
3359
|
|
|
|
|
|
|
|
3360
|
|
|
|
|
|
|
# Lay out the regex and string as does Regexp::Debugger... |
3361
|
|
|
|
|
|
|
sub _display { |
3362
|
0
|
|
|
0
|
|
|
my ($string, $regex, $flags) = @_; |
3363
|
|
|
|
|
|
|
|
3364
|
0
|
|
|
|
|
|
say "\n" x 100; |
3365
|
0
|
|
|
|
|
|
say Term::ANSIColor::colored('regex:', 'white'); |
3366
|
0
|
|
|
|
|
|
say qq{/$regex/$flags\n\n\n}; |
3367
|
0
|
|
|
|
|
|
say Term::ANSIColor::colored('string:', 'white'); |
3368
|
0
|
|
|
|
|
|
say q{'} . _ws_colourer($string) . qq{'\n\n\n}; |
3369
|
|
|
|
|
|
|
} |
3370
|
|
|
|
|
|
|
|
3371
|
|
|
|
|
|
|
|
3372
|
|
|
|
|
|
|
# Make whitespace characters visible (without using a regex)... |
3373
|
|
|
|
|
|
|
sub _quote_ws { |
3374
|
0
|
|
|
0
|
|
|
my $str = shift; |
3375
|
|
|
|
|
|
|
|
3376
|
0
|
|
|
|
|
|
my $index; |
3377
|
0
|
|
|
|
|
|
for my $ws_char ( ["\n"=>'\n'], ["\t"=>'\n'] ) { |
3378
|
|
|
|
|
|
|
SEARCH: |
3379
|
0
|
|
|
|
|
|
while (1) { |
3380
|
0
|
|
|
|
|
|
$index = index($str, $ws_char->[0]); |
3381
|
0
|
0
|
|
|
|
|
last SEARCH if $index < 0; |
3382
|
0
|
|
|
|
|
|
substr($str, $index, 1, $ws_char->[1]); |
3383
|
|
|
|
|
|
|
} |
3384
|
|
|
|
|
|
|
} |
3385
|
|
|
|
|
|
|
|
3386
|
0
|
|
|
|
|
|
return $str; |
3387
|
|
|
|
|
|
|
} |
3388
|
|
|
|
|
|
|
|
3389
|
|
|
|
|
|
|
# Hi-res sleep... |
3390
|
|
|
|
|
|
|
sub _pause { |
3391
|
0
|
|
|
0
|
|
|
select undef, undef, undef, shift; |
3392
|
|
|
|
|
|
|
} |
3393
|
|
|
|
|
|
|
|
3394
|
|
|
|
|
|
|
# Simple prompter... |
3395
|
|
|
|
|
|
|
*_prompt = eval { require IO::Prompter } |
3396
|
|
|
|
|
|
|
? sub { |
3397
|
|
|
|
|
|
|
return IO::Prompter::prompt(@_) |
3398
|
|
|
|
|
|
|
} |
3399
|
|
|
|
|
|
|
: sub { |
3400
|
0
|
|
|
0
|
|
|
my ($prompt) = @_; |
3401
|
|
|
|
|
|
|
|
3402
|
0
|
|
|
|
|
|
print "$prompt "; |
3403
|
0
|
|
|
|
|
|
my $input = readline *STDIN; |
3404
|
0
|
|
|
|
|
|
chomp $input; |
3405
|
0
|
|
|
|
|
|
return $input; |
3406
|
|
|
|
|
|
|
}; |
3407
|
|
|
|
|
|
|
|
3408
|
|
|
|
|
|
|
|
3409
|
|
|
|
|
|
|
1; # Magic true value required at end of module |
3410
|
|
|
|
|
|
|
__END__ |