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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
|
#!/usr/bin/perl
#
#//===----------------------------------------------------------------------===//
#//
#// The LLVM Compiler Infrastructure
#//
#// This file is dual licensed under the MIT and the University of Illinois Open
#// Source Licenses. See LICENSE.txt for details.
#//
#//===----------------------------------------------------------------------===//
#
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/lib";
use tools;
our $VERSION = "0.005";
my $name_rexp = qr{[A-Za-z_]+[A-Za-z0-9_]*};
my $keyword_rexp = qr{if|else|end|omp};
sub error($$$) {
my ( $input, $msg, $bulk ) = @_;
my $pos = pos( $$bulk );
$$bulk =~ m{^(.*?)\G(.*?)$}m or die "Internal error";
my ( $pre, $post ) = ( $1, $2 );
my $n = scalar( @{ [ substr( $$bulk, 0, $pos ) =~ m{\n}g ] } ) + 1;
runtime_error( "\"$input\" line $n: $msg:", ">>> " . $pre . "--[HERE]-->" . $post );
}; # sub error
sub evaluate($$$\$) {
my ( $expr, $strict, $input, $bulk ) = @_;
my $value;
{ # Signal handler will be restored on exit from this block.
# In case of "use strict; use warnings" eval issues warnings to stderr. This direct
# output may confuse user, so we need to catch it and prepend with our info.
local $SIG{ __WARN__ } = sub { die @_; };
$value =
eval(
"package __EXPAND_VARS__;\n" .
( $strict ? "use strict; use warnings;\n" : "no strict; no warnings;\n" ) .
$expr
);
};
if ( $@ ) {
# Drop location information -- increasing eval number and constant "line 3"
# is useless for the user.
$@ =~ s{ at \(eval \d+\) line \d+}{}g;
$@ =~ s{\s*\z}{};
error( $input, "Cannot evaluate expression \"\${{$expr}}\": $@", $bulk );
}; # if
if ( $strict and not defined( $value ) ) {
error( $input, "Substitution value is undefined", $bulk );
}; # if
return $value;
}; # sub evaluate
#
# Parse command line.
#
my ( @defines, $input, $output, $strict );
get_options(
"D|define=s" => \@defines,
"strict!" => \$strict,
);
if ( @ARGV < 2 ) {
cmdline_error( "Not enough argument" );
}; # if
if ( @ARGV > 2 ) {
cmdline_error( "Too many argument(s)" );
}; # if
( $input, $output ) = @ARGV;
foreach my $define ( @defines ) {
my ( $equal, $name, $value );
$equal = index( $define, "=" );
if ( $equal < 0 ) {
$name = $define;
$value = "";
} else {
$name = substr( $define, 0, $equal );
$value = substr( $define, $equal + 1 );
}; # if
if ( $name eq "" ) {
cmdline_error( "Illegal definition: \"$define\": variable name should not be empty." );
}; # if
if ( $name !~ m{\A$name_rexp\z} ) {
cmdline_error(
"Illegal definition: \"$define\": " .
"variable name should consist of alphanumeric characters."
);
}; # if
eval( "\$__EXPAND_VARS__::$name = \$value;" );
if ( $@ ) {
die( "Internal error: $@" );
}; # if
}; # foreach $define
#
# Do the work.
#
my $bulk;
# Read input file.
$bulk = read_file( $input );
# Do the replacements.
$bulk =~
s{(?:\$($keyword_rexp)|\$($name_rexp)|\${{(.*?)}})}
{
my $value;
if ( defined( $1 ) ) {
# Keyword. Leave it as is.
$value = "\$$1";
} elsif ( defined( $2 ) ) {
# Variable to expand.
my $name = $2;
$value = eval( "\$__EXPAND_VARS__::$name" );
if ( $@ ) {
die( "Internal error" );
}; # if
if ( $strict and not defined( $value ) ) {
error( $input, "Variable \"\$$name\" not defined", \$bulk );
}; # if
} else {
# Perl code to evaluate.
my $expr = $3;
$value = evaluate( $expr, $strict, $input, $bulk );
}; # if
$value;
}ges;
# Process conditionals.
# Dirty patch! Nested conditionals not supported!
# TODO: Implement nested constructs.
$bulk =~
s{^\$if +([^\n]*) *\n(.*\n)\$else *\n(.*\n)\$end *\n}
{
my ( $expr, $then_part, $else_part ) = ( $1, $2, $3 );
my $value = evaluate( $expr, $strict, $input, $bulk );
if ( $value ) {
$value = $then_part;
} else {
$value = $else_part;
}; # if
}gesm;
# Write output.
write_file( $output, \$bulk );
exit( 0 );
__END__
=pod
=head1 NAME
B<expand-vars.pl> -- Simple text preprocessor.
=head1 SYNOPSIS
B<expand-vars.pl> I<OPTION>... I<input> I<output>
=head1 OPTIONS
=over
=item B<-D> I<name>[B<=>I<value>]
=item B<--define=>I<name>[B<=>I<value>]
Define variable.
=item B<--strict>
In strict mode, the script issues error on using undefined variables and executes Perl code
with C<use strict; use warnings;> pragmas.
=back
=head2 Standard Options
=over
=item B<--doc>
=item B<--manual>
Print full help message and exit.
=item B<--help>
Print short help message and exit.
=item B<--usage>
Print very short usage message and exit.
=item B<--verbose>
Do print informational messages.
=item B<--version>
Print version and exit.
=item B<--quiet>
Work quiet, do not print informational messages.
=back
=head1 ARGUMENTS
=over
=item I<input>
Input file name.
=item I<output>
Output file name.
=back
=head1 DESCRIPTION
This script reads input file, makes substitutes and writes output file.
There are two form of substitutes:
=over
=item Variables
Variables are referenced in input file in form:
$name
Name of variable should consist of alphanumeric characters (Latin letters, digits, and underscores).
Variables are defined in command line with C<-D> or C<--define> options.
=item Perl Code
Perl code is specified in input file in form:
${{ ...code... }}
The code is evaluated, and is replaced with its result. Note: in strict mode, you should declare
variable before use. See examples.
=back
=head1 EXAMPLES
Replace occurrences of C<$year>, C<$month>, and C<$day> in C<input.txt> file with C<2007>, C<09>, C<01>
respectively and write result to C<output.txt> file:
$ cat input.var
Today is $year-$month-$day.
$ expand-vars.pl -D year=2007 -D month=09 -D day=01 input.var output.txt && cat output.txt
Today is 2007-09-01.
Using Perl code:
$ cat input.var
${{ localtime(); }}
$ expand-vars.pl -D year=2007 -D month=09 -D day=01 input.var output.txt && cat output.txt
Now Tue May 5 20:54:13 2009
Using strict mode for catching bugs:
$ cat input.var
${{ "year : " . substr( $date, 0, 4 ); }}
$ expand-vars.pl input.var output.txt && cat output.txt
year :
Oops, why it does not print year? Let us use strict mode:
$ expand-vars.pl --strict input.var output.txt && cat output.txt
expand-vars.pl: (x) "test.var": Cannot evaluate expression "${{ "year : " . substr( $date, 0, 4 ); }}": Global symbol "$date" requires explicit package name
Ok, variable is not defined. Let us define it:
$ expand-vars.pl --strict -D date=20090501 input.var output.txt && cat output.txt
expand-vars.pl: (x) "test.var": Cannot evaluate expression "${{ "year : " . substr( $date, 0, 4 ); }}": Variable "$date" is not imported
What is wrong? Variable should be declared:
$ cat input.var
${{ our $date; "year : " . substr( $date, 0, 4 ); }}
$ expand-vars.pl --strict -D date=20090501 input.var output.txt && cat output.txt
year : 2009
=cut
# end of file #
|