Reliable programming in the context of languages. Part 2 - Challengers
The first part with functional requirements is here .
Claimed as programming languages with an eye on reliability.
Alphabetically - Active Oberon, Ada, BetterC, IEC 61131-3 ST, Safe-C.
At once, the disclaimer (excuse) is by no means 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 open source compilers, and with the current level of software development - with interest, 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 clarity of the language, I chose the implementation of Dijkstra's famous multi-threaded task about dining philosophers. Implementation is in the textbooks on the language and on the forums, which facilitated my work - it remains only to adapt. For example, a recent habr article about modern C ++ contains an implementation in C ++ 17 for comparison.
It was created with an eye to the experience of Pascal, Modula, previous Oberons since 1988, Java, C #, Ada, as well as practical experience in application. It has an implementation in the form of OS A2 , which can act as runtime on top of * nix or Windows. Sources A2 and the compiler for the link .
There is also an Oberon2 to C Compiler (OOC) project that is not tied to the Oberon environment. This is a slightly different dialect, the differences are described below.
The key feature of Oberon is the exceptional brevity of the specification. These are 16 pages on the base Oberon-2 plus 23 pages on the multi-threaded Active extension.
Simple and clear syntax that excludes obvious errors.
Identifiers are case sensitive.
OOP with objects on the heap with the 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 support for multithreading with synchronization primitives.
There is no dynamic dispatching in the OOP implementation, which can easily lead to a situation - they forgot to add processing for a new type.
Streams can be assigned priority and high / realtime they are not interrupted by GC. Strings in the form of UTF-8 arrays.
Rantime (Oberon System) provides interesting opportunities for restarting a failed procedure / module / thread in case of a runtime error - memory addressing, or, for example, integer overflow.
The disadvantage is the lack of RAII, and convenient error handling - all through return codes, with the exception of the option below.
It is more convenient for experiments, since Oberon does not require OS - it compiles in ANSI C and there are no interoperability problems. Differences from the Active version - there is no built-in multithreading language - instead there is a module for working with PThreads, but there is UTF16, hierarchical modularity and a system module for working with exceptions.
There is also a relative from a slightly different development branch in the form of Modula-3. It was created on the basis of Oberon as opposed to the overdeveloped Ada. The implementation is here .
Compared to Active Oberon, generics and exceptions are added, there are libraries for practical work with Unicode, GUI, and even Postgress. Simplified integration with C. Other multithreading semantics. RAII as WITH (similar to using in C #).
But it seems that the development of Modula 3 stopped in 2010.
Disclaimer. Having launched WinAOS, I ran into TRAPs (aka abort / stacktrace or runtime error) out of the blue - even the task manager does not work correctly, and although the system / runtime did not crash - but only the application, I had a certain doubt that reliability is determined by the language programming = (
Also, AOC is sufficiently self-contained, with its approach to development.
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 realtime thread scheduler, cross-thread exchange, and a formally verified subset of the SPARK language. And much more.
I think that if Ada’s reliability needed a damn horned one, it would be attached with instructions for calling in a difficult situation =)
Implementation - GNUtaya Ada is developing, ISO / IEC standardized.
The standard provides implementation with GC, but for compiled options it is often not implemented. Manual memory management is required - and here programmer errors are possible. However, the language is geared towards using the default stack and there is the concept of managed types with destructors. You can also define your GC implementation, auto-release, or reference counting for each data type.
Ada Reference Manual 2012 contains 950 pages.
Ada’s disadvantage, besides complexity, is its excessive verbosity, which, however, was conceived for the sake of readability. Due to the specificity of the language security model, integration with foreign libraries is difficult.
The Ada-ru site has a good review translation article - the first link.
The most modern implementation of the considered. The full description of the language is quite long - 649 pages - see the original site .
Actually, this is the D language, but with restrictions with the -betterC switch. Why is that ?!
Because the standard library D is Phobos, developed by Alexandrescu and turned out to be very cunning, completely built on templates. The key to this topic is that Phobos is uncontrollable in terms of memory consumption.
The most important things that get lost in BetterC mode are multithreading, GC, strings, classes (structures remain - they are close in functionality - only on the stack) and exceptions (RAII and try-finally remain).
It is possible, however, to write part of the program in full D, and the critical part in D-BetterC. There is also a system attribute function to control the non-use of dangerous effects: pure safe @nogc.
Justification of the regime from the creator of the language.
And then the squeeze - what is cut off and what remains available.
Strings are contained in Phobos - and attempts to use them in BetterC result in infernal errors of instantiation of templates on elementary operations such as outputting a string to the console or concatenation. And in full D mode, the lines on the heap are also immutable, therefore operations with them lead to memory clutter.
I had to meet complaints about bugs in the compiler several times. Which, however, is not surprising for a language competing in complexity with C ++. While preparing the article, I also had to face 4 errors - two occurred when trying to build dlangide with a new compiler and a couple when porting the philosopher problem (for example, crash when using beginthreadex).
The mode has only recently appeared, and errors caused by the restriction of the BetterC mode get out already at the linking stage. To learn about this in advance, what features of the language are trimmed exactly - often have to do it first hand.
For comparison, the source at full D .
On the rosette you can also see options for other languages.
A niche programming language for microcontrollers. The standard implies 5 programming options, but writing an application for example in ladder logic is still an adventure. Therefore, we focus on one option - structured text.
The text of the standard GOST R IEC 61131-3-2016 - 230 pages.
There are implementations for PC / x86 and ARM - and commercial, the most famous of which is CODESYS (often also licensed with different names) and open - Beremiz - broadcast via C.
Since there is integration with C, it’s quite possible to connect the libraries necessary for applied programming. On the other hand, in this area it is accepted 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 that can already be written on anything - without realtime requirements or even any time in general ...
Multithreaded programming for a user program appeared relatively recently - in microcontrollers this was not needed before.
Type casting is mostly only explicit (relaxed in the latest standard). But overflow control is implementation dependent.
In the latest edition of the standard, OOP appeared. Error handling is done by custom interrupt handlers.
We can say that there is no dynamic memory allocation for the user. This historically happened - the amount of data processed by the microcontroller is always constant limited from above.
Experimental C with the removal of dangerous chips and with the addition of modularity and multithreading. Project site
Description about 103 pages. If you highlight the differences from C - very little, about 10 .
Working with arrays and pointers is safe, dynamic memory with automatic reference counting - with double release checks and dangling links.
The standard library has a minimal set of functions for the GUI, multithreading, network functions (including an http server).
But - this implementation is only for Windows x86. Although the compiler and library code is open.
As part of another research task, I put together a Web server layout that collects data from IoT sensors: a 75 Kb executive module, and a <1 MB partial memory set.
Finally - a summary table of compliance with functional requirements.
Surely I missed or misinterpreted something - so correct it.
Sources from the article on github .
Claimed as programming languages with an eye on reliability.
Alphabetically - Active Oberon, Ada, BetterC, IEC 61131-3 ST, Safe-C.
At once, the disclaimer (excuse) is by no means 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 open source compilers, and with the current level of software development - with interest, 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 clarity of the language, I chose the implementation of Dijkstra's famous multi-threaded task about dining philosophers. Implementation is in the textbooks on the language and on the forums, which facilitated my work - it remains only to adapt. For example, a recent habr article about modern C ++ contains an implementation in C ++ 17 for comparison.
Active Oberon (2004)
It was created with an eye to the experience of Pascal, Modula, previous Oberons since 1988, Java, C #, Ada, as well as practical experience in application. It has an implementation in the form of OS A2 , which can act as runtime on top of * nix or Windows. Sources A2 and the compiler for the link .
There is also an Oberon2 to C Compiler (OOC) project that is not tied to the Oberon environment. This is a slightly different dialect, the differences are described below.
The key feature of Oberon is the exceptional brevity of the specification. These are 16 pages on the base Oberon-2 plus 23 pages on the multi-threaded Active extension.
Simple and clear syntax that excludes obvious errors.
Identifiers are case sensitive.
OOP with objects on the heap with the 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 support for multithreading with synchronization primitives.
There is no dynamic dispatching in the OOP implementation, which can easily lead to a situation - they forgot to add processing for a new type.
Streams can be assigned priority and high / realtime they are not interrupted by GC. Strings in the form of UTF-8 arrays.
Rantime (Oberon System) provides interesting opportunities for restarting a failed procedure / module / thread in case of a runtime error - memory addressing, or, for example, integer overflow.
The disadvantage is the lack of RAII, and convenient error handling - all through return codes, with the exception of the option below.
Oberon-2 OOC
It is more convenient for experiments, since Oberon does not require OS - it compiles in ANSI C and there are no interoperability problems. Differences from the Active version - there is no built-in multithreading language - instead there is a module for working with PThreads, but there is UTF16, hierarchical modularity and a system module for working with exceptions.
Module 3
There is also a relative from a slightly different development branch in the form of Modula-3. It was created on the basis of Oberon as opposed to the overdeveloped Ada. The implementation is here .
Compared to Active Oberon, generics and exceptions are added, there are libraries for practical work with Unicode, GUI, and even Postgress. Simplified integration with C. Other multithreading semantics. RAII as WITH (similar to using in C #).
But it seems that the development of Modula 3 stopped in 2010.
Disclaimer. Having launched WinAOS, I ran into TRAPs (aka abort / stacktrace or runtime error) out of the blue - even the task manager does not work correctly, and although the system / runtime did not crash - but only the application, I had a certain doubt that reliability is determined by the language programming = (
Also, AOC is sufficiently self-contained, with its approach to development.
Source for Dining 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 2016 standard)
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 realtime thread scheduler, cross-thread exchange, and a formally verified subset of the SPARK language. And much more.
I think that if Ada’s reliability needed a damn horned one, it would be attached with instructions for calling in a difficult situation =)
Implementation - GNUtaya Ada is developing, ISO / IEC standardized.
The standard provides implementation with GC, but for compiled options it is often not implemented. Manual memory management is required - and here programmer errors are possible. However, the language is geared towards using the default stack and there is the concept of managed types with destructors. You can also define your GC implementation, auto-release, or reference counting for each data type.
Ada Reference Manual 2012 contains 950 pages.
Ada’s disadvantage, besides complexity, is its excessive verbosity, which, however, was conceived for the sake of readability. Due to the specificity of the language security model, integration with foreign libraries is difficult.
The Ada-ru site has a good review translation article - the first link.
Source for Dining 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 quite long - 649 pages - see the original site .
Actually, this is the D language, but with restrictions with the -betterC switch. Why is that ?!
Because the standard library D is Phobos, developed by Alexandrescu and turned out to be very cunning, completely built on templates. The key to this topic is that Phobos is uncontrollable in terms of memory consumption.
The most important things that get lost in BetterC mode are multithreading, GC, strings, classes (structures remain - they are close in functionality - only on the stack) and exceptions (RAII and try-finally remain).
It is possible, however, to write part of the program in full D, and the critical part in D-BetterC. There is also a system attribute function to control the non-use of dangerous effects: pure safe @nogc.
Justification of the regime from the creator of the language.
And then the squeeze - what is cut off and what remains available.
Strings are contained in Phobos - and attempts to use them in BetterC result in infernal errors of instantiation of templates on elementary operations such as outputting a string to the console or concatenation. And in full D mode, the lines on the heap are also immutable, therefore operations with them lead to memory clutter.
I had to meet complaints about bugs in the compiler several times. Which, however, is not surprising for a language competing in complexity with C ++. While preparing the article, I also had to face 4 errors - two occurred when trying to build dlangide with a new compiler and a couple when porting the philosopher problem (for example, crash when using beginthreadex).
The mode has only recently appeared, and errors caused by the restriction of the BetterC mode get out already at the linking stage. To learn about this in advance, what features of the language are trimmed exactly - often have to do it first hand.
Source for Dining 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 at full D .
On the rosette you can also see options for other languages.
IEC 61131-3 ST (1993, latest standard 2013)
A niche programming language for microcontrollers. The standard implies 5 programming options, but writing an application for example in ladder logic is still an adventure. Therefore, we focus on one option - structured text.
The text of the standard GOST R IEC 61131-3-2016 - 230 pages.
There are implementations for PC / x86 and ARM - and commercial, the most famous of which is CODESYS (often also licensed with different names) and open - Beremiz - broadcast via C.
Since there is integration with C, it’s quite possible to connect the libraries necessary for applied programming. On the other hand, in this area it is accepted 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 that can already be written on anything - without realtime requirements or even any time in general ...
Multithreaded programming for a user program appeared relatively recently - in microcontrollers this was not needed before.
Type casting is mostly only explicit (relaxed in the latest standard). But overflow control is implementation dependent.
In the latest edition of the standard, OOP appeared. Error handling is done by custom interrupt handlers.
We can say that there is no dynamic memory allocation for the user. This historically happened - 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 site
Description about 103 pages. If you highlight the differences from C - very little, about 10 .
Working with arrays and pointers is safe, dynamic memory with automatic reference counting - with double release checks and dangling links.
The standard library has a minimal set of functions for the GUI, multithreading, network functions (including an http server).
But - this implementation is only for Windows x86. Although the compiler and library code is open.
As part of another research task, I put together a Web server layout that collects data from IoT sensors: a 75 Kb executive module, and a <1 MB partial memory set.
Source for Dining 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 or misinterpreted something - so correct it.
Sources from the article on github .