ossp-pkg/shtool/sh.scpp
##
## scpp -- Sharing C Pre-Processor
## Copyright (c) 1999-2008 Ralf S. Engelschall <rse@engelschall.com>
##
## This file is part of shtool and 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 file 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, or contact Ralf S. Engelschall <rse@engelschall.com>.
##
str_tool="scpp"
str_usage="[-v|--verbose] [-p|--preserve] [-f|--filter <filter>] [-o|--output <ofile>] [-t|--template <tfile>] [-M|--mark <mark>] [-D|--define <dname>] [-C|--class <cname>] <file> [<file> ...]"
gen_tmpfile=yes
arg_spec="1+"
opt_spec="v.p.f+o:t:M:D:C:"
opt_alias="v:verbose,p:preserve,f:filter,o:output,t:template,M:mark,D:define,C:class"
opt_v=no
opt_p=no
opt_f=""
opt_o="lib.h"
opt_t="lib.h.in"
opt_M="%%MARK%%"
opt_D="cpp"
opt_C="intern"
. ./sh.common
srcs="$*"
output="${opt_o}.n"
# find a reasonable Awk
awk=''
paths=`echo $PATH |\
sed -e 's%/*:%:%g' -e 's%/$%%' \
-e 's/^:/.:/' -e 's/::/:.:/g' -e 's/:$/:./' \
-e 's/:/ /g'`
for name in gawk nawk awk; do
for path in $paths; do
if [ -r "$path/$name" ]; then
awk="$path/$name"
break
fi
done
if [ ".$awk" != . ]; then
break
fi
done
if [ ".$awk" = . ]; then
echo "$msgprefix:Error: cannot find a reasonable Awk" 1>&2
shtool_exit 1
fi
# parse source file(s)
if [ ".$opt_v" = .yes ]; then
echo "Parsing:" | $awk '{ printf("%s", $0); }' 1>&2
fi
for src in $srcs; do
if [ ".$opt_v" = .yes ]; then
echo $src | $awk '{ printf(" %s", $0); }' 1>&2
fi
if [ ".$opt_f" != . ]; then
inputcmd="sed"
OIFS="$IFS"; IFS="$ASC_NL"; set -- $opt_f; IFS="$OIFS"
for e
do
inputcmd="$inputcmd -e '$e'"
done
inputcmd="$inputcmd '$src'"
else
inputcmd="cat '$src'"
fi
eval $inputcmd |\
$awk '
BEGIN {
ln = 0;
fln = 0;
level = 0;
mode = "";
store = "";
}
{
ln++;
}
/^#if.*/ {
level++;
}
/^#if [a-zA-Z_][a-zA-Z0-9_]* *$/ {
if ($2 == define) {
mode = "D";
printf("D:#line %d \"%s\"\n", ln, src);
next;
}
}
/^#endif.*/ {
level--;
if (mode == "D" && level == 0) {
mode = "";
next;
}
}
/^[a-zA-Z_][a-zA-Z0-9_].*;.*/ {
if ($1 == class) {
printf("V:#line %d \"%s\"\n", ln, src);
printf("V:%s\n", $0);
printf("J:%s\n", $0);
next;
}
}
/^[a-zA-Z_][a-zA-Z0-9_].*=.*/ {
if ($1 == class) {
printf("V:#line %d \"%s\"\n", ln, src);
printf("V:%s\n", $0);
printf("J:%s\n", $0);
next;
}
}
/^[a-zA-Z_][a-zA-Z0-9_]*/ {
if ($1 == class) {
fln = ln;
store = $0;
mode = "F";
next;
}
}
/^\{ *$/ {
if (mode == "F") {
printf("F:#line %d \"%s\"\n", fln, src);
printf("F:%s;\n", store);
printf("I:%s;\n", store);
store = "";
mode = "";
next;
}
}
{
if (mode == "D")
printf("D:%s\n", $0);
else if (mode == "F")
store = store " " $0;
}
' "src=$src" "define=$opt_D" "class=$opt_C" >>$tmpfile
done
if [ ".$opt_v" = .yes ]; then
echo "" 1>&2
fi
# start generating output header
echo "/* $opt_o -- autogenerated from $opt_t, DO NOT EDIT! */" >$output
echo "#line 1 \"$opt_t\"" >>$output
sed <$opt_t -e "1,/^${opt_M} *\$/p" -e 'd' |\
sed -e "/^${opt_M} *\$/d" >>$output
# merge in the define blocks
grep '^D:' $tmpfile | sed -e 's/^D://' >>$output
# generate standard prolog
echo "#line 1 \"_ON_THE_FLY_\"" >>$output
echo "" >>$output
echo "/* make sure the scpp source extensions are skipped */" >>$output
echo "#define $opt_D 0" >>$output
echo "#define $opt_C /**/" >>$output
# generate namespace hiding for variables
echo "" >>$output
echo "/* move intern variables to hidden namespace */" >>$output
grep '^J:' $tmpfile | sed >>$output \
-e 's/^J://' \
-e 's/ */ /g' \
-e 's/^[^=;]*[ *]\([a-zA-Z0-9_]*\)\[\];.*$/#define \1 __\1/' \
-e 's/^[^=;]*[ *]\([a-zA-Z0-9_]*\)\[\] =.*$/#define \1 __\1/' \
-e 's/^[^=;]*[ *]\([a-zA-Z0-9_]*\);.*$/#define \1 __\1/' \
-e 's/^[^=;]*[ *]\([a-zA-Z0-9_]*\) =.*$/#define \1 __\1/'
# generate namespace hiding for functions
echo "" >>$output
echo "/* move intern functions to hidden namespace */" >>$output
grep '^I:' $tmpfile | sed >>$output \
-e 's/^I://' \
-e 's/\([ (]\) */\1/g' \
-e 's/ *\([),]\)/\1/g' \
-e 's/^[^(]*[ *]\([a-zA-Z0-9_]*\)(.*$/#define \1 __\1/'
# generate prototypes for variables
echo "" >>$output
echo "/* prototypes for intern variables */" >>$output
grep '^V:' $tmpfile | sed >>$output \
-e 's/^V://' \
-e 's/ */ /g' \
-e 's/^\([^=;]*[ *][a-zA-Z0-9_]*\[\]\);.*$/\1;/' \
-e 's/^\([^=;]*[ *][a-zA-Z0-9_]*\[\]\) =.*$/\1;/' \
-e 's/^\([^=;]*[ *][a-zA-Z0-9_]*\);.*$/\1;/' \
-e 's/^\([^=;]*[ *][a-zA-Z0-9_]*\) =.*$/\1;/' \
-e 's/ ;/;/g' \
-e "s/^$opt_C /extern /"
# generate prototypes for functions
echo "" >>$output
echo "/* prototypes for intern functions */" >>$output
grep '^F:' $tmpfile | sed >>$output \
-e 's/^F://' \
-e 's/\([ (]\) */\1/g' \
-e 's/ *\([),]\)/\1/g' \
-e 's/\([* ]\)[a-zA-Z0-9_]*,/\1,/g' \
-e 's/\([* ]\)[a-zA-Z0-9_]*);/\1);/g' \
-e 's/(\*[a-zA-Z0-9_]*)(/(*)(/g' \
-e 's/\([ (]\) */\1/g' \
-e 's/ *\([),]\)/\1/g' \
-e "s/^$opt_C /extern /"
# finish generating output header
n=`(echo ''; sed <$opt_t -e "1,/^${opt_M} *\$/p" -e 'd') |\
wc -l | sed -e 's;^ *\([0-9]*\) *$;\1;'`
echo "#line $n \"$opt_t\"" >>$output
sed <$opt_t -e "/^${opt_M} *\$/,\$p" -e 'd' |\
sed -e "/^${opt_M} *\$/d" >>$output
# create final output file
if [ -f $opt_o ]; then
if [ ".$opt_p" = .yes ]; then
grep -v '^#line' $opt_o >$tmpfile.o
grep -v '^#line' $output >$tmpfile.n
out_old="$tmpfile.o"
out_new="$tmpfile.n"
else
out_old="$opt_o"
out_new="$output"
fi
if cmp -s $out_old $out_new; then
:
else
cp $output $opt_o
fi
else
cp $output $opt_o
fi
rm -f $output
rm -f $tmpfile $tmpfile.* >/dev/null 2>&1
shtool_exit 0
##
## manual page
##
=pod
=head1 NAME
B<shtool scpp> - B<GNU shtool> C source file pre-processor
=head1 SYNOPSIS
B<shtool scpp>
[B<-v>|B<--verbose>]
[B<-p>|B<--preserve>]
[B<-f>|B<--filter> I<filter>]
[B<-o>|B<--output> I<ofile>]
[B<-t>|B<--template> I<tfile>]
[B<-M>|B<--mark> I<mark>]
[B<-D>|B<--define> I<dname>]
[B<-C>|B<--class> I<cname>]
I<file> [I<file> ...]
=head1 DESCRIPTION
This command is an additional ANSI C source file pre-processor for sharing
cpp(1) code segments, internal variables and internal functions. The intention
for this comes from writing libraries in ANSI C. Here a common shared internal
header file is usually used for sharing information between the library
source files.
The operation is to parse special constructs in I<file>s, generate a few
things out of these constructs and insert them at position I<mark> in I<tfile>
by writing the output to I<ofile>. Additionally the I<file>s are never touched
or modified. Instead the constructs are removed later by the cpp(1) phase of
the build process. The only prerequisite is that every I<file> has a
``C<#include ">I<ofile>C<">'' at the top.
This command provides the following features: First it avoids namespace
pollution and reduces prototyping efforts for internal symbols by recognizing
functions and variables which are defined with the storage class identifier
``I<cname>''. For instance if I<cname> is ``intern'', a function ``C<intern
void *foobar(int quux)>'' in one of the I<file>s is translated into both a
``C<#define foobar __foobar>'' and a ``C<extern void *foobar(int quux);>'' in
I<ofile>. Additionally a global ``C<#define> I<cname> C</**/>'' is also
created in I<ofile> to let the compiler silently ignore this additional
storage class identifier.
Second, the library source files usually want to share C<typedef>s,
C<#define>s, etc. over the source file boundaries. To achieve this one can
either place this stuff manually into I<tfile> or use the second feature of
B<scpp>: All code in I<file>s encapsulated with ``C<#if >I<dname> ...
C<#endif>'' is automatically copied to I<ofile>. Additionally a global
``C<#define> I<dname> C<0>'' is also created in I<ofile> to let the compiler
silently skip this parts (because it was already found in the header).
=head1 OPTIONS
The following command line options are available.
=over 4
=item B<-v>, B<--verbose>
Display some processing information.
=item B<-p>, B<--preserve>
Preserves I<ofile> independent of the generated ``#line'' lines. This is
useful for Makefiles if the real contents of I<ofile> will not change,
just line numbers. Default is to overwrite.
=item B<-f>, B<--filter> I<filter>
Apply one or more pre-processing sed(1) I<filter> commands (usually of
type ``C<s/.../.../>'') to each input file before their input is parsed.
This option can occur multiple times.
=item B<-o>, B<--output> I<ofile>
Output file name. Default is C<lib.h>.
=item B<-t>, B<--template> I<tfile>
Template file name. Default is C<lib.h.in>.
=item B<-M>, B<--mark> I<mark>
Mark to be replaced by generated constructs. Default is C<%%MARK%%>.
=item B<-D>, B<--define> I<dname>
FIXME. Default is C<cpp>.
=item B<-C>, B<--class> I<cname>
FIXME. Default is C<intern>.
=back
=head1 EXAMPLE
# Makefile
SRCS=foo_bar.c foo_quux.c
foo_p.h: foo_p.h.in
shtool scpp -o foo_p.h -t foo_p.h.in \
-M %%MARK%% -D cpp -C intern $(SRCS)
/* foo_p.h.in */
#ifndef FOO_P_H
#define FOO_P_H
%%MARK%%
#endif /* FOO_P_H */
/* foo_bar.c */
#include "foo_p.h"
#if cpp
#define OURS_INIT 4711
#endif
intern int ours;
static int myone = 0815;
intern int bar(void)
{
ours += myone;
}
/* foo_quux.c */
#include "foo_p.h"
int main(int argc, char *argv[])
{
int i;
ours = OURS_INIT
for (i = 0; i < 10; i++) {
bar();
printf("ours now %d\n", ours);
}
return 0;
}
=head1 HISTORY
The B<GNU shtool> B<scpp> command was originally written by Ralf S.
Engelschall E<lt>rse@engelschall.comE<gt> in 1999 for B<GNU shtool>.
Its was prompted by the need to have a pre-processing facility
in the B<GNU pth> project.
=head1 SEE ALSO
shtool(1), cpp(1).
=cut