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.
N.B. Here and on this whole site, trade marks -- being proper names -- are acknowledged by capitalizing the first character.