📜 ⬆️ ⬇️

Reliable programming in the context of languages. Part 2 - Challengers

The first part with the functional requirements here .

Declared as programming languages ​​with an eye on reliability.

In alphabetical order - Active Oberon, Ada, BetterC, IEC 61131-3 ST, Safe-C.

Immediately a disclaimer (excuse) is not an “all on the left side” campaign, and the review is rather academic — the language may not only have an actively supported modern development environment, but even a compiler for your platform.
')
On the other hand, for the languages ​​in question there are compilers with open source, and even with the current level of development of softbuilding - with interest, the not too complicated syntax allows you to make a personal compiler and integrate into some kind of Eclipse with backlight and parser.

As an indicator of the visibility of the language, I chose the implementation of Dijkstra's famous multi-thread task about the dining philosophers. The implementation is in language textbooks and forums, which made it easier for me to work - it remains only to adapt. For example, a recent habr article on modern C ++ contains an implementation in C ++ 17 for comparison.

Active Oberon (2004)


It was created with an eye on the experience of Pascal, Modules, previous Oberons from 1988, Java, C #, Ada, as well as practical experience of application. It has implementation in the form of OS A2 , which can act as runtime over * nix or Windows. Sources A2 and compiler link .

Also there is a project Oberon2 to C Compiler (OOC) not tied to the environment Oberon. This is a slightly different dialect, the differences are described below.

The key feature of Oberon is the exceptional brevity of the specification. This is 16 pages on the base Oberon-2 plus 23 pages on the multithreaded Active Extension.

Simple and clear syntax, eliminating obvious errors.

Identifiers are case sensitive.

OOP with objects on a heap with garbage collector (GC).

It differs from its predecessors in the more familiar OOP syntax in the form of Instance. Method (it used to be Method (Instance)) and multithreading support with synchronization primitives.
There is no dynamic dispatching in the implementation of OOP, which can easily lead to a situation - they forgot to add processing for a new type.

Threads can be assigned priority and high / realtime they are not interrupted by the GC. Strings as UTF-8 arrays.

Rantaym (Oberon System) provides interesting possibilities for restarting a failed procedure / module / thread in case of runtime errors - memory addressing or, for example, integer overflow.

The disadvantage is the absence of RAII, and convenient error handling - all through return codes, except for the option below.

Oberon-2 OOC


It is more convenient for experiments, because OS Oberon does not require - it is compiled into ANSI C and there are no interoperability problems. Differences from the Active version - there is no multi-threading built into the language - instead there is a module for working with PThreads, but there is UTF16, hierarchical modularity and a system module for working with exceptions.

Modula-3


There is also a relative from a slightly different branch of development in the form of Modula-3. It was created on the basis of Oberon as opposed to the over complicated Ada. The implementation is here .

Compared to Active Oberon, generics and exceptions have been added, there are libraries for practical work with Unicode, GUI, and even Postgress. Integration with C is simplified. Other semantics of multithreading. RAII in the form of WITH (similar to using in C #).

But it seems that the development of Modula-3 stopped in 2010.

Disclaimer Having run WinAOS, I ran into TRAPs (aka abort / stacktrace or runtime error) out of the blue - even the task manager is working with errors, and although the system / runtime did not crash - it was only the application that I was certain that the reliability was determined by language programming = (

Also AOC is sufficiently closed on itself, with its approach to development.

The Source of the Dinner Philosophers
MODULE Philo; (* Dining Philosophers Example from Active Oberon Language Report by Patrik Reali *) (* Adapted for running in AOS by Siemargl *) IMPORT Semaphores := Example8, Out; CONST NofPhilo = 5; (* number of philosophers *) VAR fork: ARRAY NofPhilo OF Semaphores.Semaphore; i: LONGINT; TYPE Philosopher = OBJECT VAR first, second: LONGINT; (* forks used by this philosopher *) PROCEDURE & Init(id: LONGINT); BEGIN IF id # NofPhilo-1 THEN first := id; second := (id+1) ELSE first := 0; second := NofPhilo-1 END END Init; PROCEDURE Think; (* Need lock console output *) BEGIN {EXCLUSIVE} Out.Int(first); Out.String(".... Think...."); Out.Ln; END Think; PROCEDURE Eat; BEGIN {EXCLUSIVE} Out.Int(first); Out.String(".... Eat...."); Out.Ln; END Eat; BEGIN {ACTIVE} LOOP Think; fork[first].P; fork[second].P; Eat; fork[first].V; fork[second].V END END Philosopher; VAR philo: ARRAY NofPhilo OF Philosopher; BEGIN FOR i := 0 TO NofPhilo DO NEW(fork[i], INTEGER(i)); NEW(philo[i], i); END; END Philo. Philo.Philo1 ~ 

Ada (1980, last valid standard 2016)


Actually, at first glance there is everything that I would like.

And even a little more - there are numbers with exact floating point calculations. For example, there is a real-time flow scheduler, inter-thread exchange, and a formally verifiable subset of the SPARK language. And many more things.

I think if for the reliability of Ada he needed another devilish horn, he would have attached it along with instructions on how to call in a difficult situation =)

Implementation - GNU Ada , evolving, standardized by ISO / IEC.

The standard provides for implementation with GC, but for compiled versions it is often not implemented. Manual memory management is required - and programmer errors are possible here. However, the language is sharpened to use the default stack and there is a concept of managed types with destructors. You can also define your own GC implementation, auto-release, or reference counting for each data type.

Ada Reference Manual 2012 contains 950 pages.

The disadvantage of Ada, apart from the complexity, is excessive verbosity, which, incidentally, was intended for the sake of readability. Due to the specificity of the language security model, integration with “foreign” libraries is difficult.

Ada-ru has a good overview translated article - the first link.

The Source of the Dinner Philosophers
 -- Code from https://rosettacode.org/wiki/Dining_philosophers#Ordered_mutexes -- ADA95 compatible so can run in ideone.com with Ada.Numerics.Float_Random; use Ada.Numerics.Float_Random; with Ada.Text_IO; use Ada.Text_IO; procedure Test_Dining_Philosophers is type Philosopher is (Aristotle, Kant, Spinoza, Marx, Russel); protected type Fork is entry Grab; procedure Put_Down; private Seized : Boolean := False; end Fork; protected body Fork is entry Grab when not Seized is begin Seized := True; end Grab; procedure Put_Down is begin Seized := False; end Put_Down; end Fork; Life_Span : constant := 20; -- In his life a philosopher eats 20 times task type Person (ID : Philosopher; First, Second : not null access Fork); task body Person is Dice : Generator; begin Reset (Dice); for Life_Cycle in 1..Life_Span loop Put_Line (Philosopher'Image (ID) & " is thinking"); delay Duration (Random (Dice) * 0.100); Put_Line (Philosopher'Image (ID) & " is hungry"); First.Grab; Second.Grab; Put_Line (Philosopher'Image (ID) & " is eating"); delay Duration (Random (Dice) * 0.100); Second.Put_Down; First.Put_Down; end loop; Put_Line (Philosopher'Image (ID) & " is leaving"); end Person; Forks : array (1..5) of aliased Fork; -- Forks for hungry philosophers -- Start philosophers Ph_1 : Person (Aristotle, Forks (1)'Access, Forks (2)'Access); Ph_2 : Person (Kant, Forks (2)'Access, Forks (3)'Access); Ph_3 : Person (Spinoza, Forks (3)'Access, Forks (4)'Access); Ph_4 : Person (Marx, Forks (4)'Access, Forks (5)'Access); Ph_5 : Person (Russel, Forks (1)'Access, Forks (5)'Access); begin null; -- Nothing to do in the main task, just sit and behold end Test_Dining_Philosophers; 


BetterC (dlang subset 2017, original D - 2001, D 2.0 - 2007)


The most modern implementation of the considered. The full description of the language is rather long - 649 pages - see the original site .

Actually, this is the D language, but with restrictions using the -betterC key. Why is that ?!

Because the standard library D - Phobos, is developed by Alexandrescu and turned out to be very clever, completely built on templates. The key to this topic is that Phobos is uncontrolled in terms of memory consumption.

The most important thing that is lost in BetterC mode is multithreading, GC, strings, classes (the structures remain - they are close in functionality - only on the stack) and the exceptions (RAII and try-finally remain).

It is possible, however, to write part of the program on full D, and the critical part on D -BetterC. There is also a system attribute function to control the non-use of dangerous effects: pure safe @nogc.

Justification mode from the creator of the language.

And then the squeeze - what is trimmed, and what remains available.

The strings are contained in Phobos - and attempts to use them in BetterC result in hellish errors in the template instantiation on elementary operations, such as outputting a string to a console or concatenation. And in full D mode, the lines in the heap are immutable, because operations with them lead to memory garbage.

I had to meet several complaints about bugs in the compiler. Which, incidentally, is not surprising for a language competing in complexity with C ++. When preparing the article, I also had to deal with 4 errors - two appeared when trying to compile dlangide with a new compiler and a pair when porting the philosophers problem (for example, crash when using beginthreadex).

The mode has just recently appeared and errors caused by the restriction of the BetterC mode emerge already at the linking stage. To know about it in advance, what features of the language are cut down for sure - it is often necessary on one’s own experience.

The Source of the Dinner Philosophers
 // compile dmd -betterC import core.sys.windows.windows; import core.stdc.stdio; import core.stdc.stdlib : rand; //import std.typecons; // -impossible ( //import std.string; - impossible extern (Windows) alias btex_fptr = void function(void*) /*nothrow*/; //extern (C) uintptr_t _beginthreadex(void*, uint, btex_fptr, void*, uint, uint*) nothrow; /* Dining Philosophers example for a habr.com * by Siemargl, 2019 * BetterC variant. Compile >dmd -betterC Philo_BetterC.d */ extern (C) uintptr_t _beginthread(btex_fptr, uint stack_size, void *arglist) nothrow; alias HANDLE uintptr_t; alias HANDLE Fork; const philocount = 5; const cycles = 20; HANDLE[philocount] forks; struct Philosopher { const(char)* name; Fork left, right; HANDLE lifethread; } Philosopher[philocount] philos; extern (Windows) void PhilosopherLifeCycle(void* data) nothrow { Philosopher* philo = cast(Philosopher*)data; for (int age = 0; age++ < cycles;) { printf("%s is thinking\n", philo.name); Sleep(rand() % 100); printf("%s is hungry\n", philo.name); WaitForSingleObject(philo.left, INFINITE); WaitForSingleObject(philo.right, INFINITE); printf("%s is eating\n", philo.name); Sleep(rand() % 100); ReleaseMutex(philo.right); ReleaseMutex(philo.left); } printf("%s is leaving\n", philo.name); } extern (C) int main() { version(Windows){} else { static assert(false, "OS not supported"); } philos[0] = Philosopher ("Aristotlet".ptr, forks[0], forks[1], null); philos[1] = Philosopher ("Kant".ptr, forks[1], forks[2], null); philos[2] = Philosopher ("Spinoza".ptr, forks[2], forks[3], null); philos[3] = Philosopher ("Marx".ptr, forks[3], forks[4], null); philos[4] = Philosopher ("Russel".ptr, forks[0], forks[4], null); foreach(ref f; forks) { f = CreateMutex(null, false, null); assert(f); } foreach(ref ph; philos) { ph.lifethread = _beginthread(&PhilosopherLifeCycle, 0, &ph); assert(ph.lifethread); } foreach(ref ph; philos) WaitForSingleObject(ph.lifethread, INFINITE); // Close thread and mutex handles for( auto i = 0; i < philocount; i++ ) { CloseHandle(philos[i].lifethread); CloseHandle(forks[i]); } return 0; } 


For comparison, the source on the full D.

On the rosette, you can also see options for other languages.

IEC 61131-3 ST (1993, latest standard 2013)


Niche programming language of microcontrollers. The standard implies 5 programming options, but writing an application for example in relay logic is another adventure. Therefore, let us concentrate on one variant - structured text.
The text of the standard GOST R IEC 61131-3-2016 - 230 pages.

There are implementations for PC / x86 and ARM - both commercial, the most famous of which is CODESYS (often also sublicensed with different names) and open - Beremiz - broadcasting via C.

Since there is integration with C, it is quite possible to connect the libraries necessary for application programming. On the other hand, in this area, it is assumed that the logic rotates separately and only serves as a data server for another program or system — an interface with an operator or a DBMS, which can already be written on anything — without the requirements of real-time and even any temporary at all…

Multithreaded programming for a user program appeared relatively recently - in microcontrollers this was not necessary before.

Type coercion mostly only explicit (softened in the last standard). But overflow control is implementation dependent.

The latest edition of the standard appeared OOP. Error handling is done by custom interrupt handlers.

Dynamic memory allocation for the user can say no. This has historically developed - the amount of data processed by the microcontroller is always constant limited from above.

Source (not verified)
 (* Dining Philosophers example for a habr.com * by Siemargl, 2019 * ISO61131 ST language variant. Must be specialized 4 ur PLC * ) CONFIGURATION PLC_1 VAR_GLOBAL Forks : USINT; Philo_1: Philosopher; (* Instance block - static vars *) Philo_2: Philosopher; Philo_3: Philosopher; Philo_4: Philosopher; Philo_5: Philosopher; END_VAR RESOURCE Station_1 ON CPU_1 TASK Task_1 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_2 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_3 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_4 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_5 (INTERVAL := T#100MS, PRIORITY := 1); PROGRAM Life_1 WITH Task_1: Philo_1(Name := 'Kant', 0, 1, Forks); PROGRAM Life2 WITH Task_2: Philo_2(Name := 'Aristotel', 1, 2, Forks); PROGRAM Life3 WITH Task_3: Philo_3(Name := 'Spinoza', 2, 3, Forks); PROGRAM Life4 WITH Task_4: Philo_4(Name := 'Marx', 3, 4, Forks); PROGRAM Life5 WITH Task_5: Philo_5(Name := 'Russel', 4, 0, Forks); END_RESOURCE END_CONFIGURATION FUNCTION_BLOCK Philosopher; USING SysCpuHandling.library; VAR_INPUT Name: STRING; Left: UINT; Right: UINT; END_VAR VAR_IN_OUT Forks: USINT; END_VAR VAR Thinking: BOOL := TRUE; (* States *) Hungry: BOOL; Eating: BOOL; HaveLeftFork: BOOL; TmThink: TON; TmEating: TON; END_VAR TmThink(In := Thinking; PT := T#3s); TmEating(In := Eating; PT := T#5s); IF Thinking THEN (* Just waiting Timer *) Thinking := NOT TmThink.Q; Hungry := TmThink.Q; ELSIF Hungry (* Try Atomic Lock Forks *) IF HaveLeftFork IF SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Right, bSet := 1) = ERR_OK THEN Hungry := FALSE; Eating := TRUE; ELSE RETURN; END_IF ELSIF IF SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Left, bSet := 1) = ERR_OK THEN HaveLeftFork := TRUE; ELSE RETURN; END_IF END_IF ELSIF Eating (* Waiting Timer, then lay forks *) IF TmEating.Q THEN Thinking := TRUE; Eating := FALSE; HaveLeftFork := FALSE; SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Right, bSet := 0); SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Left, bSet := 0); END_IF END_IF END_FUNCTION_BLOCK 


Safe-C (2011)


Experimental C with the removal of dangerous chips and with the addition of modularity and multithreading. Project website
Description of approximately 103 pages. If you highlight the differences from C - very little, about 10 .

Working with arrays and pointers is secure, dynamic memory with automatic reference counting - with double-release checks and dangling references.

The standard library has a minimum set of functions for GUI, multithreading, network functions (including the http server).

But - this implementation is only for Windows x86. Although the compiler code and library is open.

As part of another research task, I collected a Web server layout that collects data from IoT sensors: a 75 KB executive module, and a <1 MB partial set of memory.

The Source of the Dinner Philosophers
 /* Dining Philosophers example for a habr.com * by Siemargl, 2019 * Safe-C variant. Compile >mk.exe philosafec.c */ from std use console, thread, random; enum philos (ushort) { Aristotle, Kant, Spinoza, Marx, Russell, }; const int cycles = 10; const ushort NUM = 5; uint lived = NUM; packed struct philosopher // 32-bit { philos name; byte left, right; } philosopher philo_body[NUM]; SHARED_OBJECT forks[NUM]; void philosopher_life(philosopher philo) { int age; for (age = 0; age++ < cycles; ) { printf("%s is thinking\n", philo.name'string); delay((uint)rnd(1, 100)); printf("%s is hungry\n", philo.name'string); enter_shared_object(ref forks[philo.left]); enter_shared_object(ref forks[philo.right]); printf("%s is eating\n", philo.name'string); delay((uint)rnd(1, 100)); leave_shared_object(ref forks[philo.right]); leave_shared_object(ref forks[philo.left]); } printf("%s is leaving\n", philo.name'string); InterlockedExchange(ref lived, lived-1); } void main() { philos i; assert philosopher'size == 4; philo_body[0] = {Aristotle, 0, 1}; philo_body[1] = {Kant, 1, 2}; philo_body[2] = {Spinoza, 2, 3}; philo_body[3] = {Marx, 3, 4}; philo_body[4] = {Russell, 0, 4}; for (i = philos'first; i <= philos'last; i++) { assert run philosopher_life(philo_body[(uint)i]) == 0; } while (lived > 0) sleep 0; // until all dies for (i = philos'first; i <= philos'last; i++) { destroy_shared_object(ref forks[(uint)i]); } } 


Finally - a summary table of compliance with functional requirements.
Surely I missed something or twisted - so correct.

Sources from an article on githaba .

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


All Articles