Merge pull request #197 from xexyl/1992-buzzard.2

Fixes in 1992/buzzard.2/README.md
This commit is contained in:
Landon Curt Noll authored and GitHub committed 2023-03-28 14:44:30 -07:00
commit 156d776a89
1 file changed
+164 -160
+164 -160
View File
@@ -10,29 +10,32 @@
make all make all
### To run ## To run:
First: First:
make first make first
Second: Second:
echo help | cat third help.th - | ./first echo help | cat third help.th - | ./first
cat third demo5.th | ./first cat third demo5.th | ./first
Third: Third:
cat third help.th - | ./first cat third help.th - | ./first
NOTE: Wait until Ok is printed and the type: NOTE: Wait until Ok is printed and the type:
2 3 + . cr <-- yes you should really type the 2 letters: cr
Forth: 2 3 + . cr
Yes you should really type the 2 letters: cr.
Fourth:
Sorry, this is third! Sorry, this is third!
### Alternate code ## Alternate code:
An alternate version of this entry, `buzzard.2.alt.c`, is provided. This alternate code An alternate version of this entry, `buzzard.2.alt.c`, is provided. This alternate code
does not contain a fix that the author notes below. does not contain a fix that the author notes below.
@@ -45,191 +48,192 @@ Use `buzzard.2.alt` as you would `buzzard.2` above.
## Judges' comments ## Judges' comments
Have fun! Have fun!
## Author's comments ## Author's comments
What it does: What it does:
first implements a relatively primitive stack machine. How `first` implements a relatively primitive stack machine. How
primitive? It supplies 13 visible primitives: 3 arithmetic, primitive? It supplies 13 visible primitives: 3 arithmetic,
1 comparison, 2 memory-access, 2 character I/O, 3 primitives 1 comparison, 2 memory-access, 2 character I/O, 3 primitives
for defining new words, 1 tokenizing, and 1 special stack for defining new words, 1 tokenizing, and 1 special stack
operation. (There are also three internal operations for operation. (There are also three internal operations for
the stack machine: 'push this integer', 'call this code', the stack machine: 'push this integer', 'call this code',
and 'compile a call to this code'.) and 'compile a call to this code'.)
It is very difficult to accomplish anything with this set It is very difficult to accomplish anything with this set
of primitives, but they do have an interesting property. of primitives, but they do have an interesting property.
This--what this interesting property is, or in other words This--what this interesting property is, or in other words
what first is good for--is the major obfuscation; there are what first is good for--is the major obfuscation; there are
also minor source obfuscations, as well as some design tricks also minor source obfuscations, as well as some design tricks
that are effectively obfuscations. Details on the obfuscations that are effectively obfuscations. Details on the obfuscations
are below, and the interesting property is discussed much are below, and the interesting property is discussed much
further down. further down.
How to run it: ### How to run it:
first expects you to first enter the names of the 13 primitives, `first` expects you to first enter the names of the 13 primitives,
separated by whitespace--it doesn't care what you name them, but separated by whitespace--it doesn't care what you name them, but
if all the names aren't unique, you won't be able to use some of if all the names aren't unique, you won't be able to use some of
them. After this you may type any sequence of valid first input. them. After this you may type any sequence of valid first input.
Valid first input is defined as any sequence of whitespace-delimited Valid first input is defined as any sequence of whitespace-delimited
tokens which consist of primitives, new words you've defined, and tokens which consist of primitives, new words you've defined, and
integers (as parsed by "%d"). Invalid input behaves unpredictably, integers (as parsed by `"%d"`). Invalid input behaves unpredictably,
but gives no warning messages. A sample program, demo1.1st, is but gives no warning messages. A sample program, `demo1.1st`, is
included, but it only works on ASCII systems. included, but it only works on ASCII systems.
Do not expect to be able to do anything interesting with first. Do not expect to be able to do anything interesting with first.
To do something interesting, you need to feed first the file To do something interesting, you need to feed first the file
third first. In unix, you can do third first. In unix, you can do
% cat third help.th - | first % cat third help.th - | ./first
to do this. Hopefully most operating systems will provide a to do this. Hopefully most operating systems will provide a
way to do this. It may take some time for this to complete way to do this. It may take some time for this to complete
(I seem to remember it taking several minutes on an 8086 PC); (I seem to remember it taking several minutes on an 8086 PC);
THIRD will prompt you when it is finished. The file third has THIRD will prompt you when it is finished. The file third has
not been obfuscated, due to sheer kindness on the author's part. not been obfuscated, due to sheer kindness on the author's part.
For more information on what you can do once you've piped For more information on what you can do once you've piped
THIRD into first, type 'help' and consult FORTH manuals for THIRD into first, type 'help' and consult FORTH manuals for
further reference. Six sample THIRD programs are included further reference. Six sample THIRD programs are included
in the files demo[1-6].th. buzzard.2.README has more in the files demo[1-6].th. buzzard.2.README has more
information. information.
Keep in mind that you are still running first, and Keep in mind that you are still running first, and
are for the most part limited by first's tokenizer are for the most part limited by first's tokenizer
(notably, unknown words will attempt to be parsed as (notably, unknown words will attempt to be parsed as
integers.) It is possible to build a new parser that integers.) It is possible to build a new parser that
parses by hand, reading a single character at a time; parses by hand, reading a single character at a time;
however, such a parser cannot easily use the existing however, such a parser cannot easily use the existing
dictionary, and so would have to implement its own, dictionary, and so would have to implement its own,
thus requiring reimplementing all of first and third thus requiring reimplementing all of first and third
a second time--I did not care to tackle this project. a second time--I did not care to tackle this project.
Compiling: ### Compiling:
first is reasonably portable. You may need to adjust the `first` is reasonably portable. You may need to adjust the
size of the buffers on smaller machines; m[] needs to be size of the buffers on smaller machines; `m[]` needs to be
at least 2000 long, though. at least 2000 long, though.
I say first is portable mainly because it uses native types. I say `first` is portable mainly because it uses native types.
Unlike FORTH, which traditionally allows byte and multi-byte Unlike FORTH, which traditionally allows byte and multi-byte
operations, all operations are performed on C 'int's. That operations, all operations are performed on C `int`s. That
means first code is only as portable as the same code would means first code is only as portable as the same code would
be in C. As in C, the result of dividing -1 by 2 is machine be in C. As in C, the result of dividing -1 by 2 is machine
(or rather compiler) dependent. (or rather compiler) dependent.
How is first obfuscated? ### How is first obfuscated?
first is obfuscated in several ways. Some minor obfuscations `first` is obfuscated in several ways. Some minor obfuscations
like &w[&m[1]][s] for s+m[w+1] were in the original source like `&w[&m[1]][s]` for `s+m[w+1]` were in the original source
but are no longer because, apparently, ANSI doesn't allow it but are no longer because, apparently, ANSI doesn't allow it
(gcc -ansi -pedantic doesn't mind it, though.) (gcc -ansi -pedantic doesn't mind it, though.)
Other related obfuscations are still present. The top of the Other related obfuscations are still present. The top of the
stack is cached in a variable, which increases performance stack is cached in a variable, which increases performance
massively if the compiler can figure out to keep it in a register; massively if the compiler can figure out to keep it in a register;
it also obfuscates the code. (Unfortunately, the top of stack it also obfuscates the code. (Unfortunately, the top of stack
is a global variable and neither gcc nor most bundled compilers is a global variable and neither gcc nor most bundled compilers
seem to register allocate it.) seem to register allocate it.)
More significant are the design obfuscations. m[0] is the More significant are the design obfuscations. `m[0]` is the
"dictionary pointer", used when compiling words, and m[1] is "dictionary pointer", used when compiling words, and `m[1]` is
the return stack index. Both are used as integer offsets into the return stack index. Both are used as integer offsets into
m. Both are kept in m, instead of as separate pointers, m. Both are kept in m, instead of as separate pointers,
because they are then accessible to first programs, which is a because they are then accessible to first programs, which is a
crucial property of first. Similarly the way words are stored crucial property of first. Similarly the way words are stored
in the dictionary is not obvious, so it can be difficult to in the dictionary is not obvious, so it can be difficult to
follow exactly what the compiler words are doing. follow exactly what the compiler words are doing.
Assuming you've waded through all that, you still have Assuming you've waded through all that, you still have
to penetrate the most significant obfuscation. Traditionally, to penetrate the most significant obfuscation. Traditionally,
the question is whether a reader can answer the question "what the question is whether a reader can answer the question "what
will this do when I run it". A reader who has deciphered first will this do when I run it". A reader who has deciphered first
to this point may think they know the answer to this question, to this point may think they know the answer to this question,
but they may not know the answer to the more important question, but they may not know the answer to the more important question,
"what will this program do when given the right input?" FORTH "what will this program do when given the right input?" FORTH
afficianados, and especially FORTH implementors, may recognize aficionados, and especially FORTH implementers, may recognize
the similarity of the internal compiler format to many FORTH the similarity of the internal compiler format to many FORTH
interal representations, and, being aware that FORTH interpreters internal representations, and, being aware that FORTH interpreters
can often by self-compiling, may be suspicious that this program can often by self-compiling, may be suspicious that this program
can compile FORTH, or a significant subset of it, or at least be can compile FORTH, or a significant subset of it, or at least be
capable of doing so if fed the right input. Of course, the name capable of doing so if fed the right input. Of course, the name
"THIRD" should be a dead giveaway, if the name "first" wasn't. "THIRD" should be a dead giveaway, if the name "first" wasn't.
(These numbers were largely chosed because they were five letters (These numbers were largely chosen because they were five letters
long, like "FORTH", and would not require truncation to five long, like "FORTH", and would not require truncation to five
letters, which would be a dead giveaway. Besides, THIRD represents letters, which would be a dead giveaway. Besides, THIRD represents
a step backwards, in more ways than one.) a step backwards, in more ways than one.)
What exactly is first, then? ### What exactly is `first`, then?
first is a tiny interpreter which implements a sufficient `first` is a tiny interpreter which implements a sufficient
pseudo-subset of FORTH to allow it to bootstrap a relatively pseudo-subset of FORTH to allow it to bootstrap a relatively
complete version of FORTH (based loosely on forth79), which complete version of FORTH (based loosely on forth79), which
I call THIRD. Complete relative to what, I'm not sure. I call THIRD. Complete relative to what, I'm not sure.
I believe first is close to the smallest amount of code possible I believe first is close to the smallest amount of code possible
to get this effect *using forth-style primitives*, and still have to get this effect *using forth-style primitives*, and still have
some efficiency (it is possible to get by without multiplication some efficiency (it is possible to get by without multiplication
if you have addition, obviously). In the design file, design, if you have addition, obviously). In the design file, design,
I give a justification for why each primitive in first was included. I give a justification for why each primitive in first was included.
THIRD is sorta slow, because first has so few primitives that THIRD is sorta slow, because first has so few primitives that
many things that are primitives in FORTH (like swap) take a many things that are primitives in FORTH (like swap) take a
significant amount of time in THIRD. significant amount of time in THIRD.
When you get the 'Ok.' message from third, try out some sample When you get the 'Ok.' message from third, try out some sample
FORTH code (first has no way of knowing if keyboard input is FORTH code (first has no way of knowing if keyboard input is
waiting, so it can't actually prompt you in a normal way. It waiting, so it can't actually prompt you in a normal way. It
only prints 'Ok.' after you define a word). only prints 'Ok.' after you define a word).
2 3 + . cr ( add 2 and 3, and print it and a newline.)
and THIRD responds
5
Now try:
: test 11 1 do i . loop cr ;
test
and THIRD responds
1 2 3 4 5 6 7 8 9 10
When in THIRD, you can see how much space you're currently 2 3 + . cr ( add 2 and 3, and print it and a newline.)
using by typing
here .
The number THIRD replies is the number of machine words (ints)
that the dictionary (the first code) takes up, plus the
512 ints for the return stack. If you compile the basic
THIRD system without the help word (strings take up one
int per character in the string!), you should find that
you're using around 1000 ints (plus the return stack).
Thus THIRD gives you a relatively complete FORTH system in
less than 700 chars of C source + about 1000 ints of
memory--and it's portable too (you could copy over the
THIRD memory dump to another machine, in theory). If the
above numbers seem to you to be mixing apples and oranges
(C source and compiled THIRD code), note that you should
in theory be able to stick the compiled THIRD code into
the C source.
Software Construction Company gets credit for rekindling and THIRD responds
my interest in FORTH and thus indirectly inspiring me
to write this program. 5
Now try:
: test 11 1 do i . loop cr ;
test
and THIRD responds
1 2 3 4 5 6 7 8 9 10
When in THIRD, you can see how much space you're currently
using by typing
here .
The number THIRD replies is the number of machine words (ints)
that the dictionary (the first code) takes up, plus the
512 ints for the return stack. If you compile the basic
THIRD system without the help word (strings take up one
int per character in the string!), you should find that
you're using around 1000 ints (plus the return stack).
Thus THIRD gives you a relatively complete FORTH system in
less than 700 chars of C source + about 1000 ints of
memory--and it's portable too (you could copy over the
THIRD memory dump to another machine, in theory). If the
above numbers seem to you to be mixing apples and oranges
(C source and compiled THIRD code), note that you should
in theory be able to stick the compiled THIRD code into
the C source.
Software Construction Company gets credit for rekindling
my interest in FORTH and thus indirectly inspiring me
to write this program.
## Copyright and CC BY-SA 4.0 License: ## Copyright and CC BY-SA 4.0 License: