aboutsummaryrefslogtreecommitdiff
path: root/lang/perl/files/perlconfig.pl
blob: f169980d3b8a1ca776806bddb90d5277e624c25e (plain)
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
307
308
309
310
311
312
313
#!/usr/bin/perl

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 
# Copyright 2015 Marcel Denia

=head1 NAME

perlconfig.pl

=head1 SYNOPSIS

B<perlconfig.pl> [B<-Dsymbol>=I<value>, ...] [B<-dsymbol>=I<value>, ...] I<[files]>

Generate a configuration file suitable for (cross-)compiling perl 5.

=head1 OPTIONS

=over

=item B<-Dsymbol=value>

The symbol identified by I<name> will have the literal value I<value>.
When generating the configuration file, it's value will be printed enclosed by
single quotation marks(').

=item B<-dsymbol=value>

The symbol identified by I<name> will have the literal value I<value>.

=back

=head1 DESCRIPTION

B<perlconfig.pl> is a program to compile and generate configuration files ready
to be used as a "config.sh" file for compiling perl 5. It does so by processing
specially-made configuration files(usually called *.config), typically augmented
by command-line definitions.

B<perlconfig.pl>'s intent is to be used in place of perl 5's own Configure
script in situations where it can not be run, like cross-compiling.

=head2 File syntax

B<perlconfig.pl>'s configuration files a consist of symbol definitions in
different variations, each one assigning a specific symbol identified by a name
some value, as well as conditional blocks in order to allow for some
flexibility.

=head3 Symbol names

A symbol name has to consist entirely of alphanumeric characters as well as
the underscore(_) character. In addition, symbol names may be prefixed with an
all-lowercase string, separated by a colon(:):

  my:name=value

Having a zero-length prefix string is also valid:

  :name=value

Symbols prefixed that way are called private symbols. They act exactly like
regular symbols, with the only difference being that they will B<not> be written
to the final configuration file.

=head3 Symbol definitions

A symbol definition is in the form of a simple name/value pair, separated by
an equals sign(=):

  name=value

I<value> can be anything really. However, there are 3 notations, with
differences in quoting and interpolation:

=over

=item name=foo

The symbol identified by I<name> will have the literal value I<foo>.

=item name='foo'

The symbol identified by I<name> will have the literal value I<foo>.
When generating the configuration file, it's value will be printed enclosed by
single quotation marks(').

=item name="foo"

The symbol identified by I<name> will have the value of I<foo>
S<B<after interpolation>>(as described in L</Interpolation>).
When generating the configuration file, it's value will be printed enclosed by
single quotation marks(').

=back

=head3 Conditional blocks

A conditional block is of the form

  (condition) {
      ...
  }

B<perlconfig.pl> will execute everything enclosed in the curly braces({ and }),
or inside the BLOCK in Perl 5 terms, if I<condition> evaluates to any true
value. I<condition> will go through interpolation as described in
L</Interpolation>. It may contain any valid Perl 5 expression. Some common
examples are:

=over

=item $name eq 'foo'

Evaluates to true if configuration symbol I<name> is literally equal to I<foo>.

=item $name ne 'foo'

Evaluates to true if configuration symbol I<name> is B<not> literally equal to
I<foo>.

=item defined($name)

Evaluates to true if configuration symbol I<name> is defined(has any usable
value, see L<perlfunc/defined>).

=back

Conditional blocks may be nested inside conditional blocks. Note that the
opening curl brace({) has to be on the same line as your condition.

=head3 Comments

All lines starting with nothing or any number of whitespaces, followed by a
hash sign(#), are considered comments and will be completely ignored by
B<perlconfig.pl>.

=head3 Interpolation

In certain situations(see above), B<perlconfig.pl> will interpolate strings or
constructs in order to allow you to refer to configuration symbols or embed
code.

Interpolated strings are subject to the following rules:

=over

=item You may not include any single(') or double(") quotation marks.

You can use \qq in order to include double quotation marks(") in your string.

=item $name and ${name} reference configuration symbols

You can easily refer to existing configuration symbols using the commmon $name
or ${name} syntax. In case you want to refer to the perl variable named $name,
write \$name. This is useful for embedding code.

=item Perl 5 interpolation rules apply

Aside from the above, you may include anything that is also valid for an
interpolated(qq//) string in Perl 5. For instance, it's perfectly valid to
execute code using the @{[]} construct.

=back

=head1 EXAMPLES

As a simple example, consider the following configuration file, named
"example.config":

  recommendation='The Perl you want is'
  ($:maturity eq 'stable') {
      recommendation="$recommendation Perl 5"
  }
  ($:maturity eq 'experimental') {
      recommendation="$recommendation Perl 6(try Rakudo!)"
  }

Executing it using these command-lines will yield the following results:

=over

=item $ perlconfig.pl -D:maturity=stable example.config

  recommendation='The Perl you want is Perl 5'

=item $ perlconfig.pl -D:maturity=experimental example.config

  recommendation='The Perl you want is Perl 6(try Rakudo!)'

=back

=head1 AUTHOR

Marcel Denia <naoir@gmx.net>

=head1 COPYRIGHT AND LICENSE

Copyright 2015 Marcel Denia

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

=cut

use strict;
use warnings;
use List::Util qw/all/;
my $symbol_name_prefix_regex = '(?:[a-z]*:)';
my $symbol_name_regex = "($symbol_name_prefix_regex?(?:[a-zA-Z0-9_]+))";

my %config;

sub interpolate {
	my $string = shift;
	my %options = @_;
	
	# First, convert $foo into ${foo}
	$string =~ s/(?<!\\)\$$symbol_name_regex/\${$1}/gs;
	
	# Make ${foo} into $config{'foo'}->{value}
	$string =~ s/\$\{$symbol_name_regex\}/\$config{\'$1\'}->{value}/g;
	
	# Un-escape \$foo
	$string =~ s/\\\$/\$/g;
	
	# Turn \qq into "
	$string =~ s/\\qq/\\"/g;
	
	return $string;
}

# Parse command-line symbol definitions
while ($ARGV[0]) {
	if ($ARGV[0] =~ /^-([D|d])$symbol_name_regex=(.*)$/) {
		$config{$2} = { value => $3, quoted => $1 eq 'D' };
		shift(@ARGV);
	}
	else {
		last;
	}
}

# Process configuration files
my @condition_stack = ( 1 );
for my $file (@ARGV) {
	open(my $fh, '<', $file) or die "Can't open $file: $!\n";
	while (my $line = <$fh>) {
		chomp($line);
		
		if ($line =~ /^\s*$symbol_name_regex=(.*)$/) { # A symbol definition
			if (all {$_ == 1} @condition_stack) {
				my $symbol = $1;
				(my $quote_begin, my $value, my $quote_end) = $2 =~ /^(['|"])?([^'"]*)(['|"])?$/;
				
				$quote_begin = '' unless defined $quote_begin;
				$quote_end = '' unless defined $quote_end;
				die "$file:$.: Unmatched quotes in \"$line\"\n" unless $quote_begin eq $quote_end;
				
				if ($quote_begin eq '"') {
					$config{$symbol} = { value => eval('"' . interpolate($2) . '"'), quoted => 1 };
				}
				else {
					$config{$symbol} = { value => $2, quoted => $quote_begin eq '\'' };
				}
			}
		}
		elsif ($line =~ /^\s*\((.*)\)\s?{$/) { # A conditional block
			if (eval(interpolate($1))) {
				push(@condition_stack, 1);
			}
			else {
				push(@condition_stack, 0);
			}
		}
		elsif ($line =~ /^\s*}$/) { # Closing a conditional block
			pop(@condition_stack);
			die "$file:$.: Closing non-existent block\n" unless @condition_stack;
		}
		elsif ($line =~ (/^\s*$/) || ($line =~ /^\s*#/)) { # An empty line or comment
		}
		else {
			die "$file:$.: Malformed line: \"$line\"\n";
		}
	}
}

# Output
for (sort(keys(%config))) {
	my $quote = $config{$_}->{quoted} ? '\'' : '';
	print("$_=$quote$config{$_}->{value}$quote\n") unless $_ =~ /^$symbol_name_prefix_regex/;
}