❌ About FreshRSS

Normal view

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

Advent of Code 2023, day 10

10 December 2023 at 17:12

The puzzle: https://adventofcode.com/2023/day/10

My Ada solution: here.

In a picture (generated by the program linked above)...

Click to enlarge

Β 

It is always a pleasure to display the data created by Eric Wastl.

Here, an improved representation with the five cases (outside tile, inside tile, inside pixel on a path tile, outside pixel on a path tile, path pixel) in shown different colors:

Click to enlarge


LEA, a tiny but smart editor

14 October 2023 at 13:39

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


LEA, the Lightweight Editor for Ada, is based on the Scintilla text editor widget, which excels at colouring syntactic elements and letting the user rework his or her programs very swiftly, including manipulating rectangular selections and adding text on multiple points (usually vertically aligned).
But until recently, the cool features were limited to automatic indentation, adding comment delimiters (--) at the right column, or highlighting portions of text that match a selection.
Users were frustrated by not finding in LEA programming helpers (commonly grouped under the term "smart editor" or "intellisense") that they enjoy in their programming "studios".
An excuse would have been to dismiss such features as out of the scope of a "lightweight" editor.
But we know we can make of LEA a powerful mini-studio, with the ease of use of a simple editor, thanks to its "projectless" mode and the integrated HAC compiler.
So now, it happened.
From revision 385 of LEA (and revision 886 of HAC), you have now a good sense of intellisense:

  • mouse-hover tips: when you let the mouse pointer on an identifier for a little while, a tip appears about that identifier (typically, where it was declared)
  • context menu with a go-to-declaration entry, when applicable
  • call tips: on typing '(' after a subprogram name, a tip appears with the subprogram's parameters
  • auto-complete: on typing characters that belong to identifiers, a list of possible declared identifier appears; that list depends on where in the source code the text cursor is: declarations below the cursor are invisible and local declarations, possibly within multiple nested subprograms.Β 

Some screenshots will help illustrate the above points. Let's start with the sudoku_sample.adb example shipped with the HAC project. What's that "Sudo_Strings" type, for instance?
Just let the mouse pointer hang out around an occurrence of that keyword.

Mouse hover tip - Click image to enlarge it

Want to know more about that declaration? Right-click on it...

Go to declaration - Click image to enlarge it

...and here you go.

Declaration in the Sudoku package - Click image to enlarge it

That's it, so far, for features enabling navigation within existing code.
For writing code, here are two other key helpers. First, the call tips.
If we type '(' after a subprogram's name, we see the parameters list appear:


Call tip - Click image to enlarge it

If we type the beginning of an identifier, a list of possible completions appears:

Auto-complete - Click image to enlarge it

A few remarks about the present state of LEA's "smart editor" features:
  • Currently, LEA only supports the HAC subset - it was the first priority. GNAT and other Ada compilers have their own editors and navigation features, so there is a lesser pressure to support them in LEA.
  • The "smart editor" features are quite new and still under test and development. If you want to have them, you need to build LEA on a fresh clone (instructions are given in lea.gpr), then set (via regedit) the registry key SMART_EDITOR, in the branch HKEY_CURRENT_USER\SOFTWARE\LEA\, to "true", before starting LEA.
  • You will notice a few missing things, like a list of fields on typing '.' after a record variable. They are not forgotten and just still "under construction".
  • The "smart editor" features work on incomplete Ada sources. They might be just less present after the point of an error, especially a syntax error.

In order to develop the last point, here are a few examples on how LEA understands your program "on the fly", while you are typing it:

Smart editor on an incomplete piece of code - Click image to enlarge it

Smart editor on an incomplete piece of code, sample 2 - Click image to enlarge it

Smart editor on an incomplete piece of code, sample 3 - Click image to enlarge it

One more thing: the Windows version of LEA is contained in a single executable holding in currently 4.2 MiB, including data such as code samples and templates, and runs as a portable application (no installation required).

Some Web links:

LEA

Web site: http://l-e-a.sf.net/
Sources, site #1: https://sf.net/p/l-e-a/code/HEAD/tree/
Sources, site #2: https://github.com/zertovitch/lea
Alire Crate: Alire - LEA

HAC

Web site: https://hacadacompiler.sourceforge.io/
Sources, site #1: https://sf.net/p/hacadacompiler/code/HEAD/tree/
Sources, site #2: https://github.com/zertovitch/hac
Alire Crate: Alire - HAC

Enjoy!

Unlocking the Power of OpenAI in Ada programs

1 October 2023 at 16:33
[Ada/openai-img.jpg](Ada/openai-img.jpg)

The OpenAI(https://openai.com/) provides a service that can be queried using REST requests. The service API can be classified as follows:

  • OpenAI's GPT (generative pre-trained transformer) is the well-known text generation based on text inputs.
 It gives access to several AI models that have been trained to understand natural language and code.
  • Image generation gives access to the DALL.E(https://platform.openai.com/docs/models/dall-e) for both
  •  generating images based on a text description, or, modify an existing image based on a text description.
    
  • Speech to text gives access to the Whisper model(https://openai.com/research/whisper) that converts
  •  audio records into text (several languages are supported).
    
  • OpenAIҀ™s text embeddings measure the relatedness of text strings.
  • For a detailed description of these API have a look at the OpenAI API Introduction(https://platform.openai.com/docs/introduction) document. To use these APIs, you'll first need to register on their service and get an API key that will grant access to the operations.

    The library was generated by the OpenAPI(https://github.com/OpenAPITools/openapi-generator) code generator from the OpenAPI description of the OpenAI service and it uses the OpenAPI Ada(https://github.com/stcarrez/swagger-ada) library to make HTTP requests and perform JSON serialization and de-serialization. Each OpenAI operation is made available through an Ada procedure which takes care of building the request that was filled within some Ada record, submitting it to the OpenAI server and extract the response in another Ada record. Every request submitted to the OpenAI server is therefore strongly typed!

      1. Setup

    To use the library, you should use Alire to setup your project and use:

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

    For the HTTP connection, you can either use AWS(https://github.com/AdaCore/aws) or curl(https://curl.se/) and run one of the following commands:

    ``` alr with utilada_curl alr with utilada_aws ```

      1. Initialization

    First, make sure you import at least the following Ada packages:

    ``` with Util.Http.Clients.Curl; -- replace Curl with AWS if needed with OpenAPI.Credentials.OAuth; with OpenAI.Clients; with OpenAI.Models; ```

    If you want to use curl(https://curl.se/), the initialization should use the following:

    ``` Util.Http.Clients.Curl.Register; ```

    But if you want to use AWS(https://github.com/AdaCore/aws), you will initialize with:

    ``` Util.Http.Clients.AWS.Register; ```

    After the initialization is done, you will declare the `OpenAI` client instance to access the API operations. The OpenAI(https://openai.com/) service uses an OAuth bearer API key to authenticate requests made on the server. We will need an `OAuth2_Credential_Type` instance represented by `Cred` below.

    ``` Cred : aliased OpenAPI.Credentials.OAuth.OAuth2_Credential_Type; Client : OpenAI.Clients.Client_Type; ```

      1. Credential setup

    For the credential setup you will first need to get your access key from your account. Once you have your key as a `String`, you can configure the `Cred` object and tell the client connection entry point which credentials to use:

    ``` Api_Key : constant String := ...;

      Cred.Bearer_Token (Api_Key);
      Client.Set_Credentials (Cred'Unchecked_Access);
    

    ```

      1. OpenAPI client setup

    The last step necessary before you can make requests, is to setup the server base URL to connect for the REST requests:

    ```

     Client.Set_Server ("https://api.openai.com/v1");
    

    ```

      1. API for chat

    The `Create_Chat` is the main operation for the conversation chat generation. The request is represented by the `ChatRequest_Type` type which describes the OpenAI chat model that must be used and the query parameters. The request can be filled with a complete conversation chat which means it is possible to call it several times with previous queries and responses to proceed in the chat conversation.

    ``` C : OpenAI.Clients.Client_Type; Req : OpenAI.Models.ChatRequest_Type; Reply : OpenAI.Models.ChatResponse_Type; ...

      Req.Model := OpenAPI.To_UString ("gpt-3.5-turbo");
      Req.Messages.Append ((Role => Openapi.To_Ustring ("user"),
                            Content => Prompt,
                            others => <>));
      Req.Temperature := 0.3;
      Req.Max_Tokens := (Is_Null => False, Value => 1000);
      Req.Top_P := 1.0;
      Req.Frequency_Penalty := 0.0;
      Req.Presence_Penalty := 0.0;
      C.Create_Chat (Req, Reply);
    

    ```

    Upon successful completion, we get a list of choices in the reply that contains the text of the conversation. You can iterate over the list with the following code extract. Beware that the returned string is using UTF-8 encoding and it may need a conversion to a `Wide_Wide_String` if necessary.

    ```

      for Choice of Reply.Choices loop
         declare
            Utf8 : constant String := OpenAPI.To_String (Choice.Message.Content);
         begin
            Put_Line (Ada.Strings.UTF_Encoding.Wide_Wide_Strings.Decode (Utf8));
         end;
      end loop;
    

    ```

    The complete chat example is available at: OpenAI Chat(https://gitlab.com/stcarrez/openai-chat) .

      1. Generating an image

    The image generation is based on the DALL.E(https://platform.openai.com/docs/models/dall-e) model generator. The creation is made by populating a request object, making the call (which is an HTTP POST) and getting the result in a response. Both request and response are represented by full Ada types and are strongly typed:

    The request contains a prompt string which must be provided and is the textual description of the image to create. An optional parameter allows to control the number of images which are created (from 1 to 10). Another optional parameter controls the dimension of the final image. The OpenAI API limits the possible values to: `256x256`, `512x512` and `1024x1024`. The image creation is represented by the `Create_Image` procedure and we can call it with our request instance:

    ``` Req : OpenAI.Models.CreateImagesRequest_Type; Reply : OpenAI.Models.ImagesResponse_Type; ...

      Req.Prompt := Prompt;
      Req.N := (Is_Null => False, Value => 3);
      Req.Size := (Is_Null => False, Value => "512x512");
      C.Create_Image (Req, Reply);
    

    ```

    Once it has finished, it produces a response which basically contains a list of URLs for each generated image.

    ```

      for Url of Reply.Data loop
         if not Url.Url.Is_Null then
            Ada.Text_IO.Put_Line (OpenAPI.To_String (Url.Url.Value));
         end if;
      end loop;
    

    ```

    The complete image generation example is available at: OpenAI Image Generation(https://gitlab.com/stcarrez/openai-image) .

    For more information about the image creation, have a look at the OpenAI Images API reference(https://platform.openai.com/docs/api-reference/images).

      1. Conclusion

    The Ada OpenAI(https://gitlab.com/stcarrez/ada-openai) library is eagerly awaiting your Ada programming creativity. You can try using the chat generation example to ask the AI to generate some Ada code, but you'll likely be disappointed by the poor quality of Ada programs that OpenAI(https://openai.com/) generates. However, you may still find some small benefits in using it across different domains. I encourage you to give it a try for your enjoyment and pleasure.

    LEA: first steps as a "smart editor"

    2 September 2023 at 19:53

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


    Spot the "tool tip" on the following screenshot...

    Click to enlarge

    Translation: cross-references and source code navigation in LEA are becoming a reality since this evening!

    Β 


    Some Web links:

    LEA

    Web site: http://l-e-a.sf.net/
    Sources, site #1: https://sf.net/p/l-e-a/code/HEAD/tree/
    Sources, site #2: https://github.com/zertovitch/lea
    Alire Crate: Alire - LEA


    Since LEA embeds the HAC compiler, here are a few links about HAC as well:

    HAC

    Web site: https://hacadacompiler.sourceforge.io/
    Sources, site #1: https://sf.net/p/hacadacompiler/code/HEAD/tree/
    Sources, site #2: https://github.com/zertovitch/hac
    Alire Crate: Alire - HAC

    Ada BFD 1.3.0

    20 August 2023 at 14:14
    [Ada/ada-bfd-1.3.jpg](Ada/ada-bfd-1.3.jpg)
      1. Integration with Alire

    For Linux users only, the Ada BFD(https://github.com/stcarrez/ada-bfd) has an associated Alire crate which allows you to use it easily. To get access to the Alire crate, you should add the AWA Alire index(https://github.com/stcarrez/awa-alire-index) in your Alire configuration as follows:

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

    Then, you can get access to the crate by using

    ``` alr with bfdada ```

    Let's see how to use this library...

      1. Declarations

    The Ada BFD(https://github.com/stcarrez/ada-bfd) library provides a set of Ada bindings that give access to the BFD library. A binary file such as an object file, an executable or an archive is represented by the `Bfd.Files.File_Type` limited type. The symbol table is represented by the `Bfd.Symbols.Symbol_Table` limited type. These two types hold internal data used and managed by the BFD library.

    ```ada with Bfd.Files; with Bfd.Sections; with Bfd.Symbols; ...

     File    : Bfd.Files.File_Type;
     Symbols : Bfd.Symbols.Symbol_Table;
    

    ```

      1. Opening the BFD file

    The first step is to use the `Open` procedure to read the object or executable file whose path is given as argument. The `File_Type` parameter will be initialized to get access to the binary file information. The `Check_Format` function must then be called to let the BFD library gather the file format information and verify that it is an object file or an executable.

    ```ada Bfd.Files.Open (File, Path, ""); if Bfd.Files.Check_Format (File, Bfd.Files.OBJECT) then

       ...
    

    end if; ```

    The `File_Type` uses finalization so that it will close and reclaim resources automatically.

      1. Loading the symbol table

    The symbol table is loaded by using the `Read_Symbols` procedure.

    ```ada

      Bfd.Symbols.Read_Symbols (File, Symbols);
    

    ```

    The resources used by the symbol table will be freed when the symbol table instance is finalized.

      1. Find nearest line

    Once the symbol table is loaded, we can use the `Find_Nearest_Line` function to find the nearest line of a function knowing some address. This is almost a part of that function that the addr2line (1)(https://www.man7.org/linux/man-pages/man1/addr2line.1.html) command is using.

    ```ada File_Name, Func_Name : Ada.Strings.Unbounded.Unbounded_String; Text_Section : Bfd.Sections.Section; Line : Natural; Pc : constant Bfd.Vma_Type := ...; ...

      Text_Section := Bfd.Sections.Find_Section (File, ".text");
      Bfd.Symbols.Find_Nearest_Line (File    => File,
                                     Sec     => Text_Section,
                                     Symbols => Symbols,
                                     Addr    => Pc,
                                     Name    => File_Name,
                                     Func    => Func_Name,
                                     Line    => Line);
    

    ```

    One tricky aspect of using `Find_Nearest_Line` is the fact that the address we are giving must **sometimes** be converted to an offset within the text region. With Address space layout randomization (ASLR)(https://en.wikipedia.org/wiki/Address_space_layout_randomization) a program is mapped at a random address when it executes. Before calling `Find_Nearest_Line`, we must subtract the base address of the memory region. We must now find the virtual address of the start of the text region that is mapped in memory. While the program is running, you can find the base address of the program by looking at the `/proc/self/maps` file. This special file indicates the list of memory regions used by the process with the addresses, flags and other information. Without ASLR, the program is almost always loaded at the `0x00400000` address.

    ``` 00400000-007f9000 r-xp 00000000 fd:01 12067645 /home/... 009f8000-009fa000 r--p 003f8000 fd:01 12067645 /home/... 009fa000-00a01000 rw-p 003fa000 fd:01 12067645 /home/... ```

    But when it is mapped at a random address, we get a different address each time the program is launched:

    ``` 55d5983d9000-55d598592000 r--p 00000000 fc:02 1573554 /... 55d598592000-55d599376000 r-xp 001b9000 fc:02 1573554 /... 55d599376000-55d5997ed000 r--p 00f9d000 fc:02 1573554 /... 55d5997ee000-55d5998bb000 r--p 01414000 fc:02 1573554 /... 55d5998bb000-55d5998c6000 rw-p 014e1000 fc:02 1573554 /... ```

    In that case, the value to use it the first address of first `r--p` region associated with the program (here `0x55d5983d9000`).

    Another method to know the virtual base address is to use the dl_iterate_phdr (3)(https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html) function and look at the shared objects which are loaded. This function must be executed by the program itself: it gets as parameter a callback function which is called for each loaded shared object and a data parameter that will be passed to the callback.

    ```

    1. include <dlfcn.h>

    static int dl_callback (struct dl_phdr_info* info, size_t size, void* data) {

     /* VM base address is: info->dlpi_addr */
     return 0;
    

    } ...

      dl_iterate_phdr (dl_callback, 0);
    

    ```

    When the callback is called, you can get the name of the shared object by looking at `info->dlpi_name` and the virtual base address by looking at `info->dlpi_addr`.

    Ada BFD(https://github.com/stcarrez/ada-bfd) is a very specific library that is not always easy to use due to the complexity of binary program representation (ELF, DWARF, ...) and program execution. It is however used in very specific contexts such as the Muen Separation Kernel(https://muen.codelabs.ch/) and the Memory Analysis Tool(https://github.com/stcarrez/mat).

    HAC for native targets

    20 August 2023 at 13:57
    Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.

    HAC (the HAC Ada Compiler) was since the first of its previous lives, from Pascal-S, in 1973, to recent commits, translating high-level language exclusively to the machine code of a fictitious machine (a Virtual Machine, abbreviated VM).

    For writing a tiny compiler, a VM is very convenient:

    • You (the compiler creator) can model and adapt it to your needs.
    • You don't depend on hardware.
    • You don't need to rewrite the code generation for each new hardware.
    • You can make the VM running on many hardwares (hence the success of the JVM and .NET around year 2000).


    The HAC VM and its users are enjoying all these advantages.

    However, there is a flip side:

    • A VM is much slower than a real machine (but till year 2000, it didn't matter; you could say: don't worry, just wait a few months for a more powerful computer).
    • Since year 2000, the speed of microprocessors stopped doubling every six months (hence the declining interest in Virtual Machines). Despite the improvements in cache, RAM, and the multiplication of cores, the per-core performance improvement is slower and slower.
    • Supporting only a VM gives the impression that your compiler can only compile to a VM.


    Well, so far, the latter blame was objectively correct regarding HAC until a few weeks ago.
    Now it is changing!
    We have begun an abstract framework for emitting machine code.
    With that framework, HAC can, on demand, emit code for its captive VM or for any implemented machine.
    So you read well, it is not a just-in-time compiler translating VM instructions to native ones.
    We are implementing a direct Ada-to-native compiler - and cross-compiler, since HAC doesn't know on which machine it is running!

    The framework looks like this:

    with HAC_Sys.Defs;

    package HAC_Sys.Targets is

    Β  type Machine is limited interface;

    Β  type Abstract_Machine_Reference is access Machine'Class;

    Β  --------------------
    Β  --Β  InformationsΒ  --
    Β  --------------------

    Β  function Name (m : Machine) return String is abstract;
    Β  function CPU (m : Machine) return String is abstract;
    Β  function OS (m : Machine) return String is abstract;
    Β  function Null_Terminated_String_Literals (m : Machine) return Boolean is abstract;

    Β  ----------------------------
    Β  --Β  Machine InstructionsΒ  --
    Β  ----------------------------

    Β  procedure Emit_Arithmetic_Binary_Instruction
    Β Β Β  (mΒ Β Β Β Β Β Β Β  : in out Machine;
    Β Β Β Β  operatorΒ  :Β Β Β Β Β Β Β  Defs.Arithmetic_Binary_Operator;
    Β Β Β Β  base_typΒ  :Β Β Β Β Β Β Β  Defs.Numeric_Typ) is abstract;

    ...


    The code emission in the compiler is being changed (very slowly, be patient!) for going through this new abstract machine mechanism.
    So far we have two implementations:

    • The HAC VM - that way, we ensure HAC-for-HAC-VM works exactly as previously and can pass the test suite.
    • A real target: the AMD64, running under Windows; the machine code is emitted in Assembler form, for the Flat Assembler (FASM).


    The development for multiple targets is embryonic so far.
    However, we can already compile a "hello world"-style program:

    with HAT;

    procedure Native is
    Β  use HAT;
    Β  a : Integer;
    begin
    Β  --Β  a := 1;Β  --Β  Variables: TBD.
    Β  Put_Line ("Hello ...");
    Β  Put_Line ("... world!");
    Β  Put_Line (12340 + 5);
    Β  Put_Line (12350 - 5);
    Β  Put_Line (2469 * 5);
    Β  Put_Line (61725 / 5);
    end Native;


    With the command:
    hac -tamd64_windows_console_fasm native.adb

    HAC produces this:

    ;Β  Assembler file for the Flat Assembler - https://flatassembler.net/

    format PE64 console
    entry _start
    include 'include\win64a.inc'

    section '.code' code readable executable

    _start:
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  9
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  1
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  -1
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  -1
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r14
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r13
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r12
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r11
    Β Β Β Β Β Β Β Β  addΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r12, _hac_strings_pool
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], r12
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], _hac_end_of_line
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  10
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  11
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  -1
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  -1
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r14
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r13
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r12
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r11
    Β Β Β Β Β Β Β Β  addΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r12, _hac_strings_pool
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], r12
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], _hac_end_of_line
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  12340
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  5
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r11
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rax
    Β Β Β Β Β Β Β Β  addΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rax, r11
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rax
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  20
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  10
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  -1
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r14
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r13
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r12
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r11
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], _hac_decimal_format, r11
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], _hac_end_of_line
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  12350
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  5
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r11
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rax
    Β Β Β Β Β Β Β Β  subΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rax, r11
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rax
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  20
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  10
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  -1
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r14
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r13
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r12
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r11
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], _hac_decimal_format, r11
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], _hac_end_of_line
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  2469
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  5
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r11
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rax
    Β Β Β Β Β Β Β Β  imulΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rax, r11
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rax
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  20
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  10
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  -1
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r14
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r13
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r12
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r11
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], _hac_decimal_format, r11
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], _hac_end_of_line
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  61725
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  5
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r11
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rax
    Β Β Β Β Β Β Β Β  xorΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rdx, rdx
    Β Β Β Β Β Β Β Β  idivΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r11
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  rax
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  20
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  10
    Β Β Β Β Β Β Β Β  pushΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β  -1
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r14
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r13
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r12
    Β Β Β Β Β Β Β Β  popΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  r11
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], _hac_decimal_format, r11
    Β Β Β Β Β Β Β Β  ccallΒ Β Β Β Β Β Β Β Β Β Β Β Β Β  [printf], _hac_end_of_line
    Β Β Β Β Β Β Β Β  stdcallΒ Β Β Β Β Β Β Β Β Β Β Β  [ExitProcess],0

    section '.data' data readable writeable
    _hac_end_of_lineΒ  db 10, 0
    _hac_decimal_formatΒ  db "%d", 0
    _hac_strings_pool db "XHello ...", \
    Β Β Β  0, "... world!", 0

    section '.idata' import data readable
    library kernel,'kernel32.dll',\
    Β Β Β Β Β Β Β  msvcrt,'msvcrt.dll'
    importΒ  kernel,\
    Β Β Β Β Β Β Β  ExitProcess,'ExitProcess'
    importΒ  msvcrt,\
    Β Β Β Β Β Β Β  printf,'printf'

    As you can see, the assembler code needs badly some simplification, but, anyway: it works.
    FASM produces from it a relatively small 2048-byte executable which writes

    Hello ...
    ... world!
    12345
    12345
    12345
    12345


    The executable is full of zeroes, due to alignments. The non-zero bytes (more or less, the actual machine code and data) take 605 bytes.

    Some Web links for HAC:

    Main URL: https://hacadacompiler.sourceforge.io/
    Sources, site #1: HAC Ada Compiler download | SourceForge.net
    Sources, site #2: GitHub - zertovitch/hac: HAC Ada Compiler - a small, quick Ada compiler fully in Ada
    Alire Crate: Alire - Hac

    HAC version 0.26

    8 July 2023 at 06:40
    Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.

    Main URL: https://hacadacompiler.sourceforge.io/
    Sources, site #1: HAC Ada Compiler download | SourceForge.net
    Sources, site #2: GitHub - zertovitch/hac: HAC Ada Compiler - a small, quick Ada compiler fully in Ada
    Alire Crate: Alire - Hac

    What’s new:

    • You can use a loop’s name for the exit statement: exit Loop_Name.
    • You can exit multiple, nested, loops, by using exit Loop_Name.
    • Ada semantics are better verified:
      • Array indexing (i)(j) (array of array) and (i, j) (multi-dimensional array) are no more treated as equivalent (this feature was a remnant of the Pascal syntax).
      • Separation between Type and Subtype declarations (anonymous types are allowed only in the few cases foreseen by the language).
      • Operators of the HAT package (+, -, & for strings) are visible without prefix only in the scope of a use HAT clause.

    Note that correct Ada programs, in relation to the above points, were already accepted and parsed correctly by HAC before that change.

    Finally, a bit of cosmetics in the build messages:

    C:\Ada\hac\exm>..\hac -v2 -c pkg_demo.adb

    *******[ HAC ]*******Β Β  HAC is free and open-source. Type "hac" for license.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  HAC Ada Compiler version 0.26, 08-Jul-2023
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  Compiling main: pkg_demo.adb
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  | Compiling x_pkg_demo_s.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s1.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s11.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s11.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s12.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s12.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s13.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s13.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s1.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s2.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s21.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s21.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s22.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s22.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s23.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s23.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s2.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s3.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s31.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s31.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s32.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s32.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  \ Compiling x_pkg_demo_s33.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s33.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β Β  /Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s3.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  |Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  | Compiling x_pkg_demo_m.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  |Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_m.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  | Compiling x_pkg_demo_b.ads (specification)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  |Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_b.ads: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  Compilation of pkg_demo.adb (main) completed
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  ------Β  Compilation of eventual with'ed unit's bodies
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  | Compiling x_pkg_demo_s.adb (body)
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  |Β Β Β Β Β Β Β Β Β Β  x_pkg_demo_s.adb: done.
    Β Β Β Β Β Β  [ HAC ]Β Β Β Β Β Β Β Β Β  | Compiling x_pkg_demo_s1.adb (body)



    Recursion – implementing sum

    By: spqr
    23 May 2023 at 03:06

    There are lots of easy to understand recursive algorithms. One of the easiest is is a function sum(x) which sums up all the integers from 1 to x. Here is the function implemented in Ada.

    function sum(n: integer) return integer is
    begin
       if n = 1 then 
          return 1;
       else 
          return (n + sum(n-1));
       end if;
    end sum;
    

    Here the function sum(x) recurses until the value of x becomes 0. At this point the recursion is essentially done, and the calls to sum() backtrack. This is shown in the summary below for sum(5).

    sum(5) = (5 + sum(4))                          recursive call sum(4)
           = (5 + (4 + sum(3)))                    recursive call sum(3)
           = (5 + (4 + (3 + sum(2))))              recursive call sum(2)
           = (5 + (4 + (3 + (2 + sum(1)))))        recursive call sum(1), return 1
           = (5 + (4 + (3 + (2 + 1))))             return (2 + 1)
           = (5 + (4 + (3 + 3)))                   return (3 + 3)
           = (5 + (4 + 6))                         return (4 + 6)
           = (5 + 10)                              return (5 + 10)
           = 15

    There are four recursive calls to sum() in addition to the original call, which is not considered recursive, because the function may actually terminate, e.g. if sum(1) is invoked. So if the function were to call sum(10000), there would be 9,999 recursive calls. The problem with recursion of course is that many of these simplistic algorithms are just as easy to implement as iterative algorithms.

    Here is the same algorithm represented iteratively:

    function sumi(n: integer) return integer is
       s : integer;
    begin
       s := 0;
       for i in 1..n loop
          s := s + i;
       end loop;
       return s;
    end sumi;
    

    Reentrant scanner and parser with Aflex and Ayacc

    14 May 2023 at 19:02
    [Aflex and Ayacc](Ada/Aflex-Ayacc-code.jpg)
      1. What's new in Aflex 1.6

    - Support the flex options `%option output`, `%option nooutput`, `%option yywrap`, `%option noinput`,

     `%option noyywrap`, `%option unput`, `%option nounput`, `%option bufsize=NNN` to better control the
     generated `_IO` package.
    
    - Aflex(https://github.com/Ada-France/aflex) templates provide more control for tuning the code generation and
     they are embedded with [Advanced Resource Embedder](https://gitlab.com/stcarrez/resource-embedder)
    
    - Support to define Ada code block in the scanner that is inserted in the generated scanner - New option -P to generate a private Ada package for DFA and IO - New directive `%option reentrant` and `%yyvar` to generate a recursive scanner - New directive `%yydecl` to allow passing parameters to `YYLex`
     or change the default function name
    

    Example of `%option` directives to tell Aflex(https://github.com/Ada-France/aflex) to avoid generating several function or procedures and customize the buffer size.

    ```Ada %option nounput %option noinput %option nooutput %option noyywrap %option bufsize=1024 ```

    The tool supports some code block injection at various places in the generated scanner. The code block has the following syntax where `<block-name>` is the name of the code block:

    ```Ada %<block-name> {

     -- Put Ada code here
    

    } ```

    The `%yytype` code block can contain type declaration, function and procedure declarations. It is injected within the `YYLex` function in the declaration part. The `%yyinit` code block can contain statements that are executed at beginning of the `YYLex` function. The `%yyaction` code block can contain statements that are executed before running any action. The `%yywrap` code block can contain statements which are executed when the end of current file is reached to start parsing a next input.

      1. What's new in Ayacc 1.4

    - Support the Bison `%define variable value` option to configure the parser generator - Support the Bison `%code name { ... }` directive to insert code verbatim into the output parser - Recognize some Bison variables `api.pure`, `api.private`, `parse.error`, `parse.stacksize`,

     `parse.name`, `parse.params`, `parse.yyclearin`, `parse.yyerrok`, `parse.error`
    
    - New option `-S skeleton` to allow using an external skeleton file for the parser generator - Ayacc(https://github.com/Ada-France/ayacc) templates provide more control for tuning the code generation and
     they are embedded with [Advanced Resource Embedder](https://gitlab.com/stcarrez/resource-embedder)
    
    - New option `-P` to generate a private Ada package for the tokens package - Improvement to allow passing parameters to `YYParse` for the grammar rules - New `%lex` directive to control the call of `YYLex` function - Fix #6: ayacc gets stuck creating an infinitely large file after encountering a comment in an action

    The generator supports two code block injections, the first one `decl` is injected in the `YYParse` procedure declaration and the `init` is injected as first statements to be executed only once when the procedure is called. The syntax is borrowed from the Bison parser:

    ```Ada %code decl {

      -- Put Ada declarations
    

    } %code init {

      -- Put Ada statements
    

    } ```

    Some other Bison like improvements have been introduced to control the generation of the parser code.

    ``` %define parse.error true %define parse.stacksize 256 %define parse.yyclearin false %define parse.yyerrok false %define parse.name MyParser ```

      1. How to use

    The easiest way to use Ayacc(https://github.com/Ada-France/ayacc) and Aflex(https://github.com/Ada-France/aflex) is to use Alire(https://github.com/alire-project/alire), get the sources, build them and install them. You can do this as follows:

    ``` alr get aflex cd aflex_1.6.0_b3c21d99 alr build alr install alr get ayacc cd ayacc_1.4.0_c06f997f alr build alr install ```

    • UPDATE*: the `alr install` command is available only with Alire(https://github.com/alire-project/alire) 2.0.

    Using these tools is done in two steps:

    1. a first step to call `aflex` or `ayacc` command with the scanner file or grammar file, 2. a second step to call `gnatchop` to split the generated file in separate Ada files

    For example, with a `calc_lex.l` scanner file, you would use:

    ``` aflex calc_lex.l gnatchop -w calc_lex.ada ```

    And with a `calc.y` grammar file:

    ``` ayacc calc.y gnatchop -w calc.ada ```

    To know more about how to write a scanner file or grammar file, have a look at Aflex 1.5 and Ayacc 1.3.0(https://blog.vacs.fr/vacs/blogs/post.html?post=2021/12/18/Aflex-1.5-and-Ayacc-1.3.0) which explains more into details some of these aspects.

      1. Highlight on reentrancy

    By default Aflex(https://github.com/Ada-France/aflex) and Ayacc(https://github.com/Ada-France/ayacc) generate a scanner and a parser which use global variables declared in a generated Ada package. These global variables contain some state about the scanner such as the current file being scanned. The Ayacc(https://github.com/Ada-France/ayacc) parser generates on its side two global variables `YYLVal` and `YYVal`.

    Using global variables creates some strong restrictions when using the generated scanner and parser: we can scan and parse only one file at a time. It cannot be used in a multi-thread environment unless the scan and parse is protected from concurrent access. We cannot use easily some grammars that need to recurse and parse another file such as an included file.

        1. Reentrant scanner

    The reentrant scanner is created by using the `-R` option or the `%option reentrant` directive. The scanner will then need a specific declaration with a context parameter that will hold the scanner state and variables. The context parameter has its type generated in the `Lexer_IO` package. The `%yydecl` directive in the scanner file must be used to declare the `YYLex` function with its parameters. By default the name of the context variable is `Context` but you can decide to customize and change it to another name by using the `%yyvar` directive.

    ``` %option reentrant %yyvar Context %yydecl function YYLex (Context : in out Lexer_IO.Context_Type) return Token ```

    When the `reentrant` option is activated, Aflex(https://github.com/Ada-France/aflex) will generate a first `Context_Type` limited type in the `Lexer_DFA` package and another one in the `Lexer_IO` package. The generator can probably be improved in the future to provide a single package with a single type declaration. The `Lexer_DFA` package contains the internal data structures for the scanner to maintain its state and the `Lexer_IO` package holds the input file as well as the `YYLVal` and `YYVal` values.

        1. Reentrant parser

    On its side, Ayacc(https://github.com/Ada-France/ayacc) uses the `YYLVal` and `YYVal` variables. By default, it generates them in the `_tokens` package that contains the list of parser symbols. It must not generate them and it must now use the scanner `Context_Type` to hold them as well as the scanner internal state. The setup requires several steps:

    1. The reentrant parser is activated by using the `%define api.pure`

      directive similar to the [bison %define](https://www.gnu.org/software/bison/manual/html_node/_0025define-Summary.html).
    

    2. The `%lex` directive must be used to define how the `YYLex` function must be called since it now has some

      context parameter.
    

    3. The scanner context variable must be declared somewhere, either as parameter to the `YYParse`

      procedure or as a local variable to `YYParse`.  This is done using the new `%code decl` directive
      and allows to customize the local declaration part of the `YYParse` generated procedure.
    

    4. We must give visibility of the `YYLVal` and `YYVal` values defined in the scanner context variable.

      Again, we can do this within the `%code decl` directive.
    

    A simple reentrant parser could be defined by using:

    ```Ada %define api.pure true %lex YYLex (Scanner) %code decl {

         Scanner : Lexer_IO.Context_Type;
         YYLVal  : YYSType renames Scanner.YYLVal;
         YYVal   : YYSType renames Scanner.YYVal;
    

    } ```

    However, this simple form is not really useful as you may need to open the file and setup the scanner to read from it. It is probably better to pass the scanner context as parameter to the `YYParse` procedure. For this, we can use the `%define parse.params` directive to control the procedure parameters. The reentrant parser is declared as follows:

    ```Ada %lex YYLex (Scanner) %define api.pure true %define parse.params "Scanner : in out Lexer_IO.Context_Type" %code decl {

         YYLVal : YYSType renames Scanner.YYLVal;
         YYVal  : YYSType renames Scanner.YYVal;
    

    } ```

    To use the reentrant parser and scanner, we only need to declare the scanner context, open the file by using the `Lexer_IO.Open_Input` procedure and call the `YYParse` procedure as follows:

    ```Ada

     Scanner : Lexer_IO.Context_Type;
     ...
       Lexer_IO.Open_Input (Scanner, "file-to-scan");
       YYParse (Scanner);
    

    ```

        1. Grammar examples:

    To have a more complete example of a reentrant parser, you may have a look at the following files:

    New colour theme with LEA: Solarized Light

    7 May 2023 at 21:19
    Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.

    Today, a topic that is guaranteed frivolous: a new colour theme for LEA (a Lightweight Editor for Ada).

    Actually, it is a serious subject if you spend a large share of your time at programming (for work, for fun, or both...). For the theoretically brighter season (spring and summer), some prefer to give up the trendy Dark Side mode (which not so long ago was considered as horribly old-fashioned):

    ...and use a dark-font-bright-background scheme.

    However, the contrast is a bit violent. A convincing and carefully designed low-contrast theme is called Solarized Light (which can be turned, with an astute swap within the same 8-colours palette for base colours, into a dark-backgrounded Solarized Dark). The theme is described here.

    The Solarized Light theme is incorporated since today in LEA.

    Enjoy!


    LEA, a Lightweight Editor for Ada, aims to provide an easy, script-world-like, "look & feel" for developing Ada projects of any size and level, while enabling access to full-scale development tools like GNAT.

    • Quick start and reactivity
    • Uses the Scintilla editor widget (like Notepad++)
    • Multi-document
    • Multiple undo's & redo's
    • Multi-line edit, rectangular selections
    • Color themes, easy to switch
    • Duplication of lines and selections
    • Syntax highlighting
    • Parenthesis matching
    • Bookmarks
    • Free, Open-Source
    • Programmed in Ada
    • Includes HAC, the HAC Ada Compiler
    • Includes numerous examples of Ada programs, ready to be run
    • Single executable, runs without installation




    Libadalang, Alire, and macOS

    Background

    This exercise was prompted by the need for Scripted Testing to be supported by – as far as possible – code generation. The need is for the public or interfacing view of a supporting part (domain) of a system to be able to log calls made to it and to provide values on calls to it, using a scripting mechanism.

    Read more Β»

    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.

    ❌
    ❌