Format and typo fixes in 2019/adamovsky/README.md
This commit is contained in:
1 parent
0c543beb0f
commit
e0a9b41a32
1 file changed
+128
-121
+128
-121
@@ -1,6 +1,6 @@
|
||||
# Most functional interpreter
|
||||
|
||||
Ondřej Adamovský <oa@cmail.cz>
|
||||
Ondřej Adamovský <oa@cmail.cz>
|
||||
|
||||
## To build:
|
||||
|
||||
@@ -29,9 +29,9 @@ make
|
||||
Even after pre-processing and indenting, the C code of this entry
|
||||
is about as understandable as the Unlambda code.
|
||||
|
||||
Pretending that we don't know a bit of functional programming :) and lacking a better way
|
||||
to understand the entry but to fuzz it,
|
||||
we stumbled on a string of bytes which crashed it:
|
||||
Pretending that we don't know a bit of functional programming :) and lacking a
|
||||
better way to understand the entry but to fuzz it, we stumbled on a string of
|
||||
bytes which crashed it:
|
||||
|
||||
```
|
||||
````.0`.0`.0`c.0``sssss
|
||||
@@ -43,38 +43,40 @@ Functional programming is not a panacea against core dumps, after all.
|
||||
|
||||
**»A highly structured state state machine«**
|
||||
|
||||
### Purpose
|
||||
### Purpose:
|
||||
|
||||
The purpose of this program is to allow you to play Colossal Cave Adventure as
|
||||
implemented by [Kunihiko Sakamoto][1]. As that was written in [Unlambda programming
|
||||
language][2] designed by David Alexander Madore it has to be an Unlambda interpreter.
|
||||
|
||||
### Aesthetics
|
||||
### Aesthetics:
|
||||
|
||||
The program grew a bit too long so I had to use several macros to downsize it.
|
||||
That unfortunately reduced my options for program layout. I decided to separate
|
||||
it into two portions. The initial macro section where I had limited resources and
|
||||
you should disregard its shape, unless you like it, and the rest is a separator in the
|
||||
form of a single line followed by a section signifying the function of the program,
|
||||
a block with removed shape of lambda character. Even the official IOCCC size tool
|
||||
acknowledges this: When you call `./iocccsize -i <prog.c`, using the
|
||||
[2019 version of iocccsize](https://www.ioccc.org/2019/iocccsize.c),
|
||||
iocccsize returns **955**, which is the Unicode codepoint of the **λ** character.
|
||||
it into two portions. The initial macro section where I had limited resources
|
||||
and you should disregard its shape, unless you like it, and the rest is a
|
||||
separator in the form of a single line followed by a section signifying the
|
||||
function of the program, a block with removed shape of lambda character. Even
|
||||
the official IOCCC size tool acknowledges this: When you call `./iocccsize -i
|
||||
<prog.c`, using the [2019 version of
|
||||
iocccsize](https://www.ioccc.org/2019/iocccsize.c), iocccsize returns **955**,
|
||||
which is the Unicode codepoint of the **λ** character.
|
||||
|
||||
It might be a surprise for you that according to the IOCCC size tool the program is
|
||||
actually a *oneliner*. It certainly was for me. I like to think the tool is overwhelmed
|
||||
actually a `one-liner`. It certainly was for me. I like to think the tool is overwhelmed
|
||||
by the sheer length of the dividing line and totally overlooks the rest of the program.
|
||||
|
||||
### Usage
|
||||
### Usage:
|
||||
|
||||
When built directly
|
||||
When built directly using
|
||||
|
||||
```sh
|
||||
make
|
||||
```
|
||||
|
||||
the program (*prog*) accepts a single parameter with the name of an Unlambda program. You can
|
||||
download the [Colossal Cave Adventure][1] (the *advent.unl* file) and run it like this
|
||||
the program (`prog`) accepts a single parameter with the name of an Unlambda program. You can
|
||||
download the [Colossal Cave Adventure][1] (the [advent.unl](advent.unl) file)
|
||||
and run it like this:
|
||||
|
||||
```sh
|
||||
./prog advent.unl
|
||||
@@ -84,24 +86,24 @@ You can also try other programs from the Web. Many example programs are in the o
|
||||
[Unlambda distribution][3] on the Unlambda homepage. The most complex programs there are
|
||||
entries from [quine contest][4].
|
||||
|
||||
There is an alternative build path that requires IOCCC size tool to complete. Put the tool
|
||||
source *iocccsize.c* in the project directory and call
|
||||
There is an alternative build path that requires the 2018 IOCCC size tool to complete. Put the tool
|
||||
source [iocccsize_2018.c](iocccsize_2018.c) in the project directory and call
|
||||
|
||||
```sh
|
||||
make identify
|
||||
```
|
||||
|
||||
It will build the tool and use it to build an alternative program *prog2*, which in turn will
|
||||
produce an Unlambda program *identify.unl*. `make identify` runs this program to identify
|
||||
It will build the tool and use it to build an alternative program `prog2`, which in turn will
|
||||
produce an Unlambda program `identify.unl`. `make identify` runs this program to identify
|
||||
itself.
|
||||
|
||||
The base program (*prog*) implements an interpreter of Unlambda 2.0. It recognises all its
|
||||
builtins (k, s, i, v, c, d, .*x*, e, @, ?*x* and |) including the syntactic sugar function
|
||||
(​r​) and formating properties in the form of ignoring whitespace and comments
|
||||
to the end of line introduced by the *#* character.
|
||||
The base program (`prog`) implements an interpreter of Unlambda 2.0. It recognises all its
|
||||
builtins (`k`, `s`, `i`, `v`, `c`, `d`, `.x`, `e`, `@`, `?x` and `|`) including the syntactic sugar function
|
||||
`r` and formatting properties in the form of ignoring whitespace and comments
|
||||
to the end of line introduced by the `#` character.
|
||||
|
||||
In case of wrong usage (missing Unlambda program file or missing parameter) or use of an unseekable
|
||||
Unlambda program source if it uses the *c* function, the program returns message *fail*. It
|
||||
Unlambda program source if it uses the `c` function, the program returns message `fail`. It
|
||||
also informs you about syntactic errors in the Unlambda program should there be any (but only
|
||||
when the execution reaches them).
|
||||
|
||||
@@ -111,37 +113,37 @@ them all in sequence.
|
||||
### Unlambda
|
||||
|
||||
If you want to familiarise yourself with the language, visit its [Wikipedia page][5] for
|
||||
a brief introduction or its [homepage][2] for an indepth discussion. For further reading you
|
||||
a brief introduction or its [homepage][2] for an in depth discussion. For further reading you
|
||||
can visit Wikipedia pages on [Lambda Calculus][6] and [Combinatory Logic][7].
|
||||
|
||||
You can also play with an [online interpreter][8] by Github user inazz. There you can
|
||||
You can also play with an [online interpreter][8] by GitHub user inazz. There you can
|
||||
debug one of example programs provided or try your wits in writing your own.
|
||||
|
||||
### Hints
|
||||
### Hints:
|
||||
|
||||
Here are some pointers where to start when trying to understand the interpreter:
|
||||
|
||||
- Where is syntax error detected?
|
||||
- How is the memory managed?
|
||||
- How does a program character turn into a function?
|
||||
- How does exit function *e* work?
|
||||
- Can fD0 function be called?
|
||||
- Are the function names familiar? (warning: some are misleading)
|
||||
- Where is syntax error detected?
|
||||
- How is the memory managed?
|
||||
- How does a program character turn into a function?
|
||||
- How does exit function *e* work?
|
||||
- Can fD0 function be called?
|
||||
- Are the function names familiar? (warning: some are misleading)
|
||||
|
||||
### Obfuscation sources
|
||||
|
||||
- The elephant in the room is the Unlambda language. As we all know, functional
|
||||
programming defies sanity. Furthermore, Unlambda was specifically designed
|
||||
to be unintelligible and hard to code in. Most of its functions are quite
|
||||
simple and straightforward to implement, but two make it a mess: the promise
|
||||
operator *d* and the call with current continuation *c*.
|
||||
- Most of the additional obfuscation is derived from basic design choices. That
|
||||
is, how the state of the program is stored and how are the Unlambda functions
|
||||
implemented.
|
||||
- The elephant in the room is the Unlambda language. As we all know, functional
|
||||
programming defies sanity. Furthermore, Unlambda was specifically designed to be
|
||||
unintelligible and hard to code in. Most of its functions are quite simple and
|
||||
straightforward to implement, but two make it a mess: the promise operator `d`
|
||||
and the call with current continuation `c`.
|
||||
- Most of the additional obfuscation is derived from basic design choices. That
|
||||
is, how the state of the program is stored and how are the Unlambda functions
|
||||
implemented.
|
||||
|
||||
More on this in the Spoilers section.
|
||||
More on this in the **Spoilers** section.
|
||||
|
||||
### Build notes
|
||||
### Build notes:
|
||||
|
||||
The program does not require any special treatment by compilers. Some warnings
|
||||
had to be suppressed when compiling with `-Wall -Wextra -pedantic`. Those were
|
||||
@@ -150,105 +152,110 @@ technique. Compilation with `-Weverything` produced many more warnings, but thos
|
||||
are only relevant for multi-source-file projects and some are really questionable
|
||||
(e.g. `-Wdisabled-macro-expansion`).
|
||||
|
||||
### Portability
|
||||
### Portability:
|
||||
|
||||
Development was done on Debian 9 on x86-64 architecture using mainly Clang 3.8.1-24
|
||||
and GCC 6.3.0. The source is C11 compliant and does not use any special properties
|
||||
except the following:
|
||||
|
||||
- The interpreter sort of relies on tail call optimization. It works well even
|
||||
without it, but the stack runs out quickly with some Unlambda programs (e.g. *trivial.unl*
|
||||
example from the [reference Unlambda distribution][3] produces segfault).
|
||||
Clang with `-O3` option does this optimization well and said example does
|
||||
not segfault with it. I was not able to force GCC to optimize it out (probably
|
||||
an operator fault).
|
||||
- The only data structure of the program (the union structure) had much more
|
||||
members originally. I joined all integers to a single long long member in the name
|
||||
of obfuscation. It is used for reference counting (only 1 for every reference) among
|
||||
other uses. size_t should be used for that, but practically, I think the memory
|
||||
would run out sooner than the reference counter in any real world situation
|
||||
even when size_t is bigger than long long. It is also used for storing chars
|
||||
for later printing. This might produce some warnings, but since it was developed
|
||||
using chars it should be always used safely.
|
||||
- The interpreter sort of relies on tail call optimization. It works well even
|
||||
without it, but the stack runs out quickly with some Unlambda programs (e.g.
|
||||
`trivial.unl` example from the [reference Unlambda distribution][3] produces
|
||||
segfault). Clang with `-O3` option does this optimization well and said example
|
||||
does not segfault with it. I was not able to force GCC to optimize it out
|
||||
(probably an operator fault).
|
||||
- The only data structure of the program (the union structure) had much more
|
||||
members originally. I joined all integers to a single long long member in the
|
||||
name of obfuscation. It is used for reference counting (only 1 for every
|
||||
reference) among other uses. `size_t` should be used for that, but practically,
|
||||
I think the memory would run out sooner than the reference counter in any real
|
||||
world situation even when `size_t` is bigger than `long long`. It is also used
|
||||
for storing chars for later printing. This might produce some warnings, but
|
||||
since it was developed using chars it should be always used safely.
|
||||
|
||||
### Spoilers
|
||||
### Spoilers:
|
||||
|
||||
- You can examine the *progSpoiler.c* file. It is the original final version of
|
||||
the interpreter, before I started the downsizing. It accepts one more optional
|
||||
parameter that specifies filename for the output Unlambda function (it's the
|
||||
Unlambda function, that remains after all function applications are done).
|
||||
When no filename is provided, *stderr* is used.
|
||||
- You can examine the [progSpoiler.c](progSpoiler.c) file. It is the original
|
||||
final version of the interpreter, before I started the downsizing. It accepts
|
||||
one more optional parameter that specifies filename for the output Unlambda
|
||||
function (it's the Unlambda function, that remains after all function
|
||||
applications are done). When no filename is provided, `stderr` is used.
|
||||
|
||||
The function *output* is used for this additional functionality. You can use
|
||||
it while debugging to visualise current variables.
|
||||
The function *output* is used for this additional functionality. You can use
|
||||
it while debugging to visualise current variables.
|
||||
|
||||
- The Unlambda functions are represented by a record consisting of an array of five
|
||||
multipurpose arguments. The first one refers to the function implementing its
|
||||
application, the second and the third ones are for arguments of the function,
|
||||
the fourth one is for stack building, and the fifth one is the reference counter.
|
||||
- The Unlambda functions are represented by a record consisting of an array of
|
||||
five multipurpose arguments. The first one refers to the function implementing
|
||||
its application, the second and the third ones are for arguments of the
|
||||
function, the fourth one is for stack building, and the fifth one is the
|
||||
reference counter.
|
||||
|
||||
The function argument is used not only for evaluation of the functions, but also
|
||||
as a state variable to decide whether we want to call it, or do something else.
|
||||
The function argument is used not only for evaluation of the functions, but also
|
||||
as a state variable to decide whether we want to call it, or do something else.
|
||||
|
||||
- The interpreter reads the Unlambda source as it interprets it, translating
|
||||
the incoming character to a function using a constructor stored in translation array *T*.
|
||||
- The interpreter reads the Unlambda source as it interprets it, translating the
|
||||
incoming character to a function using a constructor stored in translation array
|
||||
`T`.
|
||||
|
||||
This means the interpreter must reposition in the source file whenever current
|
||||
continuation is called.
|
||||
This means the interpreter must reposition in the source file whenever current
|
||||
continuation is called.
|
||||
|
||||
- The state of the interpreter is represented by trees representing evaluated
|
||||
Unlambda functions stored on the C stack as local variables, plus the source file
|
||||
position. When a current continuation is created, the stack is unwound using the
|
||||
fourth argument of the nodes, stored in the continuation, and rebuilt back resuming
|
||||
the evaluation. When its called, the stack is unwound and thrown away, and the
|
||||
stack stored in the continuation is restored.
|
||||
- The state of the interpreter is represented by trees representing evaluated
|
||||
Unlambda functions stored on the C stack as local variables, plus the source
|
||||
file position. When a current continuation is created, the stack is unwound
|
||||
using the fourth argument of the nodes, stored in the continuation, and rebuilt
|
||||
back resuming the evaluation. When its called, the stack is unwound and thrown
|
||||
away, and the stack stored in the continuation is restored.
|
||||
|
||||
This makes implementation of a complete debugger a bit more challenging.
|
||||
This makes implementation of a complete debugger a bit more challenging.
|
||||
|
||||
For this to work, the functions that do the work (i.e. make series of applications
|
||||
and/or evaluations each of which may be a call to current continuation), the *s*
|
||||
combinator and the *`* application operation had to be broken down to pieces, one
|
||||
for each application/evaluation and corresponding helper functions for stack
|
||||
rebuilding. (the helper functions are named beginning with *r* instead of *f*)
|
||||
For this to work, the functions that do the work (i.e. make series of applications
|
||||
and/or evaluations each of which may be a call to current continuation), the `s`
|
||||
combinator and the `` ` `` application operation had to be broken down to pieces, one
|
||||
for each application/evaluation and corresponding helper functions for stack
|
||||
rebuilding. (the helper functions are named beginning with *r* instead of *f*)
|
||||
|
||||
- The *`* application operation does the reading of the Unlambda source when needed. It
|
||||
also implements the promise function. Whenever it would apply it, it overwrites
|
||||
the Apply constructor in translation array *T*, changing its function to build
|
||||
unevaluated subtree that is later evaluated whenever the promise is applied.
|
||||
- The `` ` `` application operation does the reading of the Unlambda source when
|
||||
needed. It also implements the promise function. Whenever it would apply it, it
|
||||
overwrites the Apply constructor in translation array `T`, changing its function
|
||||
to build unevaluated subtree that is later evaluated whenever the promise is
|
||||
applied.
|
||||
|
||||
- To further the obfuscation, I shortened the function names while downsizing,
|
||||
but preserved their meaning (with the exception of Apply functions, which I
|
||||
renamed to Y to possibly confuse with Y combinator). Simple functions retained
|
||||
their names to misguide the reader to believe to know what is what. The names
|
||||
of continuation and exit functions were swapped. The names of S and Apply
|
||||
functions were shuffled both for S functions and Apply functions in the same way
|
||||
to hide the meaning of *f* and *r* prefixes.
|
||||
- To further the obfuscation, I shortened the function names while downsizing,
|
||||
but preserved their meaning (with the exception of Apply functions, which I
|
||||
renamed to Y to possibly confuse with Y combinator). Simple functions retained
|
||||
their names to misguide the reader to believe to know what is what. The names of
|
||||
continuation and exit functions were swapped. The names of S and Apply functions
|
||||
were shuffled both for S functions and Apply functions in the same way to hide
|
||||
the meaning of `f` and `r` prefixes.
|
||||
|
||||
### Identification spoiler
|
||||
### Identification spoiler:
|
||||
|
||||
The alternative build path `make identify` output commemorates a [previous IOCCC winner][9] which uses
|
||||
similar methods to mess with the size tool.
|
||||
The alternative build path `make identify` output commemorates a [previous IOCCC
|
||||
winner][9] which uses similar methods to mess with the size tool.
|
||||
|
||||
### Judges remarks spoilers
|
||||
### Judges' remarks spoilers:
|
||||
|
||||
It is not a bug, it is a feature. :)
|
||||
|
||||
The crash is caused by a stack overflow. Conventional Unlabmda interpreters implement
|
||||
the Unlambda execution stack using data structures on the heap. This interpreter uses
|
||||
the C call stack for this purpose. In a conventional interpreter, the judges program would
|
||||
consume memory until the user (or the operating system) would lose patience. The C call stack
|
||||
is usually of a limited size and protected, so this interpreter crashes, when the judges
|
||||
program depletes the stack. You can always increase the maximal stack size through
|
||||
the system settings (on Linux) or compiler options (on Windows) if your Unlambda program
|
||||
needs it. Nevertheless, it will not help the judges program which quickly depletes any limit
|
||||
you define.
|
||||
The crash is caused by a stack overflow. Conventional Unlabmda interpreters
|
||||
implement the Unlambda execution stack using data structures on the heap. This
|
||||
interpreter uses the C call stack for this purpose. In a conventional
|
||||
interpreter, the judges' program would consume memory until the user (or the
|
||||
operating system) would lose patience. The C call stack is usually of a limited
|
||||
size and protected, so this interpreter crashes, when the judges' program
|
||||
depletes the stack. You can always increase the maximal stack size through the
|
||||
system settings (on Linux) or compiler options (on Windows) if your Unlambda
|
||||
program needs it. Nevertheless, it will not help the judges' program which
|
||||
quickly depletes any limit you define.
|
||||
|
||||
The judges, pretending not to know what they are doing, created a program that grows
|
||||
Unlambda execution stack indefinitely. You may consider it an Unlambda antipattern.
|
||||
Normally, infinite cycles in Unlambda work by creating two functions and applying one
|
||||
to the other which creates further functions to apply. The application of the function
|
||||
frees its position on the stack. This program builds one ever growing function that will
|
||||
never be complete and able to be applied.
|
||||
The judges, pretending not to know what they are doing, created a program that
|
||||
grows Unlambda execution stack indefinitely. You may consider it an Unlambda
|
||||
anti-pattern. Normally, infinite cycles in Unlambda work by creating two
|
||||
functions and applying one to the other which creates further functions to
|
||||
apply. The application of the function frees its position on the stack. This
|
||||
program builds one ever growing function that will never be complete and able to
|
||||
be applied.
|
||||
|
||||
[1]: https://github.com/irori/advent-unlambda "Adventure"
|
||||
[2]: http://www.madore.org/~david/programs/unlambda/ "Unlambda programming language"
|
||||
|
||||
Reference in new issue
Block a user