Normal view

There are new articles available, click to refresh the page.
Before yesterdayBlogs

Recursion – Compound Interest (in Ada)

By: spqr
6 March 2023 at 15:28

It might seem odd to think of a compound interest as a problem which could be solved by recursion, but it does make sense. The algorithm for calculating compound interest is of course very simple:

interest = current_balance × interest_rate
new_balance = current_balance + interest
current_balance = new_balance

Of course this calculation is done numerous times depending on how many times the interest is to be compounded. If we were to write a simple function in Ada it would look like this:

with text_io; use text_io;
with ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with ada.Float_Text_IO; use Ada.Float_Text_IO;

procedure compound is
   newBal, currBal, intRate, monRate : float;
   numMon : natural;

   function compound_interest(bal: float; intr: float; n: natural) return float is
      interest: float;
      newbal: float := bal;
   begin
      for i in 1..n loop
         interest := newbal * intr;
         newbal := newbal + interest;
      end loop;
      return newbal;
   end compound_interest;

begin
   put("Balance ($)? "); new_line;
   get(currBal); skip_line;
   put("Interest rate (e.g. 5.3)? "); new_line;
   get(intRate); skip_line;
   put("Number of months (1-12)? "); new_line;
   get(numMon); skip_line;

   monRate := intRate / 100.0 / 12.0;
   newBal := compound_interest(currBal,monrate,numMon);
   put("The new balance is $");
   put(newBal, 1, 2, 0);
end compound;

Here is the program running:

Balance ($)?
1000
Interest rate (e.g. 5.3)?
5
Number of months (1-12)?
12
The new balance is $1051.16

This nonrecursive procedure is fine, but we can also solve it recursively. If we say that the balance, b, after 0 months is the amount of the original deposit, d, and the interest rate is r, then we can write:

At month 0: b(0) = d
At month 1 : b(1) = b(0) + b(0) * r
At month 2 : b(2) = b(1) + b(1) * r
...

Then we can form a recursive definition of the form:

b(m) = b(m-1) + b(m-1) * r

Therefore we can use this to create a recursive function:

b(0) = d
b(m) = b(m-1) + b(m-1) * r

Here is a recursive version of the function compound_interest().

function compound_interestR(bal: float; intr: float; n: natural) return float is
   newbal: float;
begin
   if n = 0 then
      return bal;
   elsif n = 1 then      
      return bal + bal * intr;
   else
      newbal := compound_interestR(bal,intr,n-1);
      return newbal + newbal * intr;
   end if;
end compound_interestR;

A month in Ada

By: spqr
3 March 2023 at 17:50

The following simple Ada program prints a month in the form:

 SUN MON TUE WED THU FRI SAT
           1   2   3   4   5
   6   7   8   9  10  11  12
  13  14  15  16  17  18  19
  20  21  22  23  24  25  26
  27  28

It uses a simple process which asks the user the number of days in the month and the starting day of the week. The program demonstrates the overloading of packages in Ada, enumerations, types, subtypes, and output.

Here is the algorithm for displaying the month:

Output the heading with the names of the days of the week.
Output the initial blanks.
Set day = the first day input by the user.
loop date in 1 to number_of_days_in_the_month
   output(date)
   if (day = Saturday) then
      insert a new line
      day = Sunday
   else
      day = next day
   end
end loop
   

Here is the program written in Ada:

with text_IO; use text_io;

procedure month is

   subtype month_days is positive range 1..31;
   type week_days is (Sun,Mon,Tue,Wed,Thu,Fri,Sat);
   package positive_io is new integer_io(positive);
   use positive_io;
   package day_io is new enumeration_io(week_days);
   use day_io;
   answer: character;
   firstday: week_days;
   numberofdays: month_days;

   procedure get_number_of_days(nd: out month_days) is
   begin
      put("Enter the number of days in the month: ");
      new_line;
      get(nd);
      skip_line;
   end get_number_of_days;

   procedure get_first_day(fd: out week_days) is
   begin
      put("Enter the first day of the new month: ");
      put("e.g. Sun, Mon, etc.");
      new_line;
      get(fd);
      skip_line;
   end get_first_day;

   procedure display_month(nd: in month_days; fd: in week_days) is
      day: week_days := fd;
      four_blanks: constant string := "    ";
      width : constant integer := 4;
   begin
      for day in week_days loop
         put(' ');
         put(day);
      end loop;
      new_line;
      for blank_days in Mon..fd loop
         put(four_blanks);
      end loop;
      for date in 1..nd loop
         put(date,width);
         if day = Sat then
            day := Sun;
            new_line;
         else
            day := week_days'succ(day);
         end if;
      end loop;
      new_line;
   end display_month;

begin
   loop
      get_number_of_days(numberofdays);
      get_first_day(firstday);
      display_month(numberofdays,firstday);
      put("Do you wish to see another month? "); new_line;
      put("yes(y) or no(n): "); new_line;
      get(answer); skip_line;
      exit when answer = 'n' or answer = 'N';
   end loop;
end month;

You will notice on Line 9, the redefinition of the package enumeration_io to permit the output of the headings, and the input of the first day of the month in an intuitive manner. Also a subtype, month_days, is created on Line 5 of the program which helps ensure proper input by constraining the values allowed in month_days.

The program repeatedly prompts the user for information, and prints the associated month. Here is the programming running:

Enter the number of days in the month:
31
Enter the first day of the new month: e.g. Sun, Mon, etc.
Wed
 SUN MON TUE WED THU FRI SAT
               1   2   3   4
   5   6   7   8   9  10  11
  12  13  14  15  16  17  18
  19  20  21  22  23  24  25
  26  27  28  29  30  31
Do you wish to see another month?
yes(y) or no(n):
n

What happens if the number of days entered by the user is outside the constraints set? The program will raise a constraint error of the form:

raised CONSTRAINT_ERROR : month.adb:19 range check failed

But this can be fixed by modifying the user input routines. Here is how the procedure get_number_of_days() has been fixed.

   procedure get_number_of_days(nd: out month_days) is
      monthlen : month_days;
   begin
      loop
         begin
            put("Enter the number of days in the month: ");
            new_line;
            get(monthlen);
            skip_line;
            if monthlen in 28..31 then
               exit;
            else
               raise constraint_error;
            end if;
         exception
            when others =>
               skip_line;
               put("Bad input. Enter a value between 28-31.");
               new_line;
         end;
      end loop;
      nd := monthlen;
   end get_number_of_days;

On Lines 10-14 there is an if statement which determines if the input is valid. Input outside of the values 28-31 raises a constraint_error, but this time it is dealt with by the procedure on Lines 15-19 (instead of the system). If an exception is raised, then a message is output to the user, and in this case the loop iterates again. If the input is valid, the input loop is exited. This is a good example of how easy it is to deal with various exceptions in Ada. Here is a sample of the program running with invalid input:

Enter the number of days in the month:
42
Bad input. Enter a value between 28-31.
Enter the number of days in the month:

A basic linked list of words in Ada (ii)

By: spqr
25 February 2023 at 16:26

With a basic linked list that really only builds the list and prints it out, let’s add another function to reverse the list. The procedure below works by extracting the head element repeatedly and building it back in reverse.

procedure reverseList(head: in out list) is
   temp: list := null;
   revl: list := null;
begin
   while head /= null loop
      temp := head;
      head := head.next;
      temp.next := revl;
      revl := temp;
   end loop;
   head := revl;
end reverselist;

What is happening here? The list temp holds the extracted item, while revl holds the reversed list. Line 5 loops through the list. Line 6 extracts a node the list (head). Line 7 sets the list head to the next item, and Line 8 adds the extracted node to the reverse list. Finally Line 9 sets the input list pointer to the reversed list. Finally on Line 11, the input list pointer is set to the reversed list.

For something a little more interesting, here’s the recursive version of the procedure:

function reverseListR(head: in list) return list is
   rest: list;
begin
   if head = null or else head.next = null then
      return head;
   end if;
   rest := reverseListR(head.next);
   head.next.next := head;
   head.next := null;
   return rest;
end reverselistR;

A basic linked list of words in Ada (i)

By: spqr
23 February 2023 at 16:29

Amazingly linked lists in Ada are no different than they are in C, except actually they may be somewhat easier to interpret. You can find out about basic pointers in Ada here (although in Ada they are termed “access values”.

Let’s jump right in and create a linked list, in this case to store a series of words. We will start out with some declarations.

type node;

type list is access node;

type node is record
   word: unbounded_string;
   next: list;
end record;

head: list;

For some people the declaration of node on Line 1 will seem a little odd, but it helps resolve the circularity of the definitions. The declaration on Line 3 refers to node, and the declaration of node (Line 5) refers to list. Line 1 is somewhat of an incomplete declaration as it simply tells the compiler that node is the name of a type of some sort so that the name can be used on Line 3. Nothing else very exciting here – Lines 5-8 declares node to be a record containing the data (word), and the pointer to the next node (next).

Next, we’ll create a procedure buildList() which will create the list, adding one word at a time. There isn’t anything magical here, just a stock-standard list implementation. Remember, lists are LIFO structures, so the last term that gets added is the head of the list.

procedure buildList(head: in out list; aword: in unbounded_string) is
   newNode: list;
begin
   newNode := new node'(word=>aword, next=>null);
   newNode.next := head;
   head := newNode;
end buildList;

On Line 4, the new node is created using new, which takes a free block of memory from the heap and reserves it for use as a node type variable. At the same time it assigns the string (aword), to word, and sets next to null. Lines 5 and 6 insert the new item into the list.

Next, we are going to add a procedure, printList(), to print the list.

procedure printList(head: in list) is
   scanPtr: list;
begin
   scanPtr := head;
   loop
      exit when scanPtr = null;
      put(scanPtr.word);
      scanPtr := scanPtr.next;
      put(" ");
   end loop;
end printList;

The variable scanPtr is a node used to scan the list. Line 5 is the exit strategy for the loop, exiting when the list becomes empty. Line 7 prints the word, and line 8 goes to the next word. Now we need some code to actually run the program.

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.unbounded; use Ada.Strings.unbounded;
with Ada.Strings.unbounded.text_io; use Ada.Strings.unbounded.text_io;

procedure linkedlist is

   -- code from above goes here
   aword: unbounded_string;

begin
   loop
      put("> ");
      get_line(aword);
      exit when aword = "q";
      buildList(head, aword);
   end loop;

   put_line("the list as read :");
   printList(head);
   new_line;

end linkedlist;

Nothing special here. Lines 11-16 provides a loop, prompting the user for words, and building the list until “q” is entered, terminating the input. Here is the programming running:

> the
> cat
> sat
> on
> the
> hat
> q
the list as read :
hat the on sat cat the

Image processing in Ada (v) – the main program

By: spqr
16 February 2023 at 17:47

The last piece of the program binds the packages together. Here we read in the filename using the procedure getfilename(), and the main program which provides a menu based system to access all the subprograms in the prorgam.

with ada.Text_IO; use Ada.Text_IO;
with ada.strings.unbounded; use ada.strings.unbounded;
with ada.strings.unbounded.Text_IO; use ada.strings.unbounded.Text_IO;
with ada.directories; use ada.directories;
with imagestr; use imagestr;
with imagepgm; use imagepgm;
with imageprocess; use imageprocess;

procedure image is
   fnameI, fnameO : unbounded_string;
   img0 : imagep;
   opt : character;
   buf : unbounded_string;
   lb,ub : float;
   bit : integer;

   -- Procedure to
   procedure getfilename(fname: out unbounded_string; t: in character) is
      res : character;
      buf : unbounded_string;
   begin
      if t = 'r' then
         loop
            put_line("Enter PGM filename: ");
            get_line(fname);
            exit when exists(to_string(fname));
         end loop;
      elsif t = 'w' then
         loop
            put_line("Enter PGM filename: ");
            get_line(fname);
            if exists(to_string(fname)) then
               put_line("Overwrite? (y/n):");
               buf := get_line;
               res := element(buf,1);
               if res = 'y' then
                  exit;
               end if;
            else
               exit;
            end if;
         end loop;
      end if;
   end getfilename;

begin

   loop
      put_line("        Image Processing");
      new_line;
      put_line(" 1. Read in PGM image from file");
      put_line(" 2. Apply image invertion");
      put_line(" 3. Apply LOG function");
      put_line(" 4. Apply contrast stretching");
      put_line(" 5. Apply histogram equalization");
      put_line(" 6. Write PGM image to file");
      put_line(" 7. Apply reduce grays");
      put_line(" 8. Quit");
      put_line(" Choice? ");
      buf := get_line;
      opt := element(buf,1);

      case opt is
         when '1' => getfilename(fnameI,'r');
                     readpgm(img0,fnameI);
         when '2' => imageinv(img0);
         when '3' => imagelog(img0);
         when '4' => put_line("lower bound:");
                     buf := get_line;
                     lb := float'value(to_string(buf));
                     put_line("upper bound:");
                     buf := get_line;
                     ub := float'value(to_string(buf));
                     imagestretch(img0,lb,ub);
         when '5' => histequal(img0);
         when '6' => getfilename(fnameO,'w');
                     writepgm(img0,fnameO);
         when '7' => put_line("bit value (1-8):");
                     buf := get_line;
                     bit := integer'value(to_string(buf));
                     reducegrays(img0,bit);
         when others => exit;
      end case;

   end loop;

end image;

The getfilename(), will get a user-input name for a file, based on whether it is for reading or writing. For a file that is to be read, the procedure will continue looping until a valid filename is input. For writing, if the file already exists the user is prompted to indicate whether or not they want the file overwritten.

The problem with π (iv) – Fortran to Ada

By: spqr
18 January 2023 at 16:03

The Fortran program can also be translated to Ada. Below is the Ada program. It contains some code to output the resulting value of pi to the text file piCalc1000ADA.txt. In this code a is a “dynamic” array of integers, which is allocated using a declare block. In reality a is just of type pia, declared at a later point in the program.

with ada.Text_IO; use Ada.Text_IO;
with ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with ada.strings.unbounded; use ada.strings.unbounded;
with ada.strings.unbounded.Text_IO; use ada.strings.unbounded.Text_IO;

procedure piSpigotDYN is

    n, len : integer;
    q, x, nines, predigit : integer;
    type pia is array(integer range <>) of integer;
    infp : file_type;

begin
    create(infp,out_file,"piCalc1000ADA.txt");
    n := 1000;
    len := 10 * n / 3;

    declare
       a : pia(1..len);
    begin

    a := (1..len => 2);
    nines := 0;
    predigit := 0;

    for j in 1..n loop
       q := 0;
       for i in reverse 1..len loop
          x := 10 * a(i) + q * i;
          a(i) := x mod (2*i-1);
          q := x / (2*i-1);
       end loop;
       a(1) := q mod 10;
       q := q / 10;
       if q = 9 then
          nines := nines + 1;
       elsif q = 10 then
          put(infp,predigit+1,width=>1);
          for k in 1..nines loop
             put(infp,0,width=>1);
          end loop;
          predigit := 0;
          nines := 0;
       else
          put(infp,predigit,width=>1);
          predigit := q;
          if nines /= 0 then
             for k in 1..nines loop
                put(infp,9,width=>1);
                nines := 0;
             end loop;
          end if;
       end if;
    end loop;
    put(infp,predigit,width=>1);

    close(infp);
    end;
end piSpigotDYN;

Advent of Code 2022 in pictures

14 January 2023 at 09:25

Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.


Just as a teaser for the next Advent of Code in 10 1/2 months, we would like to show a few pictures related to last edition. The 25 puzzles, data and solutions can be found here and here. They are programmed with HAC (the HAC Ada Compiler), thus in a small subset of the Ada language. The HAC compiler is very fast, so you run your program without noticing it was ever compiled, which is perfect for completing a programming puzzle.

Day 22 run with HAC (here, embedded in the LEA environment)

However, the program will run substantially slower than compiled with a native, optimizing compiler like GNAT.
This is not an issue for most Advent of Code puzzles, but for some, it is, especially on the later days. Fortunately, changing from HAC to GNAT is trivial (just switch compilers), unlike the traditional reprogramming of Python prototypes in C++, for instance.

The pictures

Day 8's data is a map representing the height of trees. Once the puzzle was solved, I was curious how the forest looked like. Note that different users get different data, so you are unlikely to find a visualisation of exactly your data on the internet.


Day 12's puzzle is a shortest path problem with specific rules and a nice data - a terrain with a hill - which seems designed to trap depth-first-search algorithms into an almost infinite search. The yellowish path is the shortest from the green dot, elevation 'a', to blue dot, elevation 'z'.
The pinkish path is the shortest from the blue dot to any dot with elevation 'a'. Fortunately Dijkstra's algorithm (and perhaps others) allows for such a special criterion regarding the end point.

Click to enlarge


For day 22’s puzzle, a walk on a cube’s surface is involved, so it is helpful to sketch it on a piece of paper, cut it and glue the faces. A banana skin of that puzzle is that the example’s layout may be different from the data’s layout. We slipped on that one and ended up gluing and programming the face-to-face transitions for two layouts…


Click to enlarge

Other Adaists’ solutions, discussions and pictures can be found here and in the "2022 Day x" threads in the same forum.

The problem with π (iii) – Pascal to Fortran

By: spqr
13 January 2023 at 15:31

A while back I posted on the Spigot algorithm in Pascal (and C) for calculating the first 1000 decimal places of π. Now converting it to Fortran is quite easy.

program piSpigot

   integer :: n, len
   integer :: i, j, k, q, x, nines, predigit
   integer, dimension(3500) :: a

   n = 1000
   len = (10 * n) / 3

   a = 2
   nines = 0
   predigit = 0
   999 format(I1)

   do j = 1,n
      q = 0
      do i = len,1,-1
         x = 10 * a(i) + q * i
         a(i) = mod(x,2*i-1)
         q = x / (2*i-1)
      end do
      a(1) = mod(q,10)
      q = q / 10
      if (q == 9) then
         nines = nines + 1
      elseif (q == 10) then
         write(*,999,advance='no') predigit+1
         do k = 1, nines
            write(*,999,advance='no') 0
         end do
         predigit = 0
         nines = 0
      else
         write(*,999,advance='no') predigit
         predigit = q
         if (nines /= 0) then
            do k = 1,nines
               write(*,999,advance='no') 9
               nines = 0
            end do
         end if
      end if
   end do
   write(*,999,advance='no') predigit

end program piSpigot

This algorithm is good, but it could be improved upon by making the array dynamic. All that really involves is reworking some code at the top of the program. In the snippet of code below, the array a is made allocatable (Line 5), I/O is added to prompt for the number of decimal points required to be calculated, and then on Line 11 the space for a is allocated. At the end of the program, the memory is deallocated.

program piSpigot

   integer :: n, len
   integer :: i, j, k, q, x, nines, predigit
   integer, dimension(:), allocatable :: a

   write(*,*) 'Number of decimal points? '
   read(*,*) n
   len = (10 * n) / 3

   allocate(a(len))
   ...
   deallocate(a)
end program piSpigot

LEA 0.85 - the Tabs are here!

20 November 2022 at 20:05
Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.

The 0.85 version of LEA (the Lightweight Editor for Ada) is available!

Web site: http://l-e-a.sf.net/
Source repository #1: https://sf.net/p/l-e-a/code/HEAD/tree/
Source repository #2: https://github.com/zertovitch/lea

The new version has two main new features:
  - Tabs (screenshot below)
  - LEA doesn't write scilexer.dll as a file; thus, it runs as a portable application (in the sense: you can run it from a read-only drive directly, without installation)

Click to enlarge

Enjoy!

Building GCC 12.2.0 on Ventura for aarch64

These are notes on building GCC 12.2.0 and GNAT tools for Apple silicon.

There were two main problems:

  • the base package was built on an Intel machine (lockheed - named after Shadowcat’s companion dragon), running Monterey (macOS 12).
  • the build machine, an M1 mac Mini (temeraire - named after Naomi Novik’s dragon) was by this time running Ventura (macOS 13), but I wanted to make sure that users could continue to run on Monterey.
Read more »

AZip, GWindows, the Windows API and another surprise!

13 November 2022 at 12:38

 

Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.


Last post was about an improvement of the GWindows.Common_Controls.Ex_List_View widget which is distributed with the GWindows framework. Without changing the specification, it was possible to avoid Windows API calls during the performance-sensitive comparison function, leading to impressive speedup factors: from 3.3x with 4096 items to 16.5x with 32768 items.

But actually, it was only the prelude!

If you can live with data duplication, which is not nice and not always practical, it is possible to do much better. Concretely, you store the same strings in the cells of the List_View widget and in the payload data associated with each row. Then, the sorting for all columns (text, dates, numbers, ...) is using the payload data. An addition to the Ex_List_View package later (this time, enriching a bit the specification), the comparison function may exclusively use the payload of both compared rows, directly, thus avoiding any API call during the lifespan of the comparison function.

Without going into too many details, here are the performance gains in charts:



This represents a factor of ~100x to ~300x on top of the previous improvement, depending on the test machine.

Here are links to the sources:

AZip:

Project site & Subversion repository:
https://sf.net/projects/azip/
GitHub clone with Git repository:
https://github.com/zertovitch/azip

GWindows:

Project site & Subversion repository:
https://sf.net/projects/gnavi/
GitHub clone with Git repository:
https://github.com/zertovitch/gwindows

LEA 0.84 - the Build & Run button!

12 November 2022 at 11:43

 

Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.


The 0.84 version of LEA (the Lightweight Editor for Ada) is available!


Web site: http://l-e-a.sf.net/
Source repository #1: https://sf.net/p/l-e-a/code/HEAD/tree/
Source repository #2: https://github.com/zertovitch/lea

This version is essentialy a rework of LEA's ergonomy - we had recently the chance of having a group of young first-time programmers and first-time LEA users to test it!

The main addition is the green Build & Run button, which matches a (less intuitive) menu entry and the F9 key.

Click screenshot to enlarge

Another addition is a dedicated box for the "User_Abort" internal Virtual Machine exception, so that the LEA user doesn't take the exception message for an actual error (which would be the case for any other exception).

Before:


Now:

Enjoy!


AZip, GWindows, the Windows API and a good surprise!

5 November 2022 at 21:23

Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.


Programming user interfaces is often a bizarre experience where you program only one side and the other side is done by other people (for instance the programmers of the Windows components) that you cannot contact for asking questions. Even if it was possible (like working at Microsoft in Redmond), those people are probably already retired for long. So the only possibility is to experiment with the black box and skim the Internet in the hope someone else has encountered the same issues and found a solution. Fortunately, as time goes by, there are more and more solutions appearing.

Here is a typical example.

The AZip Zip archive manager is designed to operate with different user interface systems, like native Windows (it is done through the GWindows framework), or the multi-platform Gtk system (there is a draft version of that).

AZip uses the Ex_List_View_Control_Type widget (package GWindows.Common_Controls.Ex_List_View) which is an extension of List_View_Control_Type (package GWindows.Common_Controls), developed by the company KonAd GmbH, with cool features like individually coloured cells, and sorting.

A hiccup was the performance of the sorting. On Zip archives with many entries (say, 10,000 or more), the sorting became frustratingly slow. On other software sorting is MUCH faster, so there was an issue to solve.

Sorting a column in AZip - click to enlarge screenshot

Fortunately GWindows and its extensions are completely open-source, so you can inspect everything. By the way, it is also the case for GNAT (the open-source Ada compiler): you can inspect the entire run-time library. All in all, AZip is a rare software whose entire source (the program itself, the user interface, the data compression library, and the run-time library) can be comfortably browsed per mouse clicks from GNAT Studio. This amounts to more than 112,000 and 572 units (mostly packages), completely in Ada. You see here a slide from a FOSDEM presentation about AZip.

Break-down of the Ada source code of AZip

Wait, the "entire source"? Not really, since Windows is neither open-source, nor in Ada. That's where the fun begins. Back to our sorting issue (sorting is too slow). GWindows (the nice object-oriented framework) provides a Sort method, which sends behind the scenes a Windows message, LVM_SORTITEMS, which calls back a comparison function provided by GWindows, Compare_Internal, which in turn will calls a On_Compare method - built-in, or derived by the programmer, for instance for sorting the columns of a Zip archive where some columns are numerical and other ones are texts. This mechnism is convoluted but it is how it works if you want to use the List_View widget provided by Windows. It is a case where you really have to dance with the Windows API (of course, you would have the same situation for other user interface systems: Mac, Gtk, ...). After a certain amount of Internet searches, through forums, blogs, etc., it appears that there is an alternative way with a Windows message called LVM_SORTITEMSEX. Note the "EX" at the end: it is for "extended". You have probably noticed rather the last three letters, but anyway... This alternative way provides directly to the comparison call-back the indices of the rows that Windows want to compare. With the initial approach (using LVM_SORTITEMS, without EX) two messages, LVM_FINDITEM, have to be sent in order to get those row indices. So you can save those two calls and hope for a small speedup.

Now comes the surprise. The gain in terms of time is astounding!

On a certain computer, with 4096 items to be sorted, both calls (that can be skipped with the new approach) consume 70% of the entire sorting time - including the comparison itself, the sorting effort on Windows' side, the object-oriented dispatching, etc. . With 8192 items, it is 81%. With 16'384 items, it is 88%. With 32'768 items, 94% of the time is consumed by the extra calls. Seen differently, the old approach takes 14 seconds for sorting, and the new approach takes 0.85 second for the same job!

Here are performance charts on two differents computers.


 

Interestingly, the performance stays linear with the new approach.

Here are links to the sources:

AZip:

Project site & subversion repository:
https://sf.net/projects/azip/
GitHub clone with git repository:
https://github.com/zertovitch/azip

GWindows:

Project site & subversion repository:
https://sf.net/projects/gnavi/
GitHub clone with git repository:
https://github.com/zertovitch/gwindows

AWA 2.4.0

7 August 2022 at 21:12

The framework provides several ready to use and extendable modules that are common to many web applications. This includes the login, authentication, users, permissions, managing comments, tags, votes, documents, images. It provides a complete blog, question and answers and a wiki module.

AWA simplifies the Web Application development by taking care of user management authentication and by providing the foundations on top of which you can construct your own application. AWA provides a powerful permission management that gives flexibility to applications to grant access and protect your user's resources.

A typical architecture of an AWA application is represented by the picture below:

[Ada Web Application architecture](Ada/awa_architecture_overview.png)
    1. Using AWA with Alire

To use AWA with Alire, you should first register a new index to get access to the AWA crates and its dependencies.

``` alr index add git+https://github.com/stcarrez/awa-alire-index name awa ```

After this command, the `alr` command should give you access to the AWA crates and you can check that with the command:

``` alr search awa ```

Once you have setup the Alire index, you can import the `awa` framework and the `dynamo` associated tool by using the following commands:

``` alr init demo cd demo alr with awa alr with dynamo alr with servletada_aws alr build ```

Note, if you are using FreeBSD, you should probably use the following command to build (because the MariaDB shared libraries are installed in `/usr/local/lib/mariadb` directory):

``` alr build -- -largs -L/usr/local/lib/mariadb ```

Once the `dynamo` tool is built, you can use it to setup your project by using the `create-project` command. To run the `dynamo` tool, you must run it by using the `alr exec` command because Alire will setup some environment variables and `PATH` that gives access to Dynamo and AWA configuration files.

``` alr exec dynamo create-project -l apache web demo [email protected] ```

Change the `demo.gpr` GNAT project that was generated by `alr` and replace `demo.adb` by `demo-server.adb`. That later source has been generated by `dynamo` and it contains everything to setup, prepare and run the Web server. Once this is done, build the application:

``` alr build ```

The Atlas AWA Demonstrator(https://github.com/stcarrez/atlas) is also available as Alire crates. Two versions are available, the `atlas` crate is using the Ada Web Server and the `atlas_ews` is using the Embedded Web Server. To play to Atlas, you can try one of the following commands:

``` alr get atlas

  1. or

alr get altas_ews ```

    1. Debian packages

Debian packages are also available for Ubuntu 20, Ubuntu 22 and Debian 11. The repository also includes a Debian package for Alire 1.2. Choose **one** of the following configuration and add it to your `/etc/apt/sources.list` configuration.

``` deb https://apt.vacs.fr/ubuntu-focal focal main

  1. or

deb https://apt.vacs.fr/ubuntu-jammy jammy main

  1. or

deb https://apt.vacs.fr/debian-bullseye bullseye main ```

And you can run the following command to accept the signed packages:

``` wget -O - https://apt.vacs.fr/apt.vacs.fr.gpg.key | sudo apt-key add - ```

      1. AWA 2.4.0(https://github.com/stcarrez/ada-awa/releases/tag/2.4.0) Download: awa-2.4.0.tar.gz(http://download.vacs.fr/ada-awa/awa-all-2.4.0.tar.gz)
 - Add support for SQL queries embedded in applications with ARE
 - Fix #20: Do not use git:// protocol
 - New EasyMDE plugin to integrate the Easy Markdown Editor
 - Update AWA blog and AWA wiki to use the EasyMDE editor for Markdown
 - Use Dynamo 1.3.0, Ada Server Faces 1.5.0, Ada Servlet 1.6.0, OpenAPI Ada 0.6.0
 - Use Ada Wiki 1.4.0, Ada Database Objects 2.3.0
 - Use Ada Keystore 1.3.3, Ada EL 1.8.5, Ada Utility Library 2.5.0
      1. Dynamo 1.3.0(https://github.com/stcarrez/dynamo/releases/tag/1.3.0) Download: dynamo-1.3.0.tar.gz(http://download.vacs.fr/dynamo/dynamo-1.3.0.tar.gz)
 - Fix #5: Generated body does not compile when an enumeration of another UML package is used
 - Fix #7: No default type for SQL generation of a column that uses an enumeration
 - Fix #9: Option or configuration to disable some SQL generation
 - Fix #10: Definition of an UML datatype with a tagged value raises an exception
 - Fix #12: Avoid emitting a full qualified type name for types declared in the current package
 - Fix #16: Improvement in Markdown documentation generator
 - Fix #17: YAML parser: accessibility check failure
 - Fix #18: Generate database operation to reload an object
 - Fix #19: Add dynamo configuration through environment support
 - Fix #20: Give access to the man page from alire
 - Fix $21: Generated procedure Create is missing overriding keyword
      1. OpenAPI Ada 0.6.0(https://github.com/stcarrez/swagger-ada/releases/tag/0.6.0) Download: openapi-ada-0.6.0.tar.gz(http://download.vacs.fr/openapi-ada/openapi-ada-0.6.0.tar.gz)
 - Rename Swagger package into OpenAPI and provide a Swagger package for compatibility
 - Update the openapi generator to version 6.0.0
 - Add support for text/plain response
 - Add support to use external Ada types in OpenAPI definitions
 - Add support for multiple response types
 - Add support for binary responses
 - Add support for Ada enum generation for OpenAPI enums
 - Integrate Swagger UI v4.13.0
      1. Ada Server Faces 1.5.0(https://github.com/stcarrez/ada-asf/releases/tag/1.5.0) Download: ada-asf-1.5.0.tar.gz(http://download.vacs.fr/ada-asf/ada-asf-1.5.0.tar.gz)

- New widget <w:progress> to display horizontal/vertical progress bars

      1. Ada Servlet 1.6.0(https://github.com/stcarrez/ada-servlet/releases/tag/1.6.0) Download: ada-servlet-1.6.0.tar.gz(http://download.vacs.fr/ada-servlet/ada-servlet-1.6.0.tar.gz)
      2.  - Fix #4: Alire servletada_aws GNAT project fails due to missing Naming rule
         - Fix #5: The Input_Line_Size_Limit parameter is not taken into account
         - Fix #6: GNAT configuration project is not correct to build with debugging
         - Fix #7: Constraint error raised when matching empty path routes
         - Fix #11: Support for Embedded Web Server
         - Fix #12: Support for multiple response types in REST operations
        
      1. Ada Security 1.4.1(https://github.com/stcarrez/ada-security/releases/tag/1.4.1) Download: ada-security-1.4.1.tar.gz(http://download.vacs.fr/ada-security/ada-security-1.4.1.tar.gz)
 - Fix Alire GNAT project to build in debug mode
 - Fix Security.Random that generates shorter random string
      1. Ada Database Objects 2.3.0(https://github.com/stcarrez/ada-ado/releases/tag/2.3.0) Download: ada-ado-2.3.0.tar.gz(http://download.vacs.fr/ada-ado/ada-ado-2.3.0.tar.gz)
 - Fix #4: Is_Loaded predicate operation is false when an object is not yet inserted in the database
 - Fix #5: Exception raised when SQLite Query_Statement is finalized if the SQL query was invalid
 - Fix #7: Update SQLite support to 3.31.1
 - Fix #8: Add SQlite busy handler to handle the SQLITE_BUSY error
 - Fix #9: Better handling of SQLITE_BUSY error
 - Fix #10: Error 'XML query file does not exist' when the query is loaded from a static embedded loader
      1. Ada Wiki Engine 1.5.0(https://github.com/stcarrez/ada-wiki/releases/tag/1.5.0) Download: ada-wiki-1.5.0.tar.gz(http://download.vacs.fr/ada-wiki/ada-wiki-1.5.0.tar.gz)
      2.  - Add support for Textile markup language
         - Rewrote the Markdown parser to better follow the Common Mark Specification
        
      1. Ada Utility Library 2.5.0(https://github.com/stcarrez/ada-util/releases/tag/2.5.0) Download: ada-util-2.5.0.tar.gz(http://download.vacs.fr/ada-util/ada-util-2.5.0.tar.gz)
 - New examples to illustrate the IO stream composition
 - New examples for JSON parser and Util.Beans.Objects
 - Add support to set environment variables when launching a process (without changing the current process environment!)
 - Add support to indent XML output streams
 - New package Util.Files.Rolling to provide a rolling file manager
 - New package Util.Beans.Objects.Iterators to easily iterate over objects
 - Add a new log appender to support rolling log files with size and time based policies
 - New operation Util.Files.Delete_Tree to delete a directory tree and work arround
   for GNAT bug gcc/63222 and gcc/56055
 - New operation Util.Files.Realpath to find the canonicalized absolute path of a file
 - New package Util.Blobs to hold binary content with reference counting
 - New package Util.Http.Headers to provide some HTTP helper operations
 - Add support for Blob in bean objects
 - Fix compilation on NetBSD 9.2
 - Fix compilation with AWS >= 22.0

Ada development on FreeBSD 13.1

5 August 2022 at 20:22
  • Warning:* this is full of hacks and I don't pretend to provide any complete detailed and completely reproducible steps for getting a new Ada compiler.

Before proceeding, make sure you have `gmake` installed because the BSD make uses an old Makefile syntax and is not able to handle GNU specific Makefiles.

``` pkg install gmake ```

    1. Get gcc6-aux from an old FreeBSD installation

First step is to get the `gcc6-aux` port as is from an old FreeBSD installation. Basically, a tar-gz of the `/usr/local/gcc6-aux` directory tree is enougth. Basically, what I did is to run a `tar cf /tmp/gcc.tar /usr/local/gcc6-aux` on an old FreeBSD and then extract that tar as is on the FreeBSD 13.1 system (Of course, it must be the same architecture).

    1. Build gcc 12

Get the GCC 12 sources from https://gcc.gnu.org and extract them:

``` tar xzf gcc-12.1.0.tar.xz ```

Building GCC must be made in another directory and we must also setup the `PATH` to give access to the old Ada compiler. You must setup and run the configure script as follows:

``` export PATH=/usr/local/gcc6-aux/bin:$PATH mkdir build cd build ../gcc-12.1.0/configure disable-nls enable-gnu-indirect-function enable-host-shared with-as=/usr/local/bin/as with-gmp=/usr/local with-ld=/usr/local/bin/ld with-system-zlib without-zstd enable-libada localstatedir=/var prefix=/build/gcc-12.1 build=x86_64-portbld-freebsd13.0 --enable-languages=c,ada,c++ ```

The `with-as` and `with-ld` are important to use the correct `as` and `ld` commands. A subset of the above configure command is taken from the configure command that FreeBSD ports is using to build GCC 12.

After the configure, run the `gmake` command:

``` gmake ```

or to run several compilations in parallel use:

``` gmake -j4 ```

While building, the compiler is built a first time with `gcc6-aux` and another time with itself. Building the Ada libraries failed for me with:

``` /home/ciceron/src/build/./gcc/xgcc -B/home/ciceron/src/build/./gcc/ -B/build/gcc-12.1/x86_64-portbld-freebsd13.0/bin/ -B/build/gcc-12.1/x86_64-portbld-freebsd13.0/lib/ -isystem /build/gcc-12.1/x86_64-portbld-freebsd13.0/include -isystem /build/gcc-12.1/x86_64-portbld-freebsd13.0/sys-include -fchecking=1 -c -g -O2 -m32 -fpic -W -Wall -gnatpg -nostdinc -m32 s-exnllf.adb -o s-exnllf.o s-exnllf.ads:38:04: warning: in instantiation at s-dorepr.adb:82 enabled by default s-exnllf.ads:38:04: warning: in instantiation at s-exponr.adb:54 enabled by default s-exnllf.ads:38:04: warning: "Temp" overlays smaller object enabled by default s-exnllf.ads:38:04: warning: program execution may be erroneous enabled by default s-exnllf.ads:38:04: warning: size of "Temp" is 96 enabled by default s-exnllf.ads:38:04: warning: size of "Rep64" is 64 enabled by default gmake9: *** ../gcc-interface/Makefile:301: s-exnllf.o Error 1 ```

This error is caused by some inconsistency between the floating point mantissa size returned by `Machine_Mantissa'` Ada predicate and the size of the floating point number. After hacking the `gcc/ada/libgnat/s-dorepr.adb` I've managed to build the Ada libraries.

After a successful build, installation in `/build/gcc-12.1` is done with:

``` gmake install ```

    1. Build gprbuild

Yet, another challenge is building gprbuild(https://github.com/AdaCore/gprbuild). First, get the following sources:

``` git clone https://github.com/AdaCore/xmlada.git git clone https://github.com/AdaCore/gprconfig_kb git clone https://github.com/AdaCore/gprbuild.git ```

Then run the boostrap script from `gprbuild` and make sure to setup your PATH to use the new GCC Ada compiler:

``` export PATH=/build/gcc-12.1/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin cd gprbuild ./bootstrap.sh with-xmlada=../xmlada with-kb=../gprconfig_kb --prefix=./bootstrap ```

Then, build again and install in the same location as GCC:

``` gmake prefix=/build/gcc-12.1 SOURCE_DIR=`pwd` setup gmake all gmake install ```

Don't stop at building only `gprbuild` because you'll probably need the `libgpr` library (if you want to build Ada Web Server(https://github.com/AdaCore/aws) which now depends on GNAT libcoll(https://github.com/AdaCore/gnatcoll-core) which needs libgpr).

``` gmake libgpr.build gmake libgpr.install ```

    1. Installation of vscode

It is possible to use vscode and the Ada plugin for development under FreeBSD. The vscode Ada plugin contains the `ada_language_server` binary which is executed as a language server for vscode. No binary is provided for FreeBSD but since the binary is provided for Linux, we can use it by using the Linux support in FreeBSD. First, install vscode with:

``` pkg install vscode ```

then, launch it and install the `Language Support for Ada` extension. When the extension is activated, you will see a message indicating that the language server cannot be started. There are two things to do:

  • create a `freebsd` directory with the `ada_language_server` binary,
  • activate and setup the Linux support in FreeBSD

For the first part, copy

``` cd ~/.vscode-oss/extensions/adacore.ada-23.0.8 cp -rp linux freebsd ```

Now we must setup the Linux support in FreeBSD, and follow the FreeBSD Chapter 10. Linux Binary Compatibility(https://docs.freebsd.org/en/books/handbook/linuxemu/).

Add in `/etc/rc.conf`:

``` linux_enable="YES" ```

and start with:

``` sudo service linux start ```

Install the `debootsrap`:

``` sudo pkg install debootstrap sudo debootstrap focal /compat/ubuntu. ```

Add the line in `/etc/sysctl.conf`:

``` compat.linux.emul_path="/compat/ubuntu" ```

and configure with:

``` sudo sysctl -f /etc/sysctl.conf ```

Mark the binary as Linux:

``` brandelf -t Linux ~/.vscode-oss/extensions/adacore.ada-23.0.8/freebsd/ada_language_server ```

Replace the symbolic link `/compat/ubuntu/lib64/ld-linux-x86-64.so.2` to a real file otherwise the Linux linker is not found by the kernel.

Now, restart vscode and the Ada language server should be started.

The description on how to build `ada_language_server` for FreeBSD is left as an exercise to the reader :-)

Ada Wireless Driver Progress

25 July 2022 at 07:00

A few weeks ago, Raspberry Pi released the Pico W, an update to the Pico development board with the addition of a wifi chip and trace antenna. As I’ve been using the Pico for many of my projects, I’m excited by the possibility of adding wireless connectivity.

I program in Ada, so I want to add support for the Pico’s wireless controller, CYW43439 to my driver library.

First, a reality check; Raspberry Pi is going to continue producing new microcontrollers and development boards with new features. They’ve put an immense amount of effort into their C/C++ SDK and MicroPython libraries. Do I really want to continue reimplementing their drivers in Ada? Would I be better off just generating a wrapper around their SDK? The Pico SDK is pretty heavily dependent on the CMake build infrastructure right now, so I’d have to figure out how to make that work with Ada. The generated Ada bindings from C code are usually pretty awkward to work with, so I’d have to write a fair bit of code either way. For now I’m going to keep going with my native Ada libraries, but I’m going to keep asking myself if this is a worthwhile effort when good vendor libraries exist.

The wireless chip they’ve chosen, CYW43439, is a beast. Infineon (formerly Cypress, formerly Broadcom) have a public datasheet, which contains information useful to PCB designers and test engineers as well as some rather confusing timing diagrams that contain no actual timing information. The Pico W has an RP2040 connected to the CYW43439 with the "gSPI" interface with both the transmit and receive signals multiplexed onto a single microcontroller pin.

The RP2040’s PIO should be perfect for implementing this sort of odd serial protocol, but I found it to be quite frustrating in this case. The CS, CLK, and DAT signals are not accessible from any test pad or trace that I can find on the Pico W, so I can’t get a logic analyzer connected while the RP2040 is communicating with the CYW43439. I considered buying a standalone module that uses this chip so that I could connect it to some pins I can probe, but I figured I’d try working blind first. I configured the PIO to use some of the Pico’s exposed pins and connected a logic analyzer. At the very least, I can get the CS, CLK and DAT transmit timing to look correct before trying to talk to the wireless controller.

The datasheet says that the device needs up to 50ms after pulling REG_ON high before it’ll respond to gSPI commands. The host should poll the registers beginning at address 0x14 for the value 0xFEEDBEAD. Once that value is read correctly, a test write/readback at address 0x18 verifies that the gSPI bus is working and the CYW43439 is powered up and ready to accept commands.

That 0xFEEDBEAD value led to some interesting search results; this protocol has been used in Broadcom wireless chipsets as far back as the venerable WRT54G router. There are many open source drivers of varying quality for this family of chips and it seems that Broadcom/Cypress/Infineon kept the host interface mostly the same over the years, only adding new commands when necessary. I found a very detailed writeup about reverse engineering the firmware for these chips, which was quite interesting, if not immediately useful.

The Pico SDK has a PIO driver for gSPI, so I spent a lot of time reading that code and trying to understand it. They use a single PIO SM to control the DAT signal, with CLK configured as a side-set. To begin a transfer, the host CPU resets the SM and executes set pindirs, 1 to configure DAT as an output, then sets the x register to the number of bits to write and y to the number of bits to read. These instructions are not included in the main PIO program as they’re not timing critical and executing them from the CPU saves on PIO memory, which is only 32 instructions. The PIO program shifts one bit out on the DAT pin, toggles the CLK sideset, then decrements the x register. Once x reaches zero, DAT is reconfigured as an input and the SM starts shifting in bits, decrementing y as it goes.

This seems fairly reasonable. We can control the number of bits per word by changing the x and y registers before beginning a transaction. The host CPU needs to get involved after each TX/RX cycle to reset the pindirs, x, and y registers and pull CS high if the transaction has completed. The driver code uses DMA to send data to the PIO, which doesn’t seem like a huge benefit when the CPU is going to intervene after every word transferred. The DMA controller can do byte swapping, which may be useful for big endian network data, but I’m not sure if it’s worth the trouble in this case.

I decided that I’d rather use up a bit more PIO memory and have separate programs for read and write that do their own setting of pindirs and the x register. The CPU still needs to control CS though, which is inconvenient. I really wish the PIO had just a few more Delay/Side-Set bits so that we could have longer delays in PIO programs for timing the CS "back porch."

The CYW43439 datasheet also illustrates a "Read-Write" transaction, where CS stays low between the Write and Read commands. The datasheet says there should be a "fixed delay" in between read and write in this mode. The diagram shows four clock edges during this delay time and there is a bus configuration register for delay timing, but I wasn’t able to get this working. As far as I know, this is just an optimization, not a required bus mode.

Speaking of optimizations, the CYW43439 defaults to 16-bit words on the gSPI interface, but the command to switch to 32-bit mode is 2 16-bit words long, so we can send that as the first command and never have to deal with changing word sizes. As far as I can tell, this is what every driver does.

After spending far too long trying to make my program block until the SM finished clocking bits and wondering if the idle polarity of the clock really matters, I got a logic analyzer trace that looked reasonably close to what the datasheet specifies. I changed my pin config to connect the PIO to the wireless controller and got some signs of life! Polling the 0x14 register returned 0xED 0xFE, which was half of my expected 0xFEEDBEAD with swapped bytes. I played with the PIO SHIFTCTRL register a bit and got a successful read of 0xFEEDBEAD. Surprisingly, the test write/readback worked too! I guess that means I have a working gSPI implementation on PIO! I noticed that the Pico SDK runs the PIO at 33 MHz for this interface, but my test program fails at anything faster than 20 MHz. I guess I still need to get a probe on those DAT, CLK, and CS traces to get the timing right. The datasheet says this interface can run at 50 MHz, but we can worry about that later.

Now that I can communicate with the CYW43439, I started looking at cyw43-driver more seriously. The datasheet makes no mention of most of the CYW43439’s internal registers and this is not a trivial driver, so I’m going to try to interoperate with this C driver from Ada, rather than trying to rewrite it. It looks like this driver calls a bunch of symbols like cyw43_hal_pin_config that we need to provide. Luckily, exporting Ada procedures as symbols that conform to the C ABI is easy.

procedure HAL_Pin_Config
  (Pin, Mode, Pull, Alt : Interfaces.C.int)
with Export, Convention => C, External_Name => "cyw43_hal_pin_config";

On the more complex side of things, we need a TCP/IP stack. cyw43 only contains bindings for LwIP and that’s what Pico SDK uses, so I went searching for Ada bindings for LwIP. Surely someone else has tried to do embedded networking in Ada before, right?

Right! I found a directory called ipstack buried in the SPARK unit tests. If the README is to be believed, this is a formally verified rewrite of a large chunk of LwIP in SPARK. Excellent! This code predates the Alire package manager by many years, so I spent some time understanding the existing Makefiles and hierarchy of GPRbuild projects and got the whole mess built as an Alire crate I can use as a dependency. There’s a lot of room for improvement here, but I want to get some packets flowing before I start trying to improve the IP implementation. If this works at all, I’ll be impressed.

The ipstack library only has two hardware drivers: a minimal TUN/TAP implementation for testing on a Linux host and a driver for the ancient NE2000 ISA card. I spent many hours learning about firewalls with OpenBSD on a 486 with a couple of NE2K cards in my formative years, so this brings back fond memories. Back in the day, the NE2000 was not the fastest network card you could buy, but it was cheap, reliable, and rock solid under Linux, which was far from a guarantee for any peripheral back then.

So now I have three bits of code to string together: my Ada/PIO gSPI implementation, the SPARK ipstack, and cyw43-driver.

Stay tuned for my next post, where I might actually get something working.

HAC as an embedded compiler

28 June 2022 at 20:32

Here is an example to illustrate how the HAC compiler is embedded in an Ada application.

The user of the application has typically only an executable (built with a "full Ada" system like GNAT) and an "Ada-pplet" (can be a few lines) that is run from that executable.

Normally, applications have different languages for building the application and for the embedded script system: Excel is written in C/C++ but embeds VBA (Visual Basic for Applications). 3D Studio Max has MaxScript. GIMP uses Script-fu, a variant of Scheme. Others use Lua or Python. Of course nobody would think about shipping a software with a scripting language where you have to fight with pointers and conditional compilation (C/C++).

But... how about an application that is written in a compiled language, but is using the same language for its scripting purposes? Since a few days, it is possible with HAC (the HAC Ada Compiler).

If you build the demo exchange_hac_side.adb (e.g. from GNAT Studio, or via the command "gprbuild hac"), you get an executable, exchange_hac_side on Linux or exchange_hac_side.exe on Windows. If you run it, you get:

Exchange_Native_Side is started.

Native: building a HAC program: exchange_hac_side.adb

   Exchange_HAC_Side is started.

   HAC: I call a parameterless callback.
      Native: Parameterless_Callback is speaking!
   HAC: done calling.

   HAC: I send some stuff through callbacks.
      Native: HAC is saying: [I'm HAC and I say hello!]
      Native: HAC has sent me the numbers: 123 456 789
      Native: HAC has sent me the numbers: 1.23000000000000E+02 4.56000000000000E+02 7.89000000000000E+02

   HAC: message before call: [I'm HAC]
      Native: HAC is saying: [I'm HAC]
   HAC: message after call: [No, I'm Native, you loser!]
   HAC: integer before call: [12]
      Native: HAC has sent me the number: 12; I will send it back squared.
   HAC: integer after call: [144]
   HAC: float before call: [11.0]
      Native: HAC has sent me the number: 1.10000000000000E+01; I will send it back squared.
   HAC: float after call: [121.0]

   HAC: integer before call: [13]
        message before call: [I'm a HAC record field]
           enum before call: [BAT]
      Native: Enum = BAT
      Native: Matrix m:
           1.10 1.20
           1.30 1.40
      Native: Matrix n:
          -2.10 2.20
          -2.30 2.40
      Native: Matrix o = m * n:
          -5.07 5.30
          -5.95 6.22
      Native: Enum = CAT
   HAC: integer after call: [169]
        message after call: [I'm a Native message now (niarg niarg niarg)!]
           enum after call: [CAT]
        matrix product:
          -5.07 5.30
          -5.95 6.22

Messages as "Native" are from the main application.

Messages as "HAC" are from the small HAC program that is run from the application.

A little disadvantage of having the same language on both sides... is that it is the same language! It could be confusing at first glance. Plus, it is not in the tradition. That may be disturbing for a number of people.

A big advantage of having the same language is that you can share some pieces of code. For instance the following package is used for building the demo application, and compiled by HAC when the application is running!

------------------------------------------- -- HAC <-> Native data exchange demo -- ------------------------------------------- -- Package used by both HAC and -- -- Native sides -- ------------------------------------------- package Exchange_Common is type Animal is (ant, bat, cat, dog); subtype Beast is Animal; subtype Insect is Animal range ant .. ant; subtype Mammal is Animal range bat .. dog; end Exchange_Common;


HAC (HAC Ada Compiler) is a quick, small, open-source Ada compiler, covering a subset of the Ada language. HAC is itself fully programmed in Ada.

Web site: http://hacadacompiler.sf.net/ 

Source repositories:

 #1 svn: https://sf.net/p/hacadacompiler/code/HEAD/tree/trunk/

 #2 git: https://github.com/zertovitch/hac

❌
❌