Updates and fixes to 1998/fanf

The try.sh script has been added to run the various inputs the judges
recommended one try.

The Makefile now can do the intermediate steps. This is possible with
the way I fixed the entry to compile. If you look at the original code
it's different than the fixed version which is post processing (if I
recall correctly).

Updated .gitignore.

Unless there's a mistake in markdown formatting this should complete
1998/fanf's review.
This commit is contained in:
Cody Boone Ferguson committed 2023-11-12 08:19:54 -08:00
1 parent effb1a9820
commit aaa5209905
5 files changed
+100 -56

No files matched your search

+2
View File
@@ -1,5 +1,7 @@
fanf fanf
fanftmp1.c fanftmp1.c
fanftmp1
fanftmp2.c fanftmp2.c
fanftmp2
fanf.orig fanf.orig
prog.orig prog.orig
+4 -2
View File
@@ -130,8 +130,10 @@ all: data ${TARGET}
sandwich supernova deep_magic magic charon pluto sandwich supernova deep_magic magic charon pluto
${PROG}: ${PROG}.c ${PROG}: ${PROG}.c
@echo "This next step may take some time to complete:" @echo "This may take some time to complete:"
${CC} ${CFLAGS} ${PROG}.c ${LDFLAGS} -o $@ ${CC} ${PROG}.c -E > ${PROG}tmp1.c
${CC} ${PROG}tmp1.c -E > ${PROG}tmp2.c
${CC} ${CFLAGS} ${PROG}tmp2.c ${LDFLAGS} -o $@
# alternative executable # alternative executable
# #
+46 -51
View File
@@ -4,7 +4,7 @@
make all make all
``` ```
NOTE: This may take a while. Some systems may have problems building\ NOTE: this may take a while. Some systems may have problems building
this entry because of the system resources it requires. this entry because of the system resources it requires.
@@ -15,36 +15,26 @@ this entry because of the system resources it requires.
``` ```
### To use: ### Try:
Enter an expression on standard input. Here are some Enter an expression on standard input. To try some we have selected:
sample expressions that you can use:
``` ```sh
((\a(\b(\c(d)))) e) ./try.sh
(\f\g\x
(
(g x) (f x)
) K K z
)
(\f(\f\g\x( (f((\a(g(b))) e)) (g x) ) K K z))
(Y\f\n
((= n 0)
1
(* n (f (- n 1)))
)
)
``` ```
## Judges' remarks: ## Judges' remarks:
### Historical aside:
At a time the code in [fanf.c](fanf.c) was that of [fanf.orig.c](fanf.orig.c)
but to get this to compile in modern systems it had to be translated to what you
now see. The intermediate steps can still be performed but they might be
different from the past. This should be kept in mind as you read the below
remarks.
This program translates lambda expressions into combinator This program translates lambda expressions into combinator
expressions. But you do not need to know Lambda Calculus to be expressions. But you do not need to know Lambda Calculus to be
impressed by this program! impressed by this program!
@@ -56,20 +46,21 @@ Notice how large the code grows from the [fanf.c](fanf.c) into the final
`fanftmp2.c` C program. Take a look at that final C program again, `fanftmp2.c` C program. Take a look at that final C program again,
can you begin to understand what it is doing? can you begin to understand what it is doing?
Look at the first stage of the C pre-processing: Look at the first stage of the C pre-processing which was done like:
```sh ```sh
cc fanf.c -E > fanftmp1.c cc fanf.c -E > fanftmp1.c
``` ```
Skip to the bottom of `fanftmp1.c` (after all of the `#include` header Skip to the bottom of `fanftmp1.c` (after all of the C pre-processor header
stuff ... look for a line of the form: `# 2 "fanf.c"` near the bottom) stuff ... look for a line of the form: `# 38 "fanf.c"` near the bottom)
and look at the resulting code. This code, when C pre-processed and look at the resulting code. This code, when C pre-processed
will expand into over 342 times the original code (ignoring `#include` will expand into over 342 times the original code (ignoring `#include`
headers) to produce `fanftmp2.c`. That program in turn, when compiled headers) to produce `fanftmp2.c`. That program in turn, when compiled
translates a single lambda expression on standard input into translates a single lambda expression on standard input into
combinator expressions on standard output. combinator expressions on standard output.
### Extra credit question: ### Extra credit question:
What do you have to do to make this program work with an old non-ANSI C What do you have to do to make this program work with an old non-ANSI C
@@ -88,7 +79,7 @@ for translating lambda expressions into combinator expressions.
The syntax of lambda expressions recognised by the program is as The syntax of lambda expressions recognised by the program is as
follows: The basic atoms are variables which are single characters other follows: The basic atoms are variables which are single characters other
than backslash or parentheses, e.g. `a` or `b` or `@`. Variables are than backslash or parentheses, e.g. `a` or `b` or `@`. Variables are
combined by function application which is written e.g. `(f a)` which is combined by function application which is written as e.g. `(f a)` which is
the function `f` applied to the argument `a`. Application groups to the the function `f` applied to the argument `a`. Application groups to the
left, so `(f a b c)` is equivalent to `(((f a) b) c)`. Functions are left, so `(f a b c)` is equivalent to `(((f a) b) c)`. Functions are
created from lambda terms which are written in the form `\a(expr)` which created from lambda terms which are written in the form `\a(expr)` which
@@ -131,30 +122,34 @@ The algorithm for translating lambda expressions into combinator
expressions works as follows. There are three forms of lambda expression expressions works as follows. There are three forms of lambda expression
to consider: variables, applications, and abstractions. to consider: variables, applications, and abstractions.
trans `v` -> `v` (variable) ```
trans `a b` -> (trans `a`) (trans `b`) (application) trans v -> v (variable)
trans `\ab` -> abs a (trans `b`) (abstraction) trans a b -> (trans a) (trans b ) (application)
trans \ab -> abs a (trans b) (abstraction)
```
There are a further three cases to consider for the body of lambda There are a further three cases to consider for the body of lambda
expressions, where we need to do the magic that transforms away the expressions, where we need to do the magic that transforms away the
variables. variables.
abs a `f x` -> `S` (abs a `f`) (abs a `x`) ```
abs a `b` -> `K b` (b != a) abs a f x -> S (abs a f) (abs a x)
abs a `a` -> `I` abs a b -> K b (b != a)
abs a a -> I
```
E.g. suppose we had combinator expressions for `+` and `3` and we wanted E.g. suppose we had combinator expressions for `+` and `3` and we wanted
to see what the combinator expression for doubling 3 looked like: to see what the combinator expression for doubling 3 looked like:
``` ```
trans `\x(+ x x) 3` trans \x(+ x x) 3
-> (trans `\x(+ x x)`) (trans `3`) -> (trans \x(+ x x)) (trans 3)
-> (abs x (trans `+ x x`)) `3` -> (abs x (trans + x x)) 3
-> (abs x `+ x x`) `3` -> (abs x + x x) 3
-> `S` (abs x `+ x`) (abs x `x`) `3` -> S (abs x + x) (abs x x) 3
-> `S` (`S` (abs x `+`) (abs x `x`)) `I` `3` -> S (S (abs x +) (abs x x)) I 3
-> `S` (`S` (`K +`) `I`) `I` `3` -> S (S (K +) I) I 3
-> `S (S (K +) I) I 3` -> S (S (K +) I) I 3
``` ```
We can then check that this evaluates to the expected result: We can then check that this evaluates to the expected result:
@@ -176,7 +171,7 @@ same as `S (K a) (K b)`, because
-> a b -> a b
``` ```
and and
``` ```
S (K a) (K b) x S (K a) (K b) x
@@ -220,7 +215,7 @@ It is fairly well known that the lambda calculus (and hence SK
combinators) can compute anything, but mere computation is no use if you combinators) can compute anything, but mere computation is no use if you
cannot communicate with the world. This is why `OFL` includes a few cannot communicate with the world. This is why `OFL` includes a few
concessions to reality: a combinator `E` for representing characters and concessions to reality: a combinator `E` for representing characters and
testing them for equality, two IO combinators, `G` and `P`, for reading testing them for equality, two I/O combinators, `G` and `P`, for reading
and writing characters respectively, and a combinator `J` for and writing characters respectively, and a combinator `J` for
representing `false`. I also implemented the `Y` combinator directly representing `false`. I also implemented the `Y` combinator directly
rather than in terms of the primitive combinators, since it only rather than in terms of the primitive combinators, since it only
@@ -239,7 +234,7 @@ the expression evaluates to `J`.
`K` and `J` are used to represent `true` and `false` respectively; they `K` and `J` are used to represent `true` and `false` respectively; they
correspond exactly to the standard lambda calculus representations of correspond exactly to the standard lambda calculus representations of
true and false, viz. `\t\f(t)` and `\t\f(f)` respectively. In this way a `true` and `false`, viz. `\t\f(t)` and `\t\f(f)` respectively. In this way a
conditional expression can just be written `cond then else`, which is conditional expression can just be written `cond then else`, which is
even more terse than C `cond ? then : else`. An expression that even more terse than C `cond ? then : else`. An expression that
compares a character with `a`, say, can be written in the rather compares a character with `a`, say, can be written in the rather
@@ -247,7 +242,7 @@ obfuscated form `E('a') char then else`.
The `P` combinator, like the `E` combinator, starts off by evaluating The `P` combinator, like the `E` combinator, starts off by evaluating
its first argument and checking that it is some form of `E`. It then its first argument and checking that it is some form of `E`. It then
writes that character on stdout, and finally it calls its second writes that character on `stdout`, and finally it calls its second
argument as a function with the argument `I`, i.e. argument as a function with the argument `I`, i.e.
``` ```
@@ -256,7 +251,7 @@ P x f -> f I
with a side-effect. with a side-effect.
The `G` combinator just reads a character from stdin then calls its The `G` combinator just reads a character from `stdin` then calls its
argument as a function with the appropriate `E` combinator as an argument as a function with the appropriate `E` combinator as an
argument, i.e. argument, i.e.
@@ -268,12 +263,12 @@ with a side-effect.
One of the problems with non-strict languages is that it is hard to One of the problems with non-strict languages is that it is hard to
predict in advance in which order side-effects will occur. In my program predict in advance in which order side-effects will occur. In my program
I used a monadic IO structure like that used by the programming language I used a monadic I/O structure like that used by the programming language
Haskell; this allows one to write programs that manipulate external Haskell; this allows one to write programs that manipulate external
state in a manner remarkably similar to imperative programming languages state in a manner remarkably similar to imperative programming languages
like C. An examination of my program should provide an illustration of like C. An examination of my program should provide an illustration of
how well this technique works in practice. (In particular, I used the how well this technique works in practice. (In particular, I used the
CPS form of the IO monad from page 6 of "Imperative Functional CPS form of the I/O monad from page 6 of "Imperative Functional
Programming".) Programming".)
The `Y` combinator is used for implementing recursive functions. It has The `Y` combinator is used for implementing recursive functions. It has
@@ -342,8 +337,8 @@ of the program that is then interpreted.
The first pass of the compiler is implemented via the C preprocessor. It The first pass of the compiler is implemented via the C preprocessor. It
implements one optimisation, namely function in-lining. Unfortunately, implements one optimisation, namely function in-lining. Unfortunately,
this optimisation always increases object code size and execution time. this optimisation always increases object code size and execution time.
The opposite optimisation, common sub-expression elimination, (which The opposite optimisation, common sub-expression elimination (which
would offer improvements in code size and execution time) has not been would offer improvements in code size and execution time), has not been
implemented. The output of this pass is a C program that may be compiled implemented. The output of this pass is a C program that may be compiled
by a normal C compiler. by a normal C compiler.
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
make all || exit 1
echo "$ echo '((\a(\b(\c(d)))) e' | ./fanf" 1>&2
echo '((\a(\b(\c(d)))) e)' | ./fanf
echo 1>&2
echo "$ echo '
(\f\g\x
(
(g x) (f x)
) K K z
)'|./fanf" 1>&2
echo '
(\f\g\x
(
(g x) (f x)
) K K z
)'|./fanf
echo 1>&2
echo "$ echo '(\f(\f\g\x( (f((\a(g(b))) e)) (g x) ) K K z))' | ./fanf" 1>&2
echo '(\f(\f\g\x( (f((\a(g(b))) e)) (g x) ) K K z))' | ./fanf
echo 1>&2
echo "$ echo '
(Y\f\n
((= n 0)
1
(* n (f (- n 1)))
)
)' | ./fanf" 1>&2
echo '(Y\f\n
((= n 0)
1
(* n (f (- n 1)))
)
)' | ./fanf
+7 -3
View File
@@ -2050,9 +2050,13 @@ exiting the program.
## [1998/fanf](1998/fanf/fanf.c) ([README.md](1998/fanf/README.md)) ## [1998/fanf](1998/fanf/fanf.c) ([README.md](1998/fanf/README.md))
Cody fixed this for Linux. The problem was the intermediate steps to get to the Cody fixed this to compile. The problem was the intermediate steps to get to the
final code that is compiled. The entry itself is what was essentially what used final code that is compiled. The code is now what it essentially becomes when
to be compiled. Now it works with both Linux and macOS (and BSD?). processed completely. The intermediate steps can now be performed to see how it
expands but it can still compile and be used.
Cody also added the [try.sh](1998/fanf/try.sh) script to show the output of some
of the expressions that we selected.
## [1998/schnitzi](1998/schnitzi/schnitzi.c) ([README.md](1998/schnitzi/README.md])) ## [1998/schnitzi](1998/schnitzi/schnitzi.c) ([README.md](1998/schnitzi/README.md]))