📜 ⬆️ ⬇️

Stack programming with a human face (part two)

As expected, the previous post caused conflicting comments. Someone is satisfied with the existing Fort to resolve issues, someone (like me) is annoyed by its features.

image

Let's dot the i right away: I'm not trying to write a replacement for Fort. The fort is a family of medium-level programming languages ​​that continues to efficiently solve tasks and is not going to retire. But I’m thinking in another niche: high-level stack language with an emphasis on ease of reading programs for beginners (as far as possible). The great tradition and high level has its merits, but at the same time some features (including positive ones) of the Fort are lost.
')
A new imaginary language has its own philosophy and its own concepts. I will continue to write about this.

Selection of cycle designs


Each creator of a language in which there is not only recursion sooner or later thinks about a set of cyclic constructions. I prefer to first consider the construction of a general universal cycle from which the rest can be derived, and then, based on practical experience, add additional constructions for the most frequently encountered cases.

The main loop looks like this:

repeat     -     when 1 do 1     when 2 do 2     when N do N     otherwise - end-repeat 

Scary, right? But in fact, this is only a formal description, everything works elementary. Each iteration runs all any words after the repeat. Then the calculation of the conditions when. If the condition1 is true, then words1 are executed and the loop starts a new iteration from the very beginning. If the condition1 is false, then the transition to the next condition occurs. If none of the conditions is true, the branch is executed, otherwise.

This cycle is good because you can derive anything from it, that is, it is a common basic construction.
1. Infinite loop (optional when and otherwise are omitted, they are not needed):

 repeat    - end-repeat 

It is assumed that somewhere inside the loop there will be an if conditional termination and the word exit-repeat.

2. Cycle with precondition:

 repeat    when -? do -    otherwise exit-repeat end-repeat 

3. Cycle with postcondition:

 repeat    -    when - do exit-repeat end-repeat 

4. A loop with a counter (take an integer variable counter for example):

 repeat    counter @ ( )    when 100 < do      |counter @ ++| counter set (   ,    100)      -    otherwise exit-repeat end-repeat 

5. The cycle with the output in the middle:

 repeat    --    when ? do exit-repeat    otherwise     --- end-repeat 

6. And even the Dijkstra cycle!

Let's see what happened. The infinite loop is intuitive and concise, no extra words, so just leave it as it is. A cycle with a postcondition is less common for and while, so there is no sense in a separate construction. If, however, such a cycle is needed, it can easily be derived from the general construction, since it turned out to be clear and concise.

But the cycles with a precondition and a counter turned out to be more awkward. Since they are often needed, it makes sense to implement them in the form of individual words:

1. Cycle with precondition:

 while  do     end-while 


2. Cycle with counter:

 for - - to - step  do     end-for 


3. And a cycle with a post-condition (with a great desire):

 loop        until - end-loop 

Pay attention to an important point: while and loop loops can be implemented as a simple textual substitution. Indeed, if while replaced by “repeat when”, and the end-while to “otherwise exit-repeat end-repeat”, then we get a common loop. Similarly, a loop with a postcondition: loop on “repeat”, until on “”, and end-loop on “when not do exit-repeat end-repeat”. The repeat loop itself can be converted into an if set if desired.

That is, we need to implement only two loops in the translator: repeat and for. The while and loop cycles can be done on text substitutions by means of the language itself. A similar approach is adopted in Eiffel and Lisp: we have a generic construct (for example, a loop in Eiffel and cond in Lisp), which can be used very flexibly. New designs, if possible, are implemented on top of the old. In the Forte, the principle is the opposite: we have a lot of special cases and tools with which we can, if necessary, create the necessary constructions ourselves.

Each approach has its pros and cons. The approach from “general to particular” is good in high-level programming, because the programmer does not have to look into the “guts” of the translator and tinker with the implementation of the next bike. In the Fort, you will first have to study the insides of the fort system and introduce new words, but then it becomes possible to use the new word unlimitedly, which can be performed quickly and efficiently. Not that one approach is better than the other, they are just different. Since I care about the convenience of reading the code and the simplicity for beginners, I accepted the first approach as the main one.

Conditional constructions


We do the same with conditional expressions. Generalized construction (hello, Lisp!):

 cond    when 1 do 1    when 2 do 2    when N do N    otherwise - end-cond 

It works like a cond repeat, but only once, and not multiple times. For early exit, the word exit-cond is provided. Cond can also be derived from repeat: you just need to exit-repeat after each when-branch. So that!

Although cond can be used to branch any complexity, it is useful to implement some common patterns separately.

1. The simplest conditional statement:

  if    1 else    2    (, ) end-if 


2. But case construction is specific for stack programming. Suppose we have some kind of variable x and need, depending on the value of x, to perform a certain cond branch. Before each when in a cond or before each if (depending on the method of implementation), we will have to put dup, that is, to take care of duplication and / or drop of extra elements:

 : testif dup 1 = if ." One" else dup 2 = if ." Two" else dup 3 = if ." Three" then then then drop ; 

"Noise" words are completely superfluous. Indeed, if such situations occur regularly, why not automate dupes and drops, increasing readability and brevity? And if we need to compare two variables? And if three? This is how much you have to trick with a stack before each condition!

Especially for such situations, a case construction is needed - a smarter version of cond. The syntax is very similar:

 case --    when 1 do 1    when 2 do 2    when N do N    otherwise - end-cond 

The main difference is that each time before when the compared elements are doubled, and before the end-case from the stack, extra copies are removed. That is case = cond + placed dup and drop. The number of items being compared indicates how many items at the top of the stack need to be doubled:

 x @ y @ case 2 [xy -- xyxy]    when = do "" print-string    when < do "y " print-string    otherwise "x " print-string end-case 


We introduce new words and describe the substitution


Well, we already have the building blocks. Remained solution - the definition of new words. Such words are invoked via call, if we speak in terms of assembly language:

 define   end 


 define-macro   end-macro 

But these words work through textual substitution: the name is replaced with the body. As you might guess, while, loop and if can be implemented on macros as follows:

 define-macro while    repeat when end-macro define-macro end-while    otherwise exit-repeat    end-repeat end-macro define-macro loop    repeat end-macro define-macro until    when end-macro define-macro end-loop    not do exit-repeat    end-repeat end-macro define-macro if    cond when do end-macro define-macro else    otherwise end-macro define-macro end-if    end-cond end-macro 

We introduce another familiar word for beauty:

 define-macro break!    do exit-repeat end-macro 

Now you can very concisely and clearly write something like
 repeat ... when 666 = break! ... end-repeat 


Total: thanks to flexible structures of repeat and cond with the help of elementary text substitution, you can implement a whole set of building blocks for all occasions. Unlike the Fort, you don’t have to think at all about the realization of the words, we abstract from the details of the implementation.

For example, the word when in cond and repeat is rather cosmetic in nature: it allows you to visually separate the condition from other words. But in fact, only the word do plays a role. Want utmost brevity?

 define-macro ==>    when do end-macro 

and write
 cond    x @ y @ = ==> ""    x @ y @ < ==> "y "    otherwise "x " end-cond print-string 

without thinking at all about the implementation of the translator. This is not our concern, we are at a high level!

The next time the conversation will be about parsing the program source code translator, about stacks, data and variables.

Source: https://habr.com/ru/post/243051/


All Articles