2002-10-10 07:11:27 +04:00
|
|
|
#!/usr/bin/perl -w
|
|
|
|
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
|
|
|
|
#
|
|
|
|
# Preprocessor
|
|
|
|
# Version 1.0
|
|
|
|
#
|
|
|
|
# Copyright (c) 2002 by Ian Hickson
|
|
|
|
#
|
|
|
|
# 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; if not, write to the Free Software
|
|
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
|
|
|
|
# takes as arguments the files to process
|
|
|
|
# defaults to stdin
|
|
|
|
# output to stdout
|
|
|
|
|
|
|
|
my $stack = new stack;
|
|
|
|
|
|
|
|
# command line arguments
|
|
|
|
my @includes;
|
|
|
|
while ($_ = $ARGV[0], defined($_) && /^-./) {
|
|
|
|
shift;
|
2002-10-10 23:39:33 +04:00
|
|
|
last if /^--$/os;
|
|
|
|
if (/^-D(.*)$/os) {
|
2002-10-10 07:11:27 +04:00
|
|
|
for ($1) {
|
2002-10-10 23:39:33 +04:00
|
|
|
if (/^(\w+)=(.*)$/os) {
|
2002-10-10 07:11:27 +04:00
|
|
|
$stack->define($1, $2);
|
2002-10-10 23:39:33 +04:00
|
|
|
} elsif (/^(\w+)$/os) {
|
2002-10-10 07:11:27 +04:00
|
|
|
$stack->define($1, 1);
|
|
|
|
} else {
|
|
|
|
die "$0: invalid argument to -D: $_\n";
|
|
|
|
}
|
|
|
|
}
|
2002-10-11 03:18:39 +04:00
|
|
|
} elsif (/^-F(.*)$/os) {
|
|
|
|
for ($1) {
|
|
|
|
if (/^(\w+)$/os) {
|
|
|
|
$stack->filter($1, 1);
|
|
|
|
} else {
|
|
|
|
die "$0: invalid argument to -F: $_\n";
|
|
|
|
}
|
|
|
|
}
|
2002-10-10 23:39:33 +04:00
|
|
|
} elsif (/^-I(.*)$/os) {
|
2002-10-10 07:11:27 +04:00
|
|
|
push(@includes, $1);
|
2002-10-10 23:39:33 +04:00
|
|
|
} elsif (/^-E$/os) {
|
2002-10-10 07:11:27 +04:00
|
|
|
foreach (keys %ENV) {
|
|
|
|
$stack->define($_, $ENV{$_});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
die "$0: invalid argument: $_\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unshift(@ARGV, '-') unless @ARGV;
|
|
|
|
unshift(@ARGV, @includes);
|
|
|
|
|
|
|
|
# do the work
|
2002-10-13 22:04:58 +04:00
|
|
|
foreach (@ARGV) { include($stack, $_); }
|
2002-10-10 07:11:27 +04:00
|
|
|
exit(0);
|
|
|
|
|
|
|
|
########################################################################
|
|
|
|
|
|
|
|
package main;
|
|
|
|
|
|
|
|
sub include {
|
|
|
|
my($stack, $filename) = @_;
|
2002-10-14 04:54:49 +04:00
|
|
|
my $fullFilename = $stack->{'variables'}->{'DIRECTORY'} . $filename;
|
2002-10-14 02:37:17 +04:00
|
|
|
$fullFilename =~ s|^.*//||os; # strip everything up to a double slash
|
|
|
|
if ($fullFilename !~ m|^(.*/)?(.+)$|os) { # extract the directory and file portions
|
|
|
|
die "Not a valid filename: $filename\n";
|
|
|
|
}
|
|
|
|
local $stack->{'variables'}->{'DIRECTORY'} = $1;
|
|
|
|
if (not defined($stack->{'variables'}->{'DIRECTORY'})) {
|
|
|
|
$stack->{'variables'}->{'DIRECTORY'} = '';
|
|
|
|
}
|
|
|
|
local $stack->{'variables'}->{'FILE'} = $2;
|
2002-10-10 07:11:27 +04:00
|
|
|
local $stack->{'variables'}->{'LINE'} = 0;
|
|
|
|
local *FILE;
|
2002-10-14 02:37:17 +04:00
|
|
|
open(FILE, nativise($filename)) or die "Couldn't open $filename: $!\n";
|
2002-10-10 07:11:27 +04:00
|
|
|
while (<FILE>) {
|
2002-10-11 00:28:44 +04:00
|
|
|
# on cygwin, line endings are screwed up, so normalise them.
|
2002-10-11 01:44:08 +04:00
|
|
|
s/[\x0D\x0A]+$/\n/os if $^O eq 'cygwin';
|
2002-10-10 07:11:27 +04:00
|
|
|
$stack->newline;
|
2002-10-10 23:39:33 +04:00
|
|
|
if (/^\#([a-z]+)\n?$/os) { # argumentless processing instruction
|
2002-10-10 07:11:27 +04:00
|
|
|
process($stack, $1);
|
2002-10-10 23:39:33 +04:00
|
|
|
} elsif (/^\#([a-z]+)\s(.*?)\n?$/os) { # processing instruction with arguments
|
2002-10-10 07:11:27 +04:00
|
|
|
process($stack, $1, $2);
|
2002-10-10 23:39:33 +04:00
|
|
|
} elsif (/^\#\n?/os) { # comment
|
2002-10-10 07:11:27 +04:00
|
|
|
# ignore it
|
2002-10-11 03:14:01 +04:00
|
|
|
} elsif ($stack->enabled) {
|
2002-10-10 23:39:33 +04:00
|
|
|
# print it, including any newlines
|
2002-10-11 03:14:01 +04:00
|
|
|
print filtered($stack, $_);
|
2002-10-10 07:11:27 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
close(FILE);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub process {
|
|
|
|
my($stack, $instruction, @arguments) = @_;
|
|
|
|
my $method = 'preprocessor'->can($instruction);
|
|
|
|
if (not defined($method)) {
|
|
|
|
fatal($stack, 'unknown instruction', $instruction);
|
|
|
|
}
|
|
|
|
eval { &$method($stack, @arguments) };
|
|
|
|
if ($@) {
|
|
|
|
fatal($stack, "error evaluating $instruction:", $@);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-10-11 03:14:01 +04:00
|
|
|
sub filtered {
|
|
|
|
my($stack, $text) = @_;
|
|
|
|
foreach my $filter (sort keys %{$stack->{'filters'}}) {
|
|
|
|
next unless $stack->{'filters'}->{$filter};
|
|
|
|
my $method = 'filter'->can($filter);
|
|
|
|
if (not defined($method)) {
|
|
|
|
fatal($stack, 'unknown filter', $filter);
|
|
|
|
}
|
|
|
|
$text = eval { &$method($stack, $text) };
|
|
|
|
if ($@) {
|
|
|
|
fatal($stack, "error using $filter:", $@);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $text;
|
|
|
|
}
|
|
|
|
|
2002-10-10 07:11:27 +04:00
|
|
|
sub fatal {
|
|
|
|
my $stack = shift;
|
|
|
|
my $filename = $stack->{'variables'}->{'FILE'};
|
|
|
|
local $" = ' ';
|
|
|
|
print STDERR "$0:$filename:$.: @_\n";
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2002-10-14 02:37:17 +04:00
|
|
|
sub nativise {
|
|
|
|
my $filename = shift;
|
|
|
|
if ($^O eq 'linux' or
|
|
|
|
$^O eq 'cygwin') {
|
|
|
|
return $filename;
|
|
|
|
} elsif ($^O eq 'MSWin32') {
|
|
|
|
$filename =~ s|^/(.)/|$1:/|gos;
|
|
|
|
$filename =~ s|/|\\|gos;
|
|
|
|
return $filename;
|
|
|
|
} elsif ($^O eq 'MacOS') {
|
|
|
|
$filename =~ s|/|:|gos;
|
|
|
|
return $filename;
|
|
|
|
} else {
|
|
|
|
die("Platform '$^O' not recognised. Contact ian\@hixie.ch.\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-10-10 07:11:27 +04:00
|
|
|
|
|
|
|
########################################################################
|
|
|
|
|
|
|
|
package stack;
|
|
|
|
|
|
|
|
sub new {
|
|
|
|
return bless {
|
|
|
|
'variables' => {
|
|
|
|
# %ENV,
|
|
|
|
'LINE' => 0, # the line number in the source file
|
2002-10-14 02:37:17 +04:00
|
|
|
'DIRECTORY' => '', # the directory of the source filename
|
2002-10-10 07:11:27 +04:00
|
|
|
'FILE' => '', # source filename
|
|
|
|
'1' => 1, # for convenience
|
|
|
|
},
|
2002-10-11 03:14:01 +04:00
|
|
|
'filters' => {
|
|
|
|
# filters
|
|
|
|
},
|
2002-10-10 07:11:27 +04:00
|
|
|
'values' => [], # the value of the last condition evaluated at the nth lewel
|
|
|
|
'lastPrinting' => [], # whether we were printing at the n-1th level
|
|
|
|
'printing' => 1, # whether we are currently printing at the Nth level
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub newline {
|
|
|
|
my $self = shift;
|
|
|
|
$self->{'variables'}->{'LINE'}++;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub define {
|
|
|
|
my $self = shift;
|
|
|
|
my($variable, $value) = @_;
|
2002-10-11 01:42:44 +04:00
|
|
|
die "not a valid variable name: '$variable'\n" if $variable =~ m/\W/;
|
2002-10-10 07:11:27 +04:00
|
|
|
$self->{'variables'}->{$variable} = $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub defined {
|
|
|
|
my $self = shift;
|
|
|
|
my($variable) = @_;
|
2002-10-11 01:42:44 +04:00
|
|
|
die "not a valid variable name: '$variable'\n" if $variable =~ m/\W/;
|
2002-10-10 07:11:27 +04:00
|
|
|
return defined($self->{'variables'}->{$variable});
|
|
|
|
}
|
|
|
|
|
|
|
|
sub undefine {
|
|
|
|
my $self = shift;
|
|
|
|
my($variable) = @_;
|
2002-10-11 01:42:44 +04:00
|
|
|
die "not a valid variable name: '$variable'\n" if $variable =~ m/\W/;
|
2002-10-10 07:11:27 +04:00
|
|
|
delete($self->{'variables'}->{$variable});
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get {
|
|
|
|
my $self = shift;
|
|
|
|
my($variable) = @_;
|
2002-10-11 01:42:44 +04:00
|
|
|
die "not a valid variable name: '$variable'\n" if $variable =~ m/\W/;
|
2002-10-10 07:11:27 +04:00
|
|
|
my $value = $self->{'variables'}->{$variable};
|
|
|
|
if (defined($value)) {
|
|
|
|
return $value;
|
|
|
|
} else {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub push {
|
|
|
|
my $self = shift;
|
|
|
|
my($value) = @_;
|
|
|
|
push(@{$self->{'values'}}, $value);
|
|
|
|
push(@{$self->{'lastPrinting'}}, $self->{'printing'});
|
|
|
|
$self->{'printing'} = $value && $self->{'printing'};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub pop {
|
|
|
|
my $self = shift;
|
|
|
|
$self->{'printing'} = pop(@{$self->{'lastPrinting'}});
|
|
|
|
return pop(@{$self->{'values'}});
|
|
|
|
}
|
|
|
|
|
|
|
|
sub enabled {
|
|
|
|
my $self = shift;
|
|
|
|
return $self->{'printing'};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub disabled {
|
|
|
|
my $self = shift;
|
|
|
|
return not $self->{'printing'};
|
|
|
|
}
|
|
|
|
|
2002-10-11 03:14:01 +04:00
|
|
|
sub filter {
|
|
|
|
my $self = shift;
|
|
|
|
my($filter, $value) = @_;
|
|
|
|
die "not a valid filter name: '$filter'\n" if $filter =~ m/\W/;
|
|
|
|
$self->{'filters'}->{$filter} = $value;
|
|
|
|
}
|
|
|
|
|
2002-10-14 02:45:34 +04:00
|
|
|
sub expand {
|
|
|
|
my $self = shift;
|
|
|
|
my($line) = @_;
|
|
|
|
$line =~ s/__(\w+)__/$self->get($1)/gose;
|
|
|
|
return $line;
|
|
|
|
}
|
2002-10-10 07:11:27 +04:00
|
|
|
|
|
|
|
########################################################################
|
|
|
|
|
|
|
|
package preprocessor;
|
|
|
|
|
|
|
|
sub define {
|
|
|
|
my $stack = shift;
|
|
|
|
return if $stack->disabled;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
my $argument = shift;
|
|
|
|
for ($argument) {
|
2002-10-10 23:39:33 +04:00
|
|
|
/^(\w+)\s(.*)$/os && do {
|
2002-10-10 07:11:27 +04:00
|
|
|
return $stack->define($1, $2);
|
|
|
|
};
|
2002-10-10 23:39:33 +04:00
|
|
|
/^(\w+)$/os && do {
|
2002-10-10 07:11:27 +04:00
|
|
|
return $stack->define($1, 1);
|
|
|
|
};
|
2002-10-11 01:42:44 +04:00
|
|
|
die "invalid argument: '$_'\n";
|
2002-10-10 07:11:27 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub undef {
|
|
|
|
my $stack = shift;
|
|
|
|
return if $stack->disabled;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
$stack->undefine(@_);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub ifdef {
|
|
|
|
my $stack = shift;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
$stack->push($stack->defined(@_));
|
|
|
|
}
|
|
|
|
|
|
|
|
sub ifndef {
|
|
|
|
my $stack = shift;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
$stack->push(not $stack->defined(@_));
|
|
|
|
}
|
|
|
|
|
|
|
|
sub if {
|
|
|
|
my $stack = shift;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
my $argument = shift;
|
|
|
|
for ($argument) {
|
2002-10-10 23:39:33 +04:00
|
|
|
/^(\w+)==(.*)$/os && do {
|
2002-10-10 07:11:27 +04:00
|
|
|
# equality
|
|
|
|
return $stack->push($stack->get($1) eq $2);
|
|
|
|
};
|
2002-10-10 23:39:33 +04:00
|
|
|
/^(\w+)!=(.*)$/os && do {
|
2002-10-10 07:11:27 +04:00
|
|
|
# inequality
|
|
|
|
return $stack->push($stack->get($1) ne $2);
|
|
|
|
};
|
2002-10-10 23:39:33 +04:00
|
|
|
/^(\w+)$/os && do {
|
2002-10-10 07:11:27 +04:00
|
|
|
# true value
|
|
|
|
return $stack->push($stack->get($1));
|
|
|
|
};
|
2002-10-10 23:39:33 +04:00
|
|
|
/^!(\w+)$/os && do {
|
2002-10-10 07:11:27 +04:00
|
|
|
# false value
|
|
|
|
return $stack->push(not $stack->get($1));
|
|
|
|
};
|
2002-10-11 01:42:44 +04:00
|
|
|
die "invalid argument: '$_'\n";
|
2002-10-10 07:11:27 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub else {
|
|
|
|
my $stack = shift;
|
|
|
|
die "argument unexpected\n" if @_;
|
|
|
|
$stack->push(not $stack->pop);
|
|
|
|
}
|
|
|
|
|
2002-10-10 08:12:59 +04:00
|
|
|
sub elif {
|
|
|
|
my $stack = shift;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
if ($stack->pop) {
|
|
|
|
$stack->push(0);
|
|
|
|
} else {
|
|
|
|
&if($stack, @_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub elifdef {
|
|
|
|
my $stack = shift;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
if ($stack->pop) {
|
|
|
|
$stack->push(0);
|
|
|
|
} else {
|
|
|
|
&ifdef($stack, @_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub elifndef {
|
|
|
|
my $stack = shift;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
if ($stack->pop) {
|
|
|
|
$stack->push(0);
|
|
|
|
} else {
|
|
|
|
&ifndef($stack, @_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-10-10 07:11:27 +04:00
|
|
|
sub endif {
|
|
|
|
my $stack = shift;
|
|
|
|
die "argument unexpected\n" if @_;
|
|
|
|
$stack->pop;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub error {
|
|
|
|
my $stack = shift;
|
|
|
|
return if $stack->disabled;
|
|
|
|
die "argument expected\n" unless @_;
|
2002-10-14 02:45:34 +04:00
|
|
|
my $line = $stack->expand(@_);
|
|
|
|
die "$line\n";
|
2002-10-10 07:11:27 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
sub expand {
|
|
|
|
my $stack = shift;
|
|
|
|
return if $stack->disabled;
|
|
|
|
die "argument expected\n" unless @_;
|
2002-10-14 02:45:34 +04:00
|
|
|
my $line = $stack->expand(@_);
|
2002-10-10 07:11:27 +04:00
|
|
|
print "$line\n";
|
|
|
|
}
|
|
|
|
|
2002-10-11 01:56:47 +04:00
|
|
|
sub literal {
|
|
|
|
my $stack = shift;
|
|
|
|
return if $stack->disabled;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
my $line = shift;
|
|
|
|
print "$line\n";
|
|
|
|
}
|
|
|
|
|
2002-10-10 07:11:27 +04:00
|
|
|
sub include {
|
|
|
|
my $stack = shift;
|
|
|
|
return if $stack->disabled;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
main::include($stack, @_);
|
|
|
|
}
|
|
|
|
|
2002-10-11 03:14:01 +04:00
|
|
|
sub filter {
|
|
|
|
my $stack = shift;
|
|
|
|
return if $stack->disabled;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
foreach (split(/\s/os, shift)) {
|
|
|
|
$stack->filter($_, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub unfilter {
|
|
|
|
my $stack = shift;
|
|
|
|
return if $stack->disabled;
|
|
|
|
die "argument expected\n" unless @_;
|
|
|
|
foreach (split(/\s/os, shift)) {
|
|
|
|
$stack->filter($_, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
########################################################################
|
|
|
|
|
|
|
|
package filter;
|
|
|
|
|
|
|
|
sub spaces {
|
|
|
|
my($stack, $text) = @_;
|
2002-10-13 22:04:58 +04:00
|
|
|
$text =~ s/ +/ /gos; # middle spaces
|
|
|
|
$text =~ s/^ //gos; # start spaces
|
2002-10-14 02:37:17 +04:00
|
|
|
$text =~ s/ (\n?)$/$1/gos; # end spaces
|
2002-10-11 03:14:01 +04:00
|
|
|
return $text;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub slashslash {
|
|
|
|
my($stack, $text) = @_;
|
|
|
|
$text =~ s|//.*?(\n?)$|$1|gos;
|
|
|
|
return $text;
|
|
|
|
}
|
|
|
|
|
2002-10-10 07:11:27 +04:00
|
|
|
########################################################################
|