refrences
This commit is contained in:
195
references/ForthNotes/Literals_ifs_and_loops.md
Normal file
195
references/ForthNotes/Literals_ifs_and_loops.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Literals, ifs, and loops
|
||||
|
||||
**Source:** https://muforth.dev/threaded-code-literals-ifs-and-loops/
|
||||
|
||||
Threaded code: Literals, ifs, and loops – muforth
|
||||
|
||||
|
||||
# Threaded code: Literals, ifs, and loops
|
||||
|
||||
---
|
||||
|
||||
While writing my [introduction to threaded code](/threaded-code/) I suggested that non-leaf routines – colon words, in Forth lingo – can be represented as a list of addresses that get executed in sequence. This suggested that all code executes in a straight line and then returns to its caller. We all know this isn’t true, but threaded code that runs in a straight line is already tricky and tedious to explain; explaining literals and control structures at the same time seemed like too much.
|
||||
|
||||
But we are here now, and we understand how threading-in-a-straight-line works, so let’s see how loops, branches, and literals work in a threaded world.
|
||||
|
||||
---
|
||||
|
||||
In the real world, code doesn’t always execute in a straight line and then return – and yet, at the moment, which the machinery that we’ve defined so far, that’s all we can do. We also have no way to represent literal data – addresses or numbers that we might want to compute with. We can write a code word called `+` that pops two values off the data stack, adds them, and pushes the result, but if we want to add 8 to something, how do we do that?
|
||||
|
||||
With just three runtime code words we can add if/then/else, while loops, and literals to our system.
|
||||
|
||||
The names are arbitrary, but in many Forth systems the convention is to give them parenthesized names. These are not words that mere mortals normally execute; they exist as part of the hidden “runtime fabric” of the system. The parentheses suggest their “underlying” or “runtime” nature.
|
||||
|
||||
Here they are:
|
||||
|
||||
```
|
||||
(lit) Fetch into W the address pointed to by IP
|
||||
Increment IP by address size (skip literal)
|
||||
Push W onto the data stack
|
||||
Execute NEXT
|
||||
```
|
||||
|
||||
```
|
||||
(branch) Fetch into W the address pointed to by IP
|
||||
Set IP to W
|
||||
Execute NEXT
|
||||
```
|
||||
|
||||
```
|
||||
(0branch) Pop a value off the data stack
|
||||
If the value is zero, execute (branch)
|
||||
Otherwise, increment IP by address size (skip branch address)
|
||||
```
|
||||
|
||||
The `(branch)` above does an *absolute* branch. If you prefer relative branches, modify it thus:
|
||||
|
||||
```
|
||||
(branch) Fetch into W the address pointed to by IP
|
||||
Set IP to IP + W
|
||||
Execute NEXT
|
||||
```
|
||||
|
||||
Let’s assume we have a code word `+` that adds two values. To create a word that adds 8 to whatever is on the top of the stack, we would create a colon word with the following (textual) definition:
|
||||
|
||||
```
|
||||
: add8 8 + ;
|
||||
```
|
||||
|
||||
which, assuming an ITC system, would get compiled to:
|
||||
|
||||
```
|
||||
add8
|
||||
~~~~
|
||||
address of NEST ( all colon words start with this)
|
||||
address of (lit)
|
||||
8
|
||||
address of +
|
||||
address of UNNEST
|
||||
```
|
||||
|
||||
The `;` adds the address of UNNEST at the end and ends the compilation.
|
||||
|
||||
(This is getting outside the scope of our discussion, which is about the *execution machinery*, but in Forth systems `;` has *two* tasks: to compile the address of UNNEST, and to exit “compilation mode” – a special mode of *textual* interpretation that is the essence of the Forth compiler – and return to normal “interpretation” mode.)
|
||||
|
||||
For if/then/else and looping we use *flag* values – truth values – that we compute and leave on the stack for `(0branch)` to test and consume.
|
||||
|
||||
To make this even more concrete, let’s add five more code words to our system:
|
||||
|
||||
```
|
||||
< Pop two values off the data stack
|
||||
If the first is less than the second, push -1
|
||||
Otherwise, push 0
|
||||
Execute NEXT
|
||||
```
|
||||
|
||||
```
|
||||
0= Pop a value off the data stack
|
||||
If the value is zero, push -1
|
||||
Otherwise, push 0
|
||||
Execute NEXT
|
||||
```
|
||||
|
||||
```
|
||||
dup Pop a value off the data stack
|
||||
Push it back onto data stack
|
||||
Push it a second time
|
||||
Execute NEXT
|
||||
```
|
||||
|
||||
```
|
||||
2dup Pop two values off the data stack
|
||||
Push them back onto data stack, in the same order
|
||||
Push them a second time, also in the same order
|
||||
Execute NEXT
|
||||
```
|
||||
|
||||
```
|
||||
swap Pop two values off the data stack
|
||||
Push them back onto data stack, in the reverse order
|
||||
Execute NEXT
|
||||
```
|
||||
|
||||
In many Forth systems -1 is the preferred “true” value because it is represented (on almost all machines!!) by an all-ones bit pattern, which can be logically ANDed with other values... but we digress. ;-)
|
||||
|
||||
From our definition of `(0branch)` above it should be clear that *any* non-zero value is true, and only zero is false.
|
||||
|
||||
Let’s write a simple loop. (There is a better, more efficient, and more idiomatic way to do this, but it is outside the scope of this example. ;-)
|
||||
|
||||
```
|
||||
-8 begin 1 + dup 0= until
|
||||
```
|
||||
|
||||
This will get compiled to the following “address list”:
|
||||
|
||||
```
|
||||
00 address of (lit)
|
||||
04 -8
|
||||
08 address of (lit)
|
||||
0c 1
|
||||
10 address of +
|
||||
14 address of dup
|
||||
18 address of 0=
|
||||
1c address of (0branch)
|
||||
20 08
|
||||
24 ...
|
||||
```
|
||||
|
||||
We have to `dup` the value before we test it with `0=` because `0=` *consumes* the value before pushing its true or false flag.
|
||||
|
||||
The word `begin` compiles no code; it “marks” the location that `(0branch)` later branches back to.
|
||||
|
||||
I’ve added memory addresses in hex (which assume a byte-addressed machine with 32-bit addresses) to make the branch destination easier to describe.
|
||||
|
||||
We write if/then like this:
|
||||
|
||||
```
|
||||
2dup < if swap then
|
||||
```
|
||||
|
||||
This compiles to the following:
|
||||
|
||||
```
|
||||
00 address of 2dup
|
||||
04 address of <
|
||||
08 address of (0branch)
|
||||
0c 1c
|
||||
10 address of swap
|
||||
1c ...
|
||||
```
|
||||
|
||||
As before, we `2dup` the values because `<` consumes them.
|
||||
|
||||
Like `begin`, `then` compiles no code; it resolves the forward branch that `if` compiled. Words like if, then, begin, and until are called *compiling* words because they execute at compile time and do something special, like resolve a forward or backward branch address.
|
||||
|
||||
Adding an `else` clause looks like this:
|
||||
|
||||
```
|
||||
0= if 4 else 8 then +
|
||||
```
|
||||
|
||||
This code compiles to:
|
||||
|
||||
```
|
||||
00 address of 0=
|
||||
04 address of (0branch)
|
||||
08 1c
|
||||
0c address of (lit)
|
||||
10 4
|
||||
14 address of (branch)
|
||||
18 24
|
||||
1c address of (lit)
|
||||
20 8
|
||||
24 address of +
|
||||
28 ...
|
||||
```
|
||||
|
||||
`if` compiles the `(0branch)`, `else` compiles the `(branch)` and resolves the forward `(0branch)`, and `then` resolves the forward `(branch)`.
|
||||
|
||||
Remember: `(0branch)` means branch if *false*.
|
||||
|
||||
---
|
||||
|
||||
[Send feedback](/cdn-cgi/l/email-protection#ba9f8d8d9f8c8f9f8c889f8c829f8c8b9f8cde9f8d899f8d8e9f8c8f9f8d889f8e8a9f8cdf9f8c839f8cde9f8c889f8cd99f8c8f9f8cde9f8c8b9f8c899f8c829f8c839f8cdf9f8c8f9f8d899f88df9f8c899f8cdc9f8cde85c9cfd8d0dfd9ce879f8fd8d7cfdcd5c8ced29f8fde9f888aeed2c8dfdbdedfde9f888ad9d5dedf9f89db9f888af6d3cedfc8dbd6c99f88d99f888ad3dcc99f88d99f888adbd4de9f888ad6d5d5cac9) on this page (last edited 2017 May 01)
|
||||
Browse [all pages](/all-pages/)
|
||||
Return [home](/)
|
||||
Reference in New Issue
Block a user