Fixes in 2019/lynn
Format fixes in README.md. Update .gitignore based on files being created by commands suggested in the README.md file. Any made by ghc notwithstanding as I don't have that and so can't test it.
This commit is contained in:
1 parent
7253bc16f6
commit
706d4c16a5
2 files changed
+82
-62
No files matched your search
@@ -1,3 +1,9 @@
|
||||
prog
|
||||
fib
|
||||
fib.c
|
||||
hint
|
||||
hint.c
|
||||
lol
|
||||
lol.c
|
||||
scc
|
||||
scc.c
|
||||
+76
-62
@@ -30,15 +30,15 @@ A fully functional compiler. The example prints out the 30th Fibonacci number.
|
||||
|
||||
## Author's comments:
|
||||
|
||||
### Remarks
|
||||
### Remarks:
|
||||
|
||||
A Haskell compiler. Supports a subset of Haskell more than large enough to
|
||||
self-host. Like GHC with custom language extensions:
|
||||
|
||||
* `WeDontServeYourType`: Compilation failing because of inscrutable type
|
||||
checking rules? Confused by the ever-growing mire of extensions to the type
|
||||
system? The solution is simple: no type checking. It also means no
|
||||
typeclasses. Just pass dictionaries explicitly.
|
||||
* `WeDontServeYourType`: Compilation failing because of inscrutable type
|
||||
checking rules? Confused by the ever-growing mire of extensions to the type
|
||||
system? The solution is simple: no type checking. It also means no
|
||||
typeclasses. Just pass dictionaries explicitly.
|
||||
|
||||
* `ZeroPauseGarbageCollection`: Instead of disruptive stop-the-world garbage
|
||||
collection, we only tidy up when the world stops of its own accord, that is,
|
||||
@@ -49,7 +49,7 @@ self-host. Like GHC with custom language extensions:
|
||||
|
||||
* `SyntaxForTheMasses`: See below.
|
||||
|
||||
### Demos
|
||||
### Building:
|
||||
|
||||
Build the compiler:
|
||||
|
||||
@@ -57,7 +57,9 @@ Build the compiler:
|
||||
cc -o prog prog.c
|
||||
```
|
||||
|
||||
### Fibonacci numbers
|
||||
### Demos:
|
||||
|
||||
#### Fibonacci numbers:
|
||||
|
||||
Test the compiler on `fib.hs`:
|
||||
|
||||
@@ -67,14 +69,14 @@ Test the compiler on `fib.hs`:
|
||||
|
||||
Compiling the output produces a binary that prints the 30th Fibonacci number.
|
||||
|
||||
The file `ghcfib.hs` includes `fib.hs` with some glue code, and shows GHC
|
||||
also accepts our subset of Haskell:
|
||||
The file `ghcfib.hs` includes `fib.hs` with some glue code, and shows GHC also
|
||||
accepts our subset of Haskell:
|
||||
|
||||
```sh
|
||||
ghc ghcfib.hs
|
||||
```
|
||||
|
||||
### Self-hosting compiler
|
||||
#### Self-hosting compiler:
|
||||
|
||||
To avoid spoiling this entry by revealing the original Haskell source, we
|
||||
instead provide `hint.hs`, the output of a certain stage of the compiler when
|
||||
@@ -83,25 +85,29 @@ yet is accepted by our compiler:
|
||||
|
||||
```sh
|
||||
(./prog < hint.hs ; cat prog.c) > hint.c
|
||||
make hint
|
||||
./hint
|
||||
```
|
||||
|
||||
The output program behaves like the compiler itself.
|
||||
|
||||
Unlike the original source from which it is derived, GHC fails to compile
|
||||
`hint.hs`. This is because values have been replaced with their Scott encodings
|
||||
by this stage, which messes up type-checks; we'd need equirecursive types for
|
||||
by this stage, which messes up type-checks; we'd need `equirecursive` types for
|
||||
it to work.
|
||||
|
||||
### Regexes
|
||||
#### Regexes:
|
||||
|
||||
The file `lol.hs` contains an adaptation of [Doug McIlroy's elegant code from
|
||||
"Enumerating the strings of regular
|
||||
languages"](https://www.cs.dartmouth.edu/~doug/nfa.pdf). We exercise it by
|
||||
showing the first entries of the length-ordered list of all strings consisting
|
||||
of the characters a and b that contain an even number of a's.
|
||||
of the characters `a` and `b` that contain an even number of `a`s.
|
||||
|
||||
```sh
|
||||
(./prog < lol.hs ; cat prog.c) > lol.c
|
||||
make lol
|
||||
./lol
|
||||
```
|
||||
|
||||
A GHC wrapper is provided:
|
||||
@@ -110,7 +116,7 @@ A GHC wrapper is provided:
|
||||
ghc ghclol.hs
|
||||
```
|
||||
|
||||
### Strongly-connected components
|
||||
### Strongly-connected components:
|
||||
|
||||
See `scc.hs` (and its GHC wrapper `ghcscc.hs`) for an elegant way to print the
|
||||
strongly-connected components of a graph in reverse topological order.
|
||||
@@ -120,18 +126,19 @@ strongly-connected components of a graph in reverse topological order.
|
||||
```
|
||||
|
||||
It expects the input to be in a similar format as a previous entry
|
||||
(2018 vokes). Indeed, obtain the 2018 winners, and run:
|
||||
(2018 vokes). For example:
|
||||
|
||||
```sh
|
||||
./scc < 2018/vokes/example-1.txt
|
||||
./scc < 2018/vokes/example-2.txt
|
||||
make scc
|
||||
./scc < ../../2018/vokes/example-1.txt
|
||||
./scc < ../../2018/vokes/example-2.txt
|
||||
```
|
||||
|
||||
The output should agree, though our program omits line numbers and does not
|
||||
sort entries within a line. (Also, our program only treats spaces as
|
||||
whitespace, and supports any nonspace character in a vertex name.)
|
||||
whitespace, and supports any non-space character in a vertex name.)
|
||||
|
||||
## Syntax ##
|
||||
### Syntax ###
|
||||
|
||||
Some claim Haskell syntax is frightening because braces and semicolons are
|
||||
optional. Some complain about a zoo of twisty little operators, all alike. Our
|
||||
@@ -155,7 +162,7 @@ Within global scope or a let expression, each definition can only refer to
|
||||
itself or previous definitions. This implies we can only achieve mutual
|
||||
recursion by having one function pass itself to others.
|
||||
|
||||
## Caveats ##
|
||||
### Caveats ###
|
||||
|
||||
The only primitive type is `Int`. In particular, they represent characters. To
|
||||
interoperate with GHC, our compiler treats any undefined functions as the
|
||||
@@ -169,7 +176,9 @@ function, and the result is printed to standard output.
|
||||
|
||||
Bool must be defined as:
|
||||
|
||||
data Bool = True | False;
|
||||
```
|
||||
data Bool = True | False;
|
||||
```
|
||||
|
||||
so that it matches the Scott-encoded booleans internally used by the primitive
|
||||
function `(<=)`.
|
||||
@@ -177,15 +186,19 @@ function `(<=)`.
|
||||
The alternatives in a case expressions must list every data constructor in
|
||||
the order they are defined. For example, if we have:
|
||||
|
||||
data Foo a b = Bar a | Baz | Qux Int [b]
|
||||
```
|
||||
data Foo a b = Bar a | Baz | Qux Int [b]
|
||||
```
|
||||
|
||||
then a case expression that examines a term of this type must have the form:
|
||||
|
||||
case x of
|
||||
{ Bar a -> ...
|
||||
; Baz -> ...
|
||||
; Qux n bs -> ...
|
||||
}
|
||||
```
|
||||
case x of
|
||||
{ Bar a -> ...
|
||||
; Baz -> ...
|
||||
; Qux n bs -> ...
|
||||
}
|
||||
```
|
||||
|
||||
We stress braces and semicolons are required. Our fussy parser treats
|
||||
semicolons as separators, not terminators.
|
||||
@@ -194,7 +207,7 @@ The effect of erroneous input is undefined. It may be best to develop with GHC,
|
||||
but even then, be mindful of changes needed because of issues caused by the
|
||||
uniform operator precedence and the touchy format of case alternatives.
|
||||
|
||||
## Why? ##
|
||||
### Why? ###
|
||||
|
||||
One-letter variable names abound in IOCCC entries, and for good reason. These
|
||||
tiny pieces of confetti are hard to read, and leave room for more code. Then
|
||||
@@ -210,61 +223,62 @@ or rather, a compiler that accepts a subset of Haskell sufficiently large to
|
||||
self-host. You might say I wrote a tool for this contest, then ran it on itself
|
||||
to make an entry for it.
|
||||
|
||||
### Obfuscation techniques
|
||||
### Obfuscation techniques:
|
||||
|
||||
Even with Kiselyov's algorithm and some term rewriting, the compiler only fit
|
||||
after compression, which naturally obfuscates the code. More tricks were needed
|
||||
to fit within the size limits.
|
||||
|
||||
* One-letter variables, until enough is done to banish variables completely.
|
||||
* Ipse dixit. Near the end, the code declares itself to be an obfuscated
|
||||
program. (There is also an exhortation intended for the judges in a similar
|
||||
format earlier in the source.)
|
||||
* Typical C mischief: pre- and post-increment, commutative array indexing,
|
||||
ternary operators, and so on.
|
||||
* Huffman coding.
|
||||
* Base-85 because high bits are frowned upon.
|
||||
* Mixed radix encoding to game iocccsize. From a past winner (2018 bellard),
|
||||
it seems 9 11 12 32 are the only whitespace octets that may appear verbatim in
|
||||
string literals.
|
||||
* Choosing what to encode in Huffman/base-85 and what to encode in mixed radix
|
||||
was a delicate balancing act. In the end, I only had a few bytes to spare,
|
||||
which I spent on gratuitous confusion.
|
||||
* The effects of some functions depend on the order their arguments are
|
||||
evaluated, yet the program works either way. Why?
|
||||
* Ugly macros for the runtime system's jump table for lazy reduction. A
|
||||
previous winner (2013 endoh1) has a cuter solution, which I avoid because of
|
||||
originality concerns and also because my combinators are compressed.
|
||||
* Relies on the inability of C comments to nest.
|
||||
* Cheaper and more complicated to print comma-separated ints (and header and
|
||||
footer) in C.
|
||||
* Primitive functions use a trick described in depth by [Naylor and Runciman,
|
||||
* One-letter variables, until enough is done to banish variables completely.
|
||||
* Ipse dixit. Near the end, the code declares itself to be an obfuscated
|
||||
program. (There is also an exhortation intended for the judges in a similar
|
||||
format earlier in the source.)
|
||||
* Typical C mischief: pre- and post-increment, commutative array indexing,
|
||||
ternary operators, and so on.
|
||||
* Huffman coding.
|
||||
* Base-85 because high bits are frowned upon.
|
||||
* Mixed radix encoding to game iocccsize. From a past winner
|
||||
([2018/bellard](/2018/bellard/prog.c)), it seems 9 11 12 32 are the only
|
||||
whitespace octets that may appear verbatim in string literals.
|
||||
* Choosing what to encode in Huffman/base-85 and what to encode in mixed radix
|
||||
was a delicate balancing act. In the end, I only had a few bytes to spare, which
|
||||
I spent on gratuitous confusion.
|
||||
* The effects of some functions depend on the order their arguments are
|
||||
evaluated, yet the program works either way. Why?
|
||||
* Ugly macros for the runtime system's jump table for lazy reduction. A previous
|
||||
winner ([2013/endoh1](/2013/endoh1/endoh1.c)) has a cuter solution, which I
|
||||
avoid because of originality concerns and also because my combinators are
|
||||
compressed.
|
||||
* Relies on the inability of C comments to nest.
|
||||
* Cheaper and more complicated to print comma-separated ints (and header and
|
||||
footer) in C.
|
||||
* Primitive functions use a trick described in depth by [Naylor and Runciman,
|
||||
"The Reduceron reconfigured and
|
||||
re-evaluated"](https://www.cs.york.ac.uk/fp/reduceron/jfp-reduceron.pdf). We
|
||||
represent the integer n with a term equivalent to `Y(BT)n`; it works because
|
||||
`Y(BT)ne = e(Y(BT)n)`.
|
||||
* A sentinel in the heap often confused me, so ought to confuse others.
|
||||
* A sentinel in the heap often confused me, so ought to confuse others.
|
||||
|
||||
Other obfuscation techniques are better appreciated after decoding the
|
||||
compiler. See `hint.hs`.
|
||||
compiler. See [hint.hs](hint.hs).
|
||||
|
||||
* Mercilessly point-free. Everything is a combinator.
|
||||
* Scott encoding. Everything is a combinator.
|
||||
* Sum types are oddly ordered and may even contain unused data constructors
|
||||
to reduce code duplication. (In C terms, unions may have extra fields, and
|
||||
they're ordered in such a way so we can reuse code to access certain fields.)
|
||||
* Sum types are oddly ordered and may even contain unused data constructors to
|
||||
reduce code duplication. (In C terms, unions may have extra fields, and they're
|
||||
ordered in such a way so we can reuse code to access certain fields.)
|
||||
* The `undefined` function compiles to the `(.)` function.
|
||||
* Replaced the only `(&&)` with `(||)` and `not` using De Morgan's law, which
|
||||
makes some comparisons less comprehensible.
|
||||
* [Kiselyov only published "λ to SKI, Semantically" last
|
||||
year](http://okmij.org/ftp/tagless-final/ski.pdf).
|
||||
year](http://okmij.org/ftp/tagless-final/ski.pdf).
|
||||
* Recursion via the fixpoint (Y) combinator, which is represented by the
|
||||
variable "" during one stage of compilation.
|
||||
variable `""` during one stage of compilation.
|
||||
* Involves theory that may be less familiar to C programmers: maps, folds,
|
||||
parser combinators, lambda calculus, bracket abstraction, denotational
|
||||
semantics, etc.
|
||||
|
||||
### Warnings
|
||||
### Warnings:
|
||||
|
||||
On my system, it compiles cleanly with `-Wall` with older standards, e.g.
|
||||
`-std=c89`, but less cleanly if `-pedantic` is also supplied.
|
||||
@@ -272,10 +286,10 @@ On my system, it compiles cleanly with `-Wall` with older standards, e.g.
|
||||
Compiling the compiler output with `-Wall` triggers a warning about a
|
||||
strange-looking comment.
|
||||
|
||||
### Behind-the-scenes commentary
|
||||
### Behind-the-scenes commentary:
|
||||
|
||||
[My website reveals how this compiler
|
||||
works.](https://crypto.stanford.edu/~blynn/compiler/ioccc.html)
|
||||
works](https://crypto.stanford.edu/~blynn/compiler/ioccc.html).
|
||||
|
||||
## Copyright and CC BY-SA 4.0 License:
|
||||
|
||||
|
||||
Reference in new issue
Block a user