ClassVfx OOP

There are two sets of documentation for the ClassVFX system. There is a chapter in the main VFX Forth manual, and there is a full PDF manual in the Manual subdirectory of Lib\OOP\ClassVFX\.

Introduction

The source code is in the directory Lib\OOP\ClassVFX. The file MakeClassVfx.bld is compiled to produce the production version of the code. TestClassVfx.fth contains test code.

ClassVFX was developed over a number of years in collaboration with Construction Computer Software of Cape Town, South Africa. We gratefully acknowledge their collaboration and permission to release it. ClassVFX is heavily used in their construction industry planning software, which is one of the largest Forth applications ever written. Modifications to ClassVFX will only be released after the agreement of CCS.

ClassVFX is a halfway house between a full object oriented system and an intelligent structures system. Types, or classes, can be defined with single inheritance. Method names have to be predefined using

  OPERATOR: <method-name>

Field, or data member, names are private, but are accessible using a dot notation. There are no equivalents of SUPER and SELF. There is no late binding.

In this documentation types and classes are synonymous. Objects are instances of a type. Objects have a default action if no method is specified. Usually the default action is to fetch the contents of the object, but in a few cases the default action is to return an address.

Types/Classes can have both class and instance methods. The default method for a type is to create an instance. If a type is used inside a colon definition a local variable version is created and destroyed at run time.

Operators, or methods, must be declared as above before use.

How to use TYPE: words

TYPE: definitions may be used in four ways:


operator: <method1>
operator: <method2>
operator: <method3>

type: line:
  point: start
  point: end

  :m <method1>     ... ;m
  :m <method2>     ... ;m
  mruns <method3>  <some-word>
  :m <xxx>         a b c d  ;m  structure-method

end-type

Line: MyLine
  1 2 to Myline.start
  5 to Myline.end.y

At runtime, the method operates on the address of the data. Because of this, a method which requires the address of the instance structure has to be marked by the word STRUCTURE-METHOD which causes the compiler to generate the address of the instance structure, not the type structure.

ClassVFX allows both CLASS and INSTance methods to be defined for a type. INSTance methods, the default, operate on the address of the data item. CLASS methods operate on the address of the type data structure. As described above, STRUCTURE-METHODs operate on the instance data structure.

Single inheritance can be defined using SUPERCLASS <type> or INHERITS <type> before any field or method is defined.


TYPE: <type>  SUPERCLASS <supertype>
  ...
END-TYPE

At run-time, methods are provided with the address of the required data. CLASS/TYPE methods receive the address of the TYPE/CLASS data structure, INSTance methods receive the address of the data item. INSTance methods that require the address of the instance data structure must be marked by STRUCTURE-METHOD. Methods may be defined as nameless words:

  :M <method-name> ...  ;M

or as the action of a method:

  MRUNS <method-name> <action-name>

The code below is taken from the definition of the default type.


 class     \ define methods for the type
    :m default    make-inst  ;m
    :m sizeof     type-size @ ?complit  ;m
    :m addr       ?complit  ;m
 inst      \ define methods for the instance
    mruns default noop
    :m sizeof     type-size @ ?complit  ;m  structure-method
    mruns addr    noop
    :m offsetof   off-start @ ?complit  ;m  structure-method
    :m +offsetof  off-start @ ?complit+  ;m  structure-method

To use the nested field system, the Forth system has been modified to accept compound names in which the elements of the structure are separated by the `.' character. This feature is enabled and disabled by the words +STRUCTURES and -STRUCTURES.

Predefined types


char:         byte - 8 bit variable
word:         word - 16 bit variable
int:          long - 32 bit variable and synonyms
  dword:
  long:
  ptr:
xlong:        longlong - 64 bit variable
bytes:        byte array: size specified by n BYTES BYTES:
cstring:      counted string: size specified by BYTES before CSTRING:
zstring:      zero term. string: size specified by BYTES before ZSTRING:
field:        byte array, only ADDR operator, size specified by BYTES

Predefined methods/operators

Note that not all predefined types support all methods.


 0 operator default            usually a fetch operation
 1 operator ->                 store operator
 1 operator to                   "
 2 operator addr               address operator
 3 operator inc                increment by one
 4 operator dec                decrement by one
 5 operator add                n add to
 6 operator zero               set to 0
 7 operator sub                subtract from
 8 operator sizeof             size
 9 operator set                set to -1
10 operator offsetof           offset in object
11 operator +offsetof          add offset in object
12 OPERATOR FETCH              get contents
13 OPERATOR ADDR\CNT           address under count
14 OPERATOR TWIST              change endian of the data type
15 OPERATOR CONSTRUCT          build an instance of this type
15 OPERATOR MAKE               build an instance of this type
op# ADDR OPERATOR ADDROF
OPERATOR: <=>    type_addr_y <=> <type_x> --- set typedef_x = typedef_y
  op# <=> OPERATOR <copy>
OPERATOR: <blank>              blank object for object size
OPERATOR: <erase>              fill obj with null for object size
OPERATOR: <COUNT>
OPERATOR: <make>
OPERATOR: <destroy>
OPERATOR: <INIT>
OPERATOR: <fetch>

Example structure


TYPE: POINT:  \ --
\ Defines a type called POINT: with the following fields )
  PROVIDER: NOOP        \ defines the address provider, defaults to NOOP
  0 OFFSET:             \ defines the initial offset, defaults to 0
  INT: Y
  INT: X
  10 BYTES FIELD: FOO
                        \ fetch operation
  :m default
     2@
  ;m
  mruns to      2!
  ...

END-TYPE

Data structures created by TYPE:

TYPE: definitions, fields, objects and so on all use a common data structure that is generated by the defining words.

These structures are associated with a word (the address provider) that can provide the starting address of the structure implementation. By supplying the cfa of NOOP, no address is provided, and so the structure is purely a template. For templates, address provider = 0 or NOOP, an offset may also be defined. NOOP and 0 are the default address provider and offset of templates. )

A similar structure is used for instances of a TYPE:. These are created by the word MAKE-INST.

TYPE: definitions

The following structure is created by TYPE:


   header                          standard PFW layout
0  jmp do_type                     5 bytes
1  cfa of address provider         4 bytes
2  initial offset                  4 bytes  0 for class
3  link to last field defined      4 bytes
4  type size - final offset        4 bytes
5  Magic number                    4 bytes
6  anchor of instance method chain 4 bytes
7  anchor of type method chain     4 bytes
8  link to previous type defined   4 bytes
9  private wordlist                ? bytes

MAKE-INST definitions

The following structure is created by MAKE-INST


   header                          standard PFW layout
0  jmp do_inst                     5 bytes
1  cfa of address provider         4 bytes
2  offset from start of type       4 bytes
3  link to last instance of type   4 bytes
4  size of instance data           4 bytes
5  0                               4 bytes
6  pointer to TYPE/CLASS           4 bytes
7  data if static

Local variable instances

When an instance is defined inside a colon definition, an uninitialised local variable/array is built. Several instances can be built. Normally the size of all local variables is rounded up to a cell boundary by the compiler

Defining methods

: method,       \ struct "<method>" -- struct ^xt
Given a TYPE structure, lay an entry in one of the method chains.

: :M            \ struct "<method>" -- struct  ; :M <operator> <actions ...> ;M
defines the start of a method. The method/operator name must follow.

: ;M            \ struct -- struct ; SFP012
marks the end of a method definition.

: MRUNS         \ struct "<method>" "<word>" -- struct  ; MRUNS <operator> <word>
Defines a method which runs a previously defined word.

Create Instance of an object

: CREATE-INST   \ "<name>" -- ; -- addr
From VFX Forth v4.4, this is a synonym for CREATE. When compiling on previous VFX versions instances needed to be immediate.

: make-inst     \ class -- ; i*x -- j*y ; build instance of type
Builds an instance of a TYPE:. This word has serious carnal knowledge of the internal workings of VFX Forth. Don't call us for help!

Defining TYPE: and friends

create type-template    \ -- addr
The type chain from which others are derived.

CREATE ptr-template     \ -- addr
The ptr chain from which others are derived.

TYPE definition

: type:-runtime \ type-struct --
The run-time action of children of TYPE:.

: CURR-TYPE-SIZE        \ -- u
Use between TYPE: <name> and END-TYPE to return the current size of the type.

: TypeChildComp,        \ xt --
Compile a child of TYPE:.

: type:         \ -- struct ; --
Start a new TYPE: definition.

: PTR:          \ -- struct ; --
Make a new structure defining word.

: end-type      \ struct --
Finish off a TYPE: definition

: EXTEND-TYPE   \ "<type>" -- struct ; EXTEND-TYPE <type> ... END-TYPE
Extend the given TYPE: definition.

: SUPERCLASS    \ struct "<type>" -- struct
Use this inside a TYPE: definition before defining any data or methods. The current type will inherit the data and methods of the superclass.

: INHERITS      \ struct "type" -- struct
A synonym for SUPERCLASS.

: provider:     \ struct "name" -- struct ; <name> is address provider
Sets a different address provider.

: with:         \ -- ; WITH: <some-provider> LINE: <myline>
Used before declaring an instance to override the default address provider.

: SKIPPED       \ struct size -- struct
Increase overall size of struct by size. SKIPPED can be used to jump over items from a previous instance.

: OFFSET:       \ struct offset -- struct
Define the offset of a TYPE: as starting at a value other than zero. Must be used before any data is defined.

: TypeCast:     \ -- ; TYPECAST: <inst> <type>
Forces a previously defined instance to be a pointer to a type/class.

SYNONYM PointsTo: TypeCast:     \ -- ; synonym for TYPECAST:
Forces a previously defined instance to be a pointer to a type/class.

: type-self     \ -- type
Used in TYPE: <name> ... END-TYPE to refer to the type/class being defined.

: EXECUTE-MEMBER-METHOD \ struct-inst member-inst methodid ---
Attempt to execute method for inst. Return true if successful.

: EXECUTE-PTR-MEMBER-METHOD     \ member-inst methodid ---
Attempt to execute method for inst. Return true if successful.

: EXECUTE-MEMBERS       \ inst method --
Apply the given method to all members of the instance of a type/class.

: TWIST-STRUCTURE       \ inst --
Twist structure method

: INIT-STRUCTURE        \ inst --
Init structure method

Dot notation parser

In order to deal with structures and fields without having to backtrack the input stream or the execution order, an additional stage is added to the Forth parser to allow phrases of the forms:

  inst.field
  inst.field.field
  type.field
  type.field.field

to be parsed, where each item is separated by a dot character. The first item must be an instance of a type or a type. If it is an instance, the address is provided, otherwise the base address is assumed to be on the stack. Any items between the first and last item add their offsets to the address, and the last item performs the usual operation of the field as defined by an operator. For example:


  type: point:
    int: x0
    int: y0

    :m <op1> ... ;m
    :m <op2> ... ;m
  end-type

  point: Mypoint
    5 to MyPoint.x0

We might define a line as joining two points:


  type: line:
    point: p1
    point: p2
    ...
  end-type

  line: MyLine
    5 to MyLine.p2.y0

: must-be-inst-throw    \ xt --
THROW because the xt is not of an instance of a type.

: class-ise-throw       \ --
THROW because we have misconstructed a CLASS.

: COMP-1ST-STRUCT  **   \ operator cfa flag --
Compile the first part of a dotted phrase. Instances of TYPE: or POINTER: are the only valid cfa's.

: COMP-MIDDLE-STRUCT ** ( operator cfa flag --- )
Compile the middle portions of a dotted phrase. Instances of TYPE: are the only valid cfas.

Compiling for VFX v4

VFX v4 provides a hook in the interpreter loop especially for object package parsers.

: +structures   runword \ --
Switch on the structure compiler.

: -structures   runword \ --
Switch off the structure compiler.

Compiling for VFX v5

VFX v5 uses recognisers for all parsers. Installing a dot notation parser is something of a kluge as the minimum has been done to make code work without a total rewrite of the parsing code.

: dotNotation?  \ -- flag
Return true if the text at POCKET appears to be a well-formed dot notation string

2variable DotPS$        \ -- addr
Temporarily holds text string address and length.

: isDotText?    \ c-addr u -- flag
Return true if the text appears to be a well-formed dot notation string.

' doDotText  ' doDotText  ' postDotText  RecType: r:classVFX    \ -- struct
Contains the three actions for dot parsers.

: rec-classVFX  \ caddr u -- r:float | r:fail
The parser part of the floating point recogniser.

: +structures   runword \ --
Switch on the structure compiler.

: -structures   runword \ --
Switch off the structure compiler.