❌ About FreshRSS

Normal view

There are new articles available, click to refresh the page.
Before yesterdayNews from the Ada programming language world

Coding Ada: Using an exception

By: spqr
27 March 2018 at 14:01

Some people wonder where you can use a simple exception in Ada. Functions are an ideal place. Below is a program which contains a function isNumber() which returns true if the string passed to it is considered a number (integer). So 1984 us a number, but x24 is not. The program takes a word as input from the user (as an unbounded_string), and passes it to isNumber(). The function isNumber() converts the unbounded_string to a string, and then to an integer using integer’value(). If the conversion works, then the function returns true. If however, the conversion fails, then a Constraint_Error exception will be triggered, and false will be returned.

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 number is

   aWord : unbounded_string;
   num: integer;
 
   function isNumber(s: unbounded_string) return boolean is 
      temp: integer;
   begin
      temp := integer'value(to_String(s));
      return true;
      exception 
         when Constraint_Error => return false;
   end isNumber;

begin
   put_line("Enter a word: ");
   get_line(aWord);
 
   if (isNumber(aWord)) then
      num := integer'value(to_String(aWord));
      put(num);
   else
      put_line("Not a number");
   end if;
end number;

This sort of code could be used to count numbers in a document or something similar. You can use float’value to do the same for floating-point numbers.

spqr

Coding Ada: reading lines from files.

By: spqr
4 April 2018 at 22:03

How to read lines from a text file in Ada?

First create some variables for the filename, string to hold the filename, a string to hold the line of text, and a boolean value to hold the file status (nameOK):

infp : file_type;
sline : unbounded_string;
fname : unbounded_string;
nameOK : boolean := false;

Then read in the filename, continuously if it does not exist, or there are other issues with it. When this is okay, open the file for reading, and loop through the file, using the function get_line() to read a line of text, and store it in the unbounded_string sline. This can then be further processed, before a new line of text read.

put_line ("Enter the filename: ");
loop
   exit when nameOK;
   get_line(fname);
   nameOK := exists(to_string(fname));
end loop;

open(infp, in_file, to_string(fname));

loop
   exit when end_of_file(infp);
   -- Process each line from the file
   get_line(infp,sline);
   -- do something with the line of text
end loop;


 

 

 

spqr

💾

Coding Ada: Timing code and passing functions to functions

By: spqr
19 March 2019 at 14:03

If you have to time a function in a language like Ada, one option is to create a timer function that takes the function to be timed as a parameter. Here is an Ada program that times the Ackermann function. There is nothing special about the ackermann() function, except that it is recursive. The interesting part of the code involves creating a type, functype, to hold the function “pointer”, or access point. The type uses the phrase “access function” followed by the parameter list for the function in question, and the type of the return value. This denotes an Access type, which is the equivalent of a pointer in other languages. Rather than using the term “points to”, Ada prefers to refer to this type of entity as “granting access” to an object. An access to subprogram allows the caller to call a subprogram without knowing its name nor its declaration location.

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar; use Ada.Calendar;

procedure passingfunc is

   type functype is access function (x,y: integer) return integer;

   function ackermann(m,n: in integer) return integer is
   begin
      if m = 0 then
         return n+1;
      elsif n = 0 then
         return ackermann(m-1,1);
      else
         return ackermann(m-1,ackermann(m,n-1));
      end if;
   end ackermann;

   procedure timeit(Func : functype; m,n : in integer) is 
      result : integer;
      startTime, endTime : time;
      milliS : Duration;
   begin
      startTime := Clock;
      result := Func(m,n);
      endTime := Clock;
      milliS := (endTime - startTime) * 1000;
      put_line("Result: " & Integer'Image(result));
      put_line("Runtime = " & Duration'Image(milliS) & " milliseconds.");
   end timeit;

begin
   timeit(ackermann'Access, 4, 1);
end passingfunc;

The function timeit() can then be implemented. It declares Func as one of the parameters, implying that a function (pointer) can be passed to it. The remaining two parameters are the two parameters to be passed onto ackermann(). Note that the call to Func() is quite normal as well – no magic needed. The guts of the timing algorithm are described in another post, so the interested reader is referred there. When the function timeit() is called in the main program, the ackermann() function is passed using ackermann’Access.

Here’s the program running… with some trivial output:

Result: 65533
Runtime = 17441.122000000 milliseconds.

Those of you who have ever implemented a non-recursive version of Ackermann will note the runtime of 17-odd seconds, which is much faster than the 60-80 seconds of the version using a stack.

spqr

Coding Ada: Bitwise operators

By: spqr
7 April 2020 at 02:14

In Ada, you can find similar bitwise operators to other languages: and, or, xor, and operators that shift left and right. The trick is that they use modular types. These types are unsigned and have “wrap-around” semantics. Consider the following declaration:

subtype byte is unsigned_8;
package byteIO is new ada.text_io.modular_io(byte);
x, y, z: byte;

This defines an integer type whose values range from 0 to 255 (on most processors), i.e. the integers that will fit in a byte. Since it’s “modular”, it means that arithmetic operations on the type wrap around (i.e. if you add 130 + 130, you will get 4. With this declaration, you can create binary numbers:

x := 2#00011110#;
y := 2#11110100#;
z := 2#11110000#;

Here x=30, y=244, and z=240. So we can then use bitwise operators (found in the Ada package Interfaces) to swap the numbers:

x := x xor y;
y := y xor x;
x := x xor y;

The numbers can be printed out with this code:

put_line(unsigned_8'image(x));
put_line(unsigned_8'image(y));

You can also use put from the sub-package byteIO:

byteIO.put(item => z, base => 2);

If we want to convert a number input from integer to type byte, we can do so in the following manner:

w: integer;
get(w);
z := byte'val(integer'pos(w));
byteIO.put(item => z, base => 2);

The byte’val returns the base type of byte. For example, if the value input by the user is 17 (w), then the value assigned to z will be 10001. What about shift operators? There is shift_left() and shift_right(). Here’s an example:

zs: byte; 
zs := shift_left(z,1);
byteIO.put(item => zs, base => 2);

If the user inputs 12, then the value of zs will be 24, as it has been shifted left 1 bit.

spqr

Coding Ada: strings (iii) – arrays of strings

By: spqr
13 January 2021 at 20:44

The problem with strings in Ada is that they just don’t play nicely. As we have seen in previous posts, strings in Ada are either fixed, or unbounded. Now creating an array of strings in other languages isn’t that hard, nor is it in Ada – some things are just beyond its scope. Now by an array of strings, I mean a structure that could hold something like a dictionary of words. So how do we go about this?

The first example below creates a type, lexicon, with is a 5 element array of strings, 3 characters in length (so 5 three letter words). The variable words is then an instantiation of lexicon, which is assigned five three letter words. It’s not really possible to use the term string without the constraints (1..3). This is because of the fact that strings in Ada are fixed length – and it means all the strings assigned to words will have to be of length 3.

type lexicon is array(1..5) of string(1..3);
words : lexicon := ("gum","sin","cry","lug","fly");

It is also not possible to make the constraint larger, e.g. (1..10), and use words or variable length, e.g. “gum”, “housefly”, “star”. This will cause a constraint error (the word star would have to be specified as “star “, with 6 spaces filling out the word). The alternative is to unbounded strings, but they come with their own baggage. Consider the following code, changing the string to an unbounded_string.

type lexicon is array(1..5) of unbounded_string;
words : lexicon := ("gum","sin","cry","lug","star");

Compile this, and it will produce a series of errors of the form:

file.adb:10:24: expected private type "Ada.Strings.Unbounded.Unbounded_String"
file.adb:10:24: found a string type

This means that when it tried to assign the words, it considered “gum” to be a string, NOT an unbounded_string. The declaration of lexicon works fine if reading directly from a file, but not predefined strings. The fix? Not pretty, because the strings have to be converted to unbound strings using the function to_unbounded_string().

words : lexicon :=(to_unbounded_string("gum"),to_unbounded_string("sin"),to_unbounded_string("cry"),to_unbounded_string("lug"),to_unbounded_string("star"));

Messy right? There is one way of cleaning up this code and that is providing an alias for the function name to_unbounded_string(). Here is how that is done, renaming the function to tub().

function tub(Source : String) return unbounded_string renames ada.strings.unbounded.to_unbounded_string;

Then the code below it becomes:

type lexicon is array(1..5) of unbounded_string;
words : lexicon := (tub("gum"),tub("sin"),tub("cry"),tub("lug"),tub("star"));

Not perfect, but then that is Ada and strings for you.

spqr

Coding Ada: Timing code and passing functions to functions

By: spqr
19 March 2019 at 14:03

If you have to time a function in a language like Ada, one option is to create a timer function that takes the function to be timed as a parameter. Here is an Ada program that times the Ackermann function. There is nothing special about the ackermann() function, except that it is recursive. The interesting part of the code involves creating a type, functype, to hold the function “pointer”, or access point. The type uses the phrase “access function” followed by the parameter list for the function in question, and the type of the return value. This denotes an Access type, which is the equivalent of a pointer in other languages. Rather than using the term “points to”, Ada prefers to refer to this type of entity as “granting access” to an object. An access to subprogram allows the caller to call a subprogram without knowing its name nor its declaration location.

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar; use Ada.Calendar;

procedure passingfunc is

   type functype is access function (x,y: integer) return integer;

   function ackermann(m,n: in integer) return integer is
   begin
      if m = 0 then
         return n+1;
      elsif n = 0 then
         return ackermann(m-1,1);
      else
         return ackermann(m-1,ackermann(m,n-1));
      end if;
   end ackermann;

   procedure timeit(Func : functype; m,n : in integer) is 
      result : integer;
      startTime, endTime : time;
      milliS : Duration;
   begin
      startTime := Clock;
      result := Func(m,n);
      endTime := Clock;
      milliS := (endTime - startTime) * 1000;
      put_line("Result: " & Integer'Image(result));
      put_line("Runtime = " & Duration'Image(milliS) & " milliseconds.");
   end timeit;

begin
   timeit(ackermann'Access, 4, 1);
end passingfunc;

The function timeit() can then be implemented. It declares Func as one of the parameters, implying that a function (pointer) can be passed to it. The remaining two parameters are the two parameters to be passed onto ackermann(). Note that the call to Func() is quite normal as well – no magic needed. The guts of the timing algorithm are described in another post, so the interested reader is referred there. When the function timeit() is called in the main program, the ackermann() function is passed using ackermann’Access.

Here’s the program running… with some trivial output:

Result: 65533
Runtime = 17441.122000000 milliseconds.

Those of you who have ever implemented a non-recursive version of Ackermann will note the runtime of 17-odd seconds, which is much faster than the 60-80 seconds of the version using a stack.

spqr

Coding Ada: Bitwise operators

By: spqr
7 April 2020 at 02:14

In Ada, you can find similar bitwise operators to other languages: and, or, xor, and operators that shift left and right. The trick is that they use modular types. These types are unsigned and have “wrap-around” semantics. Consider the following declaration:

subtype byte is unsigned_8;
package byteIO is new ada.text_io.modular_io(byte);
x, y, z: byte;

This defines an integer type whose values range from 0 to 255 (on most processors), i.e. the integers that will fit in a byte. Since it’s “modular”, it means that arithmetic operations on the type wrap around (i.e. if you add 130 + 130, you will get 4. With this declaration, you can create binary numbers:

x := 2#00011110#;
y := 2#11110100#;
z := 2#11110000#;

Here x=30, y=244, and z=240. So we can then use bitwise operators (found in the Ada package Interfaces) to swap the numbers:

x := x xor y;
y := y xor x;
x := x xor y;

The numbers can be printed out with this code:

put_line(unsigned_8'image(x));
put_line(unsigned_8'image(y));

You can also use put from the sub-package byteIO:

byteIO.put(item => z, base => 2);

If we want to convert a number input from integer to type byte, we can do so in the following manner:

w: integer;
get(w);
z := byte'val(integer'pos(w));
byteIO.put(item => z, base => 2);

The byte’val returns the base type of byte. For example, if the value input by the user is 17 (w), then the value assigned to z will be 10001. What about shift operators? There is shift_left() and shift_right(). Here’s an example:

zs: byte; 
zs := shift_left(z,1);
byteIO.put(item => zs, base => 2);

If the user inputs 12, then the value of zs will be 24, as it has been shifted left 1 bit.

spqr

Coding Ada: strings (iii) – arrays of strings

By: spqr
13 January 2021 at 20:44

The problem with strings in Ada is that they just don’t play nicely. As we have seen in previous posts, strings in Ada are either fixed, or unbounded. Now creating an array of strings in other languages isn’t that hard, nor is it in Ada – some things are just beyond its scope. Now by an array of strings, I mean a structure that could hold something like a dictionary of words. So how do we go about this?

The first example below creates a type, lexicon, with is a 5 element array of strings, 3 characters in length (so 5 three letter words). The variable words is then an instantiation of lexicon, which is assigned five three letter words. It’s not really possible to use the term string without the constraints (1..3). This is because of the fact that strings in Ada are fixed length – and it means all the strings assigned to words will have to be of length 3.

type lexicon is array(1..5) of string(1..3);
words : lexicon := ("gum","sin","cry","lug","fly");

It is also not possible to make the constraint larger, e.g. (1..10), and use words or variable length, e.g. “gum”, “housefly”, “star”. This will cause a constraint error (the word star would have to be specified as “star “, with 6 spaces filling out the word). The alternative is to unbounded strings, but they come with their own baggage. Consider the following code, changing the string to an unbounded_string.

type lexicon is array(1..5) of unbounded_string;
words : lexicon := ("gum","sin","cry","lug","star");

Compile this, and it will produce a series of errors of the form:

file.adb:10:24: expected private type "Ada.Strings.Unbounded.Unbounded_String"
file.adb:10:24: found a string type

This means that when it tried to assign the words, it considered “gum” to be a string, NOT an unbounded_string. The declaration of lexicon works fine if reading directly from a file, but not predefined strings. The fix? Not pretty, because the strings have to be converted to unbound strings using the function to_unbounded_string().

words : lexicon :=(to_unbounded_string("gum"),to_unbounded_string("sin"),to_unbounded_string("cry"),to_unbounded_string("lug"),to_unbounded_string("star"));

Messy right? There is one way of cleaning up this code and that is providing an alias for the function name to_unbounded_string(). Here is how that is done, renaming the function to tub().

function tub(Source : String) return unbounded_string renames ada.strings.unbounded.to_unbounded_string;

Then the code below it becomes:

type lexicon is array(1..5) of unbounded_string;
words : lexicon := (tub("gum"),tub("sin"),tub("cry"),tub("lug"),tub("star"));

Not perfect, but then that is Ada and strings for you.

spqr

Ada is defensive by default

By: spqr
4 February 2022 at 20:40

The type system of Ada is not merely strongly typed, but sometimes referred to as being super-strongly typed, because it does not allow for any level of implicit conversions. None. Consider the following code in C:

#include <stdio.h>
int main(void) {
   float e;
   int a;
   e = 4.7631;
   a = e;
   printf("%d\n", a);
   return 0;
}

This is valid code that will compile, run and produce the expected result – the result printed out is 4. C has no issues performing an implicit conversion. Ada on the other hand won’t even compile similar code. Here is the Ada equivalent:

with ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure testtyped is
    a : integer;
    e : float;
begin
   e := 4.7631;
   a := e;
   put(a);
end testtyped;

When an attempt is made to compile this, the following error will occur:

testtyped.adb:8:09: expected type "Standard.Integer"
testtyped.adb:8:09: found type "Standard.Float"

The problem here is that a and e are clearly not the same. Instead, one has to explicitly convert between types, which promotes good design by preventing the mixing of types. In this case, Line 9 in the above code becomes:

a := integer(e);

Also at run-time, errors such as illegal memory accesses, buffer overflows, range violations, off-by-one errors, and array access are tested. These errors can then be handled safely instead of the way languages like C do. For example, consider the following C code:

#include <stdio.h>
int main(void) {
   int x[100];
   x[101] = 1;
   printf("%d\n", x[101]);
   return 0;
}

Again, this is valid code that will compile, run and produce unexpected results. A value is actually stored outside the array x. Consider the same code written in Ada:

with ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure testarrOOB is
    x : array (1..100) of integer;
begin
   x(101) := 1;
   put(x(101));
end testarrOOB;

Compiling the program will result in the following warnings:

testarroob.adb:6:06: warning: value not in range of subtype of "Standard.Integer" defined at line 4
testarroob.adb:6:06: warning: "Constraint_Error" will be raised at run time
testarroob.adb:7:10: warning: value not in range of subtype of "Standard.Integer" defined at line 4
testarroob.adb:7:10: warning: "Constraint_Error" will be raised at run time

It does produce an executable, but running the executable results in an exception being triggered:

raised CONSTRAINT_ERROR : testarroob.adb:6 range check failed

Why is this important? Because in real-time systems, errors such as this have to be avoided. Now ask yourself why it is possible the F-35 code written in C++ does work as intended.

spqr

Coding Ada: get vs. get_line (i) – the basics

By: spqr
7 February 2022 at 16:29

Ada is a very strict language, but then it has to be, as it was designed for creating real-time systems. It does have some idiosyncrasies, but then all languages do. Let’s look at the two functions get() and get_line().

get()

The function get(), reads a value from the standard input. It knows what to read based on the argument it is given. For example the following piece of code allows an integer to be read into value x.

x : integer;
get(x);

Here are the characteristics of get():

  • For numbers it skips whitespaces (including newlines).
  • For characters and strings it reads exactly the right number of characters, but excludes newlines in count.

For example consider the following code:

s : string(1..4);
get(s);

Then the input from the user could be either one of:

tool
t
o
o
l

get_line()

The function get_line(), reads an entire line, i.e. a string. It stops reading when it encounters a line terminator (or rather if the end of the line is encountered, it automatically calls skip_line). Ada offers two ways to use get_line:

s : string(1..4);
len : natural;
s := get_line;
get_line(s,len);

The first (Line 3) treats get_line like a function, the other (Line 4) treats it like a procedure. Note there is a second parameter in the latter call, len. This returns the length of the string read in (the second parameter should be omitted for unbound_string). When using these two, there are different outcomes. Consider the following code, which reads in a fixed string.

s : string(1..4);
s := get_line;
put(s);

This code will only work when the string input is exactly 4 characters in length. Any other length will throw an exception. This is unlike C which allows more or less to be read. Alternatively, consider the following code:

s : string(1..4);
len : natural;
get_line(s,len);
put(s); put(len);

In this case, the value stored in s will be any string less than or equal to 4 characters in length, with the size stored in len. To circumvent these issues, often an unbound_string is used, which will basically read any size string up until the line terminator (typically a <return>). The code would look like this:

buf : unbounded_string;
buf := get_line;
put(buf);

Further reading:

spqr

Coding Ada: Using multiple packages

By: spqr
8 February 2022 at 15:25

So Ada uses packages to implement a form of encapsulation, which actually existed before the whole hoopla with C++ evolved. Packages have a specification, and an implementation. In the example below, we will create three packages to generate an image comprised of random values, i.e. a noise image. Here is an example (it can be saved as a text image).

The first package only has a specification, and is called datas. It includes the data structures needed by the other packages (i) image, and (ii) print (and also the main program). Here is what the specification looks like (datas.ads):

package datas is
   type randRange is new Integer range 0..255;
   type randImg is array(1..500,1..500) of randRange;
end datas;

It includes a type randRange, which specifies integers in the range 0..255, and a type randImg, which is a 2D array of type randRange. Next is the package image – its task is to generate an image of random numbers in the range 0..255. Here is the specification (image.ads):

with ada.numerics.discrete_random;
with datas; use datas;

package image is
   package Rand_Int is new ada.numerics.discrete_random(randRange);
   use Rand_Int;
   procedure makeImage(img : out randImg);
end image;

It creates a new instance of the package discrete_random, to deal with generating numbers in the range 0..255. It also contains a function makeImage(), which uses the types in package datas to generate an image. Here is the package implementation (image.adb):

package body image is

procedure makeImage(img : out randImg) is
   gen : Generator;
begin
   reset(gen);
   for i in 1..img'last(1) loop
      for j in 1..img'last(2) loop
         img(i,j) := random(gen);
      end loop;
   end loop;
end;

end image;

The last package, print just prints out the image as a series of rows (to standard output, but it can be redirected to a file, e.g. a.out >randim.txt). Here is the specification (print.ads):

with ada.Text_IO; use Ada.Text_IO;
with ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with datas; use datas;

package print is
   procedure printImage(img : in randImg);
end print;

And the associated implementation (print.adb):

package body print is

procedure printImage(img : in randImg) is
begin
   for i in 1..img'last(1) loop
      for j in 1..img'last(2) loop
         put(randRange'Image(img(i,j)));
      end loop;
      new_line;
   end loop;
end;

end print;

Finally the main program that calls them all (test.adb)

with image; use image;
with print; use print;
with datas; use datas;

procedure test is
   im : randImg;

begin
   makeImage(im);
   printImage(im);
end test;

This program can be compiled simply using the main file:

% gnatmake -Wall test.adb

Obviously packages can be compiled,bu they can’t be linked or executed. Each of the packages can also be compiled separately:

% gnatmake -Wall image.ads image.adb
% gnatmake -Wall print.ads print.adb
% gnatmake -Wall datas.ads
% gnatmake -Wall test.adb

spqr

Coding Ada: get vs. get_line (ii) – some caveats

By: spqr
9 February 2022 at 15:08

When used separately, get and get_line rarely cause issues. When used together, there can be some issues. Consider the following code:

a : integer;
s : string(1..4);

get(a);
s := get_line;
put(a);
put_line(s);

Here is the dialog obtained when running the program containing this code:

4

raised CONSTRAINT_ERROR : testgetm.adb:14 length check failed

It allows the user to input the number 4, but then balks and pulls an exception. What went wrong? To see what happened we have to consider exactly what these input functions do. As we have seen, calls to get for numeric and string input skip over blanks and line terminators that come before the number. That’s why it is possible to press the <return> key several times between numbers being entered. However these get functions do not skip over any blanks and line terminators that come after the number. After the integer variable a is read, the input pointer is set to the line terminator after 4.

Because get_line stops reading when it encounters a line terminator, it stops and sets the length of the string to zero to indicate a null string. As the size of the (fixed) string is expected to be 4 characters in length, an exception is actioned. One way to circumvent this is to type the string on the same line as the number 4. For example:

6tool
          6tool

This illustrates another feature of numeric input. The get function for numeric data stops reading when a non-numeric character is encountered. The non-numeric character “t” is not read and is still available to be read by the next call to an input function.

This solution is not really optimal. A better idea is to use the function skip_line. When skip_line is called, it moves the input pointer to the next line. It is most useful when using a get_line function after getting a numeric value. Here is an example of its use:

a : integer;
s : string(1..4);

get(a);
skip_line;
s := get_line;
put(a);
put_line(s);

Note that if we use a unbound_string instead of the fixed string, then the program will not convulse, but it also will skip the string input… but it will print out a string. Why? Well the unbound_string will actually store the line terminator in the string… so it does read something, just not what it wanted.

To make things more consistent, it is also possible to read all input in as strings using get_line, and convert the string to numbers. Below is an example of the code. The first value is input to the unbound_string buf, which is then converted to a string, and then an integer, and assigned to the integer variable a.

a : integer;
buf : unbounded_string;

buf := get_line;
a := integer'value(to_string(buf));
buf := get_line;
put(a);
put_line(buf);

Note that weird things can also occur when using consecutive get_line() statements. For example consider the code:

s,t : string(1..4);
get_line(s,len);
put(s); put(len);
get_line(t,len);
put(t); put(len);

Here is an example of it running:

tool
tool          4�#           0

The first string input “tool”. works, the second string reads in the line terminator. The same does not occur using the alternate form of get_line. For example the code below works as it should.

s,t : string(1..4);
s := get_line;
put(s);
t := get_line;
put(t);

spqr

Ada is defensive by default

By: spqr
4 February 2022 at 20:40

The type system of Ada is not merely strongly typed, but sometimes referred to as being super-strongly typed, because it does not allow for any level of implicit conversions. None. Consider the following code in C:

#include <stdio.h>
int main(void) {
   float e;
   int a;
   e = 4.7631;
   a = e;
   printf("%d\n", a);
   return 0;
}

This is valid code that will compile, run and produce the expected result – the result printed out is 4. C has no issues performing an implicit conversion. Ada on the other hand won’t even compile similar code. Here is the Ada equivalent:

with ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure testtyped is
    a : integer;
    e : float;
begin
   e := 4.7631;
   a := e;
   put(a);
end testtyped;

When an attempt is made to compile this, the following error will occur:

testtyped.adb:8:09: expected type "Standard.Integer"
testtyped.adb:8:09: found type "Standard.Float"

The problem here is that a and e are clearly not the same. Instead, one has to explicitly convert between types, which promotes good design by preventing the mixing of types. In this case, Line 9 in the above code becomes:

a := integer(e);

Also at run-time, errors such as illegal memory accesses, buffer overflows, range violations, off-by-one errors, and array access are tested. These errors can then be handled safely instead of the way languages like C do. For example, consider the following C code:

#include <stdio.h>
int main(void) {
   int x[100];
   x[101] = 1;
   printf("%d\n", x[101]);
   return 0;
}

Again, this is valid code that will compile, run and produce unexpected results. A value is actually stored outside the array x. Consider the same code written in Ada:

with ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure testarrOOB is
    x : array (1..100) of integer;
begin
   x(101) := 1;
   put(x(101));
end testarrOOB;

Compiling the program will result in the following warnings:

testarroob.adb:6:06: warning: value not in range of subtype of "Standard.Integer" defined at line 4
testarroob.adb:6:06: warning: "Constraint_Error" will be raised at run time
testarroob.adb:7:10: warning: value not in range of subtype of "Standard.Integer" defined at line 4
testarroob.adb:7:10: warning: "Constraint_Error" will be raised at run time

It does produce an executable, but running the executable results in an exception being triggered:

raised CONSTRAINT_ERROR : testarroob.adb:6 range check failed

Why is this important? Because in real-time systems, errors such as this have to be avoided. Now ask yourself why it is possible the F-35 code written in C++ does work as intended.

Coding Ada: get vs. get_line (i) – the basics

By: spqr
7 February 2022 at 16:29

Ada is a very strict language, but then it has to be, as it was designed for creating real-time systems. It does have some idiosyncrasies, but then all languages do. Let’s look at the two functions get() and get_line().

get()

The function get(), reads a value from the standard input. It knows what to read based on the argument it is given. For example the following piece of code allows an integer to be read into value x.

x : integer;
get(x);

Here are the characteristics of get():

  • For numbers it skips whitespaces (including newlines).
  • For characters and strings it reads exactly the right number of characters, but excludes newlines in count.

For example consider the following code:

s : string(1..4);
get(s);

Then the input from the user could be either one of:

tool
t
o
o
l

get_line()

The function get_line(), reads an entire line, i.e. a string. It stops reading when it encounters a line terminator (or rather if the end of the line is encountered, it automatically calls skip_line). Ada offers two ways to use get_line:

s : string(1..4);
len : natural;
s := get_line;
get_line(s,len);

The first (Line 3) treats get_line like a function, the other (Line 4) treats it like a procedure. Note there is a second parameter in the latter call, len. This returns the length of the string read in (the second parameter should be omitted for unbound_string). When using these two, there are different outcomes. Consider the following code, which reads in a fixed string.

s : string(1..4);
s := get_line;
put(s);

This code will only work when the string input is exactly 4 characters in length. Any other length will throw an exception. This is unlike C which allows more or less to be read. Alternatively, consider the following code:

s : string(1..4);
len : natural;
get_line(s,len);
put(s); put(len);

In this case, the value stored in s will be any string less than or equal to 4 characters in length, with the size stored in len. To circumvent these issues, often an unbound_string is used, which will basically read any size string up until the line terminator (typically a <return>). The code would look like this:

buf : unbounded_string;
buf := get_line;
put(buf);

Further reading:

Coding Ada: Using multiple packages

By: spqr
8 February 2022 at 15:25

So Ada uses packages to implement a form of encapsulation, which actually existed before the whole hoopla with C++ evolved. Packages have a specification, and an implementation. In the example below, we will create three packages to generate an image comprised of random values, i.e. a noise image. Here is an example (it can be saved as a text image).

The first package only has a specification, and is called datas. It includes the data structures needed by the other packages (i) image, and (ii) print (and also the main program). Here is what the specification looks like (datas.ads):

package datas is
   type randRange is new Integer range 0..255;
   type randImg is array(1..500,1..500) of randRange;
end datas;

It includes a type randRange, which specifies integers in the range 0..255, and a type randImg, which is a 2D array of type randRange. Next is the package image – its task is to generate an image of random numbers in the range 0..255. Here is the specification (image.ads):

with ada.numerics.discrete_random;
with datas; use datas;

package image is
   package Rand_Int is new ada.numerics.discrete_random(randRange);
   use Rand_Int;
   procedure makeImage(img : out randImg);
end image;

It creates a new instance of the package discrete_random, to deal with generating numbers in the range 0..255. It also contains a function makeImage(), which uses the types in package datas to generate an image. Here is the package implementation (image.adb):

package body image is

procedure makeImage(img : out randImg) is
   gen : Generator;
begin
   reset(gen);
   for i in 1..img'last(1) loop
      for j in 1..img'last(2) loop
         img(i,j) := random(gen);
      end loop;
   end loop;
end;

end image;

The last package, print just prints out the image as a series of rows (to standard output, but it can be redirected to a file, e.g. a.out >randim.txt). Here is the specification (print.ads):

with ada.Text_IO; use Ada.Text_IO;
with ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with datas; use datas;

package print is
   procedure printImage(img : in randImg);
end print;

And the associated implementation (print.adb):

package body print is

procedure printImage(img : in randImg) is
begin
   for i in 1..img'last(1) loop
      for j in 1..img'last(2) loop
         put(randRange'Image(img(i,j)));
      end loop;
      new_line;
   end loop;
end;

end print;

Finally the main program that calls them all (test.adb)

with image; use image;
with print; use print;
with datas; use datas;

procedure test is
   im : randImg;

begin
   makeImage(im);
   printImage(im);
end test;

This program can be compiled simply using the main file:

% gnatmake -Wall test.adb

Obviously packages can be compiled,bu they can’t be linked or executed. Each of the packages can also be compiled separately:

% gnatmake -Wall image.ads image.adb
% gnatmake -Wall print.ads print.adb
% gnatmake -Wall datas.ads
% gnatmake -Wall test.adb

Coding Ada: get vs. get_line (ii) – some caveats

By: spqr
9 February 2022 at 15:08

When used separately, get and get_line rarely cause issues. When used together, there can be some issues. Consider the following code:

a : integer;
s : string(1..4);

get(a);
s := get_line;
put(a);
put_line(s);

Here is the dialog obtained when running the program containing this code:

4

raised CONSTRAINT_ERROR : testgetm.adb:14 length check failed

It allows the user to input the number 4, but then balks and pulls an exception. What went wrong? To see what happened we have to consider exactly what these input functions do. As we have seen, calls to get for numeric and string input skip over blanks and line terminators that come before the number. That’s why it is possible to press the <return> key several times between numbers being entered. However these get functions do not skip over any blanks and line terminators that come after the number. After the integer variable a is read, the input pointer is set to the line terminator after 4.

Because get_line stops reading when it encounters a line terminator, it stops and sets the length of the string to zero to indicate a null string. As the size of the (fixed) string is expected to be 4 characters in length, an exception is actioned. One way to circumvent this is to type the string on the same line as the number 4. For example:

6tool
          6tool

This illustrates another feature of numeric input. The get function for numeric data stops reading when a non-numeric character is encountered. The non-numeric character “t” is not read and is still available to be read by the next call to an input function.

This solution is not really optimal. A better idea is to use the function skip_line. When skip_line is called, it moves the input pointer to the next line. It is most useful when using a get_line function after getting a numeric value. Here is an example of its use:

a : integer;
s : string(1..4);

get(a);
skip_line;
s := get_line;
put(a);
put_line(s);

Note that if we use a unbound_string instead of the fixed string, then the program will not convulse, but it also will skip the string input… but it will print out a string. Why? Well the unbound_string will actually store the line terminator in the string… so it does read something, just not what it wanted.

To make things more consistent, it is also possible to read all input in as strings using get_line, and convert the string to numbers. Below is an example of the code. The first value is input to the unbound_string buf, which is then converted to a string, and then an integer, and assigned to the integer variable a.

a : integer;
buf : unbounded_string;

buf := get_line;
a := integer'value(to_string(buf));
buf := get_line;
put(a);
put_line(buf);

Note that weird things can also occur when using consecutive get_line() statements. For example consider the code:

s,t : string(1..4);
get_line(s,len);
put(s); put(len);
get_line(t,len);
put(t); put(len);

Here is an example of it running:

tool
tool          4�#           0

The first string input “tool”. works, the second string reads in the line terminator. The same does not occur using the alternate form of get_line. For example the code below works as it should.

s,t : string(1..4);
s := get_line;
put(s);
t := get_line;
put(t);

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.

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

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;

❌
❌