Format fixes in 1996/august

I am aware, however, of at least one possible bug. I've not addressed
this or put it in bugs.md. I will first look at it a bit more as at
first in linux I got a segfault but then I had the same problem under
macOS - a freeze. I want to see if I can duplicate the crash again
before I write about any bugs.
This commit is contained in:
Cody Boone Ferguson committed 2023-04-19 05:20:10 -07:00
1 parent bedaa23460
commit 4e2492f734
2 files changed
+137 -94

No files matched your search

+3
View File
@@ -1,3 +1,6 @@
august august
august.oc august.oc
test.oo test.oo
fac.oo
parse.oo
prog.oc
+134 -94
View File
@@ -1,65 +1,85 @@
# Best of Show # Best of Show
Lennart Augustsson Lennart Augustsson
CR&T CR&T
Stora Badhusgatan 18-20 Stora Badhusgatan 18-20
S-411 21 Goteborg S-411 21 Goteborg
Sweden Sweden
## To build: ## To build:
make all ```sh
make all
```
## To run: ## To run:
cat august.c test.oc | ./august > test.oo ```sh
./august < test.oo cat august.c test.oc | ./august > test.oo
./august < test.oo
```
NOTE: Some compilers will compile this code into infinite loops!
If the above cat line does not execute in a very short amount
of time, then you may need to fix your compiler or use gcc.
It should print a `!` followed by a newline.
NOTE: Some compilers will compile this code into infinite loops!
If the above cat line does not execute in a very short amount
of time, then you may need to fix your compiler or use gcc.
## Try: ## Try:
cat august.c fac.oc | ./august > fac.oo ```sh
./august < fac.oo cat august.c fac.oc | ./august > fac.oo
./august < fac.oo
cat august.c fac.oc | ./august | ./august cat august.c fac.oc | ./august | ./august
cat august.c parse.oc | ./august > parse.oo cat august.c parse.oc | ./august > parse.oo
cat parse.oo fac.oc | ./august | ./august cat parse.oo fac.oc | ./august | ./august
```
## Judges' remarks: ## Judges' remarks:
This entry can feed on itself. If you C preprocess the source, This entry can feed on itself. If you C pre-process the source,
you compile the interpreter: you compile the interpreter:
make august.oc ```sh
make august.oc
```
We can now compile the interpreter as follows: We can now compile the interpreter as follows:
cat august.c august.oc | ./august > august.oo ```sh
cat august.c august.oc | ./august > august.oo
```
and use the compiled interpreter to execute some previously and use the compiled interpreter to execute some previously
compiled code: compiled code:
cat august.oo test.oo | ./august ```sh
cat august.oo test.oo | ./august
```
And we can have the compiled interpreter interpret itself which And we can have the compiled interpreter interpret itself which
in turn compiles the test program: in turn compiles the test program:
cat august.oo august.oo fac.oo | ./august ```sh
cat august.oo august.oo fac.oo | ./august
```
And if you have lots of spare time, you can recurse one level deeper: And if you have lots of spare time, you can recurse one level deeper:
cat august.oo august.oo august.oo fac.oo | ./august ```sh
cat august.oo august.oo august.oo fac.oo | ./august
```
We (the judges) recommend that you spend some time studying this We (the judges) recommend that you spend some time studying this
entry as it surely will make the 'best of the IOCCC' list. It was entry as it surely will make the 'best of the IOCCC' list. It was
very well received by those who attended the IOCCC BOF. very well received by those who attended the IOCCC BOF.
## Author's remarks ## Author's remarks:
This program is a bytecode interpreter. This fact is not This program is a bytecode interpreter. This fact is not
particularly obfuscated; any experienced C programmer can see that particularly obfuscated; any experienced C programmer can see that
@@ -86,70 +106,88 @@ The compiler has absolutely no error checking, so things
can go wrong. But this is what seasoned C programmers are used to. can go wrong. But this is what seasoned C programmers are used to.
Assume that the interpreter has been compiled (source in prog.c and Assume that the interpreter has been compiled (source in prog.c and
binary in prog) and that we have this in a file named test.oc: binary in prog) and that we have this in a file named `test.oc`:
main() { putchar('!'); putchar('\n'); exit(0); } ```
main() { putchar('!'); putchar('\n'); exit(0); }
```
We can then compile by We can then compile by
cat august.c test.oc | ./august > test.oo ```sh
cat august.c test.oc | ./august > test.oo
```
And run the compiled program by And run the compiled program by
./august < test.oo ```sh
./august < test.oo
```
Or even simpler: Or even simpler:
cat august.c test.oc | ./august | ./august ```sh
cat august.c test.oc | ./august | ./august
A larger example is included fac.oc. ```
The compiler for OC is naturally written in OC (where did you think A larger example is included [fac.oc](fac.oc).
the byte code came from?). It is in the file parse.oc.
Hmm... So now we have an interpreter written in OC and a compiler The compiler for OC is naturally written in OC (where did you think the byte
for OC, can we compile the interpreter? Yes, we can! The OC code came from?). It is in the file [parse.oc](parse.oc).
compiler does not have a preprocessor (hey, what do you expect from
a program that is this small), so we need to use an external one.
To preprocess august.c use the command from the build file, but add
the -E flag and change 60000 to 40000, i.e.:
cc -E -DZ=40000 .... august.c > prog.oc Hmm... So now we have an interpreter written in OC and a compiler for OC, can
we compile the interpreter? Yes, we can! The OC compiler does not have a
preprocessor (hey, what do you expect from a program that is this small), so we
need to use an external one. To pre-process [august.c](august.c) use the
command from the Makefile, but add the `-E` flag and change 60000 to 40000,
i.e.:
If prog.oc contain some junk line starting with a `#' (most likely ```sh
cc -E -DZ=40000 .... august.c > prog.oc
```
If `prog.oc` contain some junk line starting with a `#' (most likely
it does) then remove it. it does) then remove it.
OK, we can now compile the interpreter: OK, we can now compile the interpreter:
cat august.c prog.oc | ./august > prog.oo ```sh
cat august.c prog.oc | ./august > prog.oo
```
And we can run it: And we can run it:
cat prog.oo test.oo | ./august ```sh
cat prog.oo test.oo | ./august
```
Here we have the interpreter interpreting another interpreter that runs Here we have the interpreter interpreting another interpreter that runs
the program. Did you think that was too fast? Just throw in another the program. Did you think that was too fast? Just throw in another
level of interpretation: level of interpretation:
cat prog.oo prog.oo test.oo | ./august ```sh
cat prog.oo prog.oo test.oo | ./august
```
And another... And another...
cat prog.oo prog.oo prog.oo test.oo | ./august ```sh
cat prog.oo prog.oo prog.oo test.oo | ./august
```
***** ---
Fitting the byte code for the compiler into the initial comment Fitting the byte code for the compiler into the initial comment
was a challenge. I had to strip the compiler/interpreter of was a challenge. I had to strip the compiler/interpreter of
a lot of functionality to bring down their sizes. The initial a lot of functionality to bring down their sizes. The initial
version had a full set of binary operators as well as prefix &. version had a full set of binary operators as well as prefix `&`.
Given a handful more bytes it would be easy to put them back. Given a handful more bytes it would be easy to put them back.
Anyway, the bytecode from the compiler is first optimized in Anyway, the bytecode from the compiler is first optimized in
an external optimizer (not supplied here); this brings down the an external optimizer (not supplied here); this brings down the
size of it by about 30%. The remaining code is then LZ-compressed size of it by about 30%. The remaining code is then LZ-compressed
(roughly) with a sliding window of 255 bytes. The most common (roughly) with a sliding window of 255 bytes. The most common
occuring bytes are then encoded using " \n\t;{}" to avoid having occuring bytes are then encoded using `" \n\t;{}"` to avoid having
them counted as significant. About 200 bytes of the interpreter them counted as significant. About 200 bytes of the interpreter
handles the unpacking of the byte code (this is a lot, but it pays handles the unpacking of the byte code (this is a lot, but it pays
off). off).
@@ -161,75 +199,77 @@ coder), but decompression then takes up too much code. Maybe this
would be a way to fit it all into the 1536 bytes, but I'm don't have would be a way to fit it all into the 1536 bytes, but I'm don't have
time to try it. time to try it.
***** ---
Execution starts at main(). Nothing can be used before it is Execution starts at `main()`. Nothing can be used before it is
defined. getchar() and putchar() are predefined functions. defined. `getchar()` and `putchar()` are predefined functions.
The following is a description of the OC grammar: Below is a description of the OC grammar.
OC grammar ### OC grammar
==========
Terminals are in quotes, () is used for bracketing. Terminals are in quotes, `()` is used for bracketing.
program: decl* ```
program: decl*
decl: vardecl decl: vardecl
fundecl fundecl
vardecl: type NAME ; vardecl: type NAME ;
type NAME "[" INT "]" ; type NAME "[" INT "]" ;
fundecl: type NAME "(" args ")" "{" body "}" fundecl: type NAME "(" args ")" "{" body "}"
args: /*empty*/ args: /*empty*/
( arg "," )* arg ( arg "," )* arg
arg: type NAME arg: type NAME
body: vardecl* stmt* body: vardecl* stmt*
stmt: ifstmt stmt: ifstmt
whilestmt whilestmt
dowhilestmt dowhilestmt
"return" expr ";" "return" expr ";"
expr ";" expr ";"
"{" stmt* "}" "{" stmt* "}"
";" ";"
ifstmt: "if" "(" expr ")" stmt ifstmt: "if" "(" expr ")" stmt
"if" "(" expr ")" stmt "else" stmt "if" "(" expr ")" stmt "else" stmt
whilestmt: "while" "(" expr ")" stmt whilestmt: "while" "(" expr ")" stmt
dowhilestmt: "do" stmt "while" "(" expr ")" ";" dowhilestmt: "do" stmt "while" "(" expr ")" ";"
expr: expr binop expr expr: expr binop expr
unop expr unop expr
expr "[" expr "]" expr "[" expr "]"
"(" expr ")" "(" expr ")"
expr "(" exprs ")" expr "(" exprs ")"
NAME NAME
INT INT
CHAR CHAR
STRING STRING
exprs: /*empty*/ exprs: /*empty*/
(expr ",")* expr (expr ",")* expr
binop: "+" | "-" | "*" | "/" | "%" | binop: "+" | "-" | "*" | "/" | "%" |
"=" | "=" |
"<" | "==" | "!=" "<" | "==" | "!="
unop: "!" | "-" | "*" unop: "!" | "-" | "*"
type: "int" stars type: "int" stars
"char" stars "char" stars
stars: "*"* stars: "*"*
***** ```
---
Well, that is enough ranting for now. Well, that is enough ranting for now.