nnCron and Forth Programing Language
nnCron is written in SP-Forth, which is an implementation of Forth programing language. Therefore, use of Forth has become an integral part of nnCron's make-up. Right within a task, a user is allowed to create variables and arrays, use any existing Forth words and create new ones. Actually, if one wants, he can use "pure Forth" within a task. Extended mode syntax also borrows many of its features from Forth.
Forth is a very powerful, and hence, somewhat dangerous tool. Careless use of Forth may crash nnCron or even destabilize the entire system. nnCron provides an option to put certain restrictions on how far one can go when manually writing task code: see description of variable SyntaxRestriction used in nncron.ini.
nnCron documentation contains answers to frequently asked questions about Forth, and the present section provides only elementary information, the basics of Forth which are difficult to do without when actively using nnCron. Use of variables, constants and arrays is discussed in a separate chapter.
In your experiments with Forth, you can use the Forth console which is one of nnCron's built-in tools: this will greatly facilitate understanding of how Forth works. Examples of nnCron tasks using branching, variables and Forth words can be found in file titled example.tab.
Forth syntax is very simple: it is considered one of the simplest in the entire history of programing.
In short, it can be described as follows: "A Forth program is a set of words with spaces between them". "Word" in Forth is a name assigned to a definition. In other words, a Forth word is an analogue of a function (method) in other programing languages. Words are executed in the order in which they are placed in the source text. For example, a hypothetical program emulating behavior of a typical workaholic (all work and no rest):
WAKE_UP BREAKFAST EAT WORK DINNER EAT WORK SLEEP
Forth programing adds up, essentially, to performing operations with words. Even signs of arithmetical operations (e.g.+, -, *), symbol of "comment till the end of line" (\) and even symbols of start and end of a task definition (" #( " and " )# ") are Forth words in their own right. Therefore, make sure to separate all words with spaces.
Example:
\ the line below is incorrect: \ there is no space after the "comment" word \Error! \ the first lime of the task below \ also contains an error: there is no space \ between the task name \ and the word marking the task beginning #(task_name \ error! \ ... )#
Forth uses so-called postfix notation (it is also called reverse Polish notation). In this type of notation, operators (logical or arithmetical) are placed after the values on which the operation is performed.
For example, an operation of adding two numbers would look in postfix notation like this:
2 3 +
In order to make postfix notation seem more logical to you, you can read this example in this way: "Let's take number 2 and number 3 and add them up". (I suppose that you have figured out the result? Yes, it is 5!). :)
The reason why Forth uses postfix notation is because it is convenient to use with "stack", which is an area of memory in which data are placed and removed according to LIFO principle (last in, first out). The simplest way to imagine a stack is to see it as a pile of numbers placed one upon the other.
For example, let's take the following string:
2 3 +
It actually means that numbers 2 and 3 are placed on stack and added, and 5 remains on stack.
Here is a little more complex example:
4 3 2 1 + - +
What will happen here? I will try to explain it you step by step: words "+" and "-" (and in Forth, these arithmetical signs are words in their own right) work with two uppermost elements of stack. We put on stack 4, 3, 2 and 1. Therefore, we have 2 and 1 on the top of the stack. It is these two numbers that are added by the first "postfix" plus sign:
4 3 2 1 + - +
After the first addition, we have three numbers remaining on the stack: 4, 3, and the sum we have just obtained: 3. The "postfix" minus sign also applies to the two topmost elements of the stack, therefore, now we subtract 3 from 3 (3-3):
4 3 3 - +
Now we have only two numbers on the stack: 4 and 0, and they are added by the next plus. The end result is 4:
4 0 +
Well, how do you like it? Slightly paraphrasing Aristotle, we could say that roots of study are bitter, but its fruit is sweet!. :)
Postfix notation also appears in frequently used IF THEN constructs. Instead of more traditional forms:
IF <condition> THEN <action_if_TRUE> IF <condition> THEN <action_if_TRUE> ELSE <action_if_FALSE>
we have the following constructs in Forth:
<condition> IF <action_if_TRUE> THEN <condition> IF <action_if_TRUE> ELSE <action_if_FALSE> THEN
The general rule of branching in Forth is as follows: after IF,
we write everything that should be done if a check on the condition returns
TRUE (-1), and after ELSE we write everything that
should be done if a check on the condition returns FALSE (0).
THEN just signifies that the end of construct.
ELSE is not an obligatory element, one could just use IF THEN.
Example:
FILE-EXIST: "test.txt" \ condition IF MSG: "exist!" \ this will be done if the specified file exists ELSE MSG: "not exist!" \ this will be done if the specified file does not exist THEN
Sometimes it is more convenient to use another type of construct then IF ... ELSE ... THEN: this is CASE ... OF ... ENDOF ... ENDCASE construct, which is usually used when it is necessary to select one of several known values:
<value_on_stack> CASE <a possible_value> OF action (value on stack equals the possible value) ENDOF <a possible_value> OF action (value on stack equals the possible value) ENDOF \ ... <a possible_value> OF action (value on stack equals the possible value) ENDOF <a possible_value> OF action (value on stack equals the possible value) ENDOF DUP OF <a possible_value> OF action (value on stack equals none of the possible values) ENDOF ENDCASE
Example:
#( test_case NoActive Action: START-APPW: program.exe \ placing on stack the return code of a program ExitCodeProc \ displaying a message depending on the return code CASE \ return code 0 0 OF MSG: "Everything okay!" ENDOF \ return code 1 1 OF MSG: "I can't be bothered getting started!" ENDOF \ return code 2 2 OF MSG: "Leave me alone!" ENDOF \ любые return code DUP OF MSG: "Unknown error!" ENDOF ENDCASE )#
Creating of new Forth words is very much like creating variables, but the syntax is different:
: <name_of_word> <body of word> ;
Have you noticed the spaces after ":" and before ";"? Yes, colon and semicolon are Forth words too! :) I hope, now you understand why names of variables, new words and tasks cannot contain spaces? (I will give the answer to this question, just in case: from the viewpoint of Forth, a space is a separator of words. If a name contains a space, it will be treated by Forth as two different identifiers).
Example:
\ let's create a new word "x2" which would multiply by 2 \ the uppermost value on the stack: : x2 2 * ; \ now let's test word "x2" in action: 2 x2 \ now the stack contains the result of multiplying 2 * 2 16 x2 \ now the stack contains the result of multiplying 16 * 2
New words can be defined not only through numbers, but also by using earlier defined words.
Example:
\ if word "x2" is already defined, we can \ create a new word which, for example, will \ subtract4 from the result produced by "x2". \ let's call the new word "x2minus4" : x2minus4 x2 4 - ; \ let's test "x2minus4" in action: 8 x2minus4 \ now the stack contains the result \ of expression (8 * 2) - 4 1 x2minus4 \ now the stack contains the result \ of expression (1 * 2) - 4
New Forth words should be created in the beginning of a task (before Action: section), and they can be used inside this section. Let's convert the above example to an nnCron task:
#( words_task NoActive \ we are going to start this task manually \ creating two new words : x2 2 * ; : x2minus4 x2 4 - ; Action: \ displaying the result of "3 x2" MSG: "3 x2 = %3 x2%" \ displaying the result of "6 x2minus4" MSG: "6 x2minus4 = %6 x2minus4%" \ displaying the result of "1 x2minus4" MSG: "1 x2minus4 = %1 x2minus4%" )#
New Forth words created by you are global, i. e. they can be "seen" from other tasks and are shared by all instances of any given task. In order to avoid unnecessary confusion, please make sure that all names of new words are unique.
Some of words used in Forth and in nnCron return so called double numbers, i. e. numbers which require 64 bits to represents them instead of 32 bits. A double number takes the same amount of space in stack as two usual numbers.
Forth has special words for handling double-numbers, e.g.:
D. | output a double number to console |
D+ | add two double numbers |
D- | subtract one double number from another (d1 - d2) |
DNEGATE | revert the sign of a double number |
D< | put on stack TRUE if d1 is less then d2 |
D= | put on stack TRUE if d1 equals d2 |
D0= | put on stack TRUE if d equals zero |
Literal double numbers are written with a period in the end. You can convert single number to a double number using the special word S>D. To convert a double number to a single number, use word D>S.
Examples:
\ adding two double numbers 1000000. 2000000. D+
\ comparing two double numbers and displaying a message 10240. 90000. D< IF MSG: "<" ELSE MSG: ">" THEN
\ checking if two double numbers are equal
150000. 150000. D=
IF MSG: "Equal!"
ELSE MSG: "Not equal"
THEN