aboutsummaryrefslogtreecommitdiff
path: root/src/misc/mpp
blob: 7bfca593d050ae6e0ccd7ef6469a6ae702618019 (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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
#!/usr/local/bin/perl -w
# $Header: /usr/local/svn/cvs-repositories/numrelcvs/AEIThorns/AHFinderDirect/src/misc/mpp,v 1.2 2002-09-16 14:17:44 jthorn Exp $

my $help_msg = <<'EOF';
Usage:
   mpp  [ -Dfoo -DBAR ... ]  [ --debug ]  <input.maple  >output.mm
or
   mpp  --help

This program is a preprocessor for Maple source code.  It's modeled
after a subset of the C preprocessor (cpp), but with a Maple-flavored
syntax: preprocessor directives use @ as the marker character, instead
of cpp's #.  The @include, @ifdef, @ifndef, @else, and @endif directives
are supported with the same syntax and semantics as the corresponding
cpp directives, except that
   @include "filename" optional_text
prints   optional_text   (if present) on a separate line following the
contents of the include file.

There may be white space on a line before the '@' which introduces a
preprocessor directive, but no white space between '@' and the directive
name.

Command line arguments of the form  -Dname  define macros, whose
existence may be tested with @ifdef, @ifndef, @else, and/or @endif as
usual.  For example, one might write
	@ifdef MAPLE_V_RELEASE_3
	  words(0):
	  gc(0):
	@else
	  kernelopts(printbytes=false);
	@endif
to disable Maple's printing of 'bytes used' messages.
EOF

###############################################################################

#
# ***** copyright notice and licensing terms *****
#

#
# Copyright (C) 2000, Jonathan Thornburg <jthorn@thp.univie.ac.at>
#
# 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 2 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; see the file COPYING.  If not, write to
# the Free Software Foundation, 59 Temple Place - Suite 330, Boston,
# MA 02111-1307, USA.
#

###############################################################################

#
# ***** table of contents *****
#

#
# <<<help message>>>
# <<<copyright notice and licensing terms>>>
# <<<this table of contents>>>
# <<<global data structures>>>
# main - driver
# process - recursive function to copy a named file to standard output
#
# dirname - find directory part of file name
#

###############################################################################

#
# ***** global data structures *****
#

#
# This hash table records macros:
#    key = macro name
#    value = macro definition
#
my %macro_table = ();

###############################################################################

#
# ***** main program *****
#

use strict;
use integer;
use FileHandle;

#
# constants
#

# arguments for process()
my $true = 1;
my $false = 0;

# arguments for process()
my $read_to_endif = 0;
my $read_to_else_or_endif = 1;
my $read_to_eof = -1;

# results from process()
my $got_endif = 0;
my $got_else = 1;
my $got_eof = -1;

#
# command line handling
#

my $debug = 0;

if ((scalar(@ARGV) == 1) && ($ARGV[0] eq '--help'))
	{ print $help_msg; exit(0); }

	while (my $arg = shift(@ARGV))
	{
	if    ($arg eq '--debug')
		{ $debug = 1 }
	elsif ($arg =~ /^-D(.*)$/)
		{
		my $macro_name = $1;
		if ($debug)
			{ print "defining macro :$macro_name:\n"; }
		$macro_table{$1} = 1;
		}
	else
		{ die("mpp: bad argument \"$arg\"!\n"); }
	}

process(new FileHandle('< -'), '(standard input)', 1,
	$true, $read_to_eof);
exit;

###############################################################################

#
# This (recursive) function reads input lines from a FileHandle, and
# interprets preprocessor directives (making recursive calls for each
# @include or @ifdef).  It either prints non-preprocessor lines or
# discards them, depending on a flag argument.  This function returns
# when it either reaches end of file on the FileHandle, or sees an
# @else or @endif line.
#
# Arguments:
# $fh = The FileHandle from which to copy data.
# $file_name = The file name corresponding to $fh.  This is (only) used
#	       for formatting error messages.  An empty string denotes
#	       standard input.
# $line_number = The line number within $file_name at which $fh is positioned.
#		 This is (only) used for formatting error messages.
# $print_flag = A Boolean flag:
#		$true  ==> Print non--preprocessor-directive input lines.
#		$false ==> Ignore non--preprocessor-directive input lines.
# $how_to_finish = A flag specifying how we finish our processing:
#	$read_to_endif         ==> We expect to finish by seeing an @endif.
#	$read_to_else_or_endif ==> We expect to finish by seeing either an
#				   @else or an @endif.
#	$read_to_eof           ==> We expect to finish by reaching
#				   end-of-file on the input.
#	In any case, we  die(...)  if we finish due to a wrong reason.
#
# Results:
# This function returns a 2-element list
#	($final_line_number, $how_finished)
# where
#	$final_line_number = The value of $line_number for the last input
#			     line read
#	$how_finished = Describes which termination condition actually
#			caused us to finish:
#			$got_endif ==> We saw an @ifdef
#			$got_else  ==> We saw an @else
#			$got_eof   ==> We reached end-of-file on the input
#
sub process
{
my ($fh, $file_name, $line_number, $print_flag, $how_to_finish) = @_;

# special-case standard input
my $stdin_flag = ($file_name eq '');
if ($file_name eq '')
	{ $file_name = '(standard input)'; }

if ($debug)
	{
	print
  "process(\"${file_name}\", line ${line_number}):\n",
  "        print_flag = :${print_flag}:   how_to_finish = :${how_to_finish}:\n";
	}

# main data loop
	for ( ; my $line = <$fh> ; ++$line_number)
	{
	# not an @ directive
	if    ($line !~ /^\s*@/)
		{
		if ($print_flag)
			{ print $line; }
		next;					# *** LOOP CONTROL ***
		}

	# @include
	elsif ($line =~ /^\s*\@\s*include\s+"([^"]+)"(.*)$/)
		{
		my $include_file_name = $1;
		my $suffix = $2;
		if ($debug)
			{
			print "got \@include :$include_file_name:",
			      "   suffix = :$suffix:\n";
			}

		if ($print_flag)	# if we're discarding lines, we can
					# skip processing the @include file,
					# since @ifdef ... @endif directives
					# are (assumed to be) always properly
					# nested with respect to @include
			{
			# open the @include file
			# ... first look for it in (relative to) current dir,
			#     failing that, try directory of file containing
			#     @include
			my $include_fh = new FileHandle("< $include_file_name");
			if ((! defined $include_fh) && (! $stdin_flag))
				{
				my $dir = dirname($file_name);
				my $include_fh
					= new FileHandle(
					      "< $dir/$include_file_name"
							);
				}
			if (! defined $include_fh)
				{
				die(
	   "***** mpp: unable to open \@include file \"$include_file_name\"\n",
	   "           at input file \"$file_name\", line $line_number\n"
				   );
				}

			# recursive call to process @include file
			process($include_fh, $include_file_name, 1,
				$true, $read_to_eof);
			$include_fh->close();

			# print any suffix
			print "$suffix\n";
			}
		}

	# @ifdef
	elsif ($line =~ /^\s*\@\s*if(n?)def\s+(\S+)/)
		{
		my $negate_flag = ($1 eq 'n');
		my $macro_name = $2;
		if ($debug)
			{
			print "got \@ifdef/\@ifndef:",
			      "   macro_name = :${macro_name}:",
			      "   negate_flag = :${negate_flag}:\n";
			}
		my $macro_is_defined = (exists $macro_table{$macro_name});
		my $doit_flag = $negate_flag ? (! $macro_is_defined)
					     : $macro_is_defined;
		if ($debug)
			{
			print "==> macro_is_defined = :${macro_is_defined}:",
			      "   doit_flag = :${doit_flag}:\n";
			}

		# recursive call to process body of @ifdef
		my ($final_line_number, $how_finished)
			= process($fh, $file_name, $line_number+1,
				  $print_flag && $doit_flag,
				  $read_to_else_or_endif);
		$line_number = $final_line_number;
		if ($how_finished == $got_else)
			{
			# recursive call to skip @else text
			($final_line_number, $how_finished)
				= process($fh, $file_name, $line_number+1,
					  $print_flag && !$doit_flag,
					  $read_to_endif);
			$line_number = $final_line_number;
			}
		}

	# @endif
	elsif ($line =~ /^\s*\@\s*endif/)
		{
		if (    ($how_to_finish == $read_to_endif)
		     || ($how_to_finish == $read_to_else_or_endif)    )
			{
			return ($line_number, $got_endif);# *** EARLY RETURN ***
			}
		   else {
			die(
   "***** mpp: \@endif when not within the scope of an \@ifdef\n",
   "           at input file \"$file_name\", line $line_number\n"
			   );
			}
		}

	# @else
	elsif ($line =~ /^\s*\@\s*else/)
		{
		if ($how_to_finish == $read_to_else_or_endif)
			{
			return ($line_number, $got_else); # *** EARLY RETURN ***
			}
		   else {
			die(
   "***** mpp: \@else when not within the scope of an \@ifdef\n",
   "           at input file \"$file_name\", line $line_number\n"
			   );
			}
		}

	else	{
		die(
   "***** mpp: unknown @-directive \"$line\"!\n",
   "           at input file \"$file_name\", line $line_number\n"
		   );
		}
	}

if ($how_to_finish != $read_to_eof)
	{
	die(
   "***** mpp: unexpected end-of-file (i.e. unterminated \@ifdef)\n",
   "           at input file \"$file_name\", line $line_number\n"
	   );
	}
return -1;
}

###############################################################################
###############################################################################
###############################################################################

#
# This function computes the directory part of a file name.
# It is (should be) identical in effect to the  dirname(1)  program.
#
sub dirname
{
my ($file_name) = @_;

# strip off reundant trailing /
if (($file_name =~ /\/$/) && ($file_name ne '/'))
	{ $file_name = substr($file_name, 0, length($file_name)-1); }

if (index($file_name, '/') >= 0)
	{
	# $file_name contains a /
	$file_name =~ s:/[^/]+$::;
	if ($file_name eq '')
		{ $file_name = '/'; }
	}
   else {
	# $file name doesn't contain a /
	$file_name = '.';
	}

return $file_name;
}