Forth First Aid
Where can I find more information about Forth programming language?
Here are some useful links for beginners:
Don't forget to check onine versions of Starting Forth, a great book by Leo Brodie - http://home.iae.nl/users/mhx/sf.html and Thinking Forth by Leo Brodie - http://thinking-forth.sourceforge.net/ . There also exists a Stephen Pelc book Programming Forth - http://www.mpeltd.demon.co.uk/books.htm .
There is a newsgroup, COMP.LANG.FORTH, which is dedicated to Forth language.
Where can I use pure Forth in crontabs? Can you give more details?
"Pure" Forth can be used immediately after word "#( task-name". And word ")#" returns nnCron to classic mode. Therefore, you can define any Forth words between "#(" and ")#"
Example:
#( task1 : x1 .... ; : x2 .... ; : x3 .... ; Action: x1 x2 x3 )# #( task2 Action: x3 x2 x1 )#
How can I declare a variable or define a word outside of a particular task, e.g. in the very beginning of a crontab file?
Outside of "#(" and ")#", you can use any Forth words or expression by using them within "<% ... %>" construct. This type of construct can be located in any place of a crontab, including its beginning.
Example:
<% VARIABLE x VARIABLE y : z .... x @ .... y ! ; %> #( task1 Action: z )#
Which string types are used in SP-Forth?
1) Character string (au-string):
The strings of this type are most often used strings in SP-Forth. Such a string is specified by a cell pair ( a u -- ) representing its starting address and length in characters. Use a S" xxx" syntax to create character string. For example, after you enter in console
S" my first string"
and press <ENTER>, two values will be placed on the stack - the address of the first character of the newly created string and the number of its characters.
Most of nnCron words, that are working with strings are expecting for character string as their argument. For example, you can use the word TYPE ( a u -- ) to print character string to the console:
S" my first string" TYPE
Pay attention to the fact, that the length of a strings created using S" xxx" syntax can not exceeds 255 characters.
2) Zero-terminated string (az-string):
These strings are used when calling API functions and when the string length can exceed 255 characters: the length of zero-terminated strings is not limited.
Use Z" xxx" or S" xxx" DROP syntax to create zero-terminated string:
Z" null-terminated string" S" null-terminated string" DROP
Special words are available to convert character string to zero-terminated string and vice-versa:
#( test_count NoActive Action: \ converting character string to a zero-terminated string: S" test string" S>ZALLOC \ separating string address to an address and length in characters: ASCIIZ> \ displaying the string in a message: MsgBox )#
3) Counted string (ac-string):
Counted string is a sequence of bytes, that represents the string contents.
The first byte in this sequence holds the string length in characters (binary
representation of the number of data characters). This byte is called length
character. Counted string in memory is identified by the address of its
length character. This string type was widely used in early Forth implementations.
Currently there are not too many words, that are expecting for counted strings
as their arguments. The length of counted strings can not exceeds 255 characters.
Use C" xxx" syntax to create counted string. In addition, counted string can be created by using the word PLACE
C" counter string" S" your string" buffer_name PLACE
You can convert counted string to a character string using the word COUNT ( a -- a u ), that separates counted string address to an address and length in characters.
#( test_count1 NoActive Action: \ creating counted string: Ñ" test string" \ converting counted string to a character string \ and printing it to console: COUNT TYPE CR )# #( test_count2 NoActive CREATE my_strbuffer 256 ALLOT Action: \ creating counted string: S" test string" my_strbuffer PLACE \ printing buffer address to console: my_strbuffer . CR \ separating address to an address and length in characters \ and printing character string to console: my_strbuffer COUNT TYPE CR \ displaying character string in a message: MSG: "%my_strbuffer COUNT%" )#
What means does Forth provide to check if two strings are identical?
You can use word COMPARE which returns 0 if compared objects are identical.
Example:
S" first string" S" second string" COMPARE 0= IF MSG: "Strings are the same" ELSE MSG: "Strings are different" THEN
What other "advanced" ways of working with strings are there?
This is a very difficult question to answer, for there are so many possible answers. To make the answer as precise as possible, let me use you some examples.
Example 1:
#( forth_strings \ creating 2 strings up to 255 characters each \ with static memory allocation CREATE str1 256 ALLOT CREATE str2 256 ALLOT Action: \ Word PLACE puts a string into specified location S" This is a first string" str1 PLACE \ Displaying a message with the first string. Using word COUNT \ to convert an address into address with string length. MSG: "%str1 COUNT%" \ +PLACE adds another string to the specified string S" , and this is an addition to it..." str1 +PLACE MSG: "%str1 COUNT%" \ removing 5 leftmost characters from str1 \ and placing the result to str2 str1 COUNT 5 /STRING str2 PLACE MSG: "%str2 COUNT%" \ removing 5 rightmost characters from str1 \ and placing the result to str2 str1 COUNT 5 - 0 MAX str2 PLACE MSG: "%str2 COUNT%" \ placing the first 10 characters from str1 to str2 str1 COUNT 10 MIN str2 PLACE MSG: "%str2 COUNT%" \ placing to str2 the last 10 characters from str1 str1 COUNT DUP 10 - 0 MAX /STRING str2 PLACE MSG: "%str2 COUNT%" \ placing 10 characters from str1 to str2, \ starting with the 11-th character str1 COUNT 10 /STRING 10 MIN str2 PLACE MSG: "%str2 COUNT%" )#
Example 2:
#( forth_strings1 \ creating 4 strings up to 255 characters each \ with static memory allocation CREATE str_1 256 ALLOT CREATE str_2 256 ALLOT CREATE str_3 256 ALLOT CREATE str_4 256 ALLOT \ A new word, StringReplace str2 str1 str3 str4, \ Searches str_1 for substrings matching str_3, \ and if it finds anything, it replaces all found matches by \ íà str_4. The result is placed to str_2. \ If no matches are found, it places \ the entire str_1 to str_2, without any modifications \ The example is not uneversal in that it was written to process \ a particular phrase. : StringReplace ( -- ) str_2 0! str_1 COUNT BEGIN OVER SWAP str_3 COUNT SEARCH WHILE >R SWAP 2DUP - str_2 +PLACE str_4 COUNT str_2 +PLACE R> str_3 C@ /STRING REPEAT str_2 +PLACE DROP ; \ Another new word: StringReplace str str str str \ It can function in the same way \ that the preceding one, but it is universal \ because it uses local variables, i.e.. \ it doesn't have to use str_1, str_2 etc. : StringReplace2 { a2 a1 u1 a3 u3 a4 u4 \ rest -- a2 u2 } a2 0! a1 u1 BEGIN OVER SWAP a3 u3 SEARCH WHILE TO rest SWAP 2DUP - a2 +PLACE a4 u4 a2 +PLACE rest u3 /STRING REPEAT a2 +PLACE DROP ; Action: \ placing a long phrase into str_1 S" This string is not a very good sample. " str_1 PLACE S" But not just a sample." str_1 +PLACE \ defining a search phrase S" not" str_3 PLACE \ defining a replacement phrase S" " str_4 PLACE StringReplace str_2 MSG: "%str_2 COUNT%" str_2 S" test test test" S" es" S" ouris" StringReplace2 MSG: "%str_2 COUNT%" )#
Example 3:
#( forth_strings2 VARIABLE position \ New word. It is used to search a string for matches \ with another string and (if any found) to return the position \ of the first character of this match. \ Position numbering starts with 1. If the substring is not found, \ it returns 0. Of course, it is also possible to start the position count from 0 \ and to return -1 if nothing is found : StringGetPos { a1 u1 a2 u2 -- pos} a1 u1 a2 u2 SEARCH IF DROP a1 - 1+ ELSE 2DROP 0 THEN ; Action: \ comparing the strings and writing the result \ into variable position S" 123456789" S" 567" StringGetPos position ! \ displaying the result (should be 5) MSG: "Position: %position @%" )#
There are many other words that are used to work with strings. For example:
\ returns 0 if the strings are identical S" str1" S" str2" COMPARE \ returns 0 if the strings are identical (not case-sensitive) S" str1" S" Str2" ICOMPARE \ returns TRUE if a match is found S" str1" S" ?tr*" WC-COMPARE \ the same as SEARCH, but not case-sensitive S" xxxstryyu" S" STR" ISEARCH
How to convert a number to a string and vice versa?
Number to string:
nnCron has word N>S. Here is its definition:
: N>S ( u -- addr u) DUP >R ABS S>D <# #S R> SIGN #> ;
It is used in this way:
100 N>S \ produces string 100
Similar results can be obtained with the help of construct S>D <# #S #>, and in this case one can specify how many characters should be converted, add necessary characters to the string, and convert characters with a sign:
101 S>D <# #S #> \ produces string 101 101 S>D <# # # # # # #> \ produces string 00101 -101 DUP ABS S>D <# #S ROT SIGN #> \ produces string -101 101 S>D <# # 58 HOLD #S #> \ produces string 10:1
Notes:
To get a string representation of a number in hexadecimal form, word N>H:
200 N>H \ produces string C8
String to number:
For this purpose, use S>NUM. It is used in this way:
S" 128" S>NUM \ produces number 128
Example:
#( test_int_to_string \ picking IP addresses one by one, in order, starting with 128.128.128.1 \ and ending at 128.128.128.10 NoActive CREATE STR 256 ALLOT VARIABLE NUMBER Action: BEGIN S" 128.128.128." STR PLACE NUMBER @ 1 + NUMBER ! NUMBER @ N>S STR +PLACE TMSG: "%STR COUNT%" 1 \ instead of just displaying a resulting IP address, \ you could, for example, "feed" it to HOST-EXIST: NUMBER @ 10 = UNTIL )#
What means does Forth provide for creating a simple loop?
If you know in advance how many times the loop should be repeated, then best way to do it would be to use DO ... LOOP construct (definite loop):
10 0 DO <body of the loop> LOOP
Here 10 is the limit and 0 is the index (a number which is incremented by 1 with each iteration). To access the index from the body of the loop, use word "I" (please note that this is an uppercase letter "i", not a slash or vertical bar)it puts the current value of the index into data stack .
The loop will be executed over and over until the index becomes equal to the limit. Therefore, the above loop will be executed 10 times.
Example:
Action: 3 0 DO \ message will be displayed 3 times TMSG: "Inside the loop" 1 \ numbering starts with 0, index equals 1 I 1 = \ displaying another message IF TMSG: "Second loop" 1 THEN LOOP
To cause the index to be incremented by any other number then 1, use word +LOOP:
50 0 DO <body of the loop> 5 +LOOP \ incrementing by 5
To force an exit from a definite loop, use word LEAVE:
Action: 5 0 DO \ message will be displayed four times TMSG: "Inside the loop" 1 \ exiting the loop after the fourth iteration I 3 = IF LEAVE THEN LOOP
If the number of iterations is not known, you can use the BEGIN ... UNTIL construct (indefinite loop, which is repeated until a specified condition is met). It is used in the following way:
BEGIN <body of the loop> <condition> UNTIL
Such a loop will be executed at least one time and then its iterations will go on until a certain condition becomes true:
Action: BEGIN \ repeating this message TMSG: "Warning! xxx.txt not exist!" 3 \ until the following file appears FILE-EXIST: "c:\xxx.txt" UNTIL
Here is another type of a indefinite loop (or loop with a condition):
BEGIN <condition> WHILE <body of the loop> REPEAT
The loop is repeated until the condition is true.
And here is an example showing you how you can display a value of index (I) without creating an additional variable:
3 0 DO I S>D <# 0 HOLD #S S" I=" HOLDS #> 1 TimeMsgBox LOOP
How can I "insert" a numerical variable into a text string so that I could use it, for example, in MSG: or in SEND-KEYS:?
Here's how:
%VARIABLE_NAME @%
Now this variable's value can be displayed in a message (MSG, TMSG) and/or printed with SEND-KEYS etc.
Example:
VARIABLE MyVariable \ creating a variable Action: 13 MyVariable ! \ setting the value of MyVariable to 13 MSG: "MyVariable = %MyVariable @%" START-APP: "notepad" PAUSE: 1000 BEGIN \ starting a loop SEND-KEYS: "%MyVariable @% {DELAY 300}" MyVariable @ 1 - MyVariable ! MyVariable @ 0 = UNTIL \ ending the loop SEND-KEYS: "{ENTER}That's the end, folks!"
Please keep in mind that you can also insert into a text string any Forth word which return a string ( -- addr u) or a 32-bit integer ( -- n). In order to do that, you just have to surround the word by percent signs.
Example:
\ the number of milliseconds since system startup, \ is inserted into a string MSG: "%GetTickCount%"
And what about a double number? Is it possible to "insert" it into a string, too?
Sure. Here is an example of this:
#( test_dir_size NoActive 2VARIABLE size Action: RECURSIVE DIR-SIZE: "c:\cpp" size 2! MSG: "c:\cpp size = %size 2@ <# #S #>%" )#
Handling double numbers is discussed here.
Did I get it right that within a string (between quotation marks) one can place a pair of percent signs ("%%") and insert any sequence of Forth words between them?
Yes, you got it right. "%...%" construct is dynamically processed, that is to say, when the prefix string is parsed, the sequence between "% % " signs is analyzed separately and word EVALUATE is applied to this substring. Please note that if you use postfix notation you will have to use word EVAL-SUBST in order to "unfold" contents of "%...%" construct.
Example:
\ creating a buffer CREATE buf_ex 256 ALLOT Action: \ placing a string to this buffer S" example" buf_ex PLACE \ printing the string to console including the contents of buf_ex S" string containing %buf_ex COUNT% buffer" EVAL-SUBST TYPE CR \ displaying a message with the same string MSG: "string containing %buf_ex COUNT% buffer"
To your information: if a Forth word or a variable returns a string, you can skip the %...% altogether when using postfix notation:
\ "prefix" notation: MSG: "%USERNAME%" \ postfix notation: USERNAME MsgBox \ of course, you can do it this way too (but less efficiently): S" %USERNAME%" EVAL-SUBST MsgBox
How can I see the list of all Forth words in nnCron's dictionary?
That is very simple: open FORTH console, enter command WORDS and hit <ENTER>. But be prepared to see a list of about 3000 words! :)
And the following task will send the entire dictionary to nncron.out so that you can study it later:
#( words-task NoActive Action: WORDS )#
(The console must be closed).
Hmmmm... And how can I find the word I need (I only can recall a part of it)?
It is just for such an occasion that nnCron's developer has defined word WORDS-LIKE in plugin tools.spf.
How can I use value of a variable (or current value on the stack) as a parameter of some word?
Since nnCron 1.89rc2 there are special words to use numbers and strings from the stack as a parameters:%n esPICK% and %n esPICKS%.
Besides that, each prefix-type word (ÕÕÕ:) has a corresponding postfix word (either ÕÕÕ, or some other name). And it is these postfix words that should be used when the current value on the stack is used as an argument.
Examples:
BEEP: 1000 500 -> var_name @ var_name1 @ BEEP WIN-SHOW: "xxx" -> S" xxx" WIN-SHOW WIN-HIDE: "xxx" -> S" xxx" WIN-HIDE \ ... FILE-APPEND: "file" "xxx" -> S" xxx" S" file" FAPPEND FILE-WRITE: "file" "xxx" -> S" xxx" S" file" FWRITE FILE-COPY: "file1" "file2" -> S" file1" S" file2" FCOPY
Prefix words of "ÕÕÕ:" type were introduced expressly in order to save users the trouble of puzzling out how to use the reverse Polish notation.
How to write data to a file or read from one?
You can easily write text data to a file using features provided by nnCron. Read the Help sections discussing such words as LOG:, FILE-WRITE:, FILE-APPEND:...
As to reading an entire text file, the simplest way to do this is as follows:
S" file-name" FILE
or even
FILE: filename
The first method is convenient when you have to get the file's name form a variable, and the second one is for use between percent signs (% %), so it takes as its parameter (file name) the sequence of characters to the end of the line.
\ first method
: 2nd-file S" %JT%\test.txt" EVAL-SUBST FILE ;
Action:
FILE-APPEND: "%JT%\new.txt" "%2nd-file%"
\ second method
MSG: "%FILE: test.txt%"
Example:
#( test_read_from_file NoActive \ reading 64 characters from file test.txt \ and placing them to array file_contents. \ displaying a message with the content of array. CREATE file_contents 256 ALLOT Action: PAD 64 S" test.txt" FREAD file_contents PLACE MSG: "%file_contents COUNT%" )#
To read information from a file line by line, we'll need to use SP-Forth word READ-LINE. Here is its description:
READ-LINE ( c-addr u1 fileid -- u2 flag ior ): read the next line from the file specified in fileid into memory address c-addr. No more then u1 characters will be read. String buffer c-addr must have length of minimum u1+2 characters (including end of line characters). If the action is successful, flag will be "true" and ior will equal zero. If end of line is reached before u1 characters has been read, then u2 is a number of actually read characters without "end of line" characters. If u1=u2, end of line has been reached already.
#( test_read_by_line NoActive \ Reading file test.txt line by line (in a loop) \ into array list-contents \ and displaying each line in a message box. \ Lines in file should not be longer then 255 characters. VARIABLE list-file CREATE list-contents 258 ALLOT Action: S" test.txt" R/O OPEN-FILE-SHARED THROW list-file ! \ writing the string into the second postion of array list-content BEGIN list-contents 1+ 255 list-file @ READ-LINE THROW WHILE \ Number of read characters remains on the stack.. \ We will save it in the first position of array \ in order to get a string with a character count list-contents C! MSG: "%list-contents COUNT%" REPEAT DROP list-file @ CLOSE-FILE DROP )# #( test_read_by_line1 NoActive \ Reading file test.txt line by line (in a loop) \ into variable list-contents \ and displaying each line in a message box. USER-VALUE list-file USER-VALUE list-contents \ maximum length of line (number of characters) 10240 CONSTANT max-line-size Action: \ Allocating sufficient space in memory \ and recording its address into list-contents max-line-size ALLOCATE THROW TO list-contents S" test.txt" R/O OPEN-FILE-SHARED THROW TO list-file BEGIN list-contents max-line-size 2- list-file READ-LINE THROW WHILE \ The number or read characters remains on the stack. \ Adding it to address of allocated memory \ and adding 0 to get a zero-terminated string list-contents + 0 SWAP C! MSG: "%list-contents ASCIIZ>%" REPEAT DROP list-file CLOSE-FILE DROP list-contents FREE DROP )#
A list is a special type of data structure where each element (node) contains a reference to the next element in the list which unites the data and simplifies navigation of the list. Such a list is often called "linked list". The last element of a list does not contain a reference and returns a zero if you attempt to get the address of the next element. This permits us to easily learn if the list is empty or not. To declare and name a list use word VARIABLE.
Here are the words for working with lists:
To illustrate this, let's create an elementary list, add some nodes to it, print their values to console, purge the list and make sure that it is really empty:
#( test_list NoActive \ creating a list VARIABLE MY_LIST \ defining a word that will print \ contents of a node to console : show_values NodeValue . CR ; Action: \ adding nodes and their values to the list 1 MY_LIST AppendNode 2 MY_LIST AppendNode 3 MY_LIST AppendNode \ printing to console addresses or the first, \ second and third nodes MY_LIST @ . CR
MY_LIST @ @ . CR
MY_LIST @ @ @ . CR \ now printing to console \ the values stored in the list nodes ['] show_values MY_LIST DoList \ printing the same values "manually" MY_LIST @ NodeValue . CR MY_LIST @ @ NodeValue . CR MY_LIST @ @ @ NodeValue . CR \ purging the list MY_LIST FreeList \ making sure that the list is empty: MY_LIST @ IF S" List is not empty" TYPE CR ELSE S" List is empty" TYPE CR THEN )#
Word ['] compiles the address of the next word in the definition as a literal, i.e. instead of the actual word we substitute the address where its definition is stored.
Another "real-life" example:
#( task1 NoActive VARIABLE f-list : f-action NodeValue ASCIIZ> MsgBox ; : f-free NodeValue FREE DROP ; Action: ['] faction f-list DoList ['] f-free f-list DoList f-list FreeList )# #( task2
NoActive
Action: f-list OFF FOR-FILES: x:\xxx\*.* IS-DIR? 0= IF FOUND-FULLPATH S>ZALLOC f-list AddNode THEN ;FOR-FILES f-list @ IF task1 RUN THEN )#