Convert to/format markdown in 1992/buzzard.2
The file buzzard.2.README.md was formatted in markdown style.
The file buzzard.2.design was turned into a markdown file,
buzzard.2.design.md, and formatted in markdown style. This one was
problematic in some ways but I think it's sorted out now. In a few
places it was not entirely clear if certain things should be in code
blocks or not but they were made to be as they were on the same line as
code (after). It doesn't appear to be a comment in the syntax of
comments that later appears, however. Another one I was not sure about
is if the author meant '{ .. .. }' to be a way to say this is code
without the {}s or if that should be part of the code. As these are not
clear there might be some mistakes.
This commit is contained in:
1 parent
044d3dfabb
commit
78883b5d15
3 files changed
+372
-239
No files matched your search
@@ -56,8 +56,6 @@ fix that the author notes in their remarks.
|
|||||||
|
|
||||||
### Alternate build:
|
### Alternate build:
|
||||||
|
|
||||||
To compile this alternate version:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make alt
|
make alt
|
||||||
```
|
```
|
||||||
@@ -108,7 +106,7 @@ 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](third) first. In unix, you can do
|
[third](third) first. In unix, you can do
|
||||||
@@ -124,7 +122,7 @@ minutes on an 8086 PC); `THIRD` will prompt you when it is finished. The file
|
|||||||
part.
|
part.
|
||||||
|
|
||||||
For more information on what you can do once you've piped [third](third) into
|
For more information on what you can do once you've piped [third](third) into
|
||||||
first, type 'help' and consult
|
first, type `help` and consult
|
||||||
[FORTH](https://en.wikipedia.org/wiki/Forth_(programming_language)) manuals for
|
[FORTH](https://en.wikipedia.org/wiki/Forth_(programming_language)) manuals for
|
||||||
further reference. Six sample THIRD programs are included in the files
|
further reference. Six sample THIRD programs are included in the files
|
||||||
demo[1-6].th. [buzzard.2.README.md](buzzard.2.README.md) has more information.
|
demo[1-6].th. [buzzard.2.README.md](buzzard.2.README.md) has more information.
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
buzzard.2.README this file
|
[buzzard.2.README.md](buzzard.2.README.md) this file\
|
||||||
buzzard.2.design description of FIRST (design documentation of THIRD)
|
[buzzard.2.design.md](buzzard.2.design.md) description of FIRST (design documentation of THIRD)\
|
||||||
third implementation of THIRD in FIRST
|
[third](third) implementation of THIRD in FIRST\
|
||||||
help.th online listing of THIRD primitives
|
[help.th](help.th) online listing of THIRD primitives\
|
||||||
|
|
||||||
|
|
||||||
# FIRST demos:
|
# FIRST demos:
|
||||||
|
|
||||||
Use `./first < <demo>`.
|
Use `./first < <demo>`.
|
||||||
|
|
||||||
demo1.1st prints Hello World! assuming ASCII
|
[demo1.1st](demo1.1st) prints Hello World! assuming ASCII
|
||||||
|
|
||||||
|
|
||||||
# THIRD demos:
|
# THIRD demos:
|
||||||
|
|
||||||
Use `cat third <demo> | ./first`.
|
Use `cat third <demo> | ./first`.
|
||||||
|
|
||||||
demo1.th prints Hello World! regardless of character set
|
|
||||||
demo2.th demonstrates a simple loop
|
## Demo files:
|
||||||
demo3.th demonstrates a simple if test
|
|
||||||
demo4.th recursive factorial calculating on the way up
|
[demo1.th](demo1.th) prints Hello World! regardless of character set
|
||||||
demo5.th recursive factorial calculating on the way down
|
[demo2.th](demo2.th) demonstrates a simple loop
|
||||||
demo6.th demonstrates switching from compiler to execution mode
|
[demo3.th](demo3.th) demonstrates a simple if test
|
||||||
|
[demo4.th](demo4.th) recursive factorial calculating on the way up
|
||||||
|
[demo5.th](demo5.th) recursive factorial calculating on the way down
|
||||||
|
[demo6.th](demo6.th) demonstrates switching from compiler to execution mode
|
||||||
|
|
||||||
|
|
||||||
# Interactive THIRD:
|
# Interactive THIRD:
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
FIRST & THIRD
|
# FIRST & THIRD (almost FORTH)
|
||||||
almost FORTH
|
|
||||||
|
|
||||||
FORTH is a language mostly familiar to users of "small" machines.
|
FORTH is a language mostly familiar to users of "small" machines.
|
||||||
FORTH programs are small because they are interpreted--a function
|
FORTH programs are small because they are interpreted--a function
|
||||||
call in FORTH takes two bytes. FORTH is an extendable language--
|
call in FORTH takes two bytes. FORTH is an extendable language--
|
||||||
built-in primitives are indistinguishable from user-defined
|
built-in primitives are indistinguishable from user-defined
|
||||||
@@ -10,50 +9,50 @@ can be coded in FORTH--only a small number of primitives need to
|
|||||||
be implemented. Some FORTH interpreters can also compile defined
|
be implemented. Some FORTH interpreters can also compile defined
|
||||||
words into machine code, resulting in a fast system.
|
words into machine code, resulting in a fast system.
|
||||||
|
|
||||||
FIRST is an incredibly small language which is sufficient for
|
FIRST is an incredibly small language which is sufficient for
|
||||||
defining the language THIRD, which is mostly like FORTH. There are
|
defining the language THIRD, which is mostly like FORTH. There are
|
||||||
some differences, and THIRD is probably just enough like FORTH for
|
some differences, and THIRD is probably just enough like FORTH for
|
||||||
those differences to be disturbing to regular FORTH users.
|
those differences to be disturbing to regular FORTH users.
|
||||||
|
|
||||||
The only existing FIRST interpreter is written in obfuscated C,
|
The only existing FIRST interpreter is written in obfuscated C,
|
||||||
and rings in at under 800 bytes of source code, although through
|
and rings in at under 800 bytes of source code, although through
|
||||||
deletion of whitespace and unobfuscation it can be brought to about
|
deletion of both whitespace and obfuscation it can be brought to about 650
|
||||||
650 bytes.
|
bytes.
|
||||||
|
|
||||||
This document FIRST defines the FIRST environment and primitives,
|
This document FIRST defines the FIRST environment and primitives,
|
||||||
with relevent design decision explanations. It secondly documents
|
with relevant design decision explanations. It secondly documents
|
||||||
the general strategies we will use to implement THIRD. The THIRD
|
the general strategies we will use to implement THIRD. The THIRD
|
||||||
section demonstrates how the complete THIRD system is built up
|
section demonstrates how the complete THIRD system is built up
|
||||||
using FIRST.
|
using FIRST.
|
||||||
|
|
||||||
|
|
||||||
Section 1: FIRST
|
## Section 1: FIRST
|
||||||
|
|
||||||
|
|
||||||
Environment
|
### Environment
|
||||||
|
|
||||||
FIRST implements a virtual machine. The machine has three chunks
|
FIRST implements a virtual machine. The machine has three chunks
|
||||||
of memory: "main memory", "the stack", and "string storage". When
|
of memory: "main memory", "the stack", and "string storage". When
|
||||||
the virtual machine wishes to do random memory accesses, they come
|
the virtual machine wishes to do random memory accesses, they come
|
||||||
out of main memory--it cannot access the stack or string storage.
|
out of main memory--it cannot access the stack or string storage.
|
||||||
|
|
||||||
The stack is simply a standard LIFO data structure that is used
|
The stack is simply a standard LIFO data structure that is used
|
||||||
implicitly by most of the FIRST primitives. The stack is made up
|
implicitly by most of the FIRST primitives. The stack is made up
|
||||||
of ints, whatever size they are on the host machine.
|
of `int`s, whatever size they are on the host machine.
|
||||||
|
|
||||||
String storage is used to store the names of built-in and defined
|
String storage is used to store the names of built-in and defined
|
||||||
primitives. Separate storage is used for these because it allows
|
primitives. Separate storage is used for these because it allows
|
||||||
the C code to use C string operations, reducing C source code size.
|
the C code to use C string operations, reducing C source code size.
|
||||||
|
|
||||||
Main memory is a large array of ints. When we speak of
|
Main memory is a large array of `int`s. When we speak of
|
||||||
addresses, we actually mean indices into main memory. Main memory
|
addresses, we actually mean indices into main memory. Main memory
|
||||||
is used for two things, primarily: the return stack and the dictionary.
|
is used for two things, primarily: the return stack and the dictionary.
|
||||||
|
|
||||||
The return stack is a LIFO data structure, independent of
|
The return stack is a LIFO data structure, independent of
|
||||||
the abovementioned "the stack", which is used by FIRST to keep
|
the above-mentioned "stack", which is used by FIRST to keep
|
||||||
track of function call return addresses.
|
track of function call return addresses.
|
||||||
|
|
||||||
The dictionary is a list of words. Each word contains a header
|
The dictionary is a list of words. Each word contains a header
|
||||||
and a data field. In the header is the address of the previous word,
|
and a data field. In the header is the address of the previous word,
|
||||||
an index into the string storage indicating where the name of this
|
an index into the string storage indicating where the name of this
|
||||||
word is stored, and a "code pointer". The code pointer is simply
|
word is stored, and a "code pointer". The code pointer is simply
|
||||||
@@ -63,127 +62,170 @@ names the "run some code" primitive, which pushes the current program
|
|||||||
counter onto the return stack and sets the counter to the address of
|
counter onto the return stack and sets the counter to the address of
|
||||||
the data field for this word.
|
the data field for this word.
|
||||||
|
|
||||||
There are several important pointers into main memory. There is
|
There are several important pointers into main memory. There is
|
||||||
a pointer to the most recently defined word, which is used to start
|
a pointer to the most recently defined word, which is used to start
|
||||||
searches back through memory when compiling words. There is a pointer
|
searches back through memory when compiling words. There is a pointer
|
||||||
to the top of the return stack. There is a pointer to the current
|
to the top of the return stack. There is a pointer to the current
|
||||||
end of the dictionary, used while compiling.
|
end of the dictionary, used while compiling.
|
||||||
|
|
||||||
For the last two pointers, namely the return stack pointer and
|
For the last two pointers, namely the return stack pointer and
|
||||||
the dictionary pointer, there is an important distinction: the pointers
|
the dictionary pointer, there is an important distinction: the pointers
|
||||||
themselves are stored in main memory (in FIRST's main memory). This
|
themselves are stored in main memory (in `FIRST`'s main memory). This
|
||||||
is critical, because it means FIRST programs can get at them without
|
is critical, because it means FIRST programs can get at them without
|
||||||
any further primitives needing to be defined.
|
any further primitives needing to be defined.
|
||||||
|
|
||||||
|
|
||||||
Instructions
|
### Instructions
|
||||||
|
|
||||||
There are two kinds of FIRST instructions, normal instructions and
|
There are two kinds of FIRST instructions, normal instructions and
|
||||||
immediate instructions. Immediate instructions do something significant
|
immediate instructions. Immediate instructions do something significant
|
||||||
when they are used. Normal instructions compile a pointer to their
|
when they are used. Normal instructions compile a pointer to their
|
||||||
executable part onto the end of the dictionary. As we will see, this
|
executable part onto the end of the dictionary. As we will see, this
|
||||||
means that by default FIRST simply compiles things.
|
means that by default FIRST simply compiles things.
|
||||||
|
|
||||||
Integer Operations
|
|
||||||
Symbol Name Function
|
#### Integer Operations
|
||||||
|
|
||||||
|
```
|
||||||
|
SYMBOL NAME FUNCTION
|
||||||
|
|
||||||
- binary minus pop top 2 elements of stack, subtract, push
|
- binary minus pop top 2 elements of stack, subtract, push
|
||||||
|
|
||||||
* multiply pop top 2 elements of stack, multiply, push
|
* multiply pop top 2 elements of stack, multiply, push
|
||||||
|
|
||||||
/ divide pop top 2 elements of stack, divide, push
|
/ divide pop top 2 elements of stack, divide, push
|
||||||
|
|
||||||
<0 less than 0 pop top element of stack, push 1 if < 0 else 0
|
<0 less than 0 pop top element of stack, push 1 if < 0 else 0
|
||||||
|
```
|
||||||
|
|
||||||
Note that we can synthesize addition and negation from binary minus,
|
Note that we can synthesize addition and negation from binary minus,
|
||||||
but we cannot synthesize a time efficient divide or multiply from it.
|
but we cannot synthesize a time efficient divide or multiply from it.
|
||||||
<0 is synthesizable, but only nonportably.
|
`<0` is synthesize-able, but only in a non-portable way.
|
||||||
|
|
||||||
|
|
||||||
|
#### Memory Operations
|
||||||
|
|
||||||
|
```
|
||||||
|
SYMBOL NAME FUNCTION
|
||||||
|
|
||||||
Memory Operations
|
|
||||||
Symbol Name Function
|
|
||||||
@ fetch pop top of stack, treat as address to push contents of
|
@ fetch pop top of stack, treat as address to push contents of
|
||||||
|
|
||||||
! store top of stack is address, 2nd is value; store to memory
|
! store top of stack is address, 2nd is value; store to memory
|
||||||
and pop both off the stack
|
and pop both off the stack
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Input/Output Operations
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME FUNCTION
|
||||||
|
|
||||||
Input/Output Operations
|
|
||||||
Name Function
|
|
||||||
echo output top of stack through C's putchar()
|
echo output top of stack through C's putchar()
|
||||||
key push C's getchar() onto top of stack
|
|
||||||
_read read a space-delimited word, find it in the
|
|
||||||
dictionary, and compile a pointer to
|
|
||||||
that word's code pointer onto the
|
|
||||||
current end of the dictionary
|
|
||||||
|
|
||||||
Although _read could be synthesized from key, we need _read to be able
|
key push C's getchar() onto top of stack
|
||||||
|
|
||||||
|
_read read a space-delimited word, find it in the
|
||||||
|
dictionary, and compile a pointer to
|
||||||
|
that word's code pointer onto the
|
||||||
|
current end of the dictionary
|
||||||
|
```
|
||||||
|
|
||||||
|
Although `_read` could be synthesized from key, we need `_read` to be able
|
||||||
to compile words to be able to start any syntheses.
|
to compile words to be able to start any syntheses.
|
||||||
|
|
||||||
Execution Operations
|
|
||||||
Name Function
|
#### Execution Operations
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME FUNCTION
|
||||||
|
|
||||||
exit leave the current function: pop the return stack
|
exit leave the current function: pop the return stack
|
||||||
into the program counter
|
into the program counter
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Immediate (compilation) Operations
|
||||||
|
|
||||||
|
```
|
||||||
|
SYMBOL NAME FUNCTION
|
||||||
|
|
||||||
Immediate (compilation) Operations
|
|
||||||
Symbol Name Function
|
|
||||||
: define read in the next space-delimited word, add it to
|
: define read in the next space-delimited word, add it to
|
||||||
the end of our string storage, and generate
|
the end of our string storage, and generate
|
||||||
a header for the new word so that when it
|
a header for the new word so that when it
|
||||||
is typed it compiles a pointer to itself
|
is typed it compiles a pointer to itself
|
||||||
so that it can be executed.
|
so that it can be executed.
|
||||||
immediate immediate when used immediately after a name following a ':',
|
|
||||||
makes the word being defined run whenever
|
|
||||||
it is typed.
|
|
||||||
|
|
||||||
: cannot be synthesized, because we could not synthesize anything.
|
immediate immediate when used immediately after a name following a ':',
|
||||||
immediate has to be an immediate operation, so it could not be
|
it makes the word being defined run whenever
|
||||||
|
it is typed.
|
||||||
|
```
|
||||||
|
|
||||||
|
`:` cannot be synthesized, because we could not synthesize anything.
|
||||||
|
`immediate` has to be an immediate operation, so it could not be
|
||||||
synthesized unless by default operations were immediate; but that
|
synthesized unless by default operations were immediate; but that
|
||||||
would preclude our being able to do any useful compilation.
|
would preclude our being able to do any useful compilation.
|
||||||
|
|
||||||
Stack Operations
|
|
||||||
Name Function
|
|
||||||
pick pop top of stack, use as index into stack and copy up
|
|
||||||
that element
|
|
||||||
|
|
||||||
If the data stack were stored in main memory, we could synthesize pick;
|
#### Stack Operations
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME FUNCTION
|
||||||
|
|
||||||
|
pick pop top of stack, use as index into stack and copy up
|
||||||
|
that element
|
||||||
|
```
|
||||||
|
|
||||||
|
If the data stack were stored in main memory, we could synthesize `pick`;
|
||||||
but putting the stack and stack pointer in main memory would significantly
|
but putting the stack and stack pointer in main memory would significantly
|
||||||
increase the C source code size.
|
increase the C source code size.
|
||||||
|
|
||||||
There are three more primitives, but they are "internal only"--
|
There are three more primitives, but they are "internal only"--
|
||||||
they have no names and no dictionary entries. The first is
|
they have no names and no dictionary entries. The first is
|
||||||
"pushint". It takes the next integer out of the instruction stream
|
`pushint`. It takes the next integer out of the instruction stream
|
||||||
and pushes it on the stack. This could be synthesized, but probably
|
and pushes it on the stack. This could be synthesized, but probably
|
||||||
not without using integer constants. It is generated by _read when
|
not without using integer constants. It is generated by `_read` when
|
||||||
the input is not a known word. The second is "compile me". When
|
the input is not a known word. The second is `compile me`. When
|
||||||
this instruction is executed, a pointer to the word's data field is
|
this instruction is executed, a pointer to the word's data field is
|
||||||
appended to the dictionary. The third is "run me"--the word's data
|
appended to the dictionary. The third is `run me`--the word's data
|
||||||
field is taken to be a stream of pointers to words, and is executed.
|
field is taken to be a stream of pointers to words, and is executed.
|
||||||
|
|
||||||
One last note about the environment: FIRST builds a very small
|
One last note about the environment: FIRST builds a very small
|
||||||
word internally that it executes as its main loop. This word calls
|
word internally that it executes as its main loop. This word calls
|
||||||
_read and then calls itself. Each time it calls itself, it uses
|
`_read` and then calls itself. Each time it calls itself, it uses
|
||||||
up a word on the return stack, so it will eventually trash things.
|
up a word on the return stack, so it will eventually trash things.
|
||||||
This is discussed some more in section 2.
|
This is discussed some more in section 2.
|
||||||
|
|
||||||
|
|
||||||
Here's a handy summary of all the FIRST words:
|
Here's a handy summary of all the FIRST words:
|
||||||
|
|
||||||
- * / binary integer operations on the stack
|
```
|
||||||
<0 is top of stack less than 0?
|
WORD DESCRIPTION
|
||||||
@ ! read from or write to memory
|
|
||||||
echo key output or input one character
|
|
||||||
_read read a word from input and compile a pointer to it
|
|
||||||
exit stop running the current function
|
|
||||||
: compile the header of a definition
|
|
||||||
immediate modify the header to create an immediate word
|
|
||||||
|
|
||||||
Here is a sample FIRST program. I'm assuming you're using
|
- * / binary integer operations on the stack
|
||||||
|
<0 is top of stack less than 0?
|
||||||
|
@ ! read from or write to memory
|
||||||
|
echo key output or input one character
|
||||||
|
_read read a word from input and compile a pointer to it
|
||||||
|
exit stop running the current function
|
||||||
|
: compile the header of a definition
|
||||||
|
immediate modify the header to create an immediate word
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is a sample FIRST program. I'm assuming you're using
|
||||||
the ASCII character set. FIRST does not depend upon ASCII, but
|
the ASCII character set. FIRST does not depend upon ASCII, but
|
||||||
since FIRST has no syntax for character constants, one normally has
|
since FIRST has no syntax for character constants, one normally has
|
||||||
to use decimal values. This can be gotten around using getchar, though.
|
to use decimal values. This can be avoided using `getchar(3)`, though.
|
||||||
Oh. One other odd thing. FIRST initially builds its symbol table
|
Oh. One other odd thing. FIRST initially builds its symbol table
|
||||||
by calling : several times, so it needs to get the names of the base
|
by calling `:` several times, so it needs to get the names of the base
|
||||||
symbols as its first 13 words of input. You could even name them
|
symbols as its first 13 words of input. You could even name them
|
||||||
differently if you wanted.
|
differently if you wanted.
|
||||||
These FIRST programs have FORTH comments in them: they are contained
|
|
||||||
|
These FIRST programs have FORTH comments in them: they are contained
|
||||||
inside parentheses. FIRST programs cannot have FORTH comments; but I need
|
inside parentheses. FIRST programs cannot have FORTH comments; but I need
|
||||||
some device to indicate what's going on. (THIRD programs are an entirely
|
some device to indicate what's going on. (THIRD programs are an entirely
|
||||||
different subject.)
|
different subject.)
|
||||||
|
|
||||||
|
```
|
||||||
( Our first line gives the symbols for the built-ins )
|
( Our first line gives the symbols for the built-ins )
|
||||||
: immediate _read @ ! - * / <0 exit echo key _pick
|
: immediate _read @ ! - * / <0 exit echo key _pick
|
||||||
|
|
||||||
@@ -211,100 +253,102 @@ test
|
|||||||
( The result of running this program should be:
|
( The result of running this program should be:
|
||||||
Hello
|
Hello
|
||||||
)
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Section 2: Motivating THIRD
|
||||||
|
|
||||||
Section 2: Motivating THIRD
|
What is missing from FIRST? There are a large number of
|
||||||
|
|
||||||
What is missing from FIRST? There are a large number of
|
|
||||||
important primitives that aren't implemented, but which are
|
important primitives that aren't implemented, but which are
|
||||||
easy to implement. drop , which throws away the top of the
|
easy to implement. `drop`, which throws away the top of the
|
||||||
stack, can be implemented as { 0 * + } -- that is, multiply
|
stack, can be implemented as `{ 0 * + }` -- that is, multiply
|
||||||
the top of the stack by 0 (which turns the top of the stack
|
the top of the stack by `0` (which turns the top of the stack
|
||||||
into a 0), and then add the top two elements of the stack.
|
into a `0`), and then add the top two elements of the stack.
|
||||||
|
|
||||||
dup , which copies the top of the stack, can be easily
|
`dup`, which copies the top of the stack, can be easily
|
||||||
implemented using temporary storage locations. Conveniently,
|
implemented using temporary storage locations. Conveniently,
|
||||||
FIRST leaves memory locations 3, 4, and 5 unused. So we can
|
FIRST leaves memory locations `3`, `4`, and `5` unused. So we can
|
||||||
implement dup by writing the top of stack into 3, and then
|
implement `dup` by writing the top of stack into `3`, and then
|
||||||
reading it out twice: { 3 ! 3 @ 3 @ }.
|
reading it out twice: `{ 3 ! 3 @ 3 @ }`.
|
||||||
|
|
||||||
we will never use the FIRST primitive 'pick' in building THIRD,
|
We will never use the FIRST primitive `pick` in building THIRD,
|
||||||
just to show that it can be done; 'pick' is only provided because
|
just to show that it can be done; `pick` is only provided because
|
||||||
pick itself cannot be built out of the rest of FIRST's building
|
`pick` itself cannot be built out of the rest of `FIRST`'s building
|
||||||
blocks.
|
blocks.
|
||||||
|
|
||||||
So, instead of worrying about stack primitives and the
|
So, instead of worrying about stack primitives and the
|
||||||
like, what else is missing from FIRST? We get recursion, but
|
like, what else is missing from FIRST? We get recursion, but
|
||||||
no control flow--no conditional operations. We cannot at the
|
no control flow--no conditional operations. We cannot at the
|
||||||
moment write a looping routine which terminates.
|
moment write a looping routine which terminates.
|
||||||
|
|
||||||
Another glaring dissimilarity between FIRST and FORTH is
|
Another glaring dissimilarity between FIRST and FORTH is
|
||||||
that there is no "command mode"--you cannot be outside of a
|
that there is no "command mode"--you cannot be outside of a
|
||||||
: definition and issue some straight commands to be executed.
|
`:` definition and issue some straight commands to be executed.
|
||||||
Also, as we noted above, we cannot do comments.
|
Also, as we noted above, we cannot do comments.
|
||||||
|
|
||||||
FORTH also provides a system for defining new data types,
|
FORTH also provides a system for defining new data types,
|
||||||
using the words [in one version of FORTH] <builds and does> .
|
using the words (in one version of FORTH) `builds and does`.
|
||||||
We would like to implement these words as well.
|
We would like to implement these words as well.
|
||||||
|
|
||||||
As the highest priority thing, we will build control flow
|
As the highest priority thing, we will build control flow
|
||||||
structures first. Once we have control structures, we can
|
structures first. Once we have control structures, we can
|
||||||
write recursive routines that terminate, and we are ready to
|
write recursive routines that terminate, and we are ready to
|
||||||
tackle tasks like parsing, and the building of a command mode.
|
tackle tasks like parsing, and the building of a command mode.
|
||||||
|
|
||||||
By the way, location 0 holds the dictionary pointer, location
|
By the way, location `0` holds the dictionary pointer, location
|
||||||
1 holds the return stack pointer, and location 2 should always
|
`1` holds the return stack pointer, and location `2` should always
|
||||||
be 0--it's a fake dictionary entry that means "pushint".
|
be `0`--it's a fake dictionary entry that means `pushint`.
|
||||||
|
|
||||||
|
|
||||||
Section 3: Building THIRD
|
## Section 3: Building THIRD
|
||||||
|
|
||||||
In this section, I'm going to keep my conversation
|
In this section we'll eventually have real comments.
|
||||||
indented to this depth, rather than using fake comments--
|
|
||||||
because we'll have real comments eventually.
|
|
||||||
|
|
||||||
The first thing we have to do is give the symbols for our
|
The first thing we have to do is give the symbols for our built-ins.
|
||||||
built-ins.
|
|
||||||
|
|
||||||
|
```
|
||||||
: immediate _read @ ! - * / < exit echo key _pick
|
: immediate _read @ ! - * / < exit echo key _pick
|
||||||
|
```
|
||||||
|
|
||||||
Next we want to be mildly self commenting, so we define
|
Next we want to be mildly self commenting, so we define the word `r` to push
|
||||||
the word 'r' to push the *address of the return stack
|
the *address of the return stack pointer* onto the stack--NOT the value of the
|
||||||
pointer* onto the stack--NOT the value of the return
|
return stack pointer. (In fact, when we run `r`, the value of the return stack
|
||||||
stack pointer. (In fact, when we run r, the value of
|
pointer is temporarily changed.)
|
||||||
the return stack pointer is temporarily changed.)
|
|
||||||
|
|
||||||
|
```
|
||||||
: r 1 exit
|
: r 1 exit
|
||||||
|
```
|
||||||
|
|
||||||
Next, we're currently executing a short loop that contains
|
Next, we're currently executing a short loop that contains `_read` and
|
||||||
_read and recursion, which is slowly blowing up the return
|
recursion, which is slowly blowing up the return stack. So let's define a new
|
||||||
stack. So let's define a new word, from which you can
|
word, from which you can never return. What it does is drops the top value off
|
||||||
never return. What it does is drops the top value off
|
the return stack, calls `_read`, then calls itself. Because it kills the top of
|
||||||
the return stack, calls _read, then calls itself. Because
|
the return stack, it can recurse indefinitely.
|
||||||
it kills the top of the return stack, it can recurse
|
|
||||||
indefinitely.
|
|
||||||
|
|
||||||
|
```
|
||||||
: ]
|
: ]
|
||||||
r @ Get the value of the return stack pointer
|
r @ Get the value of the return stack pointer
|
||||||
1 - Subtract one
|
1 - Subtract one
|
||||||
r ! Store it back into the return stack pointer
|
r ! Store it back into the return stack pointer
|
||||||
_read Read and compile one word
|
_read Read and compile one word
|
||||||
] Start over
|
] Start over
|
||||||
|
```
|
||||||
|
|
||||||
Notice that we don't need to exit, since we never come
|
Notice that we don't need to exit, since we never come back. Also, it's
|
||||||
back. Also, it's possible that an immediate word may
|
possible that an immediate word may get run during `_read`, and that `_read`
|
||||||
get run during _read, and that _read will never return!
|
will never return!
|
||||||
|
|
||||||
Now let's get compile running.
|
Now let's get compile running.
|
||||||
|
|
||||||
|
```
|
||||||
: main immediate ]
|
: main immediate ]
|
||||||
main
|
main
|
||||||
|
```
|
||||||
|
|
||||||
Next off, I'm going to do this the easy but non-portable
|
Next off, I'm going to do this the easy but non-portable way, and put some
|
||||||
way, and put some character constant definitions in.
|
character constant definitions in. I wanted them at the top of the file, but
|
||||||
I wanted them at the top of the file, but that would have
|
that would have burned too much of the return stack.
|
||||||
burned too much of the return stack.
|
|
||||||
|
|
||||||
|
```
|
||||||
: '"' 34 exit
|
: '"' 34 exit
|
||||||
: ')' 41 exit
|
: ')' 41 exit
|
||||||
: '\n' 10 exit
|
: '\n' 10 exit
|
||||||
@@ -313,48 +357,69 @@ main
|
|||||||
: '-' 45 exit
|
: '-' 45 exit
|
||||||
|
|
||||||
: cr '\n' echo exit
|
: cr '\n' echo exit
|
||||||
|
```
|
||||||
|
|
||||||
Next, we want to define some temporary variables for
|
Next, we want to define some temporary variables for locations `3`, `4`, and
|
||||||
locations 3, 4, and 5, since this'll make our code look
|
`5`, since this'll make our code look clearer.
|
||||||
clearer.
|
|
||||||
|
|
||||||
|
```
|
||||||
: _x 3 @ exit
|
: _x 3 @ exit
|
||||||
: _x! 3 ! exit
|
: _x! 3 ! exit
|
||||||
: _y 4 @ exit
|
: _y 4 @ exit
|
||||||
: _y! 4 ! exit
|
: _y! 4 ! exit
|
||||||
|
|
||||||
Ok. Now, we want to make THIRD look vaguely like FORTH,
|
```
|
||||||
so we're going to define ';'. What ; ought to do is
|
|
||||||
terminate a compilation, and turn control over to the
|
|
||||||
command-mode handler. We don't have one, so all we want
|
|
||||||
';' to do for now is compile 'exit' at the end of the
|
|
||||||
current word. To do this we'll need several other words.
|
|
||||||
|
|
||||||
Swap by writing out the top two elements into temps, and
|
OK. Now, we want to make THIRD look vaguely like FORTH,
|
||||||
then reading them back in the other order.
|
so we're going to define `;`. What `;` ought to do is
|
||||||
|
terminate a compilation, and turn control over to the
|
||||||
|
command-mode handler. We don't have one, so all we want
|
||||||
|
`;` to do for now is compile `exit` at the end of the
|
||||||
|
current word. To do this we'll need several other words.
|
||||||
|
|
||||||
|
Swap by writing out the top two elements into temps, and
|
||||||
|
then reading them back in the other order.
|
||||||
|
|
||||||
|
```
|
||||||
: swap _x! _y! _x _y exit
|
: swap _x! _y! _x _y exit
|
||||||
Take another look and make sure you see why that works,
|
```
|
||||||
since it LOOKS like I'm reading them back in the same
|
|
||||||
order--in fact, it not only looks like it, but I AM!
|
|
||||||
|
|
||||||
Addition might be nice to have. To add, we need to
|
Take another look and make sure you see why that works,
|
||||||
negate the top element of the stack, and then subtract.
|
since it LOOKS like I'm reading them back in the same
|
||||||
To negate, we subtract from 0.
|
order--in fact, it not only looks like it, but I AM!
|
||||||
|
|
||||||
|
Addition might be nice to have. To add, we need to
|
||||||
|
negate the top element of the stack, and then subtract.
|
||||||
|
To negate, we subtract from `0`.
|
||||||
|
|
||||||
|
```
|
||||||
: +
|
: +
|
||||||
0 swap -
|
0 swap -
|
||||||
-
|
-
|
||||||
exit
|
exit
|
||||||
|
|
||||||
Create a copy of the top of stack
|
```
|
||||||
|
|
||||||
|
Create a copy of the top of stack.
|
||||||
|
|
||||||
|
```
|
||||||
: dup _x! _x _x exit
|
: dup _x! _x _x exit
|
||||||
|
```
|
||||||
|
|
||||||
Get a mnemonic name for our dictionary pointer--we need
|
Get a mnemonic name for our dictionary pointer--we need
|
||||||
to compile stuff, so it goes through this.
|
to compile stuff, so it goes through this.
|
||||||
|
|
||||||
|
```
|
||||||
: h 0 exit
|
: h 0 exit
|
||||||
|
```
|
||||||
|
|
||||||
We're going to need to advance that pointer, so let's
|
We're going to need to advance that pointer, so let's
|
||||||
make a generic pointer-advancing function.
|
make a generic pointer-advancing function.
|
||||||
Given a pointer to a memory location, increment the value
|
Given a pointer to a memory location, increment the value
|
||||||
at that memory location.
|
at that memory location.
|
||||||
|
|
||||||
|
```
|
||||||
: inc
|
: inc
|
||||||
dup @ Get another copy of the address, and get the value
|
dup @ Get another copy of the address, and get the value
|
||||||
so now we have value, address on top of stack.
|
so now we have value, address on top of stack.
|
||||||
@@ -362,62 +427,82 @@ main
|
|||||||
swap Swap to put the address on top of the stack
|
swap Swap to put the address on top of the stack
|
||||||
! exit Write it to memory
|
! exit Write it to memory
|
||||||
|
|
||||||
, is a standard FORTH word. It should write the top of
|
```
|
||||||
stack into the dictionary, and advance the pointer
|
|
||||||
|
`,` is a standard FORTH word. It should write the top of
|
||||||
|
stack into the dictionary, and advance the pointer
|
||||||
|
|
||||||
|
```
|
||||||
: ,
|
: ,
|
||||||
h @ Get the value of the dictionary pointer
|
h @ Get the value of the dictionary pointer
|
||||||
! Write the top of stack there
|
! Write the top of stack there
|
||||||
h inc And increment the dictionary pointer
|
h inc And increment the dictionary pointer
|
||||||
exit
|
exit
|
||||||
|
```
|
||||||
|
|
||||||
' is a standard FORTH word. It should push the address
|
`'` is a standard FORTH word. It should push the address
|
||||||
of the word that follows it onto the stack. We could
|
of the word that follows it onto the stack. We could
|
||||||
do this by making ' immediate, but then it'd need to
|
do this by making `'` immediate, but then it'd need to
|
||||||
parse the next word. Instead, we compile the next word
|
parse the next word. Instead, we compile the next word
|
||||||
as normal. When ' is executed, the top of the return
|
as normal. When `'` is executed, the top of the return
|
||||||
stack will point into the instruction stream immediately
|
stack will point into the instruction stream immediately
|
||||||
after the ' . We push the word there, and advance the
|
after the `'`. We push the word there, and advance the
|
||||||
return stack pointer so that we don't execute it.
|
return stack pointer so that we don't execute it.
|
||||||
|
|
||||||
|
```
|
||||||
: '
|
: '
|
||||||
r @ Get the address of the top of return stack
|
r @ Get the address of the top of return stack
|
||||||
We currently have a pointer to the top of return stack
|
We currently have a pointer to the top of return stack
|
||||||
@ Get the value from there
|
@ Get the value from there
|
||||||
We currently have a pointer to the instruction stream
|
We currently have a pointer to the instruction stream
|
||||||
dup Get another copy of it--the bottom copy will stick
|
dup Get another copy of it--the bottom copy will stick
|
||||||
around until the end of this word
|
around until the end of this word
|
||||||
1 + Increment the pointer, pointing to the NEXT instruction
|
1 + Increment the pointer, pointing to the NEXT instruction
|
||||||
r @ ! Write it back onto the top of the return stack
|
r @ ! Write it back onto the top of the return stack
|
||||||
We currently have our first copy of the old pointer
|
We currently have our first copy of the old pointer
|
||||||
to the instruction stream
|
to the instruction stream
|
||||||
@ Get the value there--the address of the "next word"
|
@ Get the value there--the address of the "next word"
|
||||||
exit
|
exit
|
||||||
|
|
||||||
Now we're set. ; should be an immediate word that pushes
|
```
|
||||||
the address of exit onto the stack, then writes it out.
|
|
||||||
|
Now we're set. `;` should be an immediate word that pushes
|
||||||
|
the address of `exit` onto the stack, then writes it out.
|
||||||
|
|
||||||
|
```
|
||||||
: ; immediate
|
: ; immediate
|
||||||
' exit Get the address of exit
|
' exit Get the address of exit
|
||||||
, Compile it
|
, Compile it
|
||||||
exit And we should return
|
exit And we should return
|
||||||
|
```
|
||||||
|
|
||||||
Now let's test out ; by defining a useful word:
|
Now let's test out `;` by defining a useful word:
|
||||||
|
|
||||||
|
```
|
||||||
: drop 0 * + ;
|
: drop 0 * + ;
|
||||||
|
```
|
||||||
|
|
||||||
Since we have 'inc', we ought to make 'dec':
|
Since we have `inc`, we ought to make `dec`:
|
||||||
|
|
||||||
|
```
|
||||||
: dec dup @ 1 - swap ! ;
|
: dec dup @ 1 - swap ! ;
|
||||||
|
```
|
||||||
|
|
||||||
Our next goal, now that we have ;, is to implement
|
Our next goal, now that we have `;`, is to implement
|
||||||
if-then. To do this, we'll need to play fast and
|
`if-then`. To do this, we'll need to play fast and
|
||||||
loose with the return stack, so let's make some
|
loose with the return stack, so let's make some
|
||||||
words to save us some effort.
|
words to save us some effort.
|
||||||
|
|
||||||
First we want a word that pops off the top of the normal
|
First we want a word that pops off the top of the normal
|
||||||
stack and pushes it on top of the return stack. We'll
|
stack and pushes it on top of the return stack. We'll
|
||||||
call this 'tor', for TO-Return-stack. It sounds easy,
|
call this `tor`, for TO-Return-stack. It sounds easy,
|
||||||
but when tor is running, there's an extra value on the
|
but when `tor` is running, there's an extra value on the
|
||||||
return stack--tor's return address! So we have to pop
|
return stack--`tor`'s return address! So we have to pop
|
||||||
that off first... We better just bite the bullet and
|
that off first... We better just bite the bullet and
|
||||||
code it out--but we can't really break it into smaller
|
code it out--but we can't really break it into smaller
|
||||||
words, because that'll trash the return stack.
|
words, because that'll trash the return stack.
|
||||||
|
|
||||||
|
```
|
||||||
: tor
|
: tor
|
||||||
r @ @ Get the value off the top of the return stack
|
r @ @ Get the value off the top of the return stack
|
||||||
swap Bring the value to be pushed to the top of stack
|
swap Bring the value to be pushed to the top of stack
|
||||||
@@ -425,9 +510,12 @@ main
|
|||||||
r @ 1 + r ! Increment the return stack pointer--but can't use inc
|
r @ 1 + r ! Increment the return stack pointer--but can't use inc
|
||||||
r @ ! Store our return address back on the return stack
|
r @ ! Store our return address back on the return stack
|
||||||
;
|
;
|
||||||
|
```
|
||||||
|
|
||||||
Next we want the opposite routine, which pops the top
|
Next we want the opposite routine, which pops the top
|
||||||
of the return stack, and puts it on the normal stack.
|
of the return stack, and puts it on the normal stack.
|
||||||
|
|
||||||
|
```
|
||||||
: fromr
|
: fromr
|
||||||
r @ @ Save old value
|
r @ @ Save old value
|
||||||
r @ 1 - r ! Decrement pointer
|
r @ 1 - r ! Decrement pointer
|
||||||
@@ -435,32 +523,47 @@ main
|
|||||||
swap Bring return address to top
|
swap Bring return address to top
|
||||||
r @ ! Store it and return
|
r @ ! Store it and return
|
||||||
;
|
;
|
||||||
|
```
|
||||||
|
|
||||||
Now, if we have a routine that's recursing, and we
|
Now, if we have a routine that's recursing, and we
|
||||||
want to be polite about the return stack, right before
|
want to be polite about the return stack, right before
|
||||||
we recurse we can run { fromr drop } so the stack won't
|
we recurse we can run `{ fromr drop }` so the stack won't
|
||||||
blow up. This means, though, that the first time we
|
blow up. This means, though, that the first time we
|
||||||
enter this recursive routine, we blow our *real* return
|
enter this recursive routine, we blow our *real* return
|
||||||
address--so when we're done, we'll return up two levels.
|
address--so when we're done, we'll return up two levels.
|
||||||
To save a little, we make 'tail' mean { fromr drop };
|
To save a little, we make `tail` mean `{ fromr drop }`;
|
||||||
however, it's more complex since there's a new value on
|
however, it's more complex since there's a new value on
|
||||||
top of the return stack.
|
top of the return stack.
|
||||||
|
|
||||||
|
```
|
||||||
: tail fromr fromr drop tor ;
|
: tail fromr fromr drop tor ;
|
||||||
|
```
|
||||||
|
|
||||||
Now, we want to do 'if'. To do this, we need to convert
|
Now, we want to do `if`. To do this, we need to convert
|
||||||
values to boolean values. The next few words set this
|
values to boolean values. The next few words set this
|
||||||
up.
|
up.
|
||||||
|
|
||||||
minus gives us unary negation.
|
`minus` gives us unary negation.
|
||||||
|
|
||||||
|
```
|
||||||
: minus 0 swap - ;
|
: minus 0 swap - ;
|
||||||
|
```
|
||||||
|
|
||||||
If top of stack is boolean, bnot gives us inverse
|
If top of stack is boolean, `bnot` gives us inverse
|
||||||
|
|
||||||
|
```
|
||||||
: bnot 1 swap - ;
|
: bnot 1 swap - ;
|
||||||
|
```
|
||||||
|
|
||||||
To compare two numbers, subtract and compare to 0.
|
To compare two numbers, subtract and compare to `0`.
|
||||||
|
|
||||||
|
```
|
||||||
: < - <0 ;
|
: < - <0 ;
|
||||||
|
```
|
||||||
|
|
||||||
logical turns the top of stack into either 0 or 1.
|
`logical` turns the top of stack into either `0` or `1`.
|
||||||
|
|
||||||
|
```
|
||||||
: logical
|
: logical
|
||||||
dup Get two copies of it
|
dup Get two copies of it
|
||||||
0 < 1 if < 0, 0 otherwise
|
0 < 1 if < 0, 0 otherwise
|
||||||
@@ -468,18 +571,27 @@ main
|
|||||||
0 < 1 if original was > 0, 0 otherwise
|
0 < 1 if original was > 0, 0 otherwise
|
||||||
+ Add them up--has to be 0 or 1!
|
+ Add them up--has to be 0 or 1!
|
||||||
;
|
;
|
||||||
|
```
|
||||||
|
|
||||||
not returns 1 if top of stack is 0, and 0 otherwise
|
`not` returns 1 if top of stack is 0, and 0 otherwise
|
||||||
|
|
||||||
|
```
|
||||||
: not logical bnot ;
|
: not logical bnot ;
|
||||||
|
```
|
||||||
|
|
||||||
We can test equality by subtracting and comparing to 0.
|
We can test equality by subtracting and comparing to `0`.
|
||||||
|
|
||||||
|
```
|
||||||
: = - not ;
|
: = - not ;
|
||||||
|
```
|
||||||
|
|
||||||
Just to show how you compute a branch: Suppose you've
|
Just to show how you compute a branch: Suppose you've
|
||||||
compiled a call to branch, and immediately after it is
|
compiled a call to branch, and immediately after it is
|
||||||
an integer constant with the offset of how far to branch.
|
an integer constant with the offset of how far to branch.
|
||||||
To branch, we use the return stack to read the offset, and
|
To branch, we use the return stack to read the offset, and
|
||||||
add that on to the top of the return stack, and return.
|
add that on to the top of the return stack, and return.
|
||||||
|
|
||||||
|
```
|
||||||
: branch
|
: branch
|
||||||
r @ Address of top of return stack
|
r @ Address of top of return stack
|
||||||
@ Our return address
|
@ Our return address
|
||||||
@@ -488,16 +600,22 @@ main
|
|||||||
+ The address we want to execute at
|
+ The address we want to execute at
|
||||||
r @ ! Store it back onto the return stack
|
r @ ! Store it back onto the return stack
|
||||||
;
|
;
|
||||||
|
```
|
||||||
|
|
||||||
For conditional branches, we want to branch by a certain
|
For conditional branches, we want to branch by a certain
|
||||||
amount if true, otherwise we want to skip over the branch
|
amount if true, otherwise we want to skip over the branch
|
||||||
offset constant--that is, branch by one. Assuming that
|
offset constant--that is, branch by one. Assuming that
|
||||||
the top of the stack is the branch offset, and the second
|
the top of the stack is the branch offset, and the second
|
||||||
on the stack is 1 if we should branch, and 0 if not, the
|
on the stack is `1` if we should branch, and `0` if not, the
|
||||||
following computes the correct branch offset.
|
following computes the correct branch offset.
|
||||||
|
|
||||||
|
```
|
||||||
: computebranch 1 - * 1 + ;
|
: computebranch 1 - * 1 + ;
|
||||||
|
```
|
||||||
|
|
||||||
Branch if the value on top of the stack is 0.
|
Branch if the value on top of the stack is `0`.
|
||||||
|
|
||||||
|
```
|
||||||
: notbranch
|
: notbranch
|
||||||
not
|
not
|
||||||
r @ @ @ Get the branch offset
|
r @ @ @ Get the branch offset
|
||||||
@@ -505,36 +623,48 @@ main
|
|||||||
r @ @ + Calculate the new address
|
r @ @ + Calculate the new address
|
||||||
r @ ! Store it
|
r @ ! Store it
|
||||||
;
|
;
|
||||||
|
```
|
||||||
|
|
||||||
here is a standard FORTH word which returns a pointer to
|
`here` is a standard FORTH word which returns a pointer to
|
||||||
the current dictionary address--that is, the value of
|
the current dictionary address--that is, the value of
|
||||||
the dictionary pointer.
|
the dictionary pointer.
|
||||||
|
|
||||||
|
```
|
||||||
: here h @ ;
|
: here h @ ;
|
||||||
|
```
|
||||||
|
|
||||||
We're ALL SET to compile if...else...then constructs!
|
We're ALL SET to compile `if...else...then` constructs!
|
||||||
Here's what we do. When we get 'if', we compile a call
|
Here's what we do. When we get `if`, we compile a call
|
||||||
to notbranch, and then compile a dummy offset, because
|
to `notbranch`, and then compile a dummy offset, because
|
||||||
we don't know where the 'then' will be. On the *stack*
|
we don't know where the `then` will be. On the *stack*
|
||||||
we leave the address where we compiled the dummy offset.
|
we leave the address where we compiled the dummy offset.
|
||||||
'then' will calculate the offset and fill it in for us.
|
`then` will calculate the offset and fill it in for us.
|
||||||
|
|
||||||
|
```
|
||||||
: if immediate
|
: if immediate
|
||||||
' notbranch , Compile notbranch
|
' notbranch , Compile notbranch
|
||||||
here Save the current dictionary address
|
here Save the current dictionary address
|
||||||
0 , Compile a dummy value
|
0 , Compile a dummy value
|
||||||
;
|
;
|
||||||
|
```
|
||||||
|
|
||||||
then expects the address to fixup to be on the stack.
|
`then` expects the address to `fixup` to be on the stack.
|
||||||
|
|
||||||
|
```
|
||||||
: then immediate
|
: then immediate
|
||||||
dup Make another copy of the address
|
dup Make another copy of the address
|
||||||
here Find the current location, where to branch to
|
here Find the current location, where to branch to
|
||||||
swap - Calculate the difference between them
|
swap - Calculate the difference between them
|
||||||
swap ! Bring the address to the top, and store it.
|
swap ! Bring the address to the top, and store it.
|
||||||
;
|
;
|
||||||
|
```
|
||||||
|
|
||||||
Now that we can do if...then statements, we can do
|
Now that we can do `if...then` statements, we can do
|
||||||
some parsing! Let's introduce real FORTH comments.
|
some parsing! Let's introduce real FORTH comments.
|
||||||
find-) will scan the input until it finds a ), and
|
`find-)` will scan the input until it finds a `)`, and
|
||||||
exit.
|
exit.
|
||||||
|
|
||||||
|
```
|
||||||
: find-)
|
: find-)
|
||||||
key Read in a character
|
key Read in a character
|
||||||
')' = Compare it to close parentheses
|
')' = Compare it to close parentheses
|
||||||
@@ -777,4 +907,4 @@ Ok.
|
|||||||
[
|
[
|
||||||
|
|
||||||
_welcome
|
_welcome
|
||||||
|
```
|
||||||
Reference in new issue
Block a user