lucky7, Forth simplified
abstract
Forth is traditionally used for control application for small systems.
This niche is eroded by simple, easy to use interactive languages like Lua
and MicroPython and by the tendancy to enlarge Forth systems.
lucky7 is a concise (66 word) self contained language on top of Forth,
intended to be more powerful and conceptually simpler than Forth itself.
It can reach down to packages present in the underlying Forth in a similar
fashion as micro python imports c-packages and through Forth to extensions
coded in machine language, which is beyond python scope.

Introduction
Forth is an extensible language. Why then can we ever want
a radical departure from a system that indeed works?
The reason are that by extensions the language has become unwieldy.
If that were only in the field of facilities, it would be not harmful,
but the number of concepts to be understood undermines the traditional
transparancy. With the benefit of 50 years of hindsight
we are now in a position to redesign the language from
first principles meanwhile getting rid of cruft and idiosyncrasies that are
harmful, and introducing idiosyncrasies that are beneficial.
Forth bears a heavy burden, in common with all the very first languages,
FORTRAN and LISP. That is the case-sensitivity issue. Languages conceived
in the 70 where ALL-CAPS. A new generation is needed that finds a
case-sensitive language that uses lower case for its keywords
making this a non-issue for new users, at last.
I will introduce new names for familiar Forth concepts and define them
properly.

Principles
The principles Forth is based on are confirmed
1. the language is interactive, with an interpreting mode and a deferring mode
5. Memory managements is explicit. The user reserves memory and use it as he sees
fit. Bytes are visible. Machine words are visible.
2. The system knows a number of behaviours called recipees.
  A number of recipes is built in, they are coupled to a name.
  In interpreting a name the system executes the one single behaviour that is
  associated with it.
We will identify the
  behaviour with a recipe and no longer use the term behaviour.
We will say the recipe is run.
  In deferring, the system builds a recipe by combining the recipes that are
  named to run in order.
  There are provisions to add a name to the system, this is called to define.
3. A recipe can be immediate. This only affects deferring mode. In deferring mode
  it is run instead of added to the recipe being build.
4. recipes work on data remembered by the system and leave data for
  recipees that follow. This data can be recipees. It follows that a machine word
  can contain a recipee. We will identify a recipee with this machine word that encodes it.
6. There are provisions for literals, numbers, strings.
Recipes can be quoted and then are literals, which is the machine word in the previous point.
Literals in deferring mode are added to the recipee, not executed
immediately, such that code that contains literals can be pasted
into a deferred code sequence.

You see that the stack and the return stack are present, but
de-emphasized.
Users will find manipulating recipes intuitive. Practicality demands that
on a higher level however some manipulations of namespaces and source files
is possible. Hereto recipes to include files based on literals (names) and
importing commands from an underlying implementation system are a must.
In practice the search-order system of ISO-94 is used, where lucky7 is a
sealed dictionary.

The outside perspective must be that lucky7-users will consider Forth like
micro-python user consider C, that is not at all. At most they are
vaguely
aware that some experts can add new facilities that can be imported.
This only works if most facilities needed are present.

Difference with Forth.
Lucky7 has the following characteristics.
There is a hard and fast rule that there is exactly one behaviour c.q. recipee  associated
with a name, barring namespace manipulation.
All behaviours are bracketed between curly brackets.
All control structures have curly brackets,
All condition testing is done by a word starting with |.
There is but one counted looping constructed, for now.
Basically all arithmetic, logical and shift operators are present.
All comparison words are present, but unsigned comparison is abandoned.
Output facilities are severely restricted.
Operators are c/Java/C#/python/go compatible, no MOD.
There are three utilities and they have three letter names.
Errors in Forth clear the stack, in lucky7 no more.
Parsing is mainly done when "defining" , the name that is associated
with a recipee follows the word that does the "defining", such as VARIABLE.
The only other exceptions are when words to be imported or files to be
included are named.
Last but not least. The interpreting mode can be bracketed inside
a deferred mode, to define local recipees, such as variables and
auxiliary calculating recipees, that can have interpreting mode
parts and so on recursively.

Fundamentals
The observation is that : name .. ; couples recipee-building with name-giving
prematurely. Furthermore, forbidding the nesting of definitions is
explicit in the Forth standard, and is equally holding Forth back.
The fundamental construct in lucky7 is the { ... } sequence.
It is similar to a :NONAME ... ; construct, and it it leaves a recipee.
However this recipee is treated as a literal, meaning that it can be nested
inside a recipee and is then similar to a [: .. ;] construct.
The { ... } constructed can be interrupted by a [ ... ] sequence of
interpreted not deferred code, that in turn can do deferring between
{ ... }.
All are based on 4 fundamental bracketing pairs:
({) .. (}) underlies { } : a code sequence with a header
(( .. ))   stops and continues deferring, basically compile and resolve an AHEAD
;)  .. (:   saves and restores the lookup mechanism for names
(s .. s)   save and restores state that determines interpreting
Once you manage to implement those, the rest is simple.

\ `{ initiates a recipe, `} leaves it.
: { (( (s ({) ;   IMMEDIATE \ Leaves xt
: } >R (}) s) )) R> 'LITERAL EXECUTE ; IMMEDIATE

\ New context for definitions, maybe in the middle of a word.
: [  (( (s ;) ;   IMMEDIATE
: ]  (: s) )) ;   IMMEDIATE

Control structures
All the control words containing some curly bracket are just aliases for Forth
words like IF REPEAT and such. They are easy to remember.
Each { matches a }. If a condition is tested the words starts
with |.
The counted loop do has the diagram ( exclusive-limit recipee - .. )
do can be run in interpret mode.
ix is the loop counter.

Overview
lucky7 contains the following named recipees
Basic
; { } [ ] run
control
|{ }|{ }| {| | |} do ix
memory
here , reserve   poke peek bpoke bpeek
operators
+ - * / % negate   or and xor invert   << >>
logical operators
< > = <> >= <=   false true not
reordering
drop dup over nip swap pdup pdrop spswap
spare location
>r r> r
output
. x. $.
adding recipees and objects
: data variable meta
extensibility
include want from: import
Utilities
lsn clr shw

Basic words
The curly and square brackets have been discussed.
run will just run a recipee.
Nesting and closing comments is problematic because any characters
can be used in names.
In a Forth-type languages like in assembler there is just one
proper way to handle comments. One needs a reserved
punctuation type character and it runs to the end of the line.
The ; is used widely in assemblers and is the right choice in
lucky7.

memory
lucky7 is byte oriented. bytes and machine words can be peeked and
poked. reserve replaces ALLOT .

operators
The operators are compatible with mainstream, notably %.
<< >> are logical and replace arithmetic 2* 2/
''false true not'' and a full set of comparators is very valuable
for readable code.

reordering
There are no 2's in reordering operators, instead p is used.
spswap is the infamous ROT, of course.

Spare location
Can't do without good old r-words

output
The sky is the limit for output. Expect to do a lot
of importing if you really want to show off.
Here it is limited to printing numbers (in decimal),
machine words,such as recipees (in hex) and strings.

adding recipees and objects
You may not realize it, but in a Forth  you can distinguish
between recipees and objects. Basically the objects are the
CREATE-DOES words and the recipees are the rest.
An object has behaviour and data. The behaviour is what is filled
in by DOES> and the data is gotten to by >BODY.
"data variable : meta " are all followed by a name and the name is added
to the list of names.
The simplest object is created by data. It takes no arguments and
the recipee will leave the value of here after the dictionary
space used up for the recipee administration.
Next simplest object is created by variable. It takes no arguments
and the recipee will leave the value of here after the dictionary
space has been allocated for the recipee. Then it allocate space
for a machine word.
Both are examples of the use of meta
{ } { } meta data       { 0 , } { } meta variable
meta accepts two recipee's and builds a definer for an
object type. The first recipee is executed after the defining word
is executed, the second recipee is the DOES> action that works on
the here-pointer, when the new word is executed.
This is the implementation in ciforth.
; ( recipeb reciped "name" -- ) generate defining word "name".
{ >DFA @  SWAP >R >R '{ RUN  'NAME , 'DEA , 'dodoe , 'SWAP , '>CFA , '! ,
    'LIT , R> , ', ,  R> , '} RUN : } : meta
This illustrates postponing using lucky7. Normally words are just
comma-ed in. Immediate words we need to run.
First meta build a definer recipe based on recipeb and reciped and at the
end runs : to give it a name. Note that both are build into the definer
that meta creates.
Unpostponed the recipee to be build looks like
{ NAME         ; get a name from the input stream
    DEA        ; make a dictionary entry
    dodoe SWAP >CFA !  ; fill in the code is for a create does recipee
    'reciped ,         ; fill in the does> code
                       ;  >BODY points here
    recipeb            ; do whatever is needed to build.
}
The >DFA is a ciforth technicality, instead of the address of the
build code recipee we use a pointer to where the actual code starts.

extensibility
Although included would be more politically correct in this
context, it is cumbersome compared to include.  That is chosen.
The word want is borrowed from ciforth. It loads a word from
background storage. So after "want SEE" the decompile facility
of ciforth is added to the lucky7 wordlist.
"from: FORTH import FINIT F+ F*" loads some floating point utilities
into the lucky7 wordlist.

utilities
The three utilities are
lsn : show the words in lucky7 ("ls names")
clr : remove all data remembered ("clear the stack")
shw : show the data remembered ("dot-s")
These are typed in more frequently than any other word, and must not
conflict. Note that errors do not throw away data, hence clr.
This is beneficial idiosyncrasy. They will stuck in everybody's
brain after the first lucky7 session.

Example 1: a turnkey hello world program
Luckily the underlying Forth has a facility TURNKEY.
The code for this is
{ "hello world" $. } : hw
from: FORTH import turnkey
'hw hellow turnkey
Of course the code for hw could be in a file hello.l7
and added by include hello.l7

Example 2: Roman digits.
Roman digits is one of the classical examples for a
CREATE DOES> data structure. This is a demonstration of the
equivalent META word in the language lucky.

In e.g. MMXIX  (2019) the X is worth 10 and it is added to the
running total if the next digit is smaller or equal,
and subtracted if the next digit is greater. So the I is less than
the X and is subtracted.

We must initialise by a running total of 0 and a dummy first
digit of 0 that will always be replaced.
With a new digit we compare the old digit, but the
digit itself has to be remembered for the next time.
If the old digit is less we must correct, by subtracting the
remainder w.r.t. the new digit two times.
Then we end with the running total and last digit, that must
dropped.

; Roman numerals are bracketed between
{ 0 0 } : [r
{ drop } : r]

; Now the META (formerly CREATE DOES> ) construct.
{ , }  \ build, just remember the numeral
{ peek [ variable newr  ] newr poke
  newr peek < |{ dup newr peek % 2 * - }|
  newr peek +   newr peek } \ action
meta roman-digit

; And of course :
1 roman-digit I     5 roman-digit V
10 roman-digit X     50 roman-digit L
100 roman-digit C    500 roman-digit D
1000 roman-digit M

; This test succeeds:

[r M M X I I X r] . " should be 2018" $.

Implementation notes
The language is actually implemented, leaning heavily on features
of the authors version of Forth: ciforth.
ciforth is strong in compilation to native executables. The executable of
lucky7 is a stand alone binary file, with as only runtime environment the
source library for the underlying Forth. New users may not be aware that the
languages is Forth based, nor should they care.
ciforth facilities for literals are present in the minimum search order,
a wordlist called ONLY. This takes care of numbers,strings, but also of
the literal expression for recipees. This is a tick collated in front of the name, as in
'+ . There is no recipee called tick `` ' ''.
FORTH ONLY and lucky7 are names of word lists and
can be executed to manipulate the search order.

Loose ends
This is an experimental albeit working language.
Expect a number of loose ends, e.g. a word to leave a counted loop is still
missing.
The most progress towards a truly usable languages is to
be made in the use of namespaces as in the floating point example.
An import construct that understands libraries implemented in
behalf of micro-python would be a killer.
The extension facilities has rough edges as can be seen in the
floating point example. "want -fp-" loads the floating point
packages in the underlying Forth, but the words
needed still have to be imported.


You can comment via email
Go to the home page of Albert van der Horst

N.B. Here and on this whole site, trade marks -- being proper names -- are acknowledged by capitalizing the first character.