Fixes in 1992/buzzard.2/README.md

Typo/spelling fixes and formatting fixes.
This commit is contained in:
Cody Boone Ferguson committed 2023-03-28 08:22:51 -07:00
1 parent b2ff5f15fa
commit 85ddf1a25a
1 file changed
+164 -160
+164 -160
View File
@@ -10,29 +10,32 @@
make all
### To run
## To run:
First:
First:
make first
Second:
Second:
echo help | cat third help.th - | ./first
cat third demo5.th | ./first
Third:
Third:
cat third help.th - | ./first
NOTE: Wait until Ok is printed and the type:
2 3 + . cr <-- yes you should really type the 2 letters: cr
NOTE: Wait until Ok is printed and the type:
Forth:
2 3 + . cr
Yes you should really type the 2 letters: cr.
Fourth:
Sorry, this is third!
### Alternate code
## 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.
@@ -45,191 +48,192 @@ Use `buzzard.2.alt` as you would `buzzard.2` above.
## Judges' comments
Have fun!
Have fun!
## Author's comments
What it does:
What it does:
first implements a relatively primitive stack machine. How
primitive? It supplies 13 visible primitives: 3 arithmetic,
1 comparison, 2 memory-access, 2 character I/O, 3 primitives
for defining new words, 1 tokenizing, and 1 special stack
operation. (There are also three internal operations for
the stack machine: 'push this integer', 'call this code',
and 'compile a call to this code'.)
`first` implements a relatively primitive stack machine. How
primitive? It supplies 13 visible primitives: 3 arithmetic,
1 comparison, 2 memory-access, 2 character I/O, 3 primitives
for defining new words, 1 tokenizing, and 1 special stack
operation. (There are also three internal operations for
the stack machine: 'push this integer', 'call this code',
and 'compile a call to this code'.)
It is very difficult to accomplish anything with this set
of primitives, but they do have an interesting property.
It is very difficult to accomplish anything with this set
of primitives, but they do have an interesting property.
This--what this interesting property is, or in other words
what first is good for--is the major obfuscation; there are
also minor source obfuscations, as well as some design tricks
that are effectively obfuscations. Details on the obfuscations
are below, and the interesting property is discussed much
further down.
This--what this interesting property is, or in other words
what first is good for--is the major obfuscation; there are
also minor source obfuscations, as well as some design tricks
that are effectively obfuscations. Details on the obfuscations
are below, and the interesting property is discussed much
further down.
How to run it:
### How to run it:
first expects you to first enter the names of the 13 primitives,
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
them. After this you may type any sequence of valid first input.
Valid first input is defined as any sequence of whitespace-delimited
tokens which consist of primitives, new words you've defined, and
integers (as parsed by "%d"). Invalid input behaves unpredictably,
but gives no warning messages. A sample program, demo1.1st, is
included, but it only works on ASCII systems.
`first` expects you to first enter the names of the 13 primitives,
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
them. After this you may type any sequence of valid first input.
Valid first input is defined as any sequence of whitespace-delimited
tokens which consist of primitives, new words you've defined, and
integers (as parsed by `"%d"`). Invalid input behaves unpredictably,
but gives no warning messages. A sample program, `demo1.1st`, is
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
third first. In unix, you can do
To do something interesting, you need to feed first the file
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
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);
THIRD will prompt you when it is finished. The file third has
not been obfuscated, due to sheer kindness on the author's part.
to do this. Hopefully most operating systems will provide a
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);
THIRD will prompt you when it is finished. The file third has
not been obfuscated, due to sheer kindness on the author's part.
For more information on what you can do once you've piped
THIRD into first, type 'help' and consult FORTH manuals for
further reference. Six sample THIRD programs are included
in the files demo[1-6].th. buzzard.2.README has more
information.
For more information on what you can do once you've piped
THIRD into first, type 'help' and consult FORTH manuals for
further reference. Six sample THIRD programs are included
in the files demo[1-6].th. buzzard.2.README has more
information.
Keep in mind that you are still running first, and
are for the most part limited by first's tokenizer
(notably, unknown words will attempt to be parsed as
integers.) It is possible to build a new parser that
parses by hand, reading a single character at a time;
however, such a parser cannot easily use the existing
dictionary, and so would have to implement its own,
thus requiring reimplementing all of first and third
a second time--I did not care to tackle this project.
Keep in mind that you are still running first, and
are for the most part limited by first's tokenizer
(notably, unknown words will attempt to be parsed as
integers.) It is possible to build a new parser that
parses by hand, reading a single character at a time;
however, such a parser cannot easily use the existing
dictionary, and so would have to implement its own,
thus requiring reimplementing all of first and third
a second time--I did not care to tackle this project.
Compiling:
### Compiling:
first is reasonably portable. You may need to adjust the
size of the buffers on smaller machines; m[] needs to be
at least 2000 long, though.
`first` is reasonably portable. You may need to adjust the
size of the buffers on smaller machines; `m[]` needs to be
at least 2000 long, though.
I say first is portable mainly because it uses native types.
Unlike FORTH, which traditionally allows byte and multi-byte
operations, all operations are performed on C 'int's. That
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
(or rather compiler) dependent.
I say `first` is portable mainly because it uses native types.
Unlike FORTH, which traditionally allows byte and multi-byte
operations, all operations are performed on C `int`s. That
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
(or rather compiler) dependent.
How is first obfuscated?
### How is first obfuscated?
first is obfuscated in several ways. Some minor obfuscations
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
(gcc -ansi -pedantic doesn't mind it, though.)
Other related obfuscations are still present. The top of the
stack is cached in a variable, which increases performance
massively if the compiler can figure out to keep it in a register;
it also obfuscates the code. (Unfortunately, the top of stack
is a global variable and neither gcc nor most bundled compilers
seem to register allocate it.)
`first` is obfuscated in several ways. Some minor obfuscations
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
(gcc -ansi -pedantic doesn't mind it, though.)
Other related obfuscations are still present. The top of the
stack is cached in a variable, which increases performance
massively if the compiler can figure out to keep it in a register;
it also obfuscates the code. (Unfortunately, the top of stack
is a global variable and neither gcc nor most bundled compilers
seem to register allocate it.)
More significant are the design obfuscations. m[0] is the
"dictionary pointer", used when compiling words, and m[1] is
the return stack index. Both are used as integer offsets into
m. Both are kept in m, instead of as separate pointers,
because they are then accessible to first programs, which is a
crucial property of first. Similarly the way words are stored
in the dictionary is not obvious, so it can be difficult to
follow exactly what the compiler words are doing.
More significant are the design obfuscations. `m[0]` is the
"dictionary pointer", used when compiling words, and `m[1]` is
the return stack index. Both are used as integer offsets into
m. Both are kept in m, instead of as separate pointers,
because they are then accessible to first programs, which is a
crucial property of first. Similarly the way words are stored
in the dictionary is not obvious, so it can be difficult to
follow exactly what the compiler words are doing.
Assuming you've waded through all that, you still have
to penetrate the most significant obfuscation. Traditionally,
the question is whether a reader can answer the question "what
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,
but they may not know the answer to the more important question,
"what will this program do when given the right input?" FORTH
afficianados, and especially FORTH implementors, may recognize
the similarity of the internal compiler format to many FORTH
interal representations, and, being aware that FORTH interpreters
can often by self-compiling, may be suspicious that this program
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
"THIRD" should be a dead giveaway, if the name "first" wasn't.
(These numbers were largely chosed because they were five letters
long, like "FORTH", and would not require truncation to five
letters, which would be a dead giveaway. Besides, THIRD represents
a step backwards, in more ways than one.)
Assuming you've waded through all that, you still have
to penetrate the most significant obfuscation. Traditionally,
the question is whether a reader can answer the question "what
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,
but they may not know the answer to the more important question,
"what will this program do when given the right input?" FORTH
aficionados, and especially FORTH implementers, may recognize
the similarity of the internal compiler format to many FORTH
internal representations, and, being aware that FORTH interpreters
can often by self-compiling, may be suspicious that this program
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
"THIRD" should be a dead giveaway, if the name "first" wasn't.
(These numbers were largely chosen because they were five letters
long, like "FORTH", and would not require truncation to five
letters, which would be a dead giveaway. Besides, THIRD represents
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
pseudo-subset of FORTH to allow it to bootstrap a relatively
complete version of FORTH (based loosely on forth79), which
I call THIRD. Complete relative to what, I'm not sure.
`first` is a tiny interpreter which implements a sufficient
pseudo-subset of FORTH to allow it to bootstrap a relatively
complete version of FORTH (based loosely on forth79), which
I call THIRD. Complete relative to what, I'm not sure.
I believe first is close to the smallest amount of code possible
to get this effect *using forth-style primitives*, and still have
some efficiency (it is possible to get by without multiplication
if you have addition, obviously). In the design file, design,
I give a justification for why each primitive in first was included.
I believe first is close to the smallest amount of code possible
to get this effect *using forth-style primitives*, and still have
some efficiency (it is possible to get by without multiplication
if you have addition, obviously). In the design file, design,
I give a justification for why each primitive in first was included.
THIRD is sorta slow, because first has so few primitives that
many things that are primitives in FORTH (like swap) take a
significant amount of time in THIRD.
THIRD is sorta slow, because first has so few primitives that
many things that are primitives in FORTH (like swap) take a
significant amount of time in THIRD.
When you get the 'Ok.' message from third, try out some sample
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
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 you get the 'Ok.' message from third, try out some sample
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
only prints 'Ok.' after you define a word).
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.
2 3 + . cr ( add 2 and 3, and print it and a newline.)
Software Construction Company gets credit for rekindling
my interest in FORTH and thus indirectly inspiring me
to write this program.
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
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: