The Very Simple Language (VSL, in Dutch HET)
An informal introduction and manual.
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:
- 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
- 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
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.
will show a listing of all defined self-executives except the 11 single token itemary ones.
will show a listing of all values assigned by :
will wipe all DEF defined operators
will wipe all assigned values
will step completely outside the system by executing a DOS
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.
"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.
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.
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:
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
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