SVFIG presentation: My experiments with ColorForth

My primary development environment is Plan 9.


Why Color Forth?

Chuck Moore is the Diogenes of computing. I try to adapt his ideas (that I comprehend) to my computing needs.

Color Forth parses at edit time. It merges the edit, compile and run times. This removes programmers' context switches and maximizes "flow".


How does my version differ from Chuck's Color Forth:

It is native to Plan 9.

The startup dictionary words are built with Plan 9 amd64 assembly language.

It does not use Shannon encoding for storing names.

It does not use blocks for storage. It uses the Plan 9's disk and virtual file systems.


Why Plan 9?

It is comprehensible, contemporary and has a network-aware ecosystem (device drivers and applications). It is a saner UNIX.


Stacks

Parameter stack (Data stack) and Return stack similar to other Forth's.


Parser

A word to be typed into the editor is classified by what it should do. Colours distinguish actions: tags tags.h. Fonts and font styles are used for the colour blind: color blind tags - WIP.

To enter a word in the editor, We type the first character of the requisite tag, such as T for a Textcomment and then type the word followed by a space. To enter a Stringcompile, the first character is a "(double quotes). A Stringcompile ends when the pattern 'space"space' ( " ) is typed. If the first character is not a recognized tag key, the editor assumes that the word being typed uses the same tag as the previous word. Tag

The function readfile() is the parser.


Utilities to work with the source:

cf/edit Source editor

cf/run JIT interpreter. The word main() is the entry point.

totext fromtext Translates source to/from a textual representation.

tops Generates a Postscript file from the source.

test Unit tests of the startup dictionary words.

test1 More unit tests.


Incantations

helloworld Edit the source. I am redirecting the standard error to a file to see the data stack while editing.


: bin ; cf/edit helloworld.cf >>[2]edit.log

helloworld factorial Run the source.


: bin ; cf/run helloworld.cf
Hello World!
: bin ; cf/run factorial.cf
24

To build a textual representation of the source:


cf/run totext.cf helloworld.cf helloworld.cft
cf/run totext.cf factorial.cf factorial.cft

To build a PDF of the source:


cf/run tops.cf helloworld.cf helloworld.ps
ps2pdf helloworld.ps helloworld.pdf
page -Rw helloworld.pdf

Source directories

C source

Color Forth source The extension .cf identifides Color Forth source files.


Future goals:

tohtml Generate HTML files of the source.

An Operating System similar to Inferno but using Color Forth instead of Limbo.

Editor and interpreter (Plan9port utilities) for Linux (x86, x86-64, PowerPC, and ARM), FreeBSD (x86, x86-64), Mac OS X (x86, x86-64, and Power PC), NetBSD (x86 and PowerPC), OpenBSD (x86 and PowerPC), SunOS (x86-64 and Sparc), Dragonfly BSD (x86-64).


Thanks

Chuck's ColorForth, Brad's Rainbow Forth, Uriel's Felix Forth (ff), JonesForth, Leo Brodie and Karig.


Dictionaries

dict.h

A Forth and a Macro dictionary. The Words in the dictionaries at startup are in assembler.

Forth words:

bye exit the process

forth switch dictionary to forth

macro switch dictionary to macro

jump defines a jump table. word... number jump word0 word1 word2 ... ;

Here variable containing the location of the end of used code space. Executing a variable puts it's address on the stack.

Vhere variable containing the location of the end of used data space.

Base variable containing the base used to interpret numbers.

nargs number of command line arguments.

arg puts the location of the nth command line argument on the stack. 1 arg puts the location of the 1st argument on the stack.

, puts the top of stack at the end of the code space and increment Here.

1, puts a byte from top of stack at the end of the code space and increment Here.

2, puts 2 bytes from the top of stack at the end of the code space and increment Here.

2, puts 4 bytes from the top of stack at the end of the code space and increment Here.

dup ( n -- n n )

drop ( n -- )

swap ( a b -- b a )

nip ( a b -- b )

over ( a b -- a b a )

tuck ( a b -- b a b )

rot ( a b c -- b c a )

-rot ( a b c -- c a b )

+ ( n1 n2 -- n1+n2 )

- ( n1 n2 -- n1-n2 )

* ( n1 n2 -- n1*n2 )

*/ ( n1 n2 n3 -- (n1*n2)/n3 )

/ ( n1 n2 -- n1/n2 -- quotient )

/mod ( n1 n2 -- n1/n2 n1%n2 -- quotient remainder )

mod ( n1 n2 -- n1%n2 -- remainder )

2* ( n -- n*2 )

shl ( n1 n2 -- n1<

2/ ( n -- n/2 )

sar ( n1 n2 -- n1>>n2 )

not ( n -- n ) not does not change the EFLAGS register. Performs a bitwise NOT operation (each 1 is set to 0, and each 0 is set to 1) on the top of stack.

and ( n1 n2 -- n1&n2 )

or ( n1 n2 -- n1|n2 )

xor ( n1 n2 -- n1 xor n2 )

count ( a -- a+1 *a ). Parses a counted string and puts the length byte on the top of stack and the address of the contents below it.

test ( n -- n ). Doesnot change the stack contents but sets the EFLAGS for a following if(jz) or -if(jns).

execute ( a -- ). calla

belongs ( n low high -- ). Sets the ZF and SF flag for if or -if to use. If low <= n <= high, ZF = 0 and SF = 1 else ZF = 1 and SF = 0. Remember that in Forth, false = 0, true = -1

max ( n1 n2 -- n )

min ( n1 n2 -- n )

@ ( a -- n ) Fetch 8 bytes from the address a

! ( n a -- ) Store n at a

c@ ( a -- c ) Fetch a byte from the address a

c! ( c a -- ) Store a byte at address a

A ( -- a ) Load A register to the stack

A! ( a -- ) Store the top of stack into the A register

@+ ( -- n ) Fetch from address in A; increment A by 8.

c@+ ( -- c ) Fetch a byte from address in A; increment A

!+ ( -- n ) Store to address in A; increment A by 8.

c!+ ( -- c ) Store a byte to address in A; increment A

B ( -- a ) Load B register to the stack

B! ( a -- ) Store the top of stack into the B register

B@+ ( -- n ) Fetch from address in B; increment B by 8.

cB@+ ( -- c ) Fetch a byte from address in B; increment B

B!+ ( -- n ) Store to address in B; increment B by 8.

cB!+ ( -- c ) Store a byte to address in B; increment B

fill ( address count byte -- ) Fill count bytes of memory, beginning at the address, with value byte

erase ( address count -- ) Fill count bytes of memory, beginning at the address, with zeroes

move ( source destination count -- ) Copies a region of memory count bytes long beginning at source,

to memory beginning at destination. The move works even if the memory regions overlap.

mark ( -- ) store HERE, VHERE, LIST, MACRO and FORTH at MARK

forget( -- ) restore HERE, VHERE, LIST, MACRO and FORTH from MARK

sysexits ( &errorstr -- ) exit

sysopen ( cstr mode -- fd )

sysclose ( fd -- n )

sysread ( fd a n -- n2 )

callsterminate if fd == 0 and returns -1. ( fd a n -- n2|-1 )

syswrite ( fd a n -- n2|-1 )

sysfstat ( fd a n -- n2 )

sysseek ( fd pos type -- n )

syscreate ( cstr mode perm -- fd )

sysbind ( cstrold cstrnew flags -- int )

sysmount ( fd afd cstrold flags spec -- int )

Macro words:

; compiles a RET or if the last assembly instruction was a call, it changes it to a jmp.

literalcompiles code that puts the current top of stack on the stack at run time.

if jz then. This uses the Eflags ZF and not the top of stack.

-if jns then. This usse the Eflags SF.

then the preceding if or -if jumps here.

begin until jumps here if the top of stack is not zero

until jump to begin if the top of stack is not zero. If the top of stack is a zero, drop the top of stack and continue after the jmp instruction to begin.

push move the top of stack to the return stack

pop move the top of return stack to the data stack

dup drop swap nip over tuck rot -rot Stack manipulation routines as in other forths.

+ - * */ / /mod mod 2* shl 2/ sar not and or xor count test @ ! c@ c! A A! @+ !+ c@+ c!+ c!- B B! B@+ B!+ cB@+ cB!+ These macros compile the corresponding forth words' code at HERE.


Work In Progress

cf/edit

Do not calculate the memory locations of HERE, VHERE, and such state. Use hardcoded offsets load/store those memory locations.

Support UNIX style keyboard shortcuts.