Tools and Utilities

Conditional Compilation

The following words allow the use of [IF] ... [ELSE] ... [THEN] blocks to control which pieces of code are compiled/executed and which are not. These words behave in the same manner as compiled definitions of IF ... ELSE ... THEN structures but take immediate effect even outside definitions. Nesting is supported.

variable cc-level       \ -- addr
A variable used for error checking during compilation. It holds the number of [IF] ... [THEN] blocks we currently inside. Cleared at cold start.

VOCABULARY Compilation? \ --
The COMPILATON? vocabulary holds the control code for passover operations during a conditional block.

: PassOver      \ n --
Skip n levels of nested conditional code.

: have          \ "<name>" -- flag
Look to see if the word exists in the CONTEXT search order and return flag true if found.

: [defined]     \ "<name>" -- flag                            Forth200x
Look to see if the word exists in the CONTEXT search order and return flag TRUE if the word exists. This is an immediate version of HAVE.

: [undefined]   \ "<name>" -- flag                            Forth200x
The inverse of [DEFINED]. Return TRUE if <name> does not exist.

: [ELSE]        \ --                                          15.6.2.2531
Marks the start of the ELSE clause of a conditional compilation block.

: [IF]          \ flag --                                     15.6.2.2532
Marks the start of a conditional compilation clause. If flag is TRUE compile/execute the following code, otherwise ignore all up to the next [ELSE] or [THEN]. Note that the parser for [IF] and [ELSE] is really dumb. You cannot comment out an [ELSE] or or THEN. However they are nestable.

: [THEN]        \ --                                          15.6.2.2533
Marks the end of a conditional compilation clause.

: [ENDIF]       \ --
Marks the end of a conditional compilation clause.

The following definitions exist in the Compilation? vocabulary.

: [IF]          1+  1 cc-level +!  ;
[IF] increments the number of levels to skip.

: [THEN]        1-  -1 cc-level +!  ;
[THEN] decrements the number of levels to skip.

: [ENDIF]       1-  -1 cc-level +!  ;
[ENDIF] decrements the number of levels to skip.

: [ELSE]        1- dup if 1+ then ;
[ELSE] switches the redirection level.

Console and development tools

The following words provide useful diagnostic routines and/or general purpose functions in the spirit of the ANS Forth TOOLS and TOOLS EXT wordsets.

: .tabword      \ addr$ --
Displays tabbed string, CRing if required. Variable TABWORDSTOP contains the size of a tab.

: .tabwordN     \ addr$ --
Displays tabbed *NAME*, CRing if required. Variable TABWORDSTOP contains the size of a tab.

0 value PauseConsole    \ -- device
Some tools, e.g. WORDS and DUMP will pause periodically if PauseConsole returns the same value as the output device in OP-HANDLE. Any interactive console can select this behaviour with:

 op-handle @ to PauseConsole

You can stop any pausing with:

 0 to PauseConsole

: flushKeys     \ --
Flush any pending input that might be returned by KEY.

: HALT?         \ -- flag
Used in listed displays. This word will check the keyboard for a pause key (<space> or <lf> or <cr>). If a pause key is pressed it will then wait for another key. The return flag is TRUE if the second key is not a pause key. If the first key is not a pause key TRUE is returned and no key wait occurs. Line Feed characters are ignored.

: DUMP          \ addr u --                                   15.6.1.1280
Display an arbitrary block of memory in a 'hex-dump' fashion which displays in both HEX and printable ASCII.

: LDUMP         \ addr len -- ; dump 32 bit long words
Display (dump) len bytes of memory starting at addr as 32 bit words.

: .S            \ --                                          15.6.1.0220
Display to the console the current contents of the data stack. If the number base is not HEX than a dump is also made in HEX.

: .rs           \ --
Display to the console the current contents of the return stack. Where possible a word name is also displayed with the data value.

: ?             \ a-addr --                                   15.6.1.0600
Display the contents of a memory location. It has the same effect as @ ..

: WORDS         \ --
Display the names of all definitions in the wordlist at the top of the search order.

: .FREE         \ --
Text display of size of unused dictionary area in Kbytes

: mat           \ -- ; MAT <wildcardpattern>
Search the current search-order for all definitions whose name matches the wild-carded expression supplied. Expressions can contain either an asterix '*' to match 0 or more characters, or can be a query '?' to mark any single character.

: similars      { | temp[ MAX_PATH ] -- }
A slightly faster version of MAT with a limited range. The definitions listed will contain <pattern> within their name. The <pattern> can only contain printable ASCII characters.

: sim           \ -- ; SIM <pattern>
A slightly faster version of MAT with a limited range. The definitions listed will contain <pattern> within their name. <pattern> can only contain printable ASCII characters. A synonym for SIMILARS.

Zero Terminated Strings

A group of simple primitive words to work with 0 terminated ASCII strings.

: caddr>zaddr   \ caddr zaddr --
Copy a counted string to a 0 terminated string.

: .z$           \ zaddr --
TYPE a zero terminated string.

: z$.           \ zaddr --
TYPE a zero terminated string. Depending on the output device, this may be a bit more efficient than .z$.

: .z$EXPANDED           \ zaddr --
TYPE a zero terminated string after macro expansion.

: z$,           \ c-addr u --
Lay the given string in the dictionary as a zero terminated string. The end of the string is not aligned.

: $>z,          \ addr --
Lay a zero terminated string in the dictionary, given a counted string. The end of the string is not aligned.

: z",           \ "cc<quote>" --
"comma" in a zero terminated string from the following text. The end of the string is not aligned.

: $>ASCIIZ      \ caddr -- zaddr
Convert a counted string to a zero terminated string. The converted string is in a thread-local buffer of limited lifetime

: asciiz>$      \ zaddr -- caddr
Convert a zero terminated string to a counted string. The conversion happens in place.

Structures

The data structure words implement records, fields, field types, subrecords and variant records.

The following syntax is used:


STRUCT <name>
  n FIELD <field1>
  m FIELD <field2>
  SUBRECORD <subrec1>
    a FIELD <sf1>
    b FIELD <sf2>
  END-SUBRECORD
END-STRUCT

A structure may contain multiple subrecords, and subrecords may be nested.

A field adds its base offset to the given address [that of the record or subrecord]. A record returns its length, and so can be used as an input to field.


   len FIELD <name>
 n len ARRAY-OF <name>

Subrecords are checked for stack depth, like branch structures. They may be nested as required.

Variant records describe an alternative view of the current record or subrecord from the start to the current point. The variant need not be of the same length, but the larger is taken


SUBRECORD <name>
   ----
  VARIANT <name2>  ..............  END-VARIANT
END-SUBRECORD

use


<structure> BUFFER: <name>

to create a new instance of a previously defined structure.

The VFX structures package has also been enhanced to handle areas of overlapping data called "UNIONS". Consider the example:


   struct test
     int a
     int b
     union
       int c
       int d
      part
       1 field e1
       1 field e2
      part
       int f
       subrecord jim
         float jim1
         int jim2
       end-subrecord
     end-union
     20 field g
   end-struct

Each part of a union is overlapped, but fields within a part are treated as individual items. So, in the above example, c and f refer to the same cell, but c and d refer to different cells.

: struct        \ -- addr 0 ; -- size
Begin definition of a new structure. Use in the form STRUCT <name>. At run time <name> returns the size of the structure.

: end-struct    \ addr n --
Terminate definition of a structure.

: field         \ n <"name"> -- ; Exec: addr -- 'addr
Create a new field within a structure definition of size n bytes.

: int           \ <"name"> -- ; Exec: addr -- 'addr
Create a new field within a structure definition of size one cell.

: array-of      \ n #entries size -- n+(#entries*size)
Create a new field within a structure definition of size #entries*size.

: subrecord     \ n -- n csp 0  [parent]
Begin definition of a subrecord.

: end-subrecord \ n csp len -- n+len
End definition of a subrecord.

: variant       \ n -- n csp 0
Currently an alias for subrecord. Begin a variant.

: end-variant   \ n csp m -- n|m
Terminate a variant clause.

: union         \ currentOffset -- csp 0 currentOffset currentOffset
Begin UNION definition block.

: part          \ max base last -- max base start
Begin definition of alternative data description within a UNION.

: end-union     \ csp maxLength baseOffset lastOffset -- next-offset
Mark end of a UNION definition block.

: field-type    \ n --
Define a new field type of size n bytes. Use in the form <size> FIELD-TYPE <name>. When <name> executes used in the form <name> <name2> a field <name2> is created of size n bytes.

Forth200x structures

The Forth200x standards effort has adopted s notation that is compatible with VFX Forth, but changes some names.

: begin-structure       \ -- addr 0 ; -- size
Begin definition of a new structure. Use in the form BEGIN-STRUCTURE <name>. At run time <name> returns the size of the structure. The Forth200x version of the MPE word struct.

: end-structure         \ addr n --
Terminate definition of a structure. The Forth200x version of the MPE word end-struct.

: +FIELD        \ n <"name"> -- ; Exec: addr -- 'addr
Create a new field of size n bytes within a structure definition. The Forth200x version of the MPE word field.

: cfield:       \ n1 <"name"> -- n2 ; Exec: addr -- 'addr
Create a new field of size 1 CHARS within a structure definition,

: field:        \ n1 <"name"> -- n2 ; Exec: addr -- 'addr
Create a new field of size 1 CELLS within a structure definition. The field is ALIGNED.

ENVIRONMENT queries

The ENVIRONMENT system was defined by ANS Forth to enable you to find out about the underlying Forth system. The needs of modern portable libraries have proven the ENVIRONMENT system to be inadequate and so it is little used. The ENVIRONMENT system may be removed in a future standard.

You use the system through the word ENVIRONMENT?

 caddr len -- false | i*x true

where caddr/len represents the name of a query. If the system does not know this query, it just returns false (0). If it does know the query, it return the relevant value with true (-1) on top of the stack.

In VFX Forth, ENVIRONMENT? is implemented by searching a vocabulary called ENVIRONMENT. If the query is found, it is executed.

Predefined queries

The words in this section are defined in the ENVIRONMENT vocabulary.

#255 constant /COUNTED-STRING   \ -- n
Maximum length of a counted string.

picnumsize constant /HOLD       \ -- n
Maximum size of HOLD area.

padsize constant /PAD           \ -- n
Maximum size of PAD.

8 constant ADDRESS-UNIT-BITS    \ -- n
Number of bits in an address unit (byte in this system).

true constant CORE              \ -- TRUE
The full CORE wordset is present.

true constant CORE-EXT          \ -- TRUE
The full CORE-EXT wordset is present.

false constant FLOORED          \ -- flag
The standard division operators use symmetric (normal) division.

#255 constant MAX-CHAR          \ -- u ; max value of char
Characters are 8 bit units.

: MAX-D         \ -- d
Maximum positive value of a double number.

: MAX-N         \ -- n
Maximum positive value of a single signed number.

: MAX-U         \ -- u ; max size unsigned number
Maximum value of a single unsigned number.

: MAX-UD        \ -- u ; max size unsigned double
Maximum value of a double unsigned number.

: MAX-D         \ -- d
Maximum positive value of a double number.

: MAX-N         \ -- n
Maximum positive value of a single signed number.

: MAX-U         \ -- u ; max size unsigned number
Maximum value of a single unsigned number.

: MAX-UD        \ -- u ; max size unsigned double
Maximum value of a double unsigned number.

rp-size cell / constant RETURN-STACK-CELLS      \ -- n
Maximum size of the return stack (in cells).

sp-size cell / constant STACK-CELLS     \ -- n
Maximum size of the data stack (in cells).

true constant EXCEPTION         \ -- TRUE
EXCEPTION word-set is present.

true constant EXCEPTION-EXT     \ -- TRUE
EXCEPTION EXT word-set is present.

User words

' environment >body @ constant environment-wordlist     \ -- wid
The wid used by ENVIRONMENT? for look ups. You can add your own queries to this wordlist.

: ENVIRONMENT?  \ c-addr u -- false | i*x true                  6.1.1345
The text string c-addr/u is of a keyword from ANS 3.2.6 Environmental queries or the optional word sets to be checked for correspondence with an attribute of the present environment. If the system treats the attribute as unknown, the returned flag is false; otherwise, the flag is true and the i*x returned is of the type specified in the table for the attribute queried.

: [environment?]        \ "string" -- false | i*x true
As ENVIRONMENT? but is IMMEDIATE and takes the string from the input stream.

: .environment  \ --
Display a list of queries.

Automatic build numbering

The build numbering system allows you to generate a string in the system which can be used for displaying version information.

The system relies on a file (normally called BUILD.NO) which holds the complete build version string. The string can consist of any characters, e.g "Version 1.00.0034". The contents of the file can be placed as a counted string in the dictionary by BUILD$,. After successful compilation of your application, UPDATE-BUILD will update the build number file by treating all the digits in the build string as a single number to be incremented. )

: Make-Build    \ buffer --
Read the contents of the build number file and place as a counted string in the application defined buffer for later use.

: Build$,       \ --
Read the contents of the build number file and place as a counted string at HERE. ALLOT the required space.

: Date$,        \ --
Compile date as counted string.

: Time$,        \ --
Compile time as counted string

: DateTime$,    \ --
Compile date and time as counted string

: Set-BuildFile \ c-addr u --
Set the build number file.

: BuildFile     \ -- ; Buildfile <filename>
Use GetPathSpec to parse a filename from the input stream, and make it the current build number file.

: Update-Build  \ --
Update the contents of the build number file ready for the next build.

The following example, defines which file to use, loads the text into a buffer, and finally updates the build text. By placing Update-Build last in your load file, your build number file will only be updated for each successful build.


s" MyBuild.no" Set-Buildfile    \ set file to use
#256 buffer: MyVersion$  \ -- caddr
  MyVersion$ make-build         \ load version string
...
update-build                    \ put this last in load file

PDF help system

MPE documentation is produced by using DocGen to produce an indexed PDF file. The PDF help system parses the index file produced by pdftex to display the relevant page of the PDF manual. To display a particular line in a PDF file requires the following incantation for Adobe Reader v7 and beyond:

  <reader> /A "page=n=OpenActions" "<pdffile>"

The page number is the PDF file page number, not the page number in the document section. For example, to display page 10 on a Windows PC, use:

  "C:\Program Files\Adobe\Reader 8.0\Reader\AcroRd32.exe"
  /A "page=10=OpenActions" "%h%.pdf"
  "C:\Products\VfxCommunity\Sources\Manual\PDFs\VfxWin.pdf"

For VFX Forth for Windows, you can use the menu item Option -> Set PDF help ... for the configuration. For all versions, when setting the base of the PDF file name, do not add the file extension. This is because the PDF file and the index file share the same base name.

The page number is extracted from the index file VfxWin.vix, from which this example comes:


\initial {A}
\entry {\code {abell}}{11}
\entry {\code {abl}}{12}
\entry {\code {abort}}{15, 200}
\entry {\code {abort"}}{200}

The file is parsed for the entry containing the word name, the page number is extracted, and the file page is displayed. The index file is derived from the .fns file produced by pdftex.

The source code is in Lib\PDFhelp.fth.

TextMacro: p    \ -- $text
Defines the page number macro p.

TextMacro: h    \ -- $text
Define the help file macro h.

#256 constant /Help$    \ -- n
Size of the command and base string buffers.

/Help$ buffer: HelpCmd$ \ -- addr
Holds the pathname and command line of the PDF viewer as a counted string. In the command line, the page number is supplied by the text macro %p%, and the base help file path/name with no extension is supplied by the text macro %h%. This string may include other macros.

For Acrobat Reader under Windows, we must use the full reader pathname; we cannot use an association. The default string is

  "<reader>" /A \qpage=%p%=OpenActions\q %h%.pdf

where <reader> is

  "C:\Program Files\Adobe\Reader 8.0\Reader\AcroRd32.exe"

If you change the settings, use S\" as you may need to have double-quotes characters in the string around path names that include spaces.

One alternative is the free PDF-XChange Viewer from

  http://www.tracker-software.com/product/pdf-xchange-viewer
 "<path>\PDFXCview.exe" /A "page=%p%" "%h%.pdf"

Another alternative is the free Foxit Reader from

  http://www.foxitsoftware.com/

The required command strings for Foxit Reader are

"<path>\Foxit Reader.exe" /A "page=%p%" "%h%.pdf"

The rules for the Foxit Reader command line need to be checked with every new major release!

Users have also suggested Nitro, Qpdfview and Sumatra among many others. MPE has no position on this - it's a matter of personal preference. Just check the viewer's manual for the command line incantation!

For some Linux systems, e.g. Ubuntu, xpdf is installed by default. The VFX defaults are

  s\" xpdf %h%.pdf %p% &" HelpCmd$ place
  s" ~/VfxLin<ver>/Doc/VfxLin" HelpBase$ place
  #17 HelpPage0 !

If you used the default installer script, replace <ver> by by one of Eval, Standard or Pro, e.g. VfxLinStandard.

Some Linux distributions, e.g. Debian, require shared documentation files to be compressed. Type:

  sudo find / -name "VfxLin.pdf"

To see where VfxLin.pdf has been installed, and to see what extension, e.g. .pdf.gz is in use.

For OS X, the default PDF viewer is Preview. It can be run from the command line using:

 open -a Preview filename.pdf

However, going to a page number is undocumented. The best solution we have found is to install the Skim package from:

  http://skim-app.sourceforge.net/

Skim can be run using the supplied executable script Bin/skimpage.scpt. This is run in the form:

  skimpage.scpt "<file>" <pageno>

After copying the script file to a suitable directory such as /usr/bin (done by the install script), a suitable setup is:

  s\" skimpage.scpt \q%h%.pdf\q %p%" HelpCmd$ place
  s" ~/VfxForth/Doc/VfxOsx" HelpBase$ place
  #14 HelpPage0 !

/Help$ buffer: HelpBase$        \ -- addr
Contains a full path to the directory containing the help and index files, plus the base file name with no extension. This string may include macros. A counted string, e.g for Windows

  %LOAD_PATH%\..\doc\VfxMan

and for Linux

  %LOAD_PATH%/../doc/VfxLin

and for OS X

  %LOAD_PATH%/../doc/VfxOsx

variable HelpPage0      \ -- addr
Holds the offset to be added to the index page number to convert it to a PDF page number. This value may change according to the manual version, so it is extracted from the index file.

0 value DebugHelp?      \ -- flag
Set this non-zero if you are having trouble setting up the help system. The command line will be displayed.

: $Help         \ caddr len -- ior ; 0=success
Run the help file system using caddr/len as the search key.

: Help          \ "<word>" -- ; e.g. HELP dup
Get help on the given word name, e.g.

  help locate

Unlike LOCATE, HELP does not require the word to be present in the Forth dictionary and current search order, it only requires that a word have an index entry in the PDF manual.

: PDFLoadCfg    \ --
Load the PDF help configuration from the INI file. Linux and OS X only.

: PDFSaveCfg    \ --
Load the PDF help configuration from the INI file. Linux and OS X only.

INI files

If you are upgrading from a system installed before March 2012, you may/will need to change how your application specifies its INI files.

The INI file mechanism used by Windows is also available for other operating systems. This allows us to use the same configuration file mechanism for all operating systems that support shared libraries (not in VFX Forth for DOS yet).

The code accesses a derivative of the iniParser v3.0b shared library published by Nicholas Devillard at http://ndevilla.free.fr/iniparser/, where the latest version may be found. Note that the MPE versions differ from this version, but are upward compatible. We have submitted our changes to the author. A binary copy of the library can be found in the Bin folder. You are free to release this with your applications.

The full sources for iniParser as used by MPE are in the directories <VFX>\Tools\iniparser3.0b\src and <VFX>\Tools\iniparser3.0b\src.win. The relevant shared library (.so or .dll) is in the Bin folder and is copied to the Windows\System32 or /user/lib directory during installation.

The private profile file mechanism used by VFX Forth for Windows before version 4.20 may be found in <VFX>Lib\Win32\Profile.fth. The old mechanism is not portable between operating systems and is much slower. We strongly recommend that you convert any existing code that uses the PROFILE::xxx words to use the new mechanism.

The following example shows how INI files are used. A later section describes the words in detail.


: SaveSome        \ --
  s" %IniDir%\UserIde.ini" Ini.Open 0= if
    S" Options" Ini.Section
    s" FontSet" FontSet? Ini.WriteInt
    s" LogFont" lf[ LOGFONT Ini.WriteMem
    s" Editor"  szEditor zcount Ini.WriteStr
    Ini.Close
  endif
;

: LoadSome   \ --
  s" %IniDir%\UserIde.ini" Ini.Open 0= if
    S" Options" Ini.Section
    s" FontSet" 0 Ini.ReadInt dup -> FontSet? if
      s" LogFont" lf[ LOGFONT Ini.ReadMem
      lf[ CreateFontIndirect -> hConsoleFont
    endif
    s" Editor" szEditor MAX_PATH zNull zcount Ini.ReadZStr
    Ini.Close
  endif
;

The main things to note are:

Shared library interface

Library: libmpeparser64.so.0
The library reference.

: IniLib        \ -- addr|0
Used to determine if the library is available and to isolate the host-dependent library name from the rest of the code.

Library: libmpeparser.so.0
The library reference.

: IniLib        \ -- addr|0
Used to determine if the library is available and to isolate the host-dependent library name from the rest of the code.

Library: libarmmpeparser.so.0
The library reference.

: IniLib        \ -- addr|0
Used to determine if the library is available and to isolate the host-dependent library name from the rest of the code.

Extern: int iniparser_getnsec( void * dict );
This function returns the number of sections found in a dictionary. The test to recognize sections is done on the string stored in the dictionary: a section name is given as "section" whereas a key is stored as "section:key", thus the test looks for entries that do not contain a colon. This function returns -1 in case of error.

Extern: char * iniparser_getsecname( void * dict, int n );
This function locates the n-th section in a dictionary and returns its name as a pointer to a string statically allocated inside the dictionary. Do not free or modify the returned string! This function returns NULL in case of error.

Extern: void iniparser_dump_ini( void * dict, void * file );
This function dumps a given dictionary into a loadable ini file. It is Ok to specify stderr or stdout as output files.

Extern: void iniparser_dump( void * dict, void * file );
This function prints out the contents of a dictionary, one element by line, onto the provided file pointer. It is OK to specify stderr or stdout as output files. This function is meant for debugging purposes mostly.

Extern: char * iniparser_getstring( void * dict, const char * key, char * def );
This function queries a dictionary for a key. A key as read from an ini file is given as "section:key". If the key cannot be found, the pointer passed as 'def' is returned. The returned char pointer is pointing to a string allocated in the dictionary, do not free or modify it.

Extern: int iniparser_getint( void * dict, const char * key, int notfound );
This function queries a dictionary for a key. A key as read from an ini file is given as "section:key". If the key cannot be found, the notfound value is returned. Supported values for integers include the usual C notation so decimal, octal (starting with 0) and hexadecimal (starting with 0x) are supported. Examples:



 - "42"      ->  42
 - "042"     ->  34 (octal -> decimal)
 - "0x42"    ->  66 (hexa  -> decimal)

Warning: the conversion may overflow in various ways. Conversion is totally outsourced to strtol(), see the associated man page for overflow handling.

Extern: int iniparser_getboolean( void * dict, const char * key, int notfound );
This function queries a dictionary for a key. A key as read from an ini file is given as "section:key". If the key cannot be found, the notfound value is returned. A true boolean is found if one of the following is matched:


 - A string starting with 'y'
 - A string starting with 'Y'
 - A string starting with 't'
 - A string starting with 'T'
 - A string starting with '1'

A false boolean is found if one of the following is matched:


 - A string starting with 'n'
 - A string starting with 'N'
 - A string starting with 'f'
 - A string starting with 'F'
 - A string starting with '0'

The notfound value returned if no boolean is identified, does not necessarily have to be 0 or 1.

Extern: int iniparser_set( void * dict, char * entry, char * val );
If the given entry can be found in the dictionary, it is modified to contain the provided value. If it cannot be found, -1 is returned. It is Ok to set val to NULL.

Extern: void iniparser_unset( void * dict, char * entry );
If the given entry can be found, it is deleted from the dictionary.

Extern: int iniparser_find_entry( void * dict, char * entry );
Finds out if a given entry exists in the dictionary. Since sections are stored as keys with NULL associated values, this is the only way of querying for the presence of sections in a dictionary.

Extern: void * iniparser_load( const char * ininame );
This is the parser for ini files. This function is called, providing the name of the file to be read. It returns an ini dictionary object that should not be accessed directly, but through accessor functions instead. The returned dictionary must be freed using iniparser_freedict().

Extern: int iniparser_save( void * d, char * ininame );
Saves a dictionary object. This is just a wrapper around iniparser_dump_ini() to provide insulation between the caller and the file system for languages and operating systems which do not expose the libc library. The returned error code is 0 for a successful operation. You still need to call iniparser_freedict below.

Extern: void iniparser_freedict( void * dict );
Free all memory associated to an ini dictionary. It is mandatory to call this function before the dictionary object gets out of the current context.

Tools

These tools are in the SYSTEM vocabulary and may change from version to version. If all you are interested in is using the MPE parser interface API, skip this section.

0 value IniSrcFile      \ -- addr
Holds the currently loaded INI source file pathname as a zero-terminated string.

0 value IniDestFile     \ -- addr
Holds the currently loaded INI destination file pathname as a zero-terminated string.

0 value IniSection      \ -- addr
Holds the current section name as a zero-terminated string.

0 value IniKey          \ -- addr
Holds the current key as a zero-terminated string.

0 value IniData         \ -- addr
Holds the current write data as a zero-terminated string,

0 value IniDefault      \ -- addr
Holds the current default as a zero-terminated string

0 value IniScratch      \ -- addr
Holds the current scratch buffer for processing quote marks.

0 value IniDict         \ -- addr
Holds the current dictionary pointer.

: IniAlloc      \ ptr -- ior
Allocate /IniBuff bytes and place the buffer address at ptr. The first bye of ptr is set to zero.

: IniFree       \ ptr --
Free buffer memory allocated by us.

: InitIniBuffs  \ -- ior
Initialise all the buffers and pointers.

: TermIniBuffs  \ --
Free all the buffers and clear pointer.

: +DoubleQ      \ z$1 -- z$2
Convert double quote characters to pairs of double quote characters.

: -DoubleQ      \ z$1 -- z$2
Convert pairs of double quote characters to single double quote characters.

: >IniName      \ caddr len dest --
Copy name to zero terminated string.

: >IniString    \ caddr len dest --
Copy string to zero terminated string. If the last character is a '\', add a dummy comment " ; x".

: WriteIniFile  \ --
Write the current INI dictionary to the INI file.

: FormIniKey    \ caddr u --
Form the key string from the current section name and the given key. The key string is of the form "<section:<key>".

: setIniString  \ dict entry val --
Calls iniparser_set() and marks the INI file as changed.

: IniExists     \ caddr len --
If the file does not exist create an empty one.

: czplace       \ caddr len dest
Store the string caddr/len as a counted and zero-terminated string at dest. The strings must not overlap.

: nib>hex       \ 4b -- char
Convert a nibble to a hex character.

: Mem>Hex       \ caddr len zdest --
Generate an ASCII hex representation of the memory block as a zero terminated string at zdest. The length len of the memory block must be less than 128 bytes.

: Hex>Nib       \ char -- 4b
Convert a hex character to a nibble.

: Hex>Mem       \ zsrc caddr len --
Convert the zero-terminated ASCII HEX string at zsrc to its memory representation in the buffer caddr/len.

Using the library

The code here is not thread safe, you so may need a semaphore from open to close. Some data is held in global variables/buffers.

: Ini.Open      \ caddr len -- ior
Define and load the Ini file, returning zero on success. Sets the destination file to be the same as the loaded file. Macros in the file name are expanded.

: Ini.Dest      \ caddr len --
Set the destination file. This must be done after Ini.Open and before Ini.Close.

: Ini.Close     \ --
If the dictionary has been changed, write it out to the destination file.

: Ini.Section   \ caddr len --
Set the current section name.

: Ini.Section?  \ caddr u -- flag
Given a section name, make it current, and return true if it exists.

: Ini.WriteSection      \ caddr u --
Make the given section current, and write it to the dictionary.

: Ini.ReadStr   \ c-addr1 u1 c-addr2 u2 c-addr3 u3 --
Read a string value under key c-addr1/u1 and return it in the result buffer specified by c-addr2/u2. If the key couldn't be read then the default string c-addr3/u3 is placed in the result buffer. Note that the returned string is placed in the result buffer as a zero terminated and counted string.

: Ini.ReadZStr  \ c-addr1 u1 c-addr2 u2 c-addr3 u3 --
Read a string value under key c-addr1/u1 and return it in the result buffer specified by c-addr2/u2. If the key couldn't be read then the default string c-addr3/u3 is placed in the result buffer. Note that the returned string is placed in the result buffer as a zero terminated string.

: Ini.ReadInt   \ c-addr1 u1 default -- value
Attempt to read an integer value from key c-addr1/u1 and return it. If the key couldn't be read then the default is returned.

: Ini.ReadBool  \ c-addr1 u1 defbool -- bool
Attempt to read an boolean value from key c-addr1/u1 and return it. If the key couldn't be read then the default is returned.

: Ini.ReadMem   \ c-addr1 u1 c-addr2 u2 --
Read a memory block with key c-addr1/u1 and return it in the result buffer specified by c-addr2/u2. If the key couldn't be read then the result buffer is filled with zero bytes. u2 must be less than 128 bytes.

: Ini.WriteStr  \ c-addr1 u1 c-addr2 u2 --
Write a string value attached to a key, into the currently named section of the current INI file. C-addr1/u1 describes the name of the key to place the entry under, and C-addr2/u2 the string to write. If the key already exists it is updated.

: Ini.WriteZStr \ c-addr1 u1 caddrz --
Write a zero-terminated string value attached to a key, into the current section of the INI file. C-addr1/u1 describes the name of the key to place the entry under, and caddr\ the string to write. If the key already exists it is updated.

: Ini.WriteInt  \ c-addr1 u1 u2 --
Write a string value corresponding to decimal text for u2 to the key C-addr1/u1 in the current section of the current dictionary.

: Ini.WriteBool \ c-addr1 u1 bool --
Write a string value corresponding to decimal text for bool to the key C-addr1/u1 in the current section of the current dictionary.

: Ini.WriteMem  \ c-addr1 u1 c-addr2 u2 --
Write a memory block attached to a key, into the currently named section of the current INI file. C-addr1/u1 describes the name of the key to place the entry under, and C-addr2/u2 the memory block to write. If the key already exists it is updated.

: Ini.DeleteKey \ c-addr1 u1 --
Delete a key entry completely from the current section. C-addr1/u1 is the name of the key.

Operating system generics

#256 buffer: IniFile$   \ -- addr
A 256 byte buffer for the expanded INI file path name, which may include macros. The path name is stored as a counted string. This buffer is initialised at startup from the three components below.

#256 buffer: IniDir$    \ -- addr
A 256 byte buffer for the expanded INI file directory name, which may include macros. The path is stored as a counted string. This buffer is initialised at startup

#256 buffer: AppSupp$   \ -- addr
The directory where application support files go, held as a counted string This directory must already exist. May contain macros. You can change this for your own application.

#256 buffer: AppSuppDir$        \ -- addr
The directory in AppSupp$ in which the INI file is placed, held as a counted string. The directory will be created if it does not already exist. This string may be null, or may define one or two levels of directory. You can change this for your own application.

64 buffer: AppSuppIni$  \ -- addr
The name of the INI file in AppSuppDir$, held as a counted string. By default, the file is VfxForth.ini. You can change this for your own application.

256 buffer: PrevIni$    \ -- addr
Holds the full pathname of a default file copied to the INI file if it does not exist. May contain macros. You can change this for your own application.

-1 value GenINI?        \ -- flag ; true to generate INI files
If this VALUE is set true (the default condition) .INI files will be generated for VFX Forth and applications when the application performs BYE. Such files will also be loaded when VFX Forth or an application is executed.

defer CheckSysIni       \ --
This word is run at cold start before any INI file is loaded. It should provide an INI file if one is needed and does not exist.

1 value IniParserModes  \ -- modes
If you need only one INI file that could be in the directory from which the application is loaded, set this to zero.

TextMacro: IniFile
A text macro that returns the INI file name after macro expansion at startup.

TextMacro: IniDir
A text macro that returns the INI file directory after macro expansion at startup.

: -ini-exec     \ --
When used on the command line in lower case, -ini-exec causes the INI file to be loaded from PrevIni$, which is usually in the executable directory.

: (CheckSysIni) \ --
Creates the INI file directory if required and sets up the INI file macros. This is the default action of CheckSysIni.

: (CheckSysIni) \ --
Creates the INI file directory if required and sets up the INI file macros. This is the default action of CheckSysIni.

Operating system specifics

Setting the INI files has changed. You must now set up four strings rather than two, and IniFile$ is set at startup, and not by you. The defaults are shown below for three operating systems.

These changes were required by changes in the Windows security system, the desire to fit in "well" with Unix-derived systems, and the requirement for multiple application-specific data files. See the IniDir macro in particular.

Windows

By default, the INI file is %$AppLocal%\MPE\VfxForth\VfxForth.ini. If the directory or file does not exist, as may happen after installation, the directory is created and/or a default file is copied.

  s" %$AppLocal%" AppSupp$ place                \ system dir
  s" MPE\VfxForth" AppSuppDir$ place            \ our dir
  s" VfxForth.ini" AppSuppIni$ place            \ file
  s" %load_path%\VfxForth.ini" PrevIni$ place   \ default/previous

Mac OS X

By default, the INI file is %$home%/Library/Application Support/VfxForth/VfxForth.ini. If the directory or file does not exist, as may happen after installation, the directory is created and/or a default file is copied.

  s" %$home%/Library/Application Support" AppSupp$ place
  s" VfxForth" AppSuppDir$ place
  s" VfxForth.ini" AppSuppIni$ place
  s" %$home%/.VfxForth.ini" PrevIni$ place
  s" %$home%/Library/Application Support" AppSupp$ place
  s" VfxForth" AppSuppDir$ place
  s" VfxForth64.ini" AppSuppIni$ place
  s" %$home%/.VfxForth64.ini" PrevIni$ place

Linux

By default, the INI file is %$home%/.VfxForth/VfxForth.ini. If the directory or file does not exist, as may happen after installation, the directory is created and/or a default file is copied.

  s" %$home%" AppSupp$ place
  s" .VfxForth" AppSuppDir$ place
  s" VfxForth.ini" AppSuppIni$ place
  s" %$home%/.VfxForth.ini" PrevIni$ place
  s" %$home%" AppSupp$ place
  s" .VfxForth" AppSuppDir$ place
  s" VfxForth64.ini" AppSuppIni$ place
  s" %$home%/.VfxForth64.ini" PrevIni$ place

System initialisation chains

These chains are used for configurations options which are preserved when VFX Forth closes down, and are reloaded when it starts. Note that because of the position in the cold and exit chains, you must be careful that you still have the configuration data. For example, if a window is closed before configuration save, its position may have to be saved in a buffer rather than reading it directly from the window. Similarly, when the configuration information is restored, the window may/will not be open yet.

If you want to read and write directly from live information, it is more reliable to provide full handlers in your application code. However, there will be a time penalty because the INI file is repeatedly opened and closed.

variable IniLoadChain   \ -- addr
The anchor for the initialisation load sequence.

variable IniSaveChain   \ -- addr
The anchor for the initialisation save sequence.

: AtIniLoad     \ xt --
The given word is run during the INI load sequence.

: AtIniSave     \ xt --
The given word is run during the INI save sequence.

The words run must have no net stack action, and behave according to the rules of ExecChain.

: LoadSysIni    \ --
Open the INI file specified by iniFile$, run the IniLoadChain, and close the file. Run in the cold chain. No action is taken if GenINI? is zero.

: SaveSysIni    \ --
Open the INI file specified by iniFile$, run the IniSaveChain, and close/save the file. Run in the exit chain. No action is taken if GenINI? is zero.

Converting from the previous mechanism

By design, there is an almost one to one correspondence between the words in the profile and INI mechanisms. There are two major differences.

For examples of use, see LoadUserIde and SaveUserIde in Studio\XTB.FTH for the Windows versions.

Switch chains

Introduction

Switch chains provide a mechanism for generating extensible chains similar to the CASE ... OF ... ENDOF ... ENDCASE control structure, except that the user may extend the chain at any time. These chains are of particular use when defining winprocs whose action may need to be adjusted or extended after the chain itself has been defined.

The following example shows how to define a simple chain that translates numbers to text. At a later date, translations in Italian are added.

Define some words which will be executed by the chain.


: one   ." one"  ;
: two   ." two"  ;
: three ." three" ;
: many  . ." more" ;

The following definition defines a switch called NUMBERS which executes ONE when 1 is the selector, TWO if 2 is the selector, or MANY if any other number is the selector. Note that MANY must consume the selector. The word RUNS associates a word with the given selector.


[switch numbers many
  1 runs one
  2 runs two
switch]
cr 1 numbers
cr 5 numbers

The next piece of code extends the NUMBERS switch chain, and demonstrates the use of RUN: to define an action without giving it a name.


[+switch numbers
  3 runs three
  4 run: ." four" ;
  5 run: ." five" ;
switch]
cr 1 numbers
cr 5 numbers
cr 8 numbers

The following portion of this example demonstrates how selectors are overridden by the last action defined. Although an action has already been defined for selectors 1 and 2, if another action is defined, it will be found before the old ones, and so the action will be performed.


[+switch numbers
  1 run: ." uno" ;
  2 run: ." due" ;
switch]
cr 1 numbers
cr 2 numbers
cr 3 numbers
cr 5 numbers
cr 8 numbers

Switches glossary

code switch     \ i*x id switchhead -- j*x
Given an id and the head of a switch chain, SWITCH will perform the action of the given id if it is found, otherwise it will perform the default action, passing id to that action.

: [switch       \ "default" -- head ; i*x id -- j*x
Builds a new named switch list with a default action. Use in the form: [SWITCH <name> <default.action> where <default.action> must consume the selector id.

: [+switch      \ "head" -- head ; to extend an existing switch
Used in the form [+SWITCH <switch> to extend an existing switch chain.

: run:          \ head id -- head ; add nameless action to switch
Used in the form <id> RUN: <words> ; to define a nameless action in a switch chain.

: runs          \ head id "word" -- head
Used in the form <id> RUNS <word> to define a named action in a switch chain.

: switch]       \ head -- ; finishes a switch chain or extension
Used to finish a [SWITCH <name> <default> or [+SWITCH <name> chain definition.

: .switches     \ -- ; lists defined switches
Lists all the defined switch chains.

: InSwitch?     \ id xt -- flag
Returns true if the id is in the switch chain given by its xt.

First-In First-Out Queues

VFX Forth contains a set of words for managing character (8 bit ) queues. These queues are allocated from the system heap.

STRUCT FIFO     \ -- len
A structure which defines the internal format of the fifo. To create a new FIFO you must create an instance of the structure. The instance pointer (address of structure) is then used as an identifier for subsequent operations.

: InitialiseFIFO        \ *FIFO size -- ior
Initialise a FIFO with a maximum buffer 'size' bytes long. the IOR is 0 for success and non-zero for memory allocation failed.

: FreeFIFO      \ *FIFO -- ior
Destroy FIFO. Memory is released back into the heap.

: resetFIFO     \ *fifo --
Discard any characters in the FIFO.

: FIFO?         \ *FIFO -- n ; Return # bytes used in fifo
Return the number of storage bytes in use within a fifo.

: >FIFO(b)      \ BYTE *FIFO -- ior
Add a byte to the FIFO queue. IOR is 0 for success.

: FIFO>(b)      \ *FIFO -- byte ior
Remove next byte from a FIFO. IOR is 0 for success.

Random numbers

The random number system in VFX Forth is based around the DEFERred word RANDOM (see below). We have found that a single random number generator (RNG) is not adequate for all applications. Some applications need a particular degree of randomness, others require more speed. If you do not like the default RNG, you can install your own.

The implementation uses a seed in the variable *\fo{RandSeed), which is set to some time value at start up. The default implementation is in the SYSTEM vocabulary.

defer RANDOM    \ -- u
Generate a random number.

: CHOOSE        \ n1 -- n2
Generate a random number *\i{n2) in the range 0..n1-1. The algorithm is from Paul Mennen, 1991 .

: +randDigits   \ buff$ -- ; 64 bit version
Add a 32 bit random number as 8 hex digits at the end of the counted string in buff. Used for generating random names for things like semaphores.

Long Strings

In order to extract very long strings from the source code, the word PARSE/L is provided. Support is also provided for counted strings with a 16 bit count. These words allow long strings such as those required for internationalisation to be generated without the restrictions of counted strings that use a character-sized count.

The contents of this section are subject to change until the ANS Forth committee reaches a conclusion about internationalisation issues. An implementation of the system descibed in the paper on the MPE web site may be found in the file %LIB%\INTERNATIONAL.FTH.

: parse/l       \ char -- c-addr len ; like PARSE over lines
Parse the next token from the terminal input buffer using <char> as the delimiter. The text up to the delimiter is returned as a c-addr u string. PARSE/L does not skip leading delimiters. In order to support long strings, PARSE/L can operate over multiple lines of input and line terminators are not included in the text. The string returned by PARSE/L remains in a single global buffer until the next invocation of PARSE/L. PARSE/L is designed for use at compile time and is not thread-safe or winproc-safe.

: wcount         \ addr1 -- addr2 len
Given the address of a word-counted string in memory WCOUNT will return the address of the first character and the length in characters of the string.

: (W")          \ -- waddr u ; step over caller's in line string
Returns the address and length of inline 16-bit word-counted string. Steps over inline text.

: ((W"))        \ -- waddr u ; dangerous factor!
A factor provided for the generation of long string actions that have to step over an inline string. For example, to define W." which uses a long string, you might compile (W.") and then use W", to compile the inline string. The definition of (W.") then might be:


: (W.")     \ --
  ((W")) type
;

: wAppend       \ c-addr u $dest --
Add string c-addr u to the end of word-counted string $dest.

: (w$+)        \ c-addr u $dest -- ; SFP001
Add string c-addr u to the end of word-counted string $dest.

: w$+           \ $src $dest -- ; add $SRC to end of $DEST
Add word-counted string $src to the end of word-counted string $dest.

: w$,           \ caddr len --
Lay a 16 bit string string into the dictionary at HERE, reserve space for it and ALIGN the dictionary. The inline string string has a 16 bit count and 16 bit zero termination.

: w",           \ -- ; compile a word counted string
multiline version of ",. Interprets multiline text and lays down inline string string with 16 bit count and 16 bit zero termination.

: ls"           \ c: -- ; i: -- caddr len
A version of S" that can extend over several lines. Line separators are ignored.

: zls"          \ c: -- ; i: -- zaddr
A version of Z" that can extend over several lines. Line separators are ignored. The returned address is that of the start of the zero-terminated string.

Command Line parser

VFX Forth includes code for handling the OS X command line. You can access the command line using Forth versions of ARGV[] and ARGC as in C. At start up, VFX Forth attempts to recreate the original command line using argc and the argv[] strings so that the command line can be treated as a sequence of Forth commands.

The attempt to recreate the command line is only partially successful because the C parser uses double-quotes characters to denote literal strings. These are passed to the application as a single string with the double-quotes characters removed. For example:

  vfxosx s" foo   bar" type

will give a command line error. You can escape the double-quotes characters in the usual C manner, but this will not preserve the spaces.

  vfxosx s\" foo   bar\" type

Most OS X system shells, including Bash, allow you to use single-quotes characters for literal strings.

  vfxosx 's" foo   bar" type'

: argc  \ -- u
The number of defined arguments.

: argv[                 \ n -- pointer|0
Given an index of 0..argc-1 return a pointer to the command line token's zero-terminated string. 0 argv[ returns the executable's name. If the argument does not exist, the pointer is zero.

: CommandLine   \ -- c-addr len
Return the system command line. This is recreated from the data passed to VFX Forth and may not be exact.

C Language Style Helpers

VFX Forth supports a few "helper" definitions to aid the parsing of "C" header files. See VFXBase/GenTools.fth.

These are especially helpful in the parsing of Windows resource scripts which are based on the Microsoft RC Language for C, and for cutting and pasting from C header files.

: #define       \ <spaces"NAME"> <eol"value-def"> -- ; Exec: -- value
A simple version of C's #define preprocessor command. Any text between the definition name and the end of the line is EVALUATEd when <NAME> is invoked.

: //            \ --
An implementation of the C++ single line comment.

: /*            \ --
A simple implementation of the C "/* ... */" comment.

: enum          \ --
Process an enum of the form:

  enum <name> { a, b, c=10, d };

<name> is placed in the *\fo{NamedEnums) vocabulary. The elements appear as Forth constants in the *\fo{CURRENT) wordlist/vocabulary. The definition may extend over many lines. C comments may occur after the ',' separator, e.g.

 JIM = 25, // comment about this line

: enum{         \ --
Process an enum of the form:

  enum{ a, b, c=10, d };

: .NamedEnums   \ --
List all the named ENUMs.

Stack guarding

These words preserve the stack pointers, put dummy items on the stack, and restore the stack later. Use these words inside a colon definition for protection of (say) a text interpreter. See VFXBase/GenTools.fth.

  [GuardSP ... GuardSP]

8 cells constant /guard                                                   \ -- n
The amount by which the stack is guarded.

: [GuardSP \ -- ; R: -- oldS0 oldSP
Reserves guard space on the data stack and resets the data stack pointers as if the stack was empty.

: GuardSP] \ -- ; R: oldS0 oldSP --
Restore the stack condition saved by [GuardSP.

Transient word regions

There are occasions when we want to run words only once or twice in order to initialise existing data. These words occupy space. The code here allows you to build words in a transient region allocated from the heap. When the region is released, all the words in it are removed from the dictionary and the memory is freed. The regions are nestable. See VFXBase/GenTools.fth.


[tr
  : foo ....  ;
  foo
tr]

0 value anchorTR        \ -- addr|0
Anchors the linked list of transient areas.

struct /transient       \ -- len
Defines the head of the transient region.

: [TR           \ --
Allocate a new transient area

: TR]           \ --
End the current transient area and remove it

Eliminating compilation surprises

There are four things that you can do with a word:

  1. Execute the interpretation behaviour of a word. This is what happens when you type it on the console. You can pass the xt to EXECUTE.
  2. Execute the compilation behaviour of a word.
  3. Compile the interpretation behaviour of the word.
  4. Compile the compilation behaviour of the word.

The four words here provide these functions explicitly. See VFXBase/GenTools.fth.

: exec-interp   \ xt --
Execute the interpretation action as if on the console.

: exec-comp     \ xt --
Execute the compilation behaviour of the word.

: comp-interp   \ xt --
Compile the interpretation behaviour of a word.

: comp-comp     \ xt --
Compile the compilation behaviour of a word.

: (ndcs,)       \ i*x xt -- j*x
Like (COMPILE,) but executes the NDCS action for a word and may parse and/or have a stack effect during compilation.

: compile-word  \ i*x xt -- j*x
Process an XT for compilation.