OforthTutorial : Oforth syntax

I - Interpreter revisited

Oforth uses the same mecanism as Forth to compile functions and methods.

Forth mecanism, created by Charles H. Moore's, is a brillant idea. The interpreter has two modes, interpret mode and compile mode :

  • When interpreter is into "interpret" mode, each word read is performed at once.
  • When interpreter is into "compile" mode, each word read is added to the current definition.
  • Some special words are immediate and are performed even if interpreter is into "compile" mode.

Oforth implements the same philosophy. Let's see how Oforth compiles a function :

import: date

: hello
   Date now . "Hello, World " . ;

  • #: is a function. When the interpreter reachs this function, it is into "interpret" mode so it performs this function. #: reads the next word, creates the function object with name this word, creates a new definition, and changes interpreter mode to "compile".
  • Intepreter reads "Date" : it is a class. If the interpreter was into "interpret" mode, this object would be pushed on the stack. But, because interpreter is now into "compile" mode, this object is just added to the current definition.
  • Interpreter reads #now : it is a method and it is added to the current definition.
  • Same for #.
  • Now, the interpreter reads #" : it is a special function, an immediate function. Even if interpreter is into compile mode, this function is performed. #" reads all charaters until a '"' is read and creates the corresponding string into the current definition.
  • #. is just added to the current definition.
  • #; is also an immediate function so it is performed even if the interpreter is into "compile" mode. #; closes the current definition and changes interpreter mode back to "interpret".

#hello is now compiled and the interpreter is ready to perform next input.

We could say that Oforth has no syntax : interpreter reads words and performs them, thats all. Some of these words allow to create functions and methods. Some of these words are immediate functions and will compile oforth instructions into the current definition : Oforth source is... an oforth executable.

II - self and super

#self is an immediate function : it allows to push a method receiver on the stack. Remember that, when a method is performed, the receiver is removed from the stack. #self will push it back on the stack if needed. If used into a function, #self will push null on the stack : a function has no receiver.
An example :

>Integer method: square self dup * ;

#super is an immediate function used into methods: like #self, it will push the receiver on the stack. Next method to be perform will not use implementation of current class but implementation of its parent. #super is not allowed into functions.
An example :

TCPSocketClient method: initialize(aHost, aRemotePort)
   super initialize
   null := port
   aHost := host
   aRemotePort := remotePort ;

Here, #initialize first calls #initialize of TCPSocket before initializing TCPSocketClient specific features.

III - Tests

Tests are used to perform instructions only if a condition is true (1) or false (0).

#ifTrue: is an immediate function. When this instruction is met, the top of the stack is removed and compared to false. In this case, instruction flow jumps to the next ] found (at the same level, of course).

For instance :

Integer method: or self ifTrue: [ drop true ] ;

#or returns the logical or operation between two integers (see lang/Boolean.of). The receiver is pushed on the stack. If the receiver is true, the second integer is removed and the result is true. Otherwise (the receiver is false), the result is the second integer.

Other tests are :
#ifFalse: [ instructions ]
#ifNull: [instructions ]
#ifNotNull: [ instructions ]
#ifZero: [ instructions ]
Each time, the top of the stack is removed and compared. If the test is false, instructions flow jumps to the next ].

It is always possible to use #else: after a test block. In this case, instructions after #else: block are performed if the test is false.

Number virtual: sgn
   self 0 < ifTrue: [ -1 ] else: [ self 0 > ] ;

#sgn returns sign of the receiver (see lang/Number.of) : 1 if >0, -1 if <0, 0 if 0.


IV - while

#while is used to loop on instructions while a condition is true. Syntax is :

while(test) [ instructions ]

test if performed and the result is removed from the stack. If the result is true, instructions are performed and instruction flow returns to the test. When the test is false, instructions are not performed and the flow jumps after ].

Integer method: gcd self while ( dup 0 <> ) [ tuck rem ] drop ;

#gcd computes greatest common divisor between two integers (see lang/Integer.of). Loop is initialized by pushing self on the stack. While the top of the stack is not 0, the two integers are replaced by the top and the reminder of those integers. When the result is 0, gcd is the second integer. Please note #dup use into the while test : while removes the top of the stack.


V - Integer loops

Three integer loops are available. All these loops need a local variable dedicaded that will hold current index.

#loop: is an immediate function that will remove an integer (the value max of the loop) from the stack and will loop from 1 to this integer. Syntax is :

n loop: i [ instructions ]

Into instructions, i can be used to push the current index on the stack. For instance :

: .s
| i o |
   .depth ifZero: [ "Empty" .cr return ]
   .depth loop: i [
      i pick -> o
      System.Out '[' <<c i << "] (" << o class << ") " << o << cr
      ] ;

If stack is not empty, function #.s (see lang/System.of) loops on stack depth. For each index, it picks object at position i and print it.

If the initial index is not 1, #for: can be used. This function removes 2 integers from the stack and index will take values between those 2 integers.

min max for: i [ instructions ]

"n loop: i [ ... ]" is equivalent to "1 n for: i [ ... ]"

Last loop : if step is not 1, #step: can be used. #step: removes 3 objets from the stack and will loop using a step. Syntax is :

min max step step: i [ instructions ]

#step is not dedicated to integers. It can also be used with other numbers. For instance :

>: u | i | 1.2 10.2 1.3 step: i [ i .cr ] ;
>u
1.2
2.5
3.8
5.1
6.4
7.7
9
ok



VI - Loops and collections

A specific function, #forEach:, allows to traverse collections. As for integer loops, it needs a dedicated variable. Syntax is :

aCollection forEach: o [ instructions ]

#forEach: removes a collection from the stack and will loop on each element of the collection. Into instructions, the variable will hold the current object of the collection. For instance :

Object method: apply(m) | o | self forEach: o [ o m perform ] ;

Method #apply will apply a runnable on each element of a collection (see lang/collec/Collection.of). #forEach: removes the collection from the stack and loops on each element. For each element, this element will be pushed on the stack, then #apply parameter is pushed on the stack and performed.

VII - Try and catch blocks

#try: is an immediate function. It allows to perform instructions and to catch exceptions that may be thrown. #try: needs a dedicated local variable. Syntax is :

try: e [ instructions1 ] when: [ instructions2 ]

Instructions1 are performed. If an exception is not raised during instructions1, instructions2 are not performed. If an exception is raised, this exception is catched and instructions2 are performed. If so, exception catched is stored into variable e.

For instance:

: testThrow Exception throw("An exception") ;

: testCatch
| e |
   try: e [ testThrow "No exception" println ]
   when: [ "Exception catched" println e println ]
   "Done" println ;
>testCatch
Exception catched
An exception
Done

Here, exception is raised by testThrow. So "No exception" is not performed and #when: block is performed.

VIII - Other Oforth immediate functions

##[ is an immediate function used to begin a block body. Instructions after #[ and before next ] will be part of the block body. When #[ is performed, the block object is pushed on the stack.

#[ instructions ]

#return can be used to return from a block or from a function or method.

#return does not have parameter. If a method or a block must return a value, this value is returned on the stack.

#[ is an immediate function. It allows to create lists : it reads items until next #] is read.

: t [ 1, 2, 3, 4 ] ;

#\ is an immediate function. It allows to adds comments inside or outside functions, until end of line is reached.

#' is an immediate function. It reads next character until #' is met and add it to the current definition.

#" is an immediate function. It reads next string until #" is met and add it to the current definition.

## is an immediate function. It reads next word, retrieves word with this name and add it to the current definition.

#$ is an immediate function. It reads next word and creates a symbol with this name.

#0b is an immediate function. It reads next string, compute integer corresponding to this binary value and add it to the current definition.

And, last one, #0x is also an immediate function. It reads next string, compute integer corresponding to this hexadecimal value and add it to the current definition.

Oforth Documentation

Current available documentation :

I - Tutorials

  • Oforth basics : here
  • Oforth syntax : here
  • Writing Oforth programs : here
  • Oforth classes : here
  • Oforth parallelism : here
  • Oforth node : Work in progress...

II - Reference

  • Lang package reference : here