Skip to content

Mini Manual R3

Pablo Hugo Reda edited this page Aug 19, 2024 · 2 revisions

How the language works

A WORD is defined as a sequence of letters separated by spaces, there are three exceptions to improve the expressiveness of the language that are seen later.

Each word can be a number or is searched in the DICTIONARY.

If it is a valid number, in decimal 33, binary %101, hexadecimal $7f or fixed point 0.1 its value is pushed to DATA STACK.

Like all FORTH, the DATA STACK is the memory structure used to perform intermediate calculations and pass parameters between words.

If the word is NOT a valid number, then it is searched in the DICTIONARY, if it is not found, it is an ERROR, the rule is "every word used must be defined before".

The language has a BASIC DICTIONARY that is already defined, from which new WORDS are defined that will be used to build the program.

The start of execution is marked with the word colon : alone, not as a prefix.

The code is in a text file, you can include code files (word definitions) with the prefix caret ^ indicating the file name below.

The included code will only add to the caller's dictionary words that are marked as exported ( prefixes :: or ## ).

If the included code contains the start definition "colon" :, it will be executed before starting the program, to initialize structures or states.

We use | to indicate comment until the end of the line (the first exception to word separation).

We use ^ to indicate include a library, the name end in the end of the line too.

Each word can take and/or leave values of the DATA STACK, this is expressed with a state diagram of the stack before -- and after the word.

for example

+ | a b -- c

the word + takes the two elements from the top of the DATA STACK, consumes them and leaves the result. In addition to a modification in the stack, there may also be a lateral action, for example:

; | --

It does not consume or produce values in the stack but end a word

Prefixes in words

  • | ignored until the end of the line, this is a comment
  • ^ the name of the file to be included is taken until the end of the line, this allows filenames with spaces.
  • " the end of quotation marks is searched to delimit the content, if there is a double quotation mark "" it is taken as a quotation mark included in the string.
  • : define action
  • :: define action and this definition prevails when a file is included (* exported)
  • # define data
  • ## define exported data
  • $ define hexadecimal number
  • % defines binary number, allows the . like 0
  • ' means the direction of a word, this address is pushed to DATA STACK, it should be clarified that the words of the BASIC DICTIONARY have NO address, but those defined by the programmer, yes.

Programming occurs when we define our own words. We can define words as actions with the prefix:

:addmul + * ;

or data with the prefix #

#lives 3

: only is the beginning of the program, a complete program in r3 can be the following

:sum3 dup dup + + ;

: 2 sum3 ;

Conditional and Repeat

The way to build conditionals and repetitions is through the words ( and )

for example:

5 >? ( drop 5 )

The meaning of these 6 words is: check the top of the stack with 5, if it is greater, remove this value and stack a 5.

The condition produces a jump at the end of the code block if it is not met. It becomes an IF block.

r3 identifies this construction when there is a conditional word before the word (. If this does not happen the block represents a repetition and, a conditional in that this repetition that is not an IF is used with the WHILE condition.

for example:

1 ( 10 <?
	1 + ) drop

account from 1 to 9, while the Top of stack is less 10.

You have to notice some details:

There is no IF-ELSE construction, this is one of the differences with: r4, on the other hand, ColorForth also does not allow this construction, although it seems limiting, this forces to factor the part of the code that needs this construction, or reformulate the code.

In: r4 could be constructed as follows

...
1? ( notzero )( zero )
follow

It must become:

:choice 1? ( nocero ; ) zero ;

...
choice
follow

Sometimes it happens that rethinking code logic avoids ELSE without the need to do this factoring. There are also tricks with bit operations that allow you to avoid conditionals completely but this no longer depends on the language.

Another feature to note that it is possible to perform a WHILE with multiple output conditions at different points, I do not know that this construction exists in another language, in fact it emerged when the way to detect the IF and WHILE was defined

'list ( c@+
	1?
	13 <>?
	emit ) 2drop

Does this repetition meet that the byte obtained is not 0 1? and that is not 13 13 <>?, in any of the two conditions the WHILE ends

Another possible construction, that if it is in other FORTH, is the definition that continues in the following. For example, define1 adds 3 to the top of the stack while define2 adds 2.

:define1 | n -- n+3
	1 +
:define2 | n -- n+2
	2 + ;

Recursion

Recursion occurs naturally, when the word is defined with : it appears in the dictionary and it is possible to call it even when its definition is not closed.

:fibonacci | n -- f
	2 <? ( 1 nip ; )
	1 - dup 1 - fibonacci swap fibonacci + ;

Call Optimization

When the last word before a ; is a word defined by the programmer, both in the interpreter and in the compiler, the call is translated into JMP or jump and not with a CALL or call with return, this is commonly called TAIL CALL and saves a return in the chain of words called.

This feature can convert a recursion into a loop with no callback cost, the following definition has no impact on the return stack.

:loopback | n -- 0
	0? ( ; )
	1 -
	loopback ;

BASE word list:

;	| End of Word
(  )	| Word block to build IF and WHILE
[  ]	| Word block to build nameless definitions
EX	| Run a word through your address

Conditional, together with blocks make the control structures

There are three types of comparers: The simple ones, compare but do not affect stack. a -- a

0? 1?	| Zero and non-zero conditionals
+? -?	| Conditional positive and negative

The doubles, compare two numbers in stack and consume one, a b -- a

<? >?	| Comparison conditions
=? >=? 	| Comparison conditions
<=? <>?	| Comparison conditions
AND? 	| Logical conditioners AND 
NAND?	| Logical conditioners NOT AND

And the interval comparator that consumes two numbers, a b c -- a

IN?	| Conditional between

Words to modify the DATA STACK

This words modify the data stack, copy or eliminate values in the stack.

DUP	| a -- a a
DROP	| a --
OVER	| a b -- a b a
PICK2	| a b c -- a b c a
PICK3	| a b c d -- a b c d a
PICK4	| a b c d e -- a b c d e a
SWAP	| a b -- b a
NIP	| a b -- b
ROT	| a b c -- b c a
-ROT	| a b c -- c a b
2DUP	| a b -- a b a b
2DROP	| a b --
3DROP	| a b c --
4DROP	| a b c d --
2OVER	| a b c d -- a b c d a b
2SWAP	| a b c d -- c d a b

Words to modify the RETURN STACK

>R	| a --		; r: -- a
R>	| -- a 		; r: a --
R@	| -- a 		; r: a -- a

Logical operators

AND	| a b -- c
OR	| a b -- c
XOR	| a b -- c
NAND	| a b -- c
NOT	| a -- b

Arithmetic Operators

+	| a b -- c
-	| a b -- c
*	| a b -- c
/	| a b -- c
<<	| a b -- c
>>	| a b -- c
>>>	| a b -- c
MOD	| a b -- c
/MOD	| a b -- c d
*/	| a b c -- d
*>>	| a b c -- d
<</	| a b c -- d
NEG	| a -- b
ABS	| a -- b
SQRT	| a -- b
CLZ	| a -- b

Access to Memory

@ fetch a value from memory ! store a value in memory

@	| a -- [a] ; 64 bits
C@	| a -- byte[a] ; 8 bits
W@	| a -- word[a] ; 16 bits
D@	| a -- dword[a] ; 32 bits
@+	| a -- b [a] ; 64 bits
C@+	| a -- b byte[a] ; 8 bits
W@+	| a -- b word[a] ; 16 bits
D@+	| a -- b dword[a] ; 32 bits
!	| a b -- ; 64 bits
C!	| a b -- ; 8 bits
W!	| a b -- ; 16 bits
D!	| a b -- ; 32 bits
!+	| a b -- c ; 64 bits
C!+	| a b -- c ; 8 bits
W!+	| a b -- c ; 16 bits
D!+	| a b -- c ; 32 bits
+!	| a b -- ; 64 bits
C+!	| a b -- ; 8 bits
W+!	| a b -- ; 16 bits
D+!	| a b -- ; 32 bits

Help registers facility

Registers to traverse memory and read, copy or fill values

>A	| a --	; pop DATA STACK to A
A>	| -- a	; push A in DATA STACK
A+	| a --	; add to A

A@	| -- a	; 64 bits
A!	| a --
A@+	| -- a
A!+	| a --

CA@		| -- a	; 8 bits
CA!		| a --
CA@+	| -- a
CA!+	| a --

DA@		| -- a	; 32 bits
DA!		| a --
DA@+	| -- a
DA!+	| a --

>B	| a --	; pop DATA STACK to B
B>	| -- a	; push B in DATA STACK
B+	| a --	; add to B

B@	| -- a	; 64 bits
B!	| a --
B@+ | -- a
B!+ | a --

CB@	| -- a	; 8 bits
CB!	| a --
CB@+ | -- a
CB!+ | a --

DB@	| -- a	; 32 bits
DB!	| a --
DB@+ | -- a
DB!+ | a --

Copy and Memory Filling

Block memory operation, only for data memory

MOVE	| dst src cnt --	; 64 bits
MOVE>	| dst src cnt --
FILL	| dst fill cnt --
CMOVE	| dst src cnt --	; 8 bits
CMOVE>	| dst src cnt --
CFILL	| dst fill cnt --
DMOVE	| dst src cnt --	; 32 bits
DMOVE>	| dst src cnt --
DFILL	| dst fill cnt --

OS comunication

MEM		| -- a	; start free memory
LOADLIB	| "" -- a 
GETPROC | "" a -- a
SYS0	| a -- a
SYS1	| ab -- a
SYS2	| abc -- a
SYS3	| abcd -- a
SYS4	| abcde -- a
SYS5 	| abcdef -- a
SYS6	| abcdefg -- a
SYS7	| abcdefgh -- a
SYS8	| abcdefghi -- a
SYS9	| abcdefghij -- a
SYS10 	| abcdefghijk -- a