Pascal plays Go. Implementation of methods and interfaces in an amateur compiler

If I could export one feature of Go into other languages, it would be interfaces. - Russ Cox







My extremely simple Pascal compiler has already become the subject of two publications on Habré. Since their writing, the language has acquired all the missing tools required by standard Pascal, and many of the goodies that Borland added to Pascal in its golden age. The compiler also learned some of the simplest local optimizations, sufficient if only to keep your eyes from bleeding when you look at the disassembler listing.



Nevertheless, the jungle of object-oriented programming remained completely untouched. So why shouldn't the compiler serve as a testing ground for experiments in this area? And why do not we draw inspiration from the words of Russ Cox, made in the epigraph? Let's try to implement Go-style methods and interfaces in Pascal. The idea is interesting if only because all the popular Pascal compilers in the past (Delphi, Free Pascal) essentially borrowed the object model from C ++. It is curious to see how a completely different approach, adopted from Go, takes root on the same ground. If you are ready to follow me with a fair amount of irony, to drop the question “Why?” And take what is happening as a game, welcome to cat.



Principles



By “Go style” we will understand several principles on the basis of which we will implement methods and interfaces in Pascal:





Implementation



To declare methods and interfaces, Pascal's standard keywords for and interface are used in the new role. No new keywords are entered. The word for



is used to indicate the name and type of the recipient of the method (in Go terminology). Here is an example method description for the previously declared TCat



type with the Name



field:



 procedure Greet for c: TCat (const HumanName: string); begin WriteLn('Meow, ' + HumanName + '! I am ' + c.Name); end;
      
      





The receiver is actually the first argument to the method.



An interface is a regular Pascal entry, in the declaration of which the word record



is replaced by the word interface



. In this record, it is not allowed to declare any fields except fields of a procedural type. In addition, a hidden Self



field is added to the beginning of the recording. It stores a pointer to data of that particular type, which is converted to an interface type. Here is an example interface declaration:



 type IPet = interface Greet: procedure (const HumanName: string); end;
      
      





Converting to an interface type is always done explicitly:



 Pet := IPet(Cat);
      
      





In this case, the compiler checks for the presence of all the methods required by the interface and the coincidence of their signatures. Then it sets the Self



pointer, fills all the procedural fields of the interface with pointers to methods of a particular type.



Compared to Go, the current implementation of interfaces in Pascal has limitations: it is not possible to dynamically query for a specific data type that has been converted to an interface type. Accordingly, empty interfaces are meaningless. Perhaps the next development step will be to fill this gap. However, even in their current form, interfaces provide polymorphism, useful in many of the less trivial tasks. We will consider one such problem.



Example



A good example of using interfaces is a program for rendering three-dimensional scenes using the ray tracing method. The scene consists of simple geometric bodies: parallelepipeds, spheres, etc. Each ray emitted from the eye of the observer needs to be tracked (through all its reflections) until it reaches the light source or goes to infinity. To do this, an Intersect



method is assigned to each type of body, which calculates the coordinates of the point where the ray hits the surface of the body and the normal components at this point. The implementation of this method for different types of bodies is different. Accordingly, it is convenient to store information about bodies in an array of interface entries Body



, and for all elements of the array, the Intersect



method is called in turn. The interface redirects this call to a specific method depending on the type of body.



This may look like a scene constructed in the described way:







The entire source code of the program, including the description of the scene, takes 367 lines.



Summary



The simplest implementation of polymorphism in Pascal’s own compiler turned out to be easy, quickly bearing the first fruits. Some complications can be expected in the problem of dynamically defining a particular data type that has been cast to an interface type. Efforts will also require the elimination of unobvious conflicts with the standard type checking mechanisms of standard Pascal. Finally, besides all the worries about the interfaces, an unequal struggle continues with Microsoft over the false alarms of their Windows Defender when launching some compiled examples.



All Articles