Project Oberon 2013
My colleagues gave me one useful load here. I will have a quick conversation with you about the dangers of selective blindness, because with her, damned, in our industry it is not entirely successful. So, first of all, before fighting it, with our common enemy - damned blindness, let's discuss and study examples of what the community does not see in order to know for sure how it can actually be.
So, in 1986-89. Nicklaus Wirth and his colleagues developed the first version of the Oberon system, a machine, an Oberon language compiler, and the System Oberon operating system, which had a graphical user interface, advanced concepts for using text in the interface and, on the whole, was material evidence of the applicability of N. Wirth's concepts. This operating system marked the beginning of an evolutionary branch of products and languages with a difficult fate, each of which deserves a separate article.

After almost 30 years, observing the situation of uncontrolled complication of products in IT, N. Wirth decided to again present his vision of how to build small, powerful and reliable systems. The very work of the author will serve as evidence that such systems exist and work.
As it turned out, the processor model that was used in the original system has already disappeared from the market. Like its architectural counterparts. Instead of using ready-made commercial solutions, N. Wirth decided to supplement his system with one more component - his version of the FPGA programming language, which was named Lola . Consequently, the processor for the new machine was developed by Wirth himself on the basis of RISC. We will consider it in a separate section of the article. The whole system worked on the Xilinx Spartan-3 devboard with 1 MB of memory and a 25 MHz processor.
That is, we have a project in which the full cycle of developing the computing complex from scratch is implemented, and all the stages of this project available to the engineer / programmer are implemented.
RISC-architecture is distinguished by a special sequence of processor operation with data. All processor processor instructions work with data located in registers, and data is transferred to / from memory by special processor instructions, SAVE and LOAD. Thus, monotonicity of work is achieved when the number of clock cycles per instruction is predictable, and often most instructions fit into one clock cycle. A feature of the Wirth processor is also the parallel operation of the pipeline for writing data to memory, however, for this it was necessary to reduce the frequency of the resulting processor by half, from 50 to 25 MHz.
The command system is based on a 32-bit word, in which the upper four bits are given to determine the format of the command. The format of the command determines the class of operation, the number of operands and their location.
ALU commands define a set of arithmetic and logical operations with three registers or with two registers and a constant. ALU teams leave additional values for the result sign in special single-bit registers.
Memory instructions define instructions to load into memory and read from memory one word or one byte into a register.
The flow control commands allow you to control the value of the PC register, to describe conditional and unconditional transitions based on the value of the register or constant.
There are 16 registers in the system, while several higher registers are reserved by the operating system and the compiler as service ones, they store a pointer to the stack, the address of the module table, and so on.
It looks something like this:

More information about the set of instructions can be found in the generalized and specific documents. They provide a diagram of the processor and peripherals in the Verilog language, but in subsequent editions Wirth provided Lola circuits along with a description of the language, after additional debugging and verification.
Wirth also describes the scheme and implementation of work with some standard interfaces, such as RS232, and also talks about how he connected an SD card as a data storage. How peripheral devices are implemented is described in the project documentation, since I am mainly a programmer, not an electronics engineer, I will not describe the details in order to prevent very childish mistakes. Perhaps more experienced comrades will help fill this gap.
The main goal of the OS is to provide the user and programmer with a certain level of abstraction. I / O devices, storage devices and interfaces for interacting with these devices, as well as hardware limitations. The flexibility of the abstractions chosen in the Oberon system allows you to seamlessly run it as a separate program on various software platforms / OS, while the processor level or the level of software implementations of abstractions can be emulated.
Of course, Oberon was chosen as the programming language for creating the future OS. The source language was revised in 2007, and within the framework of this project, Wirth simplified the language even more, getting rid of features that were dubious in terms of effectiveness. A minimal set of features simplifies the implementation of a working compiler and improves control over the code. This is important, because we write the system from scratch.
The main features that are important for building the Oberon OS are modularity, basic meta-information, dynamic loading of modules and a memory model with the need for a garbage collector.
Briefly about each of them.
In Oberon, a module is not only a means of structuring algorithms and data structures, but also a unit of compilation, loading, and distribution. That is, the module is the smallest entity that the compiler can compile. Dependencies of one module on other modules are calculated automatically, but do not lead to the inclusion of code from one module to another. Only identifiers of imported entities and a hash code of dependency are included to control the version of the code.
A module is a loading unit, that is, except in special cases, the module code is a complete program in which there is an entry point and which can be executed for an unlimited time. That is, a complete program. Even the OS kernel is just the first module loaded into memory.
The module also assumes that it will be distributed not only in the form of a source, but also in the form of a binary, as well as in the form of an interface part, and for its launch only a certain platform or several platforms will be required. In general, these concepts are included in the concept of modularity in Oberon and constitute a module-oriented programming.
The concept of modularity assumes that any number of modules can be simultaneously present in memory, while it is not assumed that all of them can know something about each other. In this case, the user or programmer may need access to entities in such modules. To do this, the requirement for the presence of meta-information is introduced into the requirements for the Oberon language execution environment. Since specific entities in a modular system may also be unknown, in this project meta-information is only required to be able to transfer control to any exported procedure of any module by its full name.
The need for such a transfer of control follows from the need for the OS to have not just modularity, but dynamic modularity, since the OS is an infrastructure product, and by itself has no specific applications for users, users should be able to load the modules they need in the process of the program . At the same time, the module has an entry point and a body that will be executed once, while the important point is the fact that the module will remain loaded into memory and can provide its types and procedures for the further operation of other modules. So, in order for the OS to be able to transfer control to already loaded modules, and at the same time to load the modules and execute their procedures, it was necessary to be able to execute the procedures by name. Of course, this feature has unimaginably complex analogues in the mainstream, but at the moment this feature is enough. Anyway, it is implemented in a library, but nothing needs to be added to the language.
In the Oberon operating system, garbage collection is performed at the kernel level of the OS, which controls the placement of dynamic objects on the heap. For a component environment, a garbage collector is an obvious necessity, since a component that provides dynamic objects to clients cannot decide when to remove them from memory.
Here is a quote from Wirth himself on this subject:
So, when the requirements for the language and the environment of its execution (it is the OS, it is the runtime) are defined, you can start writing code. In fact, the OS for a set of functions can be described with just a few modules. Other functions can be taken over by third-party components. The compiler does not have to be part of the OS, but since you cannot create a single command for the processor without it, we will consider it first.
A programming language is an infinite set of characters from a set of keywords that are associated with a limited set of expressions that make up the syntax. Each expression is a syntactic construct with the rules for writing it. The meaning of the written is determined by the set of semantic rules associated with each construction.
The language Oberon from the very first edition was conceived as a language that does not have anything superfluous in its composition. To solve the obvious problems associated with the minimalism of the language, the concept of modularity was proposed. Thus, it was assumed that sets of modules of different functionalities could effectively replace the expansive growth of the capabilities of the language itself. Description of the language takes less than 20 pages, the compiler is implemented pretty quickly, in general, Wirth emphasized that he turned out to be an excellent language for learning (better than Pascal). Of course, evil tongues immediately picked up this phrase and rush with it, claiming that Oberon is good only for learning. Well, slander is easier to sow than to root out.
As you can see in this project, Oberon and its concepts are suitable for implementing code at any level, low-level, middleware, high-level circuit editors, mailers, etc. Wirth implemented all these applications as proof of concept in his OS.
The language served as a direct ancestor for the parallel programming language (Active Oberon), various modifications of the Oberon language for other runtimes (Component Pascal, Zonnon), was implemented on several platforms (JVM, CLR, JS), served as a prototype of the Java language. The Oberon system itself served as the prototype for the Microsoft Singularity project. Such borrowing cannot be called an unambiguously negative factor; I believe that the main negative factor is the forgetting to which the business has betrayed the source of its successful concepts.
But back to the technical part. Let us determine that the task of the compiler is to translate text content into processor instructions and is obviously divided into two stages, platform-independent analysis of the source code, and generation of platform-dependent code for a specific processor.
The architecture of the Oberon compiler can be described by this scheme:

Since in Oberon the symbol is generally not equal to character, a module is needed that scans the string of letters and returns to the parser the chain of abstract language characters.
The parser is single-pass and does not store a lot of information about the program in the process. This is achieved due to the simplicity and monotony of language and processor instructions. After generating complete information about a particular command, it is transmitted to the Code Generator, which immediately generates the ready-to-execute code for the RISC processor. Tracking the status of the registers is also assigned to the generator.
The scanner also deals with the graphic display of the location of the error in the source code, and therefore interacts with the OS modules.
No matter how complex or simple the parser is, the results of its activity will always be limited by the real world, in which iron and memory have limits that are quite tangible. Therefore, code generation and placement of data in memory is a critical process. Aligning heterogeneous fields can serve as an example of memory optimization.
Another example is a solution in which the module code is generated in such a way that it can always be run on a bare system if it can be somehow stored in memory. The exception is the bootstrap code, which will boot from the ROM when the machine starts and prepare the system for loading the first module.
Obviously, to load the second and subsequent modules in the module code, it is necessary to correct the addresses in some instructions for shifting the module body relative to zero, this is called a dynamic link, which is combined with reading the module body from the disk.
An exception to this is the statically linked core of the system, which, due to the nature of the FPGA, needs to be reloaded every time.
The compiler also generates instructions for organizing data storage in the system memory. It is assumed that in the higher registers during operation the addresses of the stack offset, heaps will be located, and during the operation of real modules the addresses of the offset data of the procedures will accumulate in the stack. The module offset addresses are also intended to be stored in known places in the module table. This whole scheme is difficult to describe, but in the implementation it is only a game with registers in the prologue and epilogue of the procedure. This is so intricate and simple at the same time.
The compiler also controls the transfer of the request for memory allocation for dynamic objects to the kernel, but this is where the compiler's functions for managing such objects end, then they are considered simply as an offset by the value of the pointer.
The core of the Oberon system is a module that mainly deals with memory management, allocating memory for a new instance, garbage collection, and catching exceptions. Also, for reasons of simplicity, a set of procedures for accessing the SD card via the SPI protocol was built into the kernel. This is done in order to minimize the number of modules in the statically linked firmware and, during the download process, immediately switch to the usual method of dynamically loading module files from disk.
The loading and dynamic linking of modules is done by the Modules module. This module also searches for commands (exported procedures) by name, and contains a list of modules. The list of modules is a bit of a magical type that looks like a linked list of pointers to structures, but actually indicates the starting position of the module itself in the table of modules. Another low-level Oberon opportunity.
The file subsystem is represented by the Files module, which inside itself hides the implementation of accessing the file system of the SD card. Thus, the clients of the Files module do not know anything about low-level features. These are obviously the high-level capabilities of the Oberon language.
Graphic and text subsystems are represented by a set of modules, which can be found in more detail in the project documentation. Here I just note that the Oberon system is originally graphical in nature, but has developed capabilities of text commands. An example of working with text commands in the graphical interface can be seen on the famous GIF.
And here is what the graphical interface looks like in the process of work:

The concept of text commands is directly related to the concept of a command as an exported module procedure. Here we can see how thin the line is between the real code and the user interface in the Oberon system, while maintaining the platform independence of the concept itself.
The graphical interface itself is tile, monochrome. This is due to the limitation of the video subsystem devboards. Graphic primitives are based on the concept of Frame, in which various modules can draw text or graphic content. The idea of frames was realized in the form of the BlackBox framework for Windows, where it was one of the central abstractions of the graphics application.
The Oberon system implements a font mechanism, although not as powerful as modern counterparts.
Application modules, or, more simply, applications, are a direct analogue of applications in popular OSs. Since the modules remain in memory after loading into memory, interaction with the user is based on asynchronous processing of events from Frames.
It should be noted that the Oberon system is single-threaded, but implements a system of asynchronous tasks (analogue of corutin), and this is done at the library and environment level, not the language level. When critically evaluating these facts, a thoughtful reader should remember that this is a single-user system and, to a greater extent, is an implementation of concepts, and not a ready-made product for the general user. All the prospects for its development are in your hands.
From the application modules presented in the standard package, one can distinguish a certain number of test modules, fractal drawing, and the like trifle. From large applications, one can single out developer tools that are implemented in a close connection between the text subsystem and the compiler.

The electronic circuit drawing tool, which can be seen in the picture above, is an example of a graphical application, as well as an excellent example of the implementation of many design patterns in the Oberon system.
You can also mark the application of the mail client and server.
As we already understood, the Oberon system contains heterogeneous components, high and low level applications that are close to hardware or abstract things, such as electronic circuits, united by a common concept of simplicity and written in the Oberon language.
The description of the Oberon system itself currently takes about two hundred pages, and it is clear that I did not set a goal to fully describe their contents in this article. Perhaps I missed some things, in the end, everyone can supplement my article on the results of familiarization with the Oberon system on their own.
Nicklaus Wirth at his venerable age continues to follow the principles that were laid down at the beginning of the IT era. Does the industry itself follow these principles? It seems to me - it should not, but this is a philosophical question, a question of goal-setting, internal consistency and self-restriction, inaccessible to the market.
Who knows, maybe now, in times of crisis and non-technical difficulties, such as conflicts, sanctions, wars, we should pay close attention to the fact that the IT industry is heavily dependent on several key players precisely because only large companies can realize those super- complex products that everyone is already used to using. Of course, there is nothing worse than breaking the principles of industry life that have been established over the years, but otherwise they will simply bury the majority of small players.
On the other hand, it is simply not clear why, with the availability of resources and a niche, the same gentlemen from the ICST have spent hundreds of efforts on creating products that will integrate into the existing ecosystem of C and GNU / Linux instead of building their own ecosystem, even if not based on Oberon (although I would like to hide something).
Now the few people who have chosen Oberon for their projects have become convinced that reliability and simplicity can simply exist, be at the basis of arbitrarily complex projects of various levels, corporate systems, server side, and drones. Of course, one could argue that this is a small sample, and all that. But there is only one answer - try, solve problems, find weaknesses, eliminate them and raise the level of technological complexity. Then your example will serve as a proof or refutation of the concepts of Nicklaus Wirth, the founder of the engineering approach in IT.
Because it’s difficult to talk about the fight against selective blindness. Maybe so, we’ll finish the conversation. Let's get down to fighting her right away. After all, we have ... (in a braided language), one thing to remember - we all, as one, we all have to fight. Must with her as one. Should we all ... fight, one should like. We all…
History
So, in 1986-89. Nicklaus Wirth and his colleagues developed the first version of the Oberon system, a machine, an Oberon language compiler, and the System Oberon operating system, which had a graphical user interface, advanced concepts for using text in the interface and, on the whole, was material evidence of the applicability of N. Wirth's concepts. This operating system marked the beginning of an evolutionary branch of products and languages with a difficult fate, each of which deserves a separate article.

After almost 30 years, observing the situation of uncontrolled complication of products in IT, N. Wirth decided to again present his vision of how to build small, powerful and reliable systems. The very work of the author will serve as evidence that such systems exist and work.
Everything flows, everything changes
As it turned out, the processor model that was used in the original system has already disappeared from the market. Like its architectural counterparts. Instead of using ready-made commercial solutions, N. Wirth decided to supplement his system with one more component - his version of the FPGA programming language, which was named Lola . Consequently, the processor for the new machine was developed by Wirth himself on the basis of RISC. We will consider it in a separate section of the article. The whole system worked on the Xilinx Spartan-3 devboard with 1 MB of memory and a 25 MHz processor.
That is, we have a project in which the full cycle of developing the computing complex from scratch is implemented, and all the stages of this project available to the engineer / programmer are implemented.
CPU
RISC-architecture is distinguished by a special sequence of processor operation with data. All processor processor instructions work with data located in registers, and data is transferred to / from memory by special processor instructions, SAVE and LOAD. Thus, monotonicity of work is achieved when the number of clock cycles per instruction is predictable, and often most instructions fit into one clock cycle. A feature of the Wirth processor is also the parallel operation of the pipeline for writing data to memory, however, for this it was necessary to reduce the frequency of the resulting processor by half, from 50 to 25 MHz.
The command system is based on a 32-bit word, in which the upper four bits are given to determine the format of the command. The format of the command determines the class of operation, the number of operands and their location.
ALU commands define a set of arithmetic and logical operations with three registers or with two registers and a constant. ALU teams leave additional values for the result sign in special single-bit registers.
Memory instructions define instructions to load into memory and read from memory one word or one byte into a register.
The flow control commands allow you to control the value of the PC register, to describe conditional and unconditional transitions based on the value of the register or constant.
There are 16 registers in the system, while several higher registers are reserved by the operating system and the compiler as service ones, they store a pointer to the stack, the address of the module table, and so on.
It looks something like this:
More information about the set of instructions can be found in the generalized and specific documents. They provide a diagram of the processor and peripherals in the Verilog language, but in subsequent editions Wirth provided Lola circuits along with a description of the language, after additional debugging and verification.
Wirth also describes the scheme and implementation of work with some standard interfaces, such as RS232, and also talks about how he connected an SD card as a data storage. How peripheral devices are implemented is described in the project documentation, since I am mainly a programmer, not an electronics engineer, I will not describe the details in order to prevent very childish mistakes. Perhaps more experienced comrades will help fill this gap.
System
The main goal of the OS is to provide the user and programmer with a certain level of abstraction. I / O devices, storage devices and interfaces for interacting with these devices, as well as hardware limitations. The flexibility of the abstractions chosen in the Oberon system allows you to seamlessly run it as a separate program on various software platforms / OS, while the processor level or the level of software implementations of abstractions can be emulated.
Tongue
Of course, Oberon was chosen as the programming language for creating the future OS. The source language was revised in 2007, and within the framework of this project, Wirth simplified the language even more, getting rid of features that were dubious in terms of effectiveness. A minimal set of features simplifies the implementation of a working compiler and improves control over the code. This is important, because we write the system from scratch.
The main features that are important for building the Oberon OS are modularity, basic meta-information, dynamic loading of modules and a memory model with the need for a garbage collector.
Briefly about each of them.
Modularity
In Oberon, a module is not only a means of structuring algorithms and data structures, but also a unit of compilation, loading, and distribution. That is, the module is the smallest entity that the compiler can compile. Dependencies of one module on other modules are calculated automatically, but do not lead to the inclusion of code from one module to another. Only identifiers of imported entities and a hash code of dependency are included to control the version of the code.
A module is a loading unit, that is, except in special cases, the module code is a complete program in which there is an entry point and which can be executed for an unlimited time. That is, a complete program. Even the OS kernel is just the first module loaded into memory.
The module also assumes that it will be distributed not only in the form of a source, but also in the form of a binary, as well as in the form of an interface part, and for its launch only a certain platform or several platforms will be required. In general, these concepts are included in the concept of modularity in Oberon and constitute a module-oriented programming.
Basic Meta Information
The concept of modularity assumes that any number of modules can be simultaneously present in memory, while it is not assumed that all of them can know something about each other. In this case, the user or programmer may need access to entities in such modules. To do this, the requirement for the presence of meta-information is introduced into the requirements for the Oberon language execution environment. Since specific entities in a modular system may also be unknown, in this project meta-information is only required to be able to transfer control to any exported procedure of any module by its full name.
Dynamic modularity
The need for such a transfer of control follows from the need for the OS to have not just modularity, but dynamic modularity, since the OS is an infrastructure product, and by itself has no specific applications for users, users should be able to load the modules they need in the process of the program . At the same time, the module has an entry point and a body that will be executed once, while the important point is the fact that the module will remain loaded into memory and can provide its types and procedures for the further operation of other modules. So, in order for the OS to be able to transfer control to already loaded modules, and at the same time to load the modules and execute their procedures, it was necessary to be able to execute the procedures by name. Of course, this feature has unimaginably complex analogues in the mainstream, but at the moment this feature is enough. Anyway, it is implemented in a library, but nothing needs to be added to the language.
Garbage collection
In the Oberon operating system, garbage collection is performed at the kernel level of the OS, which controls the placement of dynamic objects on the heap. For a component environment, a garbage collector is an obvious necessity, since a component that provides dynamic objects to clients cannot decide when to remove them from memory.
Here is a quote from Wirth himself on this subject:
The Oberon System does not provide an explicit deallocation procedure allowing the programmer to signal that a variable will no longer be referenced. The first reason for this omission is that usually a programmer would not know when to call for deallocation. And secondly, this "hint" could
not be taken as trustworthy. An erroneous deallocation, ie one occurring when there still exist references to the object in question, could lead to a multiple allocation of the same space with disastrous consequences. Hence, it appears wise to fully rely on system management to determine which areas of the store are truly reusable.
So, when the requirements for the language and the environment of its execution (it is the OS, it is the runtime) are defined, you can start writing code. In fact, the OS for a set of functions can be described with just a few modules. Other functions can be taken over by third-party components. The compiler does not have to be part of the OS, but since you cannot create a single command for the processor without it, we will consider it first.
Compiler
A programming language is an infinite set of characters from a set of keywords that are associated with a limited set of expressions that make up the syntax. Each expression is a syntactic construct with the rules for writing it. The meaning of the written is determined by the set of semantic rules associated with each construction.
Oberon
The language Oberon from the very first edition was conceived as a language that does not have anything superfluous in its composition. To solve the obvious problems associated with the minimalism of the language, the concept of modularity was proposed. Thus, it was assumed that sets of modules of different functionalities could effectively replace the expansive growth of the capabilities of the language itself. Description of the language takes less than 20 pages, the compiler is implemented pretty quickly, in general, Wirth emphasized that he turned out to be an excellent language for learning (better than Pascal). Of course, evil tongues immediately picked up this phrase and rush with it, claiming that Oberon is good only for learning. Well, slander is easier to sow than to root out.
As you can see in this project, Oberon and its concepts are suitable for implementing code at any level, low-level, middleware, high-level circuit editors, mailers, etc. Wirth implemented all these applications as proof of concept in his OS.
An example of a low-level code in the Oberon language.
Kernel module, garbage collector. github.com/ilovb/ProjectOberon2013/blob/master/Sources/Kernel.Mod#L82
(* ---------- Garbage collector ----------*)
PROCEDURE Mark*(pref: LONGINT);
VAR pvadr, offadr, offset, tag, p, q, r: LONGINT;
BEGIN SYSTEM.GET(pref, pvadr); (*pointers < heapOrg considered NIL*)
WHILE pvadr # 0 DO
SYSTEM.GET(pvadr, p); SYSTEM.GET(p-4, offadr);
IF (p >= heapOrg) & (offadr = 0) THEN q := p; (*mark elements in data structure with root p*)
REPEAT SYSTEM.GET(p-4, offadr);
IF offadr = 0 THEN SYSTEM.GET(p-8, tag); offadr := tag + 16 ELSE INC(offadr, 4) END ;
SYSTEM.PUT(p-4, offadr); SYSTEM.GET(offadr, offset);
IF offset # -1 THEN (*down*)
SYSTEM.GET(p+offset, r); SYSTEM.GET(r-4, offadr);
IF (r >= heapOrg) & (offadr = 0) THEN SYSTEM.PUT(p+offset, q); q := p; p := r END
ELSE (*up*) SYSTEM.GET(q-4, offadr); SYSTEM.GET(offadr, offset);
IF p # q THEN SYSTEM.GET(q+offset, r); SYSTEM.PUT(q+offset, p); p := q; q := r END
END
UNTIL (p = q) & (offset = -1)
END ;
INC(pref, 4); SYSTEM.GET(pref, pvadr)
END
END Mark;
PROCEDURE Scan*;
VAR p, q, mark, tag, size: LONGINT;
BEGIN p := heapOrg;
REPEAT SYSTEM.GET(p+4, mark); q := p;
WHILE mark = 0 DO
SYSTEM.GET(p, tag); SYSTEM.GET(tag, size); INC(p, size); SYSTEM.GET(p+4, mark)
END ;
size := p - q; DEC(allocated, size); (*size of free block*)
IF size > 0 THEN
IF size MOD 64 # 0 THEN
SYSTEM.PUT(q, 32); SYSTEM.PUT(q+4, -1); SYSTEM.PUT(q+8, list3); list3 := q; INC(q, 32); DEC(size, 32)
END ;
IF size MOD 128 # 0 THEN
SYSTEM.PUT(q, 64); SYSTEM.PUT(q+4, -1); SYSTEM.PUT(q+8, list2); list2 := q; INC(q, 64); DEC(size, 64)
END ;
IF size MOD 256 # 0 THEN
SYSTEM.PUT(q, 128); SYSTEM.PUT(q+4, -1); SYSTEM.PUT(q+8, list1); list1 := q; INC(q, 128); DEC(size, 128)
END ;
IF size > 0 THEN
SYSTEM.PUT(q, size); SYSTEM.PUT(q+4, -1); SYSTEM.PUT(q+8, list0); list0 := q; INC(q, size)
END
END ;
IF mark > 0 THEN SYSTEM.GET(p, tag); SYSTEM.GET(tag, size); SYSTEM.PUT(p+4, 0); INC(p, size)
ELSE (*free*) SYSTEM.GET(p, size); INC(p, size)
END
UNTIL p >= heapLim
END Scan;
High Level Code Example
Texts module, saving text files by the editor. github.com/ilovb/ProjectOberon2013/blob/master/Sources/Texts.Mod#L127
PROCEDURE Store* (VAR W: Files.Rider; T: Text);
VAR p, q: Piece;
R: Files.Rider;
off, rlen, pos: LONGINT;
N, n: INTEGER;
ch: CHAR;
Dict: ARRAY 32, 32 OF CHAR;
BEGIN pos := Files.Pos(W); Files.WriteInt(W, 0); (*place holder*)
N := 1; p := T.trailer.next;
WHILE p # T.trailer DO
rlen := p.len; q := p.next;
WHILE (q # T.trailer) & (q.fnt = p.fnt) & (q.col = p.col) & (q.voff = p.voff) DO
rlen := rlen + q.len; q := q.next
END;
Dict[N] := p.fnt.name;
n := 1;
WHILE Dict[n] # p.fnt.name DO INC(n) END;
Files.WriteByte(W, n);
IF n = N THEN Files.WriteString(W, p.fnt.name); INC(N) END;
Files.WriteByte(W, p.col); Files.WriteByte(W, p.voff); Files.WriteInt(W, rlen);
p := q
END;
Files.WriteByte(W, 0); Files.WriteInt(W, T.len);
off := Files.Pos(W); p := T.trailer.next;
WHILE p # T.trailer DO
rlen := p.len; Files.Set(R, p.f, p.off);
WHILE rlen > 0 DO Files.Read(R, ch); Files.Write(W, ch); DEC(rlen) END ;
p := p.next
END ;
Files.Set(W, Files.Base(W), pos); Files.WriteInt(W, off); (*fixup*)
T.changed := FALSE;
IF T.notify # NIL THEN T.notify(T, unmark, 0, 0) END
END Store;
The language served as a direct ancestor for the parallel programming language (Active Oberon), various modifications of the Oberon language for other runtimes (Component Pascal, Zonnon), was implemented on several platforms (JVM, CLR, JS), served as a prototype of the Java language. The Oberon system itself served as the prototype for the Microsoft Singularity project. Such borrowing cannot be called an unambiguously negative factor; I believe that the main negative factor is the forgetting to which the business has betrayed the source of its successful concepts.
Parser
But back to the technical part. Let us determine that the task of the compiler is to translate text content into processor instructions and is obviously divided into two stages, platform-independent analysis of the source code, and generation of platform-dependent code for a specific processor.
The architecture of the Oberon compiler can be described by this scheme:
Since in Oberon the symbol is generally not equal to character, a module is needed that scans the string of letters and returns to the parser the chain of abstract language characters.
The parser is single-pass and does not store a lot of information about the program in the process. This is achieved due to the simplicity and monotony of language and processor instructions. After generating complete information about a particular command, it is transmitted to the Code Generator, which immediately generates the ready-to-execute code for the RISC processor. Tracking the status of the registers is also assigned to the generator.
The scanner also deals with the graphic display of the location of the error in the source code, and therefore interacts with the OS modules.
Code Generation
No matter how complex or simple the parser is, the results of its activity will always be limited by the real world, in which iron and memory have limits that are quite tangible. Therefore, code generation and placement of data in memory is a critical process. Aligning heterogeneous fields can serve as an example of memory optimization.
Another example is a solution in which the module code is generated in such a way that it can always be run on a bare system if it can be somehow stored in memory. The exception is the bootstrap code, which will boot from the ROM when the machine starts and prepare the system for loading the first module.
Obviously, to load the second and subsequent modules in the module code, it is necessary to correct the addresses in some instructions for shifting the module body relative to zero, this is called a dynamic link, which is combined with reading the module body from the disk.
An exception to this is the statically linked core of the system, which, due to the nature of the FPGA, needs to be reloaded every time.
Memory organization
The compiler also generates instructions for organizing data storage in the system memory. It is assumed that in the higher registers during operation the addresses of the stack offset, heaps will be located, and during the operation of real modules the addresses of the offset data of the procedures will accumulate in the stack. The module offset addresses are also intended to be stored in known places in the module table. This whole scheme is difficult to describe, but in the implementation it is only a game with registers in the prologue and epilogue of the procedure. This is so intricate and simple at the same time.
The compiler also controls the transfer of the request for memory allocation for dynamic objects to the kernel, but this is where the compiler's functions for managing such objects end, then they are considered simply as an offset by the value of the pointer.
Kernel and system modules
The core of the Oberon system is a module that mainly deals with memory management, allocating memory for a new instance, garbage collection, and catching exceptions. Also, for reasons of simplicity, a set of procedures for accessing the SD card via the SPI protocol was built into the kernel. This is done in order to minimize the number of modules in the statically linked firmware and, during the download process, immediately switch to the usual method of dynamically loading module files from disk.
The loading and dynamic linking of modules is done by the Modules module. This module also searches for commands (exported procedures) by name, and contains a list of modules. The list of modules is a bit of a magical type that looks like a linked list of pointers to structures, but actually indicates the starting position of the module itself in the table of modules. Another low-level Oberon opportunity.
The file subsystem is represented by the Files module, which inside itself hides the implementation of accessing the file system of the SD card. Thus, the clients of the Files module do not know anything about low-level features. These are obviously the high-level capabilities of the Oberon language.
Graphic and text subsystems are represented by a set of modules, which can be found in more detail in the project documentation. Here I just note that the Oberon system is originally graphical in nature, but has developed capabilities of text commands. An example of working with text commands in the graphical interface can be seen on the famous GIF.
GIF

And here is what the graphical interface looks like in the process of work:
The concept of text commands is directly related to the concept of a command as an exported module procedure. Here we can see how thin the line is between the real code and the user interface in the Oberon system, while maintaining the platform independence of the concept itself.
The graphical interface itself is tile, monochrome. This is due to the limitation of the video subsystem devboards. Graphic primitives are based on the concept of Frame, in which various modules can draw text or graphic content. The idea of frames was realized in the form of the BlackBox framework for Windows, where it was one of the central abstractions of the graphics application.
The Oberon system implements a font mechanism, although not as powerful as modern counterparts.
Application modules
Application modules, or, more simply, applications, are a direct analogue of applications in popular OSs. Since the modules remain in memory after loading into memory, interaction with the user is based on asynchronous processing of events from Frames.
It should be noted that the Oberon system is single-threaded, but implements a system of asynchronous tasks (analogue of corutin), and this is done at the library and environment level, not the language level. When critically evaluating these facts, a thoughtful reader should remember that this is a single-user system and, to a greater extent, is an implementation of concepts, and not a ready-made product for the general user. All the prospects for its development are in your hands.
From the application modules presented in the standard package, one can distinguish a certain number of test modules, fractal drawing, and the like trifle. From large applications, one can single out developer tools that are implemented in a close connection between the text subsystem and the compiler.
The electronic circuit drawing tool, which can be seen in the picture above, is an example of a graphical application, as well as an excellent example of the implementation of many design patterns in the Oberon system.
You can also mark the application of the mail client and server.
Conclusion
As we already understood, the Oberon system contains heterogeneous components, high and low level applications that are close to hardware or abstract things, such as electronic circuits, united by a common concept of simplicity and written in the Oberon language.
The description of the Oberon system itself currently takes about two hundred pages, and it is clear that I did not set a goal to fully describe their contents in this article. Perhaps I missed some things, in the end, everyone can supplement my article on the results of familiarization with the Oberon system on their own.
Nicklaus Wirth at his venerable age continues to follow the principles that were laid down at the beginning of the IT era. Does the industry itself follow these principles? It seems to me - it should not, but this is a philosophical question, a question of goal-setting, internal consistency and self-restriction, inaccessible to the market.
Who knows, maybe now, in times of crisis and non-technical difficulties, such as conflicts, sanctions, wars, we should pay close attention to the fact that the IT industry is heavily dependent on several key players precisely because only large companies can realize those super- complex products that everyone is already used to using. Of course, there is nothing worse than breaking the principles of industry life that have been established over the years, but otherwise they will simply bury the majority of small players.
On the other hand, it is simply not clear why, with the availability of resources and a niche, the same gentlemen from the ICST have spent hundreds of efforts on creating products that will integrate into the existing ecosystem of C and GNU / Linux instead of building their own ecosystem, even if not based on Oberon (although I would like to hide something).
Now the few people who have chosen Oberon for their projects have become convinced that reliability and simplicity can simply exist, be at the basis of arbitrarily complex projects of various levels, corporate systems, server side, and drones. Of course, one could argue that this is a small sample, and all that. But there is only one answer - try, solve problems, find weaknesses, eliminate them and raise the level of technological complexity. Then your example will serve as a proof or refutation of the concepts of Nicklaus Wirth, the founder of the engineering approach in IT.
Because it’s difficult to talk about the fight against selective blindness. Maybe so, we’ll finish the conversation. Let's get down to fighting her right away. After all, we have ... (in a braided language), one thing to remember - we all, as one, we all have to fight. Must with her as one. Should we all ... fight, one should like. We all…
References
- Official project page www.inf.ethz.ch/personal/wirth/ProjectOberon/index.html
- Oberon System emulator in schierlm.github.io/OberonEmulator browser
- Unofficial github.com/ilovb/ProjectOberon2013 project source repository
- Russian-speaking community of Oberon-programmers forum.oberoncore.ru
- Papers from Oberon Day 2014 in Russia www.youtube.com/playlist?list=PLoKr-_Vv5yq7KbxkSITmtanYElP73xsxU