-
Notifications
You must be signed in to change notification settings - Fork 1
/
VeryKiwiMorpher.pm
168 lines (139 loc) · 4.91 KB
/
VeryKiwiMorpher.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package VeryKiwiMorpher; # consider changing name to Morphers !
use strict; use warnings;
use VeryKiwiHelpers;
############################### GLOBALS FOR ALL MORPHERS ###############################
our $prefix = qr/sq?|mq?|tr|y/;
our %opentoclose = ( '('=>')', '{'=>'}', '['=>']', '<'=>'>' );
####################################### MORPHER ########################################
sub new{
my $packagename = shift;
my ($raw) = @_; # The input should be a translator string (tr/search/replace/opts)
$raw = trim($raw);
my $delim; if( $raw =~ m/^${prefix}([^\s\w])/ ){ $delim = $1 }else{ die 'No delim.' }
bless { raw=>$raw, delim=>$delim, delim_subregex=>undef } => $packagename
# anything stored with memoization must be listed here as undef
}
sub is_regex{
my $morpher = shift;
return ref($morpher) eq 'VeryKiwiRegex'
}
sub is_translator{
my $morpher = shift;
return ref($morpher) eq 'VeryKiwiTranslator'
}
sub raw{
my $morpher = shift;
return $$morpher{raw}
}
sub prefix{
my $morpher = shift;
if( $morpher->raw =~ m/^${prefix}/ ){ return $& }else{ die 'The morpher has no valid prefix.' }
}
sub qless_prefix{
my $morpher = shift;
#return $morpher->prefix =~ s/q$//r; # this, although awesome, will not work on older versions of perl (such as Snow Leopards 5.10.0)
my $prefix = $morpher->prefix;
$prefix =~ s/q$//;
return $prefix;
}
sub delim{
my $morpher = shift;
my ($Q) = @_; # Q for quotemeta
return (defined $Q)? quotemeta($$morpher{delim}): $$morpher{delim};
}
sub escape_the_delim{ # takes any strings and escapes all occurances of $delim
my $morpher = shift;
my @strings = @_;
my $delim = $morpher->delim('Q');
foreach my $string (@strings){ $string =~ s/$delim/\\$delim/g }
return @strings
}
*mind_the_delim = \&escape_the_delim;
sub delim_is_paired{
my $morpher = shift;
return exists $opentoclose{$morpher->delim}
}
sub open{
my $morpher = shift;
my ($Q) = @_;
return $morpher->delim($Q)
}
sub close{
my $morpher = shift;
my ($Q) = @_;
my $close = $opentoclose{$morpher->open};
return (defined $Q)? quotemeta($close): $close;
}
sub start_middle_end{
my $morpher = shift;
my ($Q) = @_; # Q for quotemeta
return ($morpher->delim_is_paired)?
( $morpher->open($Q), $morpher->close($Q).$morpher->open($Q), $morpher->close($Q) ):
( $morpher->delim($Q) ) x 3;
}
sub end{
my $morpher = shift;
my ($Q) = @_;
return ($morpher->delim_is_paired)? $morpher->close($Q): $morpher->delim($Q);
}
sub is_quoted{
return 0 # things are not quoted by default in a morpher. regex will override this with its own is_quoted function
}
sub pair_subregex{
my $morpher = shift;
my ($recursenum) = @_;
my ($open,$close) = ( $morpher->open('Q'), $morpher->close('Q') );
my $neither = ($morpher->is_quoted)? qr/(?>(?:(?!$open|$close).)*)/: qr/(?>(?:\\.|(?!\\|$open|$close).)*)/;
return qr/($neither (?>(?: $open(?$recursenum)$close )?) $neither )*/x;
}
sub delim_subregex{ # behavior is undefined for delims like \, , and strings of length greater than 1
my $morpher = shift;
my ($recursenum) = @_;
if( !defined $$morpher{delim_subregex} ){
if( $morpher->delim_is_paired ){
$$morpher{delim_subregex} = $morpher->pair_subregex($recursenum)
}
else{
my $delim = $morpher->delim('Q');
$$morpher{delim_subregex} = ($morpher->is_quoted)? qr/(?>(?:(?!$delim).)*)/: qr/(?>(?:\\.|(?!\\|$delim).)*)/;
# even though backtracking is disabled, we need the second \\ to avoid a lone \ at the end of the string (above)
}
}
return $$morpher{delim_subregex}
}
sub opts{
return qr/(?:g?m?o?p?i?s?x?e?c?d?s?)*/ # this will be overriden for the specific regex and tr morphers
}
sub validity_subregex{
my $morpher = shift;
my ($start,$middle,$end) = $morpher->start_middle_end('Q');
my $delim_subregex = $morpher->delim_subregex(1); # the recurse is the 1ST capture group in the following regex.
my $opts = $morpher->opts;
return qr/$prefix $start (?'search'$delim_subregex) $middle (?'replace'$delim_subregex) $end(?'options'$opts)/x;
}
sub is_valid{
my $morpher = shift;
my $validity_subregex = $morpher->validity_subregex;
if( $morpher->raw =~ /^$validity_subregex$/ ){
# populate search, replace, options info for future use:
( $$morpher{search}, $$morpher{replace}, $$morpher{options} ) = ( $+{search}, $+{replace}, $+{options} );
return 1
}
return 0
}
sub search_replace_options{
my $morpher = shift;
my $search = (shift)? quotemeta($$morpher{search}): $$morpher{search};
my $replace = (shift)? quotemeta($$morpher{replace}): $$morpher{replace};
my $options = (shift)? quotemeta($$morpher{options}): $$morpher{options};
return ( $search, $replace, $options )
}
sub export{ # assumes the regex is valid
my $morpher = shift;
if( !$morpher->is_quoted ){ return $morpher->raw }
my $prefix = $morpher->qless_prefix;
my ($start,$middle,$end) = $morpher->start_middle_end;
my ($search,$replace,$options) = $morpher->search_replace_options('Q','Q', 0 );
return "$prefix$start$search$middle$replace$end$options"
}
1