HCC!Forth

HCC!forth

The Very Simple Language (VSL, in Dutch HET)

An informal introduction and manual.

Introduction.

HET (in Dutch: Heel Eenvoudige Taal) was designed originally as an extremely simple programming language by Lambert Meertens. At the Techn. University of Delft several implementations of HET were made in a few programming languages: e.g. C, Algol68 and LISP. The C implementation was by far the fastest; the Algol68 was somewhere in between and has been more an experiment in the usablility of Algol68; the LISP implementation was by far the shortest and had the advantage that even with the HET operators one can look inside the underlying LISP functions. This is the implementation which will be described in the short manual.

HETLISP is a stack-oriented language. There is a working stack WS and a programming stack PS (mostly invisible to the user). Names are any character strings of one or more characters.

<token> ::= <any printable character except '(',')','"' and ".">
<name> ::= <one or more tokens>
The separation between names is a space (or carriage return).
<sequence> ::= <one or more names separated by spaces>
<item> ::=  name | <list>
<list> ::= ( zero or more items )

The empty list can be represented as well by () as by NIL. The tokens ( , ) , " ,and . had already a significance in the embedding language LISP. Internally only capital letters are used. However, the user may type in small letters. They will all be converted to capital letters. For clarity all examples will be given as if typed in capitals.

Names have no significance as such

But the following names have special meaning as operators:

  ;         Delete top name of stack (voiding)
  ^         Get value of name (dereferencing)
  !         Execute required action (elaborate)
  +         Concatenate name to list
  \         Take first item from list (as CAR in LISP)
  >         List -> name (as PACK in LISP)
  <         Name -> list (as UNPACK in LISP)
  =         Test equality of [top] and [top-1]
  ?         Test type. l=list,s=self exe oper,w=name
  :         Assign value to name ([top]:=[top-1])
  '         quote next name

These tokens only have the significance of operator when standing alone (between spaces or parentheses). E.g. !! is a name without special meaning. The exact action of these operators will be treated in detail in the following chapters. The token " (double quote) had a significance in the embedding language LISP and can be used in HETLISP to make multi-word sequences into a single name. E.g. "hello world" is regarded as a single name with an embedded space.

Apart from these single token names the following "reserved words" are used as monitoring en controlling operators:

  DEF          Define selfexecuting operator
  FORGET       Forget definition for redefinition
  READDEF      Read definitions from file
  SAVEDEF      Save all definitions to file
  ALLDEF/ALLVALUE     Show all defined operators/values
  CLEARDEF/CLEARVALUE Clear all defined ops/values
  DOS          Execute name as DOS command
  WS           Show working stack for intermediate printing
  TRON         Set tracing mode on
  TROFF        Set tracing mode off
  LISP         Change control to the underlying LISP system
  COUNT        Toggle a switch for counting single token operators

Simple list manipulations

The HET system is a conversational system.

One can type names and operators and their interaction will immediately be effectuated and the result shown. It is most easily understood by giving a few examples: (the triple chevron is simply the so-called prompt, inviting to input something).

>>>    A           is typed. This places A on the Working Stack
WS:A               WS: shows the present WS
>>>    B           B is typed which places B on top of WS
WS:B A

The ; (semicolon token) for voiding operator
>>>    ;           The operator ; voids (discards) the top item
WS:A
>>>    ;           The next ; also voids A
WS:
>>>    (A B C)     A list is placed on the stack
WS:(A B C)

The  > (greater than token) for packing operator
>>>    >           The operator > packs the list into an name
WS:ABC

The original document on the description of HET by Meertens did not define what should happen when a list contains a sublist. As the implementation of this version of HET is based and makes use of the underlying language LISP function PACK it appears that

>>>    (A (B C) D)
WS:(A (B C) D)
>>>    >           results in: 
WS:AD            

The < (less than token) for unpacking operator
>>>    ABC
WS:ABC
>>>    <           The operator < unpacks the name into a list
WS:(A B C)

Similarly to the pack operator the unpack is not well defined for the case a list is tried to unpack.

The \ (backslash) for first operator
>>>    \           The operator \ takes the first item from
WS:A (B C)         the list and puts it on top of the rest.
As it does not make sense to take the first element of a non-list
the \ operator does nothing in that situation.

The + (plus token) for concatenate operator
>>>    +           The operator + joins the top item again
WS:(A B C)         to the list
>>>    ;           Void the stack
WS:
>>>    3           Put 3 on the stack. NB: 3 is not the number 3
WS:3               but just a token 3 ! It has no arithmetic value
>>>    A           Put A on the stack
WS:A 3

In the same way as on the \ operator it does not make sense to have the + operator add an element to a non-list. Also in that case nothing will be done. As NIL is regarded as an empty list, using the + on a single atom on the stack will result in packing the single element as a list. E.g.:

>>>    A
WS:A
>>>    +
WS:(A)

The : (colon) for assign operator
>>>    :           Assign 3 to the name A, thus storing 3 in A
WS:3               The value stays on the stack
>>>    ;           Void the stack
WS:

The ^ (caret) for dereferencing operator
>>>    A ^         Put A on the stack. The ^ operator
WS:3               "dereferences", or "takes the value" of A
                   When no value has been assigned to A (or no
                   operator has been DEF'ed to A) the value of A
                   is A.
>>>    C B : ;     Here a few operations are combined on the
WS:                same line. B gets value C
>>>    B A : ;     A gets value B. Both times stack is voided.
WS:
>>>    A ^ ^       Now dereference A twice. Gives C.
WS:C               This is multiple indirection.

Defining new operators

More complicated operators can be made by grouping all required sub-operations together by grouping them as a list. E.g. one could interchange the top items of the stack by first assigning the top item to a name, assigning the second item to another name, and then taking the value out of their names in reverse order. The auxiliary names used to store intermediate values in the most frequently used operators are using letters followed by ^ not to give name clash with other freely chosen names.

>>>    (X^ : ; Y^ : ; X^ ^ Y^ ^) SWAP :
                                       Store top in X subtop in
WS:(X^ : ; Y^ : ; X^ ^ Y^ ^)           Y
>>>    ;                               void
WS:
>>>    SWAP ^                          Look: the value of SWAP is
WS:(X^ : ; Y^ : ; X^ ^ Y^ ^)           indeed that list
>>>    ;                               Void
WS:
>>>    A B                             Now put B and then A on
WS:B A                                 stack
>>>    SWAP                            The word SWAP only places
WS:SWAP A B                            the name SWAP on WS
>>>    ^                               Take the value of SWAP
WS:(X^ : ; Y^ : ; X^ ^ Y^ ^) A B
>>>    !                               and execute this
WS:B A

This would be very impractical for frequently used operators. Hence another kind of assignment has been defined, called DEF which makes e.g. SWAP a so-called self-executive operator. So now the same can be done with:

>>>    (X^ : ; Y^ : ; X^ ^ Y^ ^) SWAP DEF
                                       Store top in X subtop in Y
WS:                                    In contrast to the : the DEF
operator does not leave the result on the stack.
>>>    SWAP ^                          The value of SWAP still
WS:(X^ : ; Y^ : ; X^ ^ Y^ ^)           looks the same
>>>    ;                               Void
WS:
>>>    A B                             Now put B and then A on
WS:B A                                 stack
>>>    SWAP                            But now SWAP acts as if
WS:A B                                 automatically followed by
                                       ^ !

All 11 single token operators mentioned in the beginning, are in fact self-executing and are not further expressible in a smaller subset.

The next operator is the ' (quote).

This operator indicates that not the action of the operator should be effectuated but the name itself is meant. In this way one can look inside the : or the DEF operator (which is showing the LISP implementation in this case). The ' may be (but does not need to be) written immediately adjacent to the name following. This is a consequence of the fact that the HET quote is the same as the LISP quote.

>>>    ': ^
lambda : (),
  block
    if cadr (stack^) = nil
    then
      put (car (stack^), quote (value), quote (nill))
    else,
    put (car (stack^), quote (value), cadr (stack^))
  endblock,
  remprop (car (stack^), quote (selfexec)),
  stack^ : cdr (stack^)
endlam
WS:

This is saying that on : the assignment is putting something in the name with the property VALUE. A colon assignment will remove the DEF operator with the same name.

>>>    'DEF ^
lambda def (),
  put (caar (stack^), quote (selfexec), cadr (stack^)),
  remprop (caar (stack^), quote (value)),
  stack^ : cddr (stack^)
endlam
WS:

This is saying that on DEF the assignment is putting something in the name with the property SELFEXEC. The DEF operator will remove the VALUE property of the same name. Only in rare cases one will need the ' inside a proper HET program.

The = (equals token)

A few operators have not been discussed yet. One is the = . The equals token will deliver T^ when both objects on top and subtop are the same, otherwise it will deliver F^. Do not think that T^ stands for True and F^ for False. = simply delivers the object T^ or F^ (which objects could very well have another value assigned to them). The names for T^ and F^ are chosen as written not to give a name clash with a T of F which the user might have chosen as a single letter name.

>>>    A A =       A is the same as A hence = delivers T^
WS:T^
>>>    ;
WS:
>>>    A B =       A is not the same as B hence result is F^.
WS:F^              It could very well be that the values of F^ and
                   T^ are equal !

A fine example is the definition of IF (defined as a self-executive).

>>>    IF ^
WS:(T^ : ; F^ : ; = ^ !)     This is the list value of IF
>>>    ;
WS:
>>>    3 4 NO YES IF         Now compare 3 and 4
WS:NO                        Answer is NO. NO could very well be
                             another list or defined operator,
                             coming into action. The YES is not
                             executed.
>>>    ;
WS:
>>>    3 3 NO YES IF         Now only the YES is done
WS:YES

During the elaboration values are assigned to T^ and F^ and later compared with =.

Skipping one of the branches of an IF operator is sometimes called the "law of if": The non-chosen branch is not elaborated. This is very distinct to a function in normal mathematics where if(3,3,no,yes) would first elaborate all its arguments before the function if is applied. This could lead into infinite recursion.

The remaining operator is ?.

When an name is on top of the stack, ? delivers W, when a list is on top of the stack, ? delivers L, and when a self-executive is on top, ? delivers S. Pay attention to the fact that NIL (or the empty list ()) is regarded as a Word, and will deliver W.

The other built-in operators

We have seen the DEF operator for making self-executive operators.

The SAVEDEF operator

The operator SAVEDEF serves to file away a set of useful defined operators and values on an external medium. This operator has three different ways in which it can be applied. filename SAVEDEF will store all present definition and values. E.g. USEFUL SAVEDEF saves all defs and values in the file USEFUL.HET.

 (name1 name2 ...) SAVEDEF

will file all names listed under the first name in the list. E.g. (DUP SWAP) SAVEDEF will create a file DUP.HET in which the operators DUP and SWAP are stored.

 (name1 name2 ...) filename SAVEDEF

will file the definitions and values of all names in the list under the file name filename. Especially with the help of the last option one can selectively file the set of names with which one is experimenting. Two warnings are appropriate here:

  1. If a definition with " in them will be saved with SAVEDEF,
    the double quotes will disappear in the written file. On
    reading in this file again one will not get back the same
    definition. See e.g. the definition of MAN in the file
    MINIMUM.HET .
  2. If there are names on the stack before SAVEDEF is given,
    these names are used as arguments for SAVEDEF. E.g. with
    (JOHN) on the stack, the next line JACK SAVEDEF will save
    the name JOHN in the file JACK.HET, which is possibly not
    what is intended. Advice: clear the stack before using the
    SAVEDEF command.

The READDEF operator

The operator READDEF will read in a set of definition from a file. E.g. USEFUL READDEF will read all definitions from the file USEFUL.HET. These external .HET files can be edited externally by any available text editor.

The FORGET operator

name FORGET will forget a definition. Be careful: if that name happens to be a self-executive then the action of name has been performed before the FORGET is done. In that case the name should be quoted. E.g. 'SWAP FORGET will forget the operator SWAP as show before. SWAP FORGET would first do a swap and then forget the item now at the top of the stack, which is not what is generally meant.

ALLDEF

will show a listing of all defined self-executives except the 11 single token itemary ones.

ALLVALUE

will show a listing of all values assigned by :

CLEARDEF

will wipe all DEF defined operators

CLEARVALUE

will wipe all assigned values

DOS

will step completely outside the system by executing a DOS command. E.g. DIR DOS will print the directory listing of the current directory. With the help of the token " one can even execute complete command lines with DOS. Example:
"PRINT HETLISP.DOC" DOS will print the HETLISP manual on the printer.

TRON and TROFF

will switch on, resp off a tracing mode, showing all intermediate steps when complicated operators are executed. The Trace On state will be automatically switched off when the Program Stack is empty. Once Trace is On, every single step must be acknowledged by pressing ENTER. E.g.:
A B TRON SWAP
would enable the swapping operation to be made visible. Beware: as operations are so extremely elementary, one can easily underrate the immense output a tracing of a seemingly simple problem can produce. Use it only to debug simple examples. During trace on the PS (program stack) is also made visible. A nice application of TRON and TROFF is the following little defined operator:
(TRON TROFF ;) TT DEF
If one wants to see what happens at a specific place in a program one can insert <marker> TT at that spot, where <marker> can be any name. The PS and WS will then be shown with the <marker> as identification for the traced spot.

Escape from a long tracing can be done with ` (backquote) which brings the system in the same state as the command LISP. Choosing H from the menu will return to HET and tracing is switched off.

For insiders: if one is tracing complicated functions it would be undesirable to have to trace through well known operators such as SWAP, DUP or IF. For this reason an operator TRRES! is provided which restores the tracing state at the end. E.g. if one redefines IF as:
(TROFF T^ : ; F^ : ; = ^ TRRES! !) IF DEF ;
then TROFF will stop tracing but remembers whether tracing was on or off at the beginning and restores the tracing state at the end.

LISP

escapes to the surrounding LISP environment into which this whole system has been embedded. This is only for insiders. This manual will not deal with any of the details of LISP. Let it be sufficient to say that a command H in the first command mode of LISP will bring back the system in HET.

COUNT

is a built-in operator which toggles a counter, counting all single token elementary operators. It can give an impression how many elementary operations are necessary for even simple programs. On start-up the COUNT toggle is off. The first COUNT switches the counter on, the next COUNT switches off.

Defined operators in the package MINIMUM.HET

The following operations are not built-in into the system but are automatically loaded as if MINIMUM READDEF had been given.

;;   will clear the Working Stack. This is a very useful
     operation to remove all rubbish from the Working Stack. It
     is defined as (DUP NIL (; ;;) (; ; ; ; ; ;) IF) ;; DEF and
     is in itself a tail-recursive function.

MAN  is another command in the util set for quickly looking into
     the manual. It is defined by: ("LIST HETLISP.DOC" DOS) MAN
     DEF Remark that the double quotes serve to include spaces
     and dots in the command string for DOS. These commands are
     wiped when CLEARDEF is executed.

EDIT calls (via UTIL) a lister and an editor. EDIT brings up the
     listing mode. With the cursor keys the file to be edited can
     be brought in focus and the editor can be started with E.

As a stand-alone program in DOS it can be used with
UTIL L             for calling the listing feature. See above.
UTIL TED filename  for calling the editor alone.
                   The editor is practically self-explanatory and
                   can also be used with the customary Wordstar
                   keys. Some more utilities are provided in UTIL
                   Type UTIL in DOS (or "UTIL" DOS in HET) to
                   find out.
DUP  will duplicate the top element of the stack.
SWAP will swap the elements on top and sub-top.
REVERS will reverse all elements of a list.

One could run REVERS as follows
>>>    (A B C D E)
WS:(A B C D E)
>>>    REVERS
WS:(E D C B A)
Or a word could be reversed by first unpacking and packing again
with
>>>    (< REVERS >) REVWORD DEF ;
WS:
>>>    'REVERS REVWORD          Mind the quote, otherwise the
                                action revers would be done.
WS:SREVER

A demo program for doing decimal arithmetic is included in the package which can be loaded by:
DEMO READDEF
It includes among others INC a number, DEC a number and PLUSS, which will add two decimal numbers. (The name PLUS was already in use in LISP). Looking at ALLVALUES, remark that 1 has the value (0 2), 2 has the value (1 3) etc. There are no numerical standard operators. The value (0 2) for 1 represents the predecessor and successor of 1. The 10 single digit definitions, together with operations for incrementing, decrementing, and representing numbers, these are the building blocks for the arithmetic operators. E.g.
>>> 235 987 PLUSS
WS:1222
Try to trace a very simple sum or put on COUNT and marvel about the enormous number of operations necessary to do such a simple sum in such a primitive system.

W.L. van der Poel
Issue 38 6feb2005