home .. forth .. misc mail list archive ..

SVFig talk part 5


Dear MISC readers:

Here is part 5 of 6 of Chuck Moore's presentation to the Silicon
Valley chapter of the Forth Interest Group on 5/22/99.
There is an html version at www.UltraTechnology.com/cm52299.htm
which is more colorful, shorter, and easier to read.

(part 5)
It is 4 of my 256 byte screens. Here is a bit of Color Forth

(>BLUE) FRAME (>BLACK) EMPTY VARIABL
E (>RED) BUF W H (>BLACK) : (>RED) ROW (>GREEN) A! (>GREY) 1
69 (>GREEN) BEGIN @+ IF + ; THEN
DROP NEXT (>GREY) 0 (>GREEEN) ; (>RED> RO
WS (>GREEN) DUP (>BLACK) BUF (>GREEN) ! ROW IF
DROP DROP ; THEN DROP (>GREY) -1 (>BLACK) H (>GREEN) +! DUP A + R
OWS ; (>GREY) 448 (>BLACK) H ! VGA (>GREY) 0 (>BLACK)
OVER ROWS (>GREY) -1280 (>BLACK) SWAP (>GREY) 640 447 (>BLACK) * + ROWS

First I am starting with a 640x480 screen full of stuff. I have a little
inverter in the middle of that screen and I want to convert that inverter
to a BMP file. But first I want to throw away all the margins.
So that what this waord FRAME implies.  I am throwing away the frame on
the image. I appologize because this code does not work with an emtpy screen,
it will crash.  But someday I will fix it and meanwhile I promise not to
construct and empty BMP file.

EMPTY throwing away everything else.  So what I have got is a
minimalist Forth system underneith here. Essentially nothing more
than Machine Forth is available to me at this point and time.
I'm defining some variables I ended up needing three: BUF W and H.
Width, Height, and an address to the buffer where I am constructing an image.

The first thing I do is define the word ROW which
is going to scan accress a row of the image checking to see if it is empty.
And if it is it returns a zero apparently if it falls out of this BEGIN
NEXT loop.  It uses fetch plus. It isn't very long, it is rather fast.
It goes through 159, which is 160 4 byte fetches looking for a non-zero
in the course of doing this.  So I am taking advantage of the fact that
the image is um; oh I forgot one tiny thing: this is a vga image
packed in a horrible way, first I have a little machine language word
that goes out and takes this and stores this in bytes.  So I really have
a byte image here. That cheat will go away as soon as I get to 256 colors
instead of 16.  This is the way it will be.

Given the word ROW I define the word ROWS to scan rows until I come to
something non-zero and that extends to here.  ROWS repeats, it's just
another loop.  These counted loops are a real pain.
I don't like counted loops but in a case like this this is the way you
have to do it.  So I have compiled this stuff then
I switch to interpreter and reference it.  I set H to 448.  I call VGA
to construct my image. VGA tells me where to put it.
I call ROWS to come down from the top until I find something non-zero.
I call ROWS to come up from the bottom until I find something non-zero
and then I go on.  Guess what happens next?

(>RED) COL (>GREEN) A! (>BLACK) H @ (>GREY) -1 (>BLACK) + (>GREEN) BEG
IN @+ (>LTBLUE) FF (>GREEN) AND IF + ;
THEN DROP A (>GREY) -544 (>GREEN) + A!
NEXT (>GREY) 0 (>GREEN) ; (>RED) COLS (>GREEN) DUP
(>BLACK) BUF (>GREEN) ! COL IF DROP ;
THEN DROP (>GREY) -1 (>GREEN) (>BLACK) W (>GREEN) +! D
UP A + COLS ; (>GREY) 640 (>BLACK) W
! BUF @ H @ (>GREY) 640 (>BLACK) * (>GREY) -1 (>BLACK)
 + OVER (>GREY) 639 (>BLACK) + COLS (>GREY) 2 (>BLACK)
 + SWAP COLS DROP W
@  (>GREY) 1  (>BLACK) + (>GREY) -2 (>BLACK) AND W !

The final word COL and COLS to do the same thing.
Then I switch to interpret and I use COL and COLS to
bracket the image other way.
And I end up with BUF marking the beginning of the image,
H containing the height, and W the width.

But there are some interesting things happening.
First up here in COL I want to know the height of the column
that I have got to examine. That is stored in H.
So I fetch the value of H and I subtract 1 from it
and I pass that as the controling argument for my
BEGIN NEXT loop.
And this is executed at compile time becauses it's black.
And when it switches from black to green it is compiled as a literal.
So this is an expression that is evaluated. Here is an example of BUF.
Now BUF is a variable but a black BUF is executed at compile time
so here I am getting an inline literal representing the
address of the buffer
instead of a subroutine call to the code for variables.
This is faster, a little bit bigger.

Here again in COLS I want to subtract 1 from the width.  -1 W plus-store.
-1 is a literal this reference to W is black so it gets executed
at compile time. As an inline literal again just like BUF.
Unfortunately there is a little problem here.
If this -1 had a white (black) space following it then it would be left
on the stack. I don't want it to be left on the stack.
I want it compiled as a literal. So this is a green space here
and then there is a white (black) space to be the prefix for W.
Now it's a little bit obscure on the listing but if you move a
cursor through here you will see the cursor change colors so
it will look alright.

Q: about Chuck's cursor being the ring character.
Chuck: I was thinking about that.  We need a secret decoder ring.
(laughter)

OK, so there is the code for framing the images that I am after.
Now here is where we get into the BMP file.  Here is the nice blue
BMP and all the nice hex digits. This is the boiler plate necessary to
build up the header for the BMP file.

(>BLUE) BMP (>BLACK) BUF @ (>LTBLUE) B71000 (>BLACK) BU
F ! , (>GREY) 4 (>RED> N, (>GREEN) SWAP (>BLACK) BUF
@ ! BUF (>GREEN) +1 ; (>RED) 2, (>GREY) 2 (>GREEN) N,
 ; (>BLACK) BM (>LTBLUE) 4D42 (>BLACK) 2, W @ H
@ (>GREY) 2 (>BLACK) */ (>GREY) 16 4 (>BLACK) * + (>GREY) 54 (>BLACK) +
 , (>GREY) 0 (>BLACK) , (>GREY) 118 (>BLACK) , (>GREY) 40 (>BLACK) , W
 @ , H @ , (>GREY) 1 (>BLACK) 2, (>GREY) 4 (>BLACK) 2, (>GREY)
0 (>BLACK) , W @ H @ (>GREY) 2 (>BLACK) */ , (>GREY) 0
(>BLACK) , (>GREY) 0 (>BLACK) , (>GREY) 0 (>BLACK) , (>GREY) 0 (>BLACK) , ORGB
(>LTBLUE) FFFFFF (>BLACK) , (>LTBLUE) FF00 (>BLACK) , (>LTBLUE) FF (>BLACK) ,
(>LTBLUE) FFFFF (>BLACK) , (>LTBLUE) E00000 (>BLACK) , (>LTBLUE) E0C
000 (>BLACK) , (>LTBLUE) FFFF00 (>BLACK) ,


(>LTBLUE) 808000 (>BLACK) , (>LTBLUE) 408080 (>BLACK) , (>LTBLUE) 4
0F040 (>BLACK) , (>LTBLUE) 40FC (>BLACK) , (>LTBLUE) E000C
0 (>BLACK) , (>LTBLUE) E00040 (>BLACK) , (>LTBLUE) C0FFFF (>BLACK)
, (>LTBLUE) 404040 (>BLACK) , (>LTBLUE) FCFCFC (>BLACK) , (>RED)
PACK (>GREEN) DUP (>GREY) 1 256 (>BLACK) */ (>GREEN) SW
AP (>GREY) 16 (>GREEN) * OR ; (>RED) ROW (>BLACK) W @
 2/ (>GREY) -1 (>BLACK) + (>GREEN) BEGIN @+ PA
CK (>GREY) 1 (>GREEN) N, A (>GREY) -2 (>GREEN) + A! NE
XT ; (>RED) ROWS (>GREEN) A! (>BLACK) H @ (>GREY) -1 (>BLACK)
+ (>GREEN) BEGIN ROW A (>BLACK) W @ -
(>GREY) 639 (>BLACK) + (>GREEN) + A! NEXT ; (>BLACK) R
OWS END

I have redefined the word comma to put down 32 bits constants as
they want. Then I use the comma also to put down a 32 bit color
lookup table appropriate for this image. And it slopped over into
the next screen. Then low and behold I define the words ROW and ROWS
again to go scan the image and put out the pixels. And I have defined
the word PACK to pack the data 2 pixels per byte as in inverted order
if they want it. And then finally I reference the word ROWS and I'm done.
I've got the file in a buffer. I know where the buffer is.
I can go back DOS and run a little program that writes it out to disk.

I would argue that these two screens which is all that is
all that is involved in BMP is a pretty small amount of code
to write a BMP file. The equivalent 'C' in the Supercharged Bit
Graphics that I looked at that told me how to do this was two
pages of 'C' I guess.  And of course that had lots of comments
and I don't have very many comments. I do have BMP at the top though
and that counts.

I also make the claim that given any application that you
want I can do it in about this much code that will do it. If
I want an editor I will have an editor in a handful of
these screens. If you want a protocol stack OK I will do some more.
There are some considerations. I don't have all this stuff compiled
in memory at the same time. I don't have a system which does everything.
All I have is enough code to generate the code that I need.

I would do this with any application. I used to this at Forth Inc.
If you wanted to do a report from your database you compiled the
code to do the report at the time that you requested the report.
It didn't take any time. It didn't require any name conflict resolution.
It gave you a small self-contained description of the report.  It was
a very efficient way of writing software.

And I think that this has gotten lost in these mega-Forth systems.
They have lost the ability to compile at runtime. All you can is
execute. They have lost the ability to do some things in interpret mode
in the course of compiling. Interpretting is more efficient than executing.
In order to compile anything you have to interpret it first.  If you are
going to interpret it you may as well execute it and be done with it
instead of putting it down and coming back and looking at it again
at execute time.

The side effect of doing this is that you end up with a system that
consists of a large number of small applications instead of a large
system will all the applications somehow embedded in it.
I think that is the cause of a lot of trouble.
It goes back to the comments I made about the standard.
The standard has everything built into the CORE wordset
if it were factored a little bit better it would be easier to understand
and provide better versitility.

I have a list of more questions people have asked.  But let me ask
you for some questions first before I go into those. (no questions)
You are not prepared?

part 6
questions