fix Makefile, README.md and bash script for 2001/herrmann1

This commit is contained in:
Landon Curt Noll committed 2023-03-17 15:11:42 -07:00
1 parent 7621291e2f
commit bad0cd868f
3 files changed
+556 -469

No files matched your search

+258 -169
View File
@@ -1,18 +1,9 @@
#!/usr/bin/env make
#
# IOCCC 2001 entry Makefile
# IOCCC 2001 winning entry - herrmann1
################################################################################
#
# An Historical Makefile NOTE:
#
# The orig.Makefile, if it exits, is the original Makefile before this IOCCC
# year directory was reworked so that entries are in separate directories.
# This also applies to any orig.ansi.mk and orig.common.mk files.
#
# Several Makefiles were modified / formed in 2022, and as such it may have
# elements that are not historically compatible with way entries were built.
#
# Regarding individual entries:
#
# IOCCC winning entry code may not work on your system. Even with standards,
@@ -45,192 +36,290 @@
# See: http://creativecommons.org/licenses/by-sa/3.0/
SHELL= /bin/sh
CAT= cat
RM= rm
SED= sed
TRUE= true
#CINCLUDE= -I ${HOMEBREW_PREFIX}/include/SDL2
# optimization
#############################
# shell used by this Makefile
#############################
#
# Most compiles will safely use -O2. Some can use only -O.
# A few compilers have broken optimizers and thus you may
# not want anything.
SHELL= bash
#######################
# common tool locations
#######################
#
include ../../var.mk
# Set X11_LIBDIR to the directory where the X11 library resides
#
#X11_LIBDIR= /usr/X11R6/lib
#X11_LIBDIR= /usr/X11/lib
X11_LIBDIR= /opt/X11/lib
# Set X11_INCLUDEDIR to the directory where the X11 include files reside
#
#X11_INCDIR= /usr/X11R6/include
#X11_INCDIR= /usr/X11/include
X11_INCDIR= /opt/X11/include
# Common C compiler warnings to silence
#
CSILENCE=
#CSILENCE= -Wno-implicit-int
# Common C compiler warning flags
#
#CWARN= ${CSILENCE}
#CWARN= -Wall ${CSILENCE}
#CWARN= -Wall -Wextra ${CSILENCE}
CWARN= -Wall -Wextra -pedantic ${CSILENCE}
# Compiler standard
#
#CSTD=
#CSTD= -ansi
CSTD= -std=gnu99
# Compiler bit architecture
#
# Some entries require 32-bitness:
# ARCH= -m32
#
# Some entries require 64-bitness:
# ARCH= -m64
#
# By default we assume nothing:
#
ARCH=
# Defines that are needed to compile
#
CDEFINE=
#CDEFINE= -DIOCCC
# Include files that are needed to compile
#
CINCLUDE=
#CINCLUDE= -include stdlib.h
#CINCLUDE= -include stdio.h
#CINCLUDE= -include stdlib.h -include stdio.h
#CINCLUDE= -I ${X11_INCDIR} -I ${X11_INCDIR}/X11
#CINCLUDE= `${SDL2_CONFIG} --cflags`
# Optimization
#
# Most compiles will safely use -O2. Some can use only -O1 or -O.
# A few compilers have broken optimizers or this entry make break
# under those buggy optimizers and thus you may not want anything.
# Reasonable compilers will allow for -O3.
#
#OPT=
#OPT= -O
OPT= -O2
#OPT= -O1
#OPT= -O2
OPT= -O3
# flags for ANSI compiles
# Default flags for ANSI C compilation
#
# NOTE: Some ANSI compilers make use of -Xa to turn on ANSI mode,
# others such as gcc may want -ansi, others (Compaq Tru64 Unix)
# want -std1, and others may want nothing at all.
# Adjust the CFLAGS line as needed.
#
# NOTE: Some compilers cannot optimize, or optimize some entries
# incorrectly. You might want to turn on -O to see if your
# compiler can handle them.
#
#
#CFLAGS= -Xa ${OPT}
#CFLAGS= -std1 $(OPT)
CFLAGS= -ansi ${OPT}
CFLAGS= ${CSTD} ${CWARN} ${ARCH} ${CDEFINE} ${CINCLUDE} ${OPT}
# NOTE: On some systems, the library path does not include the location
# of the X11 libraries. You might need to use a -L/path/to/X11/lib/dir
# in order to compile with these libs.
# Libraries needed to build
#
X11CCFLAGS= -I/usr/X11R6/include -I/usr/X11R6/include/X11 -I/usr/include/X11
#X11CCFLAGS=
X11LDFLAGS= -L/usr/X11R6/lib -Wl,-R/usr/X11R6/lib
#X11LDFLAGS=
LIBS=
#LIBS= -lm
#LIBS= -L ${X11_LIBDIR} -lX11
#LIBS= `${SDL2_CONFIG} --libs`
# ANSI compiler
#
# Set CC to the name of your ANSI compiler.
#
# Some entries seem to need gcc. If you have gcc, set
# both CC and MAY_NEED_GCC to gcc.
#
# If you do not have gcc, set CC to the name of your ANSI compiler, and
# set MAY_NEED_GCC to either ${CC} (and hope for the best) or to just :
# to disable such programs.
# C compiler to use
#
CC= cc
MAY_NEED_GCC= gcc
#CC=clang
#CC=gcc
# winners that compile under ANSI C
# Compiler add-ons or replacements for clang only
#
WINNERS= anonymous bellard bellard.otccex cheong coupard \
ctk dgbeards herrmann1 herrmann2 jason kev ollinger rosten \
schweikh westley williams
ALT_NAMES= westley.orig
DATA_FILES= anonymous_files bellard_files herrmann1_files herrmann2_files
all: ${WINNERS}
# Most likely to amaze
ifeq ($(CC),clang)
#
anonymous: anonymous.c
magic='${MAY_NEED_GCC} -O1'; \
X='-DA(X)=#X'; \
warning='-pedantic -Dprocessor'; \
$$magic $$warning -Dmagic= $$X "-DX=A($$magic \"$$X\")" \
-o anonymous anonymous.c
anonymous_files: anonymous.ten.c
# Best abuse of the rules
#CSILENCE+=
#
bellard: bellard.c
${CC} -rdynamic ${CFLAGS} bellard.c -o bellard -ldl
bellard_files: bellard.otccex.c
# Best short program
CWARN+= -Weverything
#
cheong: cheong.c
${CC} ${CFLAGS} cheong.c -o cheong
endif
# Most obfuscated sound
# Specific add-ons or replacements for gcc only
#
coupard: coupard.c
${CC} ${CFLAGS} -o coupard coupard.c
# Worst Driver
ifeq ($(CC),gcc)
#
ctk: ctk.c
${CC} ${CFLAGS} -o ctk ctk.c
# Worst AI
#CSILENCE+=
#
dgbeards: dgbeards.c
${CC} ${CFLAGS} -DE=break -DF=char -DK=case -DP=int -DR=return \
-DI=0xFFFF dgbeards.c -o dgbeards
# Best abuse of the C preprocessor
#CWARN+=
#
herrmann1: herrmann1.c herrmann1.sh
@echo "NOTE: $@ must be built/run using the script $@.sh"
${SHELL} ./herrmann1.sh
endif
herrmann1_files: herrmann1.gcd herrmann1.sh herrmann1.times2
# Most eye-crossing
##############################
# Special flags for this entry
##############################
#
# This entry requires the GCC -include feature.
ENTRY= herrmann1
PROG= ${ENTRY}
#
herrmann2: herrmann2.c
${MAY_NEED_GCC} ${CFLAGS} herrmann2.c -o herrmann2 \
-include /usr/include/stdlib.h -include /usr/include/stdio.h \
-include /usr/include/time.h -include /usr/include/unistd.h
herrmann2_files: herrmann2.cup herrmann2.ioccc
# Best Of Show
OBJ= ${PROG}.o
DATA= herrmann1.gcd herrmann1.sh herrmann1.times2
TARGET= ${PROG}
#
jason: jason.c
${CC} ${CFLAGS} -o jason jason.c
ALT_OBJ=
ALT_TARGET=
# Best Curses Game
#################
# build the entry
#################
#
kev: kev.c
${CC} ${CFLAGS} -DPORT=5455 -DSPEED=50 -o kev kev.c \
-lcurses -ltermcap \
-Dl_="socket(il.sin_family=AF_INET,SOCK_STREAM,0)"
# Best primal ASCII graphics
#
ollinger: ollinger.c
${CC} ${CFLAGS} ollinger.c -o ollinger
# Best abuse of the user
#
rosten: rosten.c
${CC} ${CFLAGS} rosten.c ${X11CCFLAGS} ${X11LDFLAGS} -pedantic -lX11 \
-o rosten
# Best one-liner
#
schweikh: schweikh.c
${CC} ${CFLAGS} schweikh.c -o schweikh
# Best position-independant code
#
westley: westley.c
${CC} ${CFLAGS} westley.c -o westley
westley.orig: westley.orig.c
${CC} ${CFLAGS} westley.orig.c -o westley.orig
# Best position-independant code
#
williams: williams.c
${CC} williams.c ${X11CCFLAGS} ${X11LDFLAGS} -lX11 -o williams
everything: ${WINNERS} ${ALT_NAMES} ${DATA_FILES}
love:
@echo 'not war?'
haste:
${MAKE} waste
waste:
@echo 'waste'
clean:
${RM} -f *.o a.out core ${WINNERS}
clobber: clean
${RM} -f ${WINNERS}
${RM} -f ${ALT_NAMES}
nuke: clobber
all: data ${TARGET}
@${TRUE}
install: all
${CAT} ${WINNERS} ${ALT_NAMES} > /dev/null
.PHONY: all alt data everything clean clobber install \
love haste waste maker easter_egg sandwich \
supernova deep_magic magic charon pluto
${PROG}: ${PROG}.c ${PROG}.sh
${SHELL} ./herrmann1.sh
# alternative executable
#
alt: data ${ALT_TARGET}
@${TRUE}
# data files
#
data: ${DATA}
@${TRUE}
# both all and alt
#
everything: all alt
###############
# utility rules
###############
#
clean:
${RM} -f ${OBJ} ${ALT_OBJ}
@-if [ -f indent.c ]; then \
echo ${RM} -f indent.c; \
${RM} -f indent.c; \
fi
clobber: clean
${RM} -f ${TARGET} ${ALT_TARGET}
@-if [ -e sandwich ]; then \
${RM} -f sandwich; \
echo 'ate sandwich'; \
fi
install:
@echo "Dr. Spock says that is not logical!"
@${TRUE}
# you may be able to misunderstand the source by reading indent.c
#
indent.c: ${PROG}.c
@if which "${GINDENT}" >/dev/null 2>&1; then \
echo ${RM} -f $@; \
${RM} -f $@; \
echo "${GINDENT} < $< > $@"; \
${GINDENT} < $< > $@; \
elif which "${INDENT}" >/dev/null 2>&1; then \
echo ${RM} -f $@; \
${RM} -f $@; \
echo "${INDENT} < $< > $@"; \
${INDENT} < $< > $@; \
else \
echo "no intent tool found, ident $< yourself, sorry"; \
echo "exit 1"; \
exit 1; \
fi
##################
# 133t hacker rulz
##################
#
love:
@echo 'not war?'
@${TRUE}
haste:
$(MAKE) waste
@${TRUE}
waste:
@echo 'haste'
@${TRUE}
maker:
@echo 'Participate in a maker faire'
@${TRUE}
easter_egg:
@echo "you expected to often misunderstand this $${RANDOM} magic"
@echo "chongo '<was here>' /\\oo/\\"
@echo "Eggy"
sandwich: easter_egg
@if [ `id -u` -eq 0 ]; then \
echo 'Okay.'; \
echo "$${RANDOM}`date +%s`$${RANDOM}" > $@; \
else \
echo 'What? Make it yourself.'; \
exit 1; \
fi
# Understand the history of the Homebrew Computer Club
# as well as the West Coast Computer Faire and
# you might be confused different. :-)
#
supernova: sandwich
@-if [ -r .code_anal ]; then \
${RM} -f .code_anal_v6; \
else \
echo "planet deniers, like some members of the IAU, are so cute when they try to defend their logic"; \
fi
@echo "A $@ helps ${MAKE} the elements that help form planets"
@${TRUE}
deep_magic: supernova
@-if [ -r .code_anal ]; then \
ccode_analysis --deep_magic 7889066cdbdf09b0408021754586e895be641cc9be29e7eafd3ca65359541f17 \
--FNV1a_hash_512_bit -v2 "${PROG}" "${PROG}.c"; \
else \
echo "Wrong! Do it again!"; \
sleep 2; \
echo "Wrong! Do it again!"; \
sleep 2; \
echo "Wrong! Do it again!"; \
fi
@${TRUE}
magic: deep_magic
@-if [ -r .code_anal ]; then \
ccode_analysis --level 20003 --mode 5717175 --FNV1a_hash_512_bit -v2 "${PROG}" "${PROG}.c"; \
else \
echo "If you don't eat yer meat, you can't have any pudding!"; \
echo "How can you have any pudding if you don't eat yer meat?!"; \
fi
@${TRUE}
# The IOCCC resident astronomer states
#
charon: magic
@echo "$@ is a dwarf planet"
@echo "dwarf is a type of planet"
@echo "therefore IAU, $@ is a planet"
pluto: charon
${MAKE} charon
@echo "$@ is a dwarf planet"
@echo "dwarf is a type of planet"
@echo "therefore, $@ is a planet"
@echo "having lots of planets is good feature because supernovae can help ${MAKE} a lot of them"
+297 -299
View File
@@ -1,4 +1,4 @@
Best abuse of the C preprocessor:
# Best abuse of the C preprocessor:
Immanuel Herrmann
University of Freiburg, Germany
@@ -8,356 +8,354 @@ Best abuse of the C preprocessor:
http://pcpool.mathematik.uni-freiburg.de/~immi/
# To build:
Judges' Comments:
```sh
make
```
To build:
### To run:
Running and building are combined for this entry.
```sh
./herrmann1.sh 'prg=file'
```
Try:
./herrmann1.sh 'prg=herrmann1.times2'
-or-
./herrmann1.sh 'prg=herrmann1.gcd'
### Try:
There are some clear hints in the source code as to the
purpose of this program. Perhaps the only way to figure out what is
going on is to look at the differences between the runs of the C
preprocessor! Computer Science students should recognize what is going
on.
```sh
./herrmann1.sh 'prg=herrmann1.times2'
```
-or-
Selected Author's Comments:
```sh
./herrmann1.sh 'prg=herrmann1.gcd'
```
Many cool things have been done with the preprocessor,
e. g. calculation of prime numbers or the tower of Hanoi. However,
up to now, no one proved that the preprocessor is Turing complete,
and I wondered why. Well, the answer is simply that the
preprocessor is _not_ Turing complete, at least not if the program
is preprocessed only once. This is true even if the program is
allowed to include itself. (The reason being that for a given
program, the preprocessor has only a finite number of states, plus
a stack consisting of the places which the file has been included
from. This is only a push-down automaton.)
## Judges' comments:
Well, this entry _is_ a Turing machine implemented in the
preprocessor; so of course, it has to be preprocessed several
times. Here are the main features:
There are some clear hints in the source code as to the
purpose of this program. Perhaps the only way to figure out what is
going on is to look at the differences between the runs of the C
preprocessor! Computer Science students should recognize what is going
on.
- Easy to program: Write your own Turing programs in a nice
syntax.
## Author's comments:
- Easy to debug: (some) errors in the Turing machine program will
be reported nicely. (That's something which is not often seen in
IOCCC programs.)
Many cool things have been done with the preprocessor,
e. g. calculation of prime numbers or the tower of Hanoi. However,
up to now, no one proved that the preprocessor is Turing complete,
and I wondered why. Well, the answer is simply that the
preprocessor is _not_ Turing complete, at least not if the program
is preprocessed only once. This is true even if the program is
allowed to include itself. (The reason being that for a given
program, the preprocessor has only a finite number of states, plus
a stack consisting of the places which the file has been included
from. This is only a push-down automaton.)
- Ultra-fast: The resulting C program will print the output in
time O(n), where n is the length of the output. This means for
example that you can solve NP hard problems in polynomial time!
;-)
Well, this entry _is_ a Turing machine implemented in the
preprocessor; so of course, it has to be preprocessed several
times. Here are the main features:
- And best of all: During the preprocessing, watch the animated
Turing machine running over the tape and doing its work in your
terminal!
- Easy to program: Write your own Turing programs in a nice
syntax.
- Easy to debug: (some) errors in the Turing machine program will
be reported nicely. (That's something which is not often seen in
IOCCC programs.)
- Ultra-fast: The resulting C program will print the output in
time O(n), where n is the length of the output. This means for
example that you can solve NP hard problems in polynomial time!
;-)
How to use it
=============
- And best of all: During the preprocessing, watch the animated
Turing machine running over the tape and doing its work in your
terminal!
For the impatient
-----------------
### How to use it
The info files "herrmann1.turing" and "herrmann1.gcd" are sample
programs for the Turing machine. For example, type
For the impatient
-----------------
./herrmann1.sh 'prg=herrmann1.times2'
The info files "herrmann1.turing" and "herrmann1.gcd" are sample
programs for the Turing machine. For example, type
and watch the animated Turing machine multiply 8 by two (in
unary representation). Here's a screen shot during compilation:
```sh
./herrmann1.sh 'prg=herrmann1.times2'
```
/*
*
* State: st2.
*
* _ _ _ _ _ * _ _ _ _ _ _ _ _ _ _ _ _ _
* ... O O I I I I I O I I I I I O O ...
* ~ ~ ~ ~ ~ * ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
*
*/
and watch the animated Turing machine multiply 8 by two (in
unary representation). Here's a screen shot during compilation:
(As on real videos, you'll see a bit of "white noise" at the
beginning and at the end.;-) After compilation has terminated,
type ./prog to see the final result. The output will be
/*
*
* State: st2.
*
* _ _ _ _ _ * _ _ _ _ _ _ _ _ _ _ _ _ _
* ... O O I I I I I O I I I I I O O ...
* ~ ~ ~ ~ ~ * ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
*
*/
Final tape:
(As on real videos, you'll see a bit of "white noise" at the
beginning and at the end.;-) After compilation has terminated,
type ./prog to see the final result. The output will be
*
..OOIIIIIIIIIIIIIIIIOOOO..
*
Final tape:
(Note that the last frame of the animation is _not_ the final
result.)
*
..OOIIIIIIIIIIIIIIIIOOOO..
*
(Note that the last frame of the animation is _not_ the final
result.)
In the previous example, the Turing machine started on the default
tape of the program "times2.turing". To provide your own tape, type
(for example)
./build prg=times2.turing tape="O O O I I I O O O"
In the previous example, the Turing machine started on the default
tape of the program "times2.turing". To provide your own tape, type
(for example)
(The O's and I's are letters, not digits.) You might prefer to
provide only a finite portion of the tape, like I did above. In
this case, the remainder of the tape is filled with O's. ;-)
./build prg=times2.turing tape="O O O I I I O O O"
If you just type
./build
and run the program, you'll get a usage message (and the return
code will be 2).
(The O's and I's are letters, not digits.) You might prefer to
provide only a finite portion of the tape, like I did above. In
this case, the remainder of the tape is filled with O's. ;-)
Note: To get a really nice animation, you'll need a terminal which
is able to scroll fast enough. (xterm is not very good. The
kde terminal "konsole" or the text console under linux are much
better. Simply try some out.)
If you just type:
```
./build
```
and run the program, you'll get a usage message (and the return
code will be 2).
Writing your own programs
-------------------------
Note: To get a really nice animation, you'll need a terminal which
is able to scroll fast enough. (xterm is not very good. The
kde terminal "konsole" or the text console under linux are much
better. Simply try some out.)
Now, let's assume you want to write a program which adds 1 to a
number in unary representation. (That is, a row of I's is
expected, and one more I is appended to the row.) Let's call it
"plus1.turing". Here's what it could look like:
### Writing your own programs
/* Search beginning of number. */
#define start_O O, right, start
/* Number found. Move one step to the left. */
#define start_I I, left, st2
/* Write a I and stop */
#define st2_O I, left, stop
Now, let's assume you want to write a program which adds 1 to a
number in unary representation. (That is, a row of I's is
expected, and one more I is appended to the row.) Let's call it
"plus1.turing". Here's what it could look like:
Some explanations:
- The tape alphabet is always O and I.
- The states can be anything starting with "st". "start" and
"stop" are the start and stop states, respectively.
- to define a state, use two lines: one for each letter on the
tape. You may omit one of the lines if the Turing machine will
never enter the state reading that letter. (In the example, the
line defining st2_I has been left out.)
- The order of the lines is irrelevant.
- You may use c-style comments. (Would you have guessed it?)
/* Search beginning of number. */
#define start_O O, right, start
/* Number found. Move one step to the left. */
#define start_I I, left, st2
/* Write a I and stop */
#define st2_O I, left, stop
Now, the program can be used with
Some explanations:
- The tape alphabet is always O and I.
- The states can be anything starting with "st". "start" and
"stop" are the start and stop states, respectively.
- to define a state, use two lines: one for each letter on the
tape. You may omit one of the lines if the Turing machine will
never enter the state reading that letter. (In the example, the
line defining st2_I has been left out.)
- The order of the lines is irrelevant.
- You may use c-style comments. (Would you have guessed it?)
./herrmann1.sh 'prg=plus1.turing' 'tape="I I I"'
Now, the program can be used with
But what happens if no tape is provided? Up to now, the Turing
machine would start on an empty tape (that is, a tape filled with
O's). For many programs, this doesn't make much sense (as is
the case for "plus1.turing"). So you might want to provide a
default tape by adding a line like
```sh
./herrmann1.sh 'prg=plus1.turing' 'tape="I I I"'
```
#define tape O O O O O I I I I
But what happens if no tape is provided? Up to now, the Turing
machine would start on an empty tape (that is, a tape filled with
O's). For many programs, this doesn't make much sense (as is
the case for "plus1.turing"). So you might want to provide a
default tape by adding a line like
to your program.
#define tape O O O O O I I I I
to your program.
### Debugging
When writing long programs, the most common errors are to forget
to define a state or to misspell one. Let's assume you forgot the
line
#define start_I I, left, st2
Remove it and type
./herrmann1 'prg=plus1.turing'
The output will be
Undefined state start.
Tape:
Debugging
---------
*
..OOOOOOOOIIIIOO..
*
When writing long programs, the most common errors are to forget
to define a state or to misspell one. Let's assume you forgot the
line
and the return code will be 1. As you can see, the Turing machine
moved over the O's, but when it arrived at the I (still in state
start), the error occurred.
#define start_I I, left, st2
Note that other kinds of errors can cause compilation to fail.
If this happens, the build script will write the error message of
gcc into a file called "error" and then exit.
Remove it and type
### What the build script does
./herrmann1 'prg=plus1.turing'
In principle, the build script simply runs the preprocessor over
and over again, until nothing changes anymore; then, the program
is compiled. After each preprocessing step, the script outputs the
beginning of the program; this is where the animation takes place
(except in the first and last few steps).
However, there's a technical problem to overcome: Some compilers
(including gcc) insert a lot of white space during the
preprocessing. This is bad for two reasons. First, the animated
picture won't look right. And second, after some time, the file
will grow too big for your hard disk. (The amount of white space
grows exponentially.) So after each preprocessing, the first thing
the build script does is remove all the unnecessary white space
using sed.
(gcc would even insert #line directives if I didn't suppress
them with the -P option.)
### Some more details
- The arguments passed to the build script are passed on to gcc
with a "-D" before. I had a slight technical problem doing this
(especially with the spaces that can appear within the tape
argument). That made the build script a bit longer.
- It would be enough to pass the defines in the first
preprocessing. The program then "remembers" them. However, it
doesn't matter if they are passed in all preprocessings. (This
doesn't sound very impressive, but it has required some extra
lines of code.)
- During the preprocessing, intermediate files "herrmann1-1.c" and
"herrmann1-2.c" are generated.
- Also, a file "error" is generated. This file is removed after
the compilation, except if gcc produced an error; then it contains
the error message. (I can't let gcc output all its messages
directly, because the warnings would clobber the animation. I
could have made the compile script output the error file in case
of an error, but I didn't want to make it yet longer.)
The output will be
### Some technical details / obfuscations
Undefined state start.
- The program is compressed. In the first three preprocessing
passes, it uncompresses itself and initializes the
"variables". So if you want to understand how it works, the best
thing is to preprocess it three times and then look at it
again. (However, I wouldn't have wasted three passes if they
weren't needed anyway to initialize the tape in certain
cases. Find out why.)
Tape:
- The first problem I had to solve when writing this program was
to write a preprocessor program which outputs itself. Of course,
each program which doesn't contain any preprocessor directives
is such a program, but that's not what I needed. In fact, to do
this, the program has to include itself, but even then, it's not
easy.
*
..OOOOOOOOIIIIOO..
*
- The next problem to solve was how to pass results of a
calculation of one preprocessing to the next one. This is done
by outputting #defines with the result. But there's a problem
with this: it is impossible to output a "#define result xxx",
where xxx depends on the previous value of result. Try it if you
don't believe it. And find out what I did instead.
and the return code will be 1. As you can see, the Turing machine
moved over the O's, but when it arrived at the I (still in state
start), the error occurred.
- What's a useful way to store information, especially the content
of the tape? Of course, a "#define tape OOIOOIOIIIIO" contains
the information, but then, how do you access the information?
(For example, you won't be able to write preprocessor code which
replaces the first O by I.) My answer looks very much like the
way a stack would have been implemented in lambda calculus. (The
tape is implemented as two stacks, to be exact.) And still,
there were some technical problems to solve. Look at the program
for more details.
Note that other kinds of errors can cause compilation to fail.
If this happens, the build script will write the error message of
gcc into a file called "error" and then exit.
What the build script does
==========================
In principle, the build script simply runs the preprocessor over
and over again, until nothing changes anymore; then, the program
is compiled. After each preprocessing step, the script outputs the
beginning of the program; this is where the animation takes place
(except in the first and last few steps).
However, there's a technical problem to overcome: Some compilers
(including gcc) insert a lot of white space during the
preprocessing. This is bad for two reasons. First, the animated
picture won't look right. And second, after some time, the file
will grow too big for your hard disk. (The amount of white space
grows exponentially.) So after each preprocessing, the first thing
the build script does is remove all the unnecessary white space
using sed.
(gcc would even insert #line directives if I didn't suppress
them with the -P option.)
Some more details
-----------------
- The arguments passed to the build script are passed on to gcc
with a "-D" before. I had a slight technical problem doing this
(especially with the spaces that can appear within the tape
argument). That made the build script a bit longer.
- It would be enough to pass the defines in the first
preprocessing. The program then "remembers" them. However, it
doesn't matter if they are passed in all preprocessings. (This
doesn't sound very impressive, but it has required some extra
lines of code.)
- During the preprocessing, intermediate files "herrmann1-1.c" and
"herrmann1-2.c" are generated.
- Also, a file "error" is generated. This file is removed after
the compilation, except if gcc produced an error; then it contains
the error message. (I can't let gcc output all its messages
directly, because the warnings would clobber the animation. I
could have made the compile script output the error file in case
of an error, but I didn't want to make it yet longer.)
Some technical details / obfuscations
====================================
- The program is compressed. In the first three preprocessing
passes, it uncompresses itself and initializes the
"variables". So if you want to understand how it works, the best
thing is to preprocess it three times and then look at it
again. (However, I wouldn't have wasted three passes if they
weren't needed anyway to initialize the tape in certain
cases. Find out why.)
- The first problem I had to solve when writing this program was
to write a preprocessor program which outputs itself. Of course,
each program which doesn't contain any preprocessor directives
is such a program, but that's not what I needed. In fact, to do
this, the program has to include itself, but even then, it's not
easy.
- The next problem to solve was how to pass results of a
calculation of one preprocessing to the next one. This is done
by outputting #defines with the result. But there's a problem
with this: it is impossible to output a "#define result xxx",
where xxx depends on the previous value of result. Try it if you
don't believe it. And find out what I did instead.
- What's a useful way to store information, especially the content
of the tape? Of course, a "#define tape OOIOOIOIIIIO" contains
the information, but then, how do you access the information?
(For example, you won't be able to write preprocessor code which
replaces the first O by I.) My answer looks very much like the
way a stack would have been implemented in lambda calculus. (The
tape is implemented as two stacks, to be exact.) And still,
there were some technical problems to solve. Look at the program
for more details.
- There are many other interesting things to look at, e.g. the way
to access the information in the user-provided program, the way
a non-defined state is detected, the way the picture is
produced, etc. If you want a real challenge, try to understand
what the line "w j k(P ## x)" does.
- Due to the fact that our alphabet has only 53 ;-) letters, I had
to use many variables more than once.
- I couldn't refrain from doing a little bit of nasty stuff like
#define a 0&0
#if !a
Of course, it is a bit more subtle, and even useful.
- The layout of the source code..., well, let's call it the
"modern art of programming" - a mixture of program-like
indenting and modern art. There are even some comments...
Compatibility
=============
- It works with many versions of gcc. I tested it with versions
2.7.2.1, 2.8.1, 2.95.2, and ecgs-2.91.66. However, an old version
of gcc produced "internal compiler errors" (obviously not my
fault).
- The only warnings I get tell me that I'm undefining __FILE__ and
that preprocessing directives are not recognized within macro
args. My answer to this is "yes, I know this and it's all
right". With "-Wall", I also get some warnings about "/*"
inside comments (Well, _I_ like some well-placed "/*/*/".) as
well as some suggestions where to place some more parentheses
(as if I didn't know the operator precedence).
- Different versions of cc all produced only rubbish. Apparently,
the preprocessors didn't really work. (I don't feel guilty
there, either. Even with very simple programs, they produced
mysterious output.)
- For Mac-Users: It works with the C compiler of Codewarrior (an
old version). This compiler even leaves out all that unnecessary
white space, so that no clean-up is needed. (It would be useful
to write a kind of script which does the equivalent to the build
script, so that one does not have to choose "preprocess" from
the menu over and over again. I leave this as an exercise to the
experienced Mac programmer.)
- In the build script, I passed the -P option to gcc to suppress
the insertion of #line directives. If your compiler doesn't
support this option (and does insert #line directives), you
should change the call to sed to remove them, too: Add
-e "/^#line/ d"
to the sed parameters. Or, if your #line directives look like
# 15 "filename"
add
-e "/^# [0-9]/ d"
instead.
- First, I had "-ansi -pedantic" turned on, but on some systems,
the stdio.h is not compatible to that, so I turned it back off.
- The build script probably needs a bash to run in. So perhaps you
have to add "#!/bin/bash" or something like this at the
beginning.
- On SunOS, I had to remove the "-q" option from diff in the build
script.
- There are many other interesting things to look at, e.g. the way
to access the information in the user-provided program, the way
a non-defined state is detected, the way the picture is
produced, etc. If you want a real challenge, try to understand
what the line "w j k(P ## x)" does.
- Due to the fact that our alphabet has only 53 ;-) letters, I had
to use many variables more than once.
- I couldn't refrain from doing a little bit of nasty stuff like
#define a 0&0
#if !a
Of course, it is a bit more subtle, and even useful.
- The layout of the source code..., well, let's call it the
"modern art of programming" - a mixture of program-like
indenting and modern art. There are even some comments...
### Compatibility
- It works with many versions of gcc. I tested it with versions
2.7.2.1, 2.8.1, 2.95.2, and ecgs-2.91.66. However, an old version
of gcc produced "internal compiler errors" (obviously not my
fault).
- The only warnings I get tell me that I'm undefining __FILE__ and
that preprocessing directives are not recognized within macro
args. My answer to this is "yes, I know this and it's all
right". With "-Wall", I also get some warnings about "/*"
inside comments (Well, _I_ like some well-placed "/*/*/".) as
well as some suggestions where to place some more parentheses
(as if I didn't know the operator precedence).
- Different versions of cc all produced only rubbish. Apparently,
the preprocessors didn't really work. (I don't feel guilty
there, either. Even with very simple programs, they produced
mysterious output.)
- For Mac-Users: It works with the C compiler of Codewarrior (an
old version). This compiler even leaves out all that unnecessary
white space, so that no clean-up is needed. (It would be useful
to write a kind of script which does the equivalent to the build
script, so that one does not have to choose "preprocess" from
the menu over and over again. I leave this as an exercise to the
experienced Mac programmer.)
- In the build script, I passed the -P option to gcc to suppress
the insertion of #line directives. If your compiler doesn't
support this option (and does insert #line directives), you
should change the call to sed to remove them, too: Add
-e "/^#line/ d"
to the sed parameters. Or, if your #line directives look like
# 15 "filename"
add
-e "/^# [0-9]/ d"
instead.
- First, I had "-ansi -pedantic" turned on, but on some systems,
the stdio.h is not compatible to that, so I turned it back off.
- The build script probably needs a bash to run in. So perhaps you
have to add "#!/bin/bash" or something like this at the
beginning.
- On SunOS, I had to remove the "-q" option from diff in the build
script.
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env bash
name=herrmann1