Format fixes and URL fix in 1998/fanf/README.md
This commit is contained in:
1 parent
c0e237528b
commit
e9196fc245
1 file changed
+251
-216
+251
-216
@@ -1,24 +1,27 @@
|
|||||||
# Most Obfuscated Translator
|
# Most Obfuscated Translator
|
||||||
|
|
||||||
Tony Finch
|
Tony Finch
|
||||||
Demon Internet
|
Demon Internet
|
||||||
322 Regent's Park Road
|
322 Regent's Park Road
|
||||||
Finchley
|
Finchley
|
||||||
London N3 2QQ
|
London N3 2QQ
|
||||||
United Kingdom
|
United Kingdom
|
||||||
|
<https://dotat.at>
|
||||||
http://www.inch.demon.co.uk/
|
|
||||||
|
|
||||||
## To build:
|
## To build:
|
||||||
|
|
||||||
make all
|
```sh
|
||||||
|
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.
|
||||||
|
|
||||||
## To run:
|
## To run:
|
||||||
|
|
||||||
./fanf
|
```sh
|
||||||
|
./fanf
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### To use:
|
### To use:
|
||||||
@@ -26,25 +29,27 @@ this entry because of the system resources it requires.
|
|||||||
Enter an expression on standard input. Here are some
|
Enter an expression on standard input. Here are some
|
||||||
sample expressions that you can use:
|
sample expressions that you can use:
|
||||||
|
|
||||||
((\a(\b(\c(d)))) e)
|
```
|
||||||
|
((\a(\b(\c(d)))) e)
|
||||||
|
|
||||||
|
|
||||||
(\f\g\x
|
(\f\g\x
|
||||||
(
|
(
|
||||||
(g x) (f x)
|
(g x) (f x)
|
||||||
) K K z
|
) K K z
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
(\f(\f\g\x( (f((\a(g(b))) e)) (g x) ) K K z))
|
(\f(\f\g\x( (f((\a(g(b))) e)) (g x) ) K K z))
|
||||||
|
|
||||||
|
|
||||||
(Y\f\n
|
(Y\f\n
|
||||||
((= n 0)
|
((= n 0)
|
||||||
1
|
1
|
||||||
(* n (f (- n 1)))
|
(* n (f (- n 1)))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
```
|
||||||
|
|
||||||
## Judges' remarks:
|
## Judges' remarks:
|
||||||
|
|
||||||
@@ -55,19 +60,21 @@ impressed by this program!
|
|||||||
See the file [fanf.lambda](fanf.lambda) for more examples of lambda calculus
|
See the file [fanf.lambda](fanf.lambda) for more examples of lambda calculus
|
||||||
source.
|
source.
|
||||||
|
|
||||||
Notice how large the code grows from the fanf.c into the final
|
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:
|
||||||
|
|
||||||
cc fanf.c -E > fanftmp1.c
|
```sh
|
||||||
|
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 `#include` header
|
||||||
stuff ... look for a line of the form: # 2 "fanf.c" near the bottom)
|
stuff ... look for a line of the form: `# 2 "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.
|
||||||
|
|
||||||
@@ -83,8 +90,7 @@ theoretical computer science. The application it implements is a program
|
|||||||
for translating lambda expressions into combinator expressions.
|
for translating lambda expressions into combinator expressions.
|
||||||
|
|
||||||
|
|
||||||
What the program does
|
### What the program does
|
||||||
=====================
|
|
||||||
|
|
||||||
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
|
||||||
@@ -102,10 +108,12 @@ simple expression is `(I a)` which is equivalent to `(\x(x) a)`, which
|
|||||||
has the value `a`. Another function called `K` is defined as `\x\y(x)`
|
has the value `a`. Another function called `K` is defined as `\x\y(x)`
|
||||||
and it evaluates as follows:
|
and it evaluates as follows:
|
||||||
|
|
||||||
|
```
|
||||||
K p q
|
K p q
|
||||||
-> \x\y(x) p q
|
-> \x\y(x) p q
|
||||||
-> \y(p) q
|
-> \y(p) q
|
||||||
-> p
|
-> p
|
||||||
|
```
|
||||||
|
|
||||||
Another function of interest is `S`, defined as `\f\g\x((f x) (g x))`.
|
Another function of interest is `S`, defined as `\f\g\x((f x) (g x))`.
|
||||||
My program demonstrates the fact that any lambda expression can be
|
My program demonstrates the fact that any lambda expression can be
|
||||||
@@ -114,6 +122,7 @@ only `S`, `K`, `I` (the combinators), and application, with no
|
|||||||
variables or lambda abstractions. In fact, even `I` is unnecessary,
|
variables or lambda abstractions. In fact, even `I` is unnecessary,
|
||||||
since it is equivalent to `S K K`:
|
since it is equivalent to `S K K`:
|
||||||
|
|
||||||
|
```
|
||||||
I z
|
I z
|
||||||
-> \a(a) z
|
-> \a(a) z
|
||||||
-> z
|
-> z
|
||||||
@@ -123,6 +132,7 @@ since it is equivalent to `S K K`:
|
|||||||
-> (K z) (K z)
|
-> (K z) (K z)
|
||||||
-> \x\y(x) z (K z)
|
-> \x\y(x) z (K z)
|
||||||
-> z
|
-> z
|
||||||
|
```
|
||||||
|
|
||||||
The algorithm for translating lambda expressions into combinator
|
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
|
||||||
@@ -143,6 +153,7 @@ 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`
|
||||||
@@ -151,243 +162,267 @@ to see what the combinator expression for doubling 3 looked like:
|
|||||||
-> `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:
|
||||||
|
|
||||||
|
```
|
||||||
S (S (K +) I) I 3
|
S (S (K +) I) I 3
|
||||||
-> S (K +) I 3 (I 3)
|
-> S (K +) I 3 (I 3)
|
||||||
-> K + 3 (I 3) (I 3)
|
-> K + 3 (I 3) (I 3)
|
||||||
-> + (I 3) (I 3)
|
-> + (I 3) (I 3)
|
||||||
-> + 3 3
|
-> + 3 3
|
||||||
|
```
|
||||||
|
|
||||||
It is possible to perform a slightly more compact translation with a
|
It is possible to perform a slightly more compact translation with a
|
||||||
couple of simple optimisations. For example, note that `K (a b)' is the
|
couple of simple optimisations. For example, note that `K (a b)` is the
|
||||||
same as `S (K a) (K b)', because
|
same as `S (K a) (K b)`, because
|
||||||
|
|
||||||
|
```
|
||||||
K (a b) x
|
K (a b) x
|
||||||
-> a b
|
-> a b
|
||||||
|
```
|
||||||
|
|
||||||
and
|
and
|
||||||
|
|
||||||
|
```
|
||||||
S (K a) (K b) x
|
S (K a) (K b) x
|
||||||
-> K a x (K b x)
|
-> K a x (K b x)
|
||||||
-> a b
|
-> a b
|
||||||
|
```
|
||||||
|
|
||||||
Also, `S (K a) I' is the same as just `a' because
|
Also, `S (K a) I` is the same as just `a` because
|
||||||
|
|
||||||
|
```
|
||||||
S (K a) I x
|
S (K a) I x
|
||||||
-> K a x (I x)
|
-> K a x (I x)
|
||||||
-> a x
|
-> a x
|
||||||
|
```
|
||||||
|
|
||||||
The program simply reads a lambda expression from the standard input,
|
The program simply reads a lambda expression from the standard input,
|
||||||
translates it into a combinator expression using the transformation and
|
translates it into a combinator expression using the transformation and
|
||||||
optimisations described above, and prints out the result.
|
optimisations described above, and prints out the result.
|
||||||
|
|
||||||
|
|
||||||
Why this is a practical application
|
### Why this is a practical application
|
||||||
===================================
|
|
||||||
|
|
||||||
Combinator expressions are easier to evaluate than lambda expressions
|
Combinator expressions are easier to evaluate than lambda expressions
|
||||||
because there are no problems with variables. For example, if I naively
|
because there are no problems with variables. For example, if I naively
|
||||||
evaluated the expression `\x\y(x y) y' I might get `\y(y y)' which is
|
evaluated the expression `\x\y(x y) y` I might get `\y(y y)` which is
|
||||||
not the same as the correct result because the first `y' in the body
|
not the same as the correct result because the first `y` in the body
|
||||||
should refer to a global `y' rather than the local one. David Turner was
|
should refer to a global `y` rather than the local one. David Turner was
|
||||||
the first person to realise that because of this fact, SK combinators
|
the first person to realise that because of this fact, SK combinators
|
||||||
provide a simple way to implement non-strict functional programming
|
provide a simple way to implement non-strict functional programming
|
||||||
languages, either in software or directly in hardware.
|
languages, either in software or directly in hardware.
|
||||||
|
|
||||||
My program demonstrates this by being based on a combinator evaluation
|
My program demonstrates this by being based on a combinator evaluation
|
||||||
engine which is used to implement a language called "OFL" (pronounced
|
engine which is used to implement a language called `OFL` (pronounced
|
||||||
like "offal" and short for "Obfuscated Functional Language"). I then
|
like "offal" and short for "Obfuscated Functional Language"). I then
|
||||||
used OFL to implement all the high-level functionality described above.
|
used `OFL` to implement all the high-level functionality described above.
|
||||||
This contributes a lot to the obfuscation because even small amounts of
|
This contributes a lot to the obfuscation because even small amounts of
|
||||||
OFL are quite unpalatable, and large expressions are completely
|
`OFL` are quite unpalatable, and large expressions are completely
|
||||||
incomprehensible.
|
incomprehensible.
|
||||||
|
|
||||||
It is fairly well known that the lambda calculus (and hence SK
|
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 IO 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
|
||||||
required five characters to do so (viz. `Y W q Q g'). However this code
|
required five characters to do so (viz. `Y W q Q g`). However this code
|
||||||
is not enabled because the definition of Y in terms of other combinators
|
is not enabled because the definition of `Y` in terms of other combinators
|
||||||
is more cute.
|
is more cute.
|
||||||
|
|
||||||
The `E' combinator comes in a large number of forms, one for each
|
The `E` combinator comes in a large number of forms, one for each
|
||||||
character and one for end of file. It is written `E(c)' for the variant
|
character and one for end of file. It is written `E(c)` for the variant
|
||||||
of `E' corresponding to the character `c'. An expression of the form
|
of `E` corresponding to the character `c`. An expression of the form
|
||||||
`E(c) x' is evaluated by first evaluating `x'. If `x' doesn't evaluate
|
`E(c) x` is evaluated by first evaluating `x`. If `x` doesn't evaluate
|
||||||
to some form of `E', then evaluation of the expression terminates. If
|
to some form of `E`, then evaluation of the expression terminates. If
|
||||||
`x' evaluates to `E(c)', then the expression evaluates to `K'. If `x'
|
`x` evaluates to `E(c)`, then the expression evaluates to `K`. If `x`
|
||||||
evaluates to `E(d)' (where `d' is a different character to `c'), then
|
evaluates to `E(d)` (where `d` is a different character to `c`), then
|
||||||
the expression evaluates to `J'.
|
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's `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
|
||||||
obfuscated form `E('a') char then else'.
|
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.
|
||||||
P x f -> f I
|
|
||||||
with a side-effect.
|
|
||||||
|
|
||||||
The `G' combinator just reads a character from stdin then calls its
|
```
|
||||||
argument as a function with the appropriate `E' combinator as an
|
P x f -> f I
|
||||||
argument, i.e.
|
```
|
||||||
G f -> f E(c)
|
|
||||||
with a side-effect.
|
|
||||||
|
|
||||||
One of the problems with non-strict languages is that it is hard to
|
with a side-effect.
|
||||||
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
|
|
||||||
Haskell; this allows one to write programs that manipulate external
|
|
||||||
state in a manner remarkably similar to imperative programming languages
|
|
||||||
like C. An examination of my program should provide an illustration of
|
|
||||||
how well this technique works in practice. (In particular, I used the
|
|
||||||
CPS form of the IO monad from page 6 of "Imperative Functional
|
|
||||||
Programming".)
|
|
||||||
|
|
||||||
The `Y' combinator is used for implementing recursive functions. It has
|
The `G` combinator just reads a character from stdin then calls its
|
||||||
the evaluation rule
|
argument as a function with the appropriate `E` combinator as an
|
||||||
Y f -> f (Y f)
|
argument, i.e.
|
||||||
so that it can be used to call a function with itself as an argument.
|
|
||||||
For example, if I had the appropriate arithmetic operators defined, I
|
|
||||||
could write a factorial function in the lambda calculus like this:
|
|
||||||
Y \f\n((= n 0) 1 (* n (f (- n 1))))
|
|
||||||
|
|
||||||
OFL provides a facility for naming expressions so that they can be used
|
```
|
||||||
more than once without writing them in full. A definition must be written
|
G f -> f E(c)
|
||||||
on one line in the form `l name b expression' where `l' is short for
|
```
|
||||||
"let" and `b' is short for "be". Names must be more than one character
|
|
||||||
long. A complete OFL program starts off by defining the primitives it
|
|
||||||
will use with likes of the form `p (S)' etc., then goes on to define the
|
|
||||||
names it will use, and finishes off with the main expression followed by
|
|
||||||
a single `e' short for "end". OFL expressions must use the minimum
|
|
||||||
number of brackets needed to be unambiguous, except that the whole
|
|
||||||
expression must be surrounded by brackets.
|
|
||||||
|
|
||||||
OFL has a standard library which adds another five combinators to the
|
with a side-effect.
|
||||||
basic eight. These combinators provide greater scope for optimisation
|
|
||||||
when translating an expression from the lambda calculus to combinators,
|
|
||||||
and therefore more compact programs. Their evaluation rules are:
|
|
||||||
|
|
||||||
B f g x -> f (g x)
|
One of the problems with non-strict languages is that it is hard to
|
||||||
C f g x -> (f x) g
|
predict in advance in which order side-effects will occur. In my program
|
||||||
BB c f g x -> c (f (g x))
|
I used a monadic IO structure like that used by the programming language
|
||||||
CC c f g x -> c (f x) g
|
Haskell; this allows one to write programs that manipulate external
|
||||||
SS c f g x -> c (f x) (g x)
|
state in a manner remarkably similar to imperative programming languages
|
||||||
|
like C. An examination of my program should provide an illustration of
|
||||||
|
how well this technique works in practice. (In particular, I used the
|
||||||
|
CPS form of the IO monad from page 6 of "Imperative Functional
|
||||||
|
Programming".)
|
||||||
|
|
||||||
I used them to help fit my program into the required space; unfortunately
|
The `Y` combinator is used for implementing recursive functions. It has
|
||||||
I couldn't fit the version that fully optimises the lambda expressions
|
the evaluation rule
|
||||||
it reads into the required space. The complete optimisation rules
|
|
||||||
including the ones it doesn't use are:
|
|
||||||
|
|
||||||
S (K p) I -> p
|
```
|
||||||
S (K p) (K r) -> K ( p r )
|
Y f -> f (Y f)
|
||||||
S (K p) (B r s) -> BB p r s
|
```
|
||||||
S (K p) r -> B p r
|
|
||||||
S (B p q) (K r) -> CC p q r
|
so that it can be used to call a function with itself as an argument.
|
||||||
S p (K r) -> C p r
|
For example, if I had the appropriate arithmetic operators defined, I
|
||||||
S (B p q) r -> SS p q r
|
could write a factorial function in the lambda calculus like this:
|
||||||
S p r -> S p r
|
|
||||||
|
```
|
||||||
|
Y \f\n((= n 0) 1 (* n (f (- n 1))))
|
||||||
|
```
|
||||||
|
|
||||||
|
`OFL` provides a facility for naming expressions so that they can be used
|
||||||
|
more than once without writing them in full. A definition must be written
|
||||||
|
on one line in the form `l name b expression` where `l` is short for
|
||||||
|
"let" and `b` is short for "be". Names must be more than one character
|
||||||
|
long. A complete `OFL` program starts off by defining the primitives it
|
||||||
|
will use with likes of the form `p (S)` etc., then goes on to define the
|
||||||
|
names it will use, and finishes off with the main expression followed by
|
||||||
|
a single `e` short for "end". `OFL` expressions must use the minimum
|
||||||
|
number of brackets needed to be unambiguous, except that the whole
|
||||||
|
expression must be surrounded by brackets.
|
||||||
|
|
||||||
|
`OFL` has a standard library which adds another five combinators to the
|
||||||
|
basic eight. These combinators provide greater scope for optimisation
|
||||||
|
when translating an expression from the lambda calculus to combinators,
|
||||||
|
and therefore more compact programs. Their evaluation rules are:
|
||||||
|
|
||||||
|
```
|
||||||
|
B f g x -> f (g x)
|
||||||
|
C f g x -> (f x) g
|
||||||
|
BB c f g x -> c (f (g x))
|
||||||
|
CC c f g x -> c (f x) g
|
||||||
|
SS c f g x -> c (f x) (g x)
|
||||||
|
```
|
||||||
|
|
||||||
|
I used them to help fit my program into the required space; unfortunately
|
||||||
|
I couldn't fit the version that fully optimises the lambda expressions
|
||||||
|
it reads into the required space. The complete optimisation rules
|
||||||
|
including the ones it doesn't use are:
|
||||||
|
|
||||||
|
```
|
||||||
|
S (K p) I -> p
|
||||||
|
S (K p) (K r) -> K ( p r )
|
||||||
|
S (K p) (B r s) -> BB p r s
|
||||||
|
S (K p) r -> B p r
|
||||||
|
S (B p q) (K r) -> CC p q r
|
||||||
|
S p (K r) -> C p r
|
||||||
|
S (B p q) r -> SS p q r
|
||||||
|
S p r -> S p r
|
||||||
|
```
|
||||||
|
|
||||||
|
### The implementation of `OFL`
|
||||||
|
|
||||||
|
Although `OFL` has completely different syntax and semantics to C, all you
|
||||||
|
need to compile `OFL` programs is a C compiler and a header file (which in
|
||||||
|
my entry to the competition has been included in-line). The header file
|
||||||
|
implements a two-pass optimising compiler which produces a representation
|
||||||
|
of the program that is then interpreted.
|
||||||
|
|
||||||
|
The first pass of the compiler is implemented via the C preprocessor. It
|
||||||
|
implements one optimisation, namely function in-lining. Unfortunately,
|
||||||
|
this optimisation always increases object code size and execution time.
|
||||||
|
The opposite optimisation, common sub-expression elimination, (which
|
||||||
|
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
|
||||||
|
by a normal C compiler.
|
||||||
|
|
||||||
|
The second pass is performed by the initialisation phase of the C
|
||||||
|
program. It builds a data structure representing the abstract syntax
|
||||||
|
tree of the program.
|
||||||
|
|
||||||
|
The program is then executed by an interpreter. This operates by
|
||||||
|
performing manipulations on the AST that correspond to the evaluation
|
||||||
|
rules of the combinators that I described above.
|
||||||
|
|
||||||
|
The first 54 lines of the program are mostly the interpreter; the next 8
|
||||||
|
lines are pass one of the compiler; the next nine declare the `OFL`
|
||||||
|
primitives; the next five define the `OFL` standard library; and the rest
|
||||||
|
implement the application described in the first part of this document.
|
||||||
|
This part of the program is transformed into the second pass of the
|
||||||
|
compiler by the first pass of the compiler.
|
||||||
|
|
||||||
|
The first pass of the compiler relies on some magic macros that expand
|
||||||
|
in a mutually-overlapping and almost recursive way in order to
|
||||||
|
perpetrate a complete change of syntax. Interestingly enough, if I
|
||||||
|
hadn't used the same identifier for more than one thing in various
|
||||||
|
places in the program this transformation would work with old-style cpp
|
||||||
|
as well and ANSI cpp. It expands the program from about 2.2KB to about
|
||||||
|
740KB. The resulting code does some tricky stuff to build a data
|
||||||
|
structure of the same form as the code itself.
|
||||||
|
|
||||||
|
The interpreter is written in fairly straightforward C, although it is
|
||||||
|
highly compressed. The C itself is written in a fairly functional style,
|
||||||
|
in that every function consists of a single expression. In the tradition
|
||||||
|
of functional programming languages it assumes that memory is infinite
|
||||||
|
and so never frees anything. It also assumes that the C compiler
|
||||||
|
implements the tail recursion optimisation, which is probably not true.
|
||||||
|
|
||||||
|
|
||||||
The implementation of OFL
|
### References
|
||||||
=========================
|
|
||||||
|
|
||||||
Although OFL has completely different syntax and semantics to C, all you
|
I used these references in the course of writing this program and they
|
||||||
need to compile OFL programs is a C compiler and a header file (which in
|
explain some of the ideas behind it in more detail. Arthur Norman (one
|
||||||
my entry to the competition has been included in-line). The header file
|
of the authors of the first paper) did a lot to enthuse me about this
|
||||||
implements a two-pass optimising compiler which produces a representation
|
topic.
|
||||||
of the program that is then interpreted.
|
|
||||||
|
|
||||||
The first pass of the compiler is implemented via the C preprocessor. It
|
Clarke, T. J. W., et al., 1980. SKIM - the S, K, I Reduction Machine.
|
||||||
implements one optimisation, namely function in-lining. Unfortunately,
|
Proceedings of the 1980 ACM LISP Conference, pp. 128-135.
|
||||||
this optimisation always increases object code size and execution time.
|
|
||||||
The opposite optimisation, common sub-expression elimination, (which
|
|
||||||
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
|
|
||||||
by a normal C compiler.
|
|
||||||
|
|
||||||
The second pass is performed by the initialisation phase of the C
|
Curry, H. B. and Feys, R., 1958. Combinatory Logic, Vol. 1.
|
||||||
program. It builds a data structure representing the abstract syntax
|
North-Holland.
|
||||||
tree of the program.
|
|
||||||
|
|
||||||
The program is then executed by an interpreter. This operates by
|
Gordon, M. J. C., 1988. Programming Language Theory and its
|
||||||
performing manipulations on the AST that correspond to the evaluation
|
Implementation. Prentice-Hall.
|
||||||
rules of the combinators that I described above.
|
|
||||||
|
|
||||||
The first 54 lines of the program are mostly the interpreter; the next 8
|
Peterson, J, et al., 1997. Report on the Programming Language Haskell,
|
||||||
lines are pass one of the compiler; the next nine declare the OFL
|
version 1.4.
|
||||||
primitives; the next five define the OFL standard library; and the rest
|
|
||||||
implement the application described in the first part of this document.
|
|
||||||
This part of the program is transformed into the second pass of the
|
|
||||||
compiler by the first pass of the compiler.
|
|
||||||
|
|
||||||
The first pass of the compiler relies on some magic macros that expand
|
Peyton Jones, S. L., 1987. The implementation of Functional
|
||||||
in a mutually-overlapping and almost recursive way in order to
|
Programming Languages, Chapters 2, 11, 12, 16. Prentice-Hall.
|
||||||
perpetrate a complete change of syntax. Interestingly enough, if I
|
|
||||||
hadn't used the same identifier for more than one thing in various
|
|
||||||
places in the program this transformation would work with old-style cpp
|
|
||||||
as well and ANSI cpp. It expands the program from about 2.2KB to about
|
|
||||||
740KB. The resulting code does some tricky stuff to build a data
|
|
||||||
structure of the same form as the code itself.
|
|
||||||
|
|
||||||
The interpreter is written in fairly straightforward C, although it is
|
Peyton Jones, S. L., and Wadler, P., 1992. Imperative Functional
|
||||||
highly compressed. The C itself is written in a fairly functional style,
|
Programming. Proceedings of the ACM Symposium on the Principles of
|
||||||
in that every function consists of a single expression. In the tradition
|
Programming Languages 1993, pp. 71-84.
|
||||||
of functional programming languages it assumes that memory is infinite
|
|
||||||
and so never frees anything. It also assumes that the C compiler
|
|
||||||
implements the tail recursion optimisation, which is probably not true.
|
|
||||||
|
|
||||||
|
Turner, D. A., 1979. A new implementation technique for applicative
|
||||||
|
languages. Software- Practice and Experience. Vol. 9, pp. 31-49.
|
||||||
|
|
||||||
References
|
Turner, D. A., 1979. Another algorithm for bracket abstraction. Journal
|
||||||
==========
|
of Symbolic Logic. Vol. 44, no. 2, pp. 67-270.
|
||||||
|
|
||||||
I used these references in the course of writing this program and they
|
Wadler, P., 1990. Comprehending Monads. Proceedings of the ACM
|
||||||
explain some of the ideas behind it in more detail. Arthur Norman (one
|
conference on Lisp and Functional Programming, 1990.
|
||||||
of the authors of the first paper) did a lot to enthuse me about this
|
|
||||||
topic.
|
|
||||||
|
|
||||||
Clarke, T. J. W., et al., 1980. SKIM - the S, K, I Reduction Machine.
|
Wadler, P., 1995. How to Declare an Imperative. Proceedings of the ACM
|
||||||
Proceedings of the 1980 ACM LISP Conference, pp. 128-135.
|
International Logic Programming Symposium, 1995.
|
||||||
|
|
||||||
Curry, H. B. and Feys, R., 1958. Combinatory Logic, Vol. 1.
|
|
||||||
North-Holland.
|
|
||||||
|
|
||||||
Gordon, M. J. C., 1988. Programming Language Theory and its
|
|
||||||
Implementation. Prentice-Hall.
|
|
||||||
|
|
||||||
Peterson, J, et al., 1997. Report on the Programming Language Haskell,
|
|
||||||
version 1.4.
|
|
||||||
|
|
||||||
Peyton Jones, S. L., 1987. The implementation of Functional
|
|
||||||
Programming Languages, Chapters 2, 11, 12, 16. Prentice-Hall.
|
|
||||||
|
|
||||||
Peyton Jones, S. L., and Wadler, P., 1992. Imperative Functional
|
|
||||||
Programming. Proceedings of the ACM Symposium on the Principles of
|
|
||||||
Programming Languages 1993, pp. 71-84.
|
|
||||||
|
|
||||||
Turner, D. A., 1979. A new implementation technique for applicative
|
|
||||||
languages. Software- Practice and Experience. Vol. 9, pp. 31-49.
|
|
||||||
|
|
||||||
Turner, D. A., 1979. Another algorithm for bracket abstraction. Journal
|
|
||||||
of Symbolic Logic. Vol. 44, no. 2, pp. 67-270.
|
|
||||||
|
|
||||||
Wadler, P., 1990. Comprehending Monads. Proceedings of the ACM
|
|
||||||
conference on Lisp and Functional Programming, 1990.
|
|
||||||
|
|
||||||
Wadler, P., 1995. How to Declare an Imperative. Proceedings of the ACM
|
|
||||||
International Logic Programming Symposium, 1995.
|
|
||||||
|
|
||||||
## Copyright and CC BY-SA 4.0 License:
|
## Copyright and CC BY-SA 4.0 License:
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user