📜 ⬆️ ⬇️

Dagaz: Factorial is easy!

image Scripting is perhaps the most important (though not the most difficult) part of the project I have conceived. In order for everything to work, I need a general-purpose language, with variables, conditional execution, loops, and exceptions. I don't need something complicated, like anonymous functions or closures . Most likely, even recursion will not come in handy to me, in any case, as long as no application was found for it, in any of my cases . In this language there will be no syntactic sugar at all, since all the tasks of metaprogramming will be taken over by XSLT . In general, this language will be as simple as possible, but ... not simpler.

Let me remind you that the script, in my understanding, contains not only the definition of a number of functions defining the "behavior" of the objects participating in the game, but, in addition, must define the structure of quite complex objects, such as the board, the figures, etc. P. Here is an example of such a description:

Definition of '' board ''
(board (grid (dimensions "A_J" "1") (direction (name forward) 1 0) (direction (name backward) -1 0) ) (grid (dimensions "A_J" "2") (direction (name forward) -1 0) (direction (name backward) 1 0) ) (grid (dimensions "A_K" "3") (direction (name forward) 1 0) (direction (name backward) -1 0) ) (grid (dimensions "a_d")) (link (name forward) (J1 J2) (A2 A3) (K3 K3)) (link (name backward) (J2 J1) (A3 A2) (K3 K3)) (zone (name rebirth) (positions F2)) (zone (name protect) (positions F3 G3 H3 I3 J3)) (zone (name beauty) (positions F3)) (zone (name water) (positions G3)) (zone (name truth) (positions H3)) (zone (name ra) (positions I3)) (zone (name stop) (positions F3 K3)) (zone (name end) (positions K3)) (zone (name dices) (positions abcd)) ) 


This definition is a kind of “map” of a board game and can be very complex. It was for the analysis of such hierarchical structures that I needed XPath , which, in turn, led to the need to use XML for the internal representation of the script in memory. Analysis of such structures is verbose , but rather straightforward. Code is another matter.

 (define (factorial na) (if (<= n 1) a else (factorial (- n 1) (* an)) ) ) (define (factorial n) (factorial n 1) ) (define main (set! out (factorial 5)) ) 

I hope you recognized him. Yes, the factorial value is calculated here, using the “tail recursion”, and no, I am not going to engage in the optimization of the “tail recursion”. As I said, I hardly need recursion. For those who suddenly do not know what “tail recursion” is and why it may need to be optimized, I recommend reading this wonderful book .
')
This code is just a compact test, covering as far as possible all the functionality that interests me at the moment. It uses the conditional operator, calls and function definitions, overloading and working with parameters. Since I / O operators are not provided in the language (they are not required), in the test, the output will be emulated by assigning the total value of the “variable” out .

By the way speaking
The complexity of the selected test is quite justified. Calculating the factorial of the three, I noticed that the resulting value “2” is somewhat different from the expected one. An investigation of this situation led to the following fix . I could well not have noticed this error using a simpler test.

It can be noted that all control constructs of a language are functions, in the sense that they always return a certain value . For example, the result of an expression sequence is the result of the last expression executed. We should also mention the exceptions :

 (define dice-drop (check (in-zone? dices)) (check is-empty?) drop-pieces add-move ) 

At any time, a validation of some Boolean expression can be performed. In the event that the resulting value is false, execution must be immediately interrupted. I do not know a tool that is more suitable for this task than exceptions. CheckExpression computes the value of its argument in a boolean context and "throws" a CheckException if the condition is violated (it is obvious that this function will always return the true value, unless it comes down to it before returning the value).

Values ​​of the function arguments will be calculated strictly , before the control is passed to the functions. There will be no total laziness of calculations, but functions such as if , and or or will calculate the values ​​of their arguments only when necessary. To call arbitrary functions (defined in the script body), the ApplyExpression construction is intended . The resulting argument values ​​are nothing else but local variables whose scope is limited to the function being called. All variables are accessed via the IEnvironment interface, passed to each calculated expression:

 public interface IEnvironment { ... void letValue(String name, IValue value) throws EvaluationException; void setValue(String name, IValue value) throws EvaluationException; IValue getValue(String name, boolean isQuoted) throws ValueNotFoundException; ... } 

Using this interface, you can create local variables (using the let function), change their value (using the set! Function), and also get the value by name. For the latter of these operations, a special function is not provided. Any atom that is not a string or numeric literal will be treated as a reference to a variable (or a function parameter), provided that a function with the same name and arity has not been defined in the script.

Details for the curious
Collisions between function calls and variable calls could be avoided by always framing function calls with parentheses, as follows:

 (define dice-drop ... (drop-pieces) (add-move) ) 

I found this record too cumbersome. In addition, there was a problem with the internal representation of such structures in XML. Another unrealized feature is the calculation of the name of the function being called:

 ((if (< ab) "+" else "-") ab) 

In any case, I do not think that the use of such structures is necessary.

In order to simplify the language as much as possible, I did not explicitly implement the QUOTE operator (therefore, it is unlikely that I could write Quine in this language). There is a citation in the language, but it is implicit. For example, such expressions as let and set! “Know” that the first argument should not be calculated (the overridden IExpression.isQuoted method is responsible for this). Thus, the first argument, in these expressions, is perceived as the name of a variable, and not an attempt to obtain its value.

More details
In the application code, there may be more complex situations associated with implicit quoting:

 (define (piece-move direction) (check up) (check direction) ... ) ... (moves (piece-move north) (piece-move south) (piece-move east) (piece-move west) ... ) 

Such code will occur very often. Here, the atoms north , south , east and west should be interpreted as names of directions, but only under the condition that such directions on the board are defined (otherwise, it is a reference to variables). Thus, the direction parameter will contain a string with the name of the corresponding direction.

Positioning and navigation are performed by referring to pseudo-variables with appropriate names. Calling on the “variable” up (provided that such a direction is determined on the board) will move the marker of the current position (as a side effect), and also return true if this movement was completed successfully.

But what happens when you call the direction parameter? If no additional action is taken, a non-empty string value will be received, which, in a boolean context, will be interpreted as true . This is clearly not what we would like. To remedy the situation, the module that provides access to the calculation of the course of motion must check if the resulting string is a name of a position or a direction. If so, the get operation must be re-executed and its result must already be returned to the dial peer. This is a good illustration of the thesis that simplifying a language can lead to a complication of its implementation.

Taking into account all the above, code generation becomes quite trivial . It is required only to find the definitions of functions in XML and recursively construct hierarchies of the expressions corresponding to them. Expressions themselves are created by ExpressionFactory , which allows you to easily expand the list of supported system functions (up to the implementation of new language constructs).

Of course, this is not the end of the road. In order for such scripting to be useful, it is necessary to associate it with navigation in the game space and a change in the course calculation state. Need to determine the mass of system functions that work in terms of the subject area, such as is-friend? or is-empty? . Finally, to implement a move generator, you need to realize the possibility of non-deterministic calculations . Compared to all these things, factorial is really easy.

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


All Articles