Skip to content

Forth semantics

Dmitry Ponyatov edited this page Jul 16, 2019 · 1 revision

Forth semantics

For the first time, we'll see how the simplest programming language in a world works, how it can be implemented in a modern runtime environment, and especially how can we use it to progress. I immediately want to emphasize that I do not propose to write programs in Forth. This is an ugly language to do it. Forth was designed in 70th as a command system to control radio-telescope equipment. Factically it was something like command shell with the ability to be reprogrammed by a user, and implemented in limits of computers were available that days. Nowadays Forth still has that power to be great command shell, especially for embedded devices, due to its simplicity to be implemented in a few days by more or less experienced programmer for any system.

Forth-like language engine needs only a few elements to work (the minimal interpreter core):

  • data stack
  • vocabulary
  • virtual machine command set

Forth is a stack machine which has operations manipulates data on the stack:

S = [ ]

and vocabulary as an associative array maps names (symbols) to passive (data) and active (executable) objects:

W = { }

To make the system work we need some set of executable primitives. In global architecture here, where we have only a single stack and one global vocabulary, we can define virtual machine commands as parameterless functions:

function NOP()	     {                    		  }
function PUSH(what)  { S.push(what)        	 }
function TOP()       { return S[S.length-1] 	}
function DUP()       { PUSH(TOP())         	 }

The problem is here is VM globality: if we need to do multitasking or especially cross-actor message passing, we need to have its own execution context in every thread and actor. As you’ll see later I solved this by making every object in a system to be a Forth machine (see frames). But just to understand the idea, we’ll only group stack and data into simple context object, and rewrite VM command functions:

As you can see, here we introduced next term -- execution context.

vm = {S:[],W:{}} 

function NOP(ctx)       {                              }
function PUSH(ctx,what) { ctx.S.push(what)             }
function TOP(ctx)       { return ctx.S[ctx.S.length-1] }
function DUP(ctx)       { PUSH(ctx,TOP(ctx))           }

As you can see, here we introduced next term -- execution context. This entity has a dynamic nature and corresponds to a state of a running thread:

  • pointer in a program marks a place where execution happens (in case of sequential code execution)
  • stack state
  • vocabulary binds symbols to data object values
  • connected resources, garbage collector and scheduler structures, any other data we want to account individually for every thread

Contextes not only holds thread states in the multitasking system, see also context switch happens when scheduler switches execution between multiple threads. Linked structure of multiple contexts also forms a search path when we do a lookup of some variable or call a method by its symbolic nam -- the well-known scope in programming languages.

Symbols lookup

Clone this wiki locally