Showing posts with label Ada. Show all posts
Showing posts with label Ada. Show all posts

Saturday, July 2, 2022

New release of Ada for Slackware 15.0

Finally, after many years of inactivity, a new release is out for Slacware 15.0.

Major changes of this release: contains the Alire; the GNATStudio is using the binary GNAT Studio Continuous Release 20220512, I failed to build it from source due to lots of dependcies and stuck in the Ada langguare server; a build script that boostrap gprbuild and build/install all the basic packages (XMLAda, GTKAda, Florist, AUnit, GNATCOLL, etc).

Wednesday, October 4, 2017

New Ada for Slackware released

Finally, long after released of GNAT GPL 2017 and GCC in 7.x series in Slackware-current, I got some time to work on the Ada environment for Slackware and got a working version of GPS.

The new release is at GitHub

Thursday, May 4, 2017

GCC 7.1 is out

GCC 7.1 was released on May 2.

I tried it out and recompiled all my Ada related builds, finally I got a stable GPS that does not crash after opening a project file. It still crashed on me, maybe I should revert to an older version?

I have updated my build scripts ada-4-slackware on gitHub. There is still some problems with ASIS 2016, some API changed for the new compiler, will update the build script once got time.

Tuesday, September 13, 2016

Ada programming environment based on GCC 6.2 and GNAT GPL 2016 in Slackware64 14.2

New release of Slackware and new version of GNAT GPL 2016 prompt me to update the Ada programming environment under Slackware.

First, I was trying to use the stock 5.3 compiler, but encounter gnat compiler error when compiling gprbuild. So has to use the FSF GCC 6.2 as the based.

The FSF GCC compiler

At the writing, the latest release of GCC is 6.2.0. First I update the build script to matched what's in the current tree. I have changed the build directory to a fixed location to avoid the random location in the original script. As it is needed for gnat_util build, a fixed location is easier for the script to find the right files. Built and install without problem.

GNAT Environment package

This package contains startup scripts to setup Ada project path environment variable ADA_PROJECT_PATH and GPR_PROJECT_PATH. They will be installed into /etc/profile.d by the doinst.sh.

Also soft link of the GNAT Ada run time libraries are also created in doinst.sh.

XMLAda

For XMLAda GPL 2016, the default build process is making use of gprbuild, which depends on XMLAda. I followed what was done on Arch Linux and created a custom Makefile.SlackBuild. But I installed the package with the following scheme, similar to what GNAT GPL 2016 is doing but with sub-directories for each sub-module:
 usr/include/xmlada
             |- input
             |- unicode
             ...
 usr/lib64/xmlada
           |- input
           |  |- static
           |  `- relocatable
           |- unicode
           |  |- static
           |  `- relocatable
           ...
The document build requires Sphinx, as SlackBuilds.orgprovides all necessary build scripts, it is a matter of installing the relevant packages and dependencies.

GPRBuild

The 2016 version of gprbuild required gprbuild itself. So Similar to XMLAda, a custom Makefile was borrowed from the Arch Linux project and modified to suite the needs of Slackware. A patch was developed for the gprinstall program, so that its default library subdir and library soft link subdir points to lib64 under Slackware64. A gpr library is also created, as it is needed by a few other programs

GNAT_UTIL

The GNAT Util Libraryprovides access to GNAT compiler internals for AdaCore utilities. The latest release is 5.1.0, it is used for our GCC 6.2.0. The build went well.

GTKAda

It does not build very well at the first few builds, it keeps installing libs into /usr/lib in stead of /usr/lib64 on 64-bit system. It seems the generate file still insist on the lib's path. So more sed commands are added to change the lib path to lib64 on 64-bit system. After that, it built without problem with proper installation path.

Aunit

Aunit's build script has no big change from the previous version, except for the version bump.

GNATColl

I used version 16.1 from GitHub. GNATColl compiled OK, with minor problem with lib's installation path, similar to the problem in GtkAda. I always got gpr.gpr problem when compiling GPS, latter I found that not sure why, the installing process of gnatcoll will install a copy of the gpr.gpr project file and related object file and source into its own directory structure with some modification to gpr.gpr and causing error on the project file. I have to manually remove all gpr related files and directories and entries in the manifests file. Then GPS will compile with correct project file.

GPS

After fixing the gprbuild, gpr and gnatcoll libraries, GPS compile without problem. When running GPS, it gave python script warning about pep8 and jedi, after install those two from SlackBuilds.org, everything is working fine.

Florist

No trouble when compile and install florist.

Asis

Asis has no problem to compile and install after fixed the gprbuild install path.

Gnatmem and Ahven

Gnatmem (need to compile the binutils for Slackware 14.2 and use the build tree) and Aheven gave no trouble when compiled for the 2016 version.

All build scripts are available at GitHub.

Thursday, September 24, 2015

gnatpp could not align exception declaration properly

I normally use the GNAT Pretty-Printer gnatpp to format my Ada code.  The output of it is not perfect but acceptable and make my code look consistent.

It has a small alignment bug, for exception declaration, it could not handle it properly.  For this specification align_test.ads:
package Align_Test is
    Index: Integer;
    Number_Test : Integer;

    Bad_File : exception;
    Bad_Argument : exception;
end Align_Test;
gnatpp -r align_test.ads will produce:
package Align_Test is
   Index       : Integer;
   Number_Test : Integer;

   Bad_File : exception;
   Bad_Argument : exception;
end Align_Test;
I have to manually align the declaration and add specific comments to fence it from gnatpp to change my alignment in future. 
package Align_Test is
   Index       : Integer;
   Number_Test : Integer;

   --!pp off
   Bad_File     : exception;
   Bad_Argument : exception;
   --!pp on
end Align_Test;
It does not look good, but acceptable.  Wish next version of gnatpp could solve this problem.

P.S. Hooray, today (May. 19, 2016) AdaCore fixed the alignment problem. It will properly show up in GNAT GPL 2016.

Thursday, September 10, 2015

Getting Started with GtkAda - Content

This is the content for my previous Getting Started with GtkAda blogs.

Building Ada programming environment with GCC 5 and GNAT GPL 2015

I would like to rebuild the Ada programming environment based on GCC 5 and GNAT GPL 2015 in Slackware64 14.1, as Ada 2012 support is more mature in GCC 5.

The FSF GCC compiler

At the writing, the latest release of GCC is 5.2.0.  First I update the build script to matched what's in the current tree.  I would to like to replace the stock Slackware64 14.1's compiler with the new one, but GCC changed the default standard C++ library ABI since 5.1.0.  To make it compatible with GCC 4, a configure switch (--with-default-libstdcxx-abi=gcc4-compatible) was added to the build script.  Also, I have changed the build directory to a fixed location to avoid the random location in the original script.  As it is needed for gnat_util build, a fixed location is easier for the script to find the right files.  I had problem on i586 platform, which I had to explicitly add the --disable-multilib switch to the configuration and remove the conditional test for it.  Built and install without problem.

P.S. Due to GPRBuild will look for GNAT's runtime library as libgnat-5.2.so in stead of libgnat-5.so, build script was modified to create the softlink when packaging the GNAT compiler.  Also created libgnarl-5.2.so from libgnarl-5.so

XMLAda

For XMLAda GPL 2015, the default build process is making use of gprbuild, which depends on XMLAda.  I followed what was done on Arch Linux and created a custom Makefile.SlackBuild.  But I installed the package with the following scheme, similar to what GNAT GPL 2015 is doing but with sub-directories for each sub-module:
 usr/include/xmlada
             |- input
             |- unicode
             ...
 usr/lib64/xmlada
           |- input
           |  |- static
           |  `- relocatable
           |- unicode
           |  |- static
           |  `- relocatable
           ...
The document build requires Sphinx, as SlackBuilds.org provides all necessary build scripts, it is a matter of installing the relevant packages and dependencies.

GPRBuild

The 2015 version of gprbuild required gprbuild itself.  So Similar to XMLAda, a custom Makefile was borrowed from the Arch Linux project and modified to suite the needs of Slackware.  My first build was without error, but it failed to build when trying it for gnat_uitl, complained about:
gnat_util.gpr:21:09: warning: no compiler specified for language "C", ignoring all its sources
gnat_util.gpr:21:09: warning: no compiler specified for language "Ada", ignoring all its sources
Further investigation showed that the default target for gprconfig was wrong, it was set to "Slackware" when generating gprconfig-sdefault.ads in my build script, due to not fully understand what it means.  After set it to "$ARCH-slackare-linux", it still failed on linking, looking for GNAT's runtime library libgnat-5.2.so, which does not exist for a standard GCC build, as it only has libgnat-5.so.  After created a soft link from libgnat-5.so, it builds and working.

GNAT_UTIL

The GNAT Util Library provides access to GNAT compiler internals for AdaCore utilities.  The latest release is 5.1.0, since there are no changes on Ada part of the GCC for 5.2.0, it is sufficient for us.  The build went well except for the initial gprbuild target problem.

GTKAda

GTKAda require Gtk+3 version after 3.8.3, the stock version in Slackware 14.1 is 3.8.2, need to upgrade to a latter version.  The version in Slackware current tree is too new, I settled on the latest 3.8 release 3.8.9 and rebuild the GTK+3 package.  The GL support is turned on for this version, but when building test program, it complained:
gtkada-gpl-2015-src/src/lib/gtkada_gl/static/libgtkada_gl.a(gdkgl.o): undefined reference to symbol 'XFree'
/usr/lib/libX11.so.6: error adding symbols: DSO missing from command line
After patching the testgtk.gpr with linker option to link with -lX11 -lm, it built and run fine.

Aunit

Aunit's build script needed to adapt to gprinstall command, other than that, it built smoothly.

GNATColl

GNATColl compiled OK, with minor problem with libgnarl-5.2.so.

GPS

The IDE compiler could be compiled now given all dependencies had been built.  The build did not go smoothly related to some xmlada library issues, back and forth a few times, finally got it compiled.  When running GPS, it gave python script warning about pep8 and jedi, after install those two from SlackBuilds.org, everything is working fine.

Florist

The florist library changed to use gprinstall, which breaks the install process.  After adjust the build script and patched the Makefile.in generated proper project file for used with others, it built and install fine.

Asis

Asis has little problem for installing to correct path, some patch needed to be re-do, after that, everything built and installed correctly.

Gnatmem and Ahven

Gnatmem gave no trouble when compiled for the 2015 version.  Ahven's 2.6 version changed the build process and I had to create a gpr file for it.  Other than that, there is no big problems.

Where are the build scripts

All build scripts are available at GitHub.

Monday, May 4, 2015

Building Ada promgramming enviroment for Slackware

Slackware has finally upgraded the system GCC to 4.9.2 in current. Here I presented my work to compile GPS using FSF's GCC 4.9.2. It is based on my previous attempt to build the environment with GCC 4.7.0. This time lots of things have changed, new GCC and GNAT GPL released and they make the attempt much more easier than previous one. GCC 5.1 has just been released, but Slackware use 4.9.2 in the current branch for now, so we stick with 4.9.2.

The Compiler

There is no need to change the build script any more, but we need some of the compiler internal files. So run the standard build script for GCC.

XMLAda

For this one, I am using the xmlada-for-gps-6.0.1-src.tgz from GNAT GPL 2014, in stead of xmlada-gpl-2014-src.tar.gz. They are almost exactly the same with minor changes.

GPRbuild

I am using the gprbuild-gpl-2014-src.tar.gz. GPRbuild expected some support functions that are not in the FSF GNAT compiler. Namely the "GNAT.Rewrite_Data" package and some low level support functions in os_lib.c. I gather the missing functions from the GNAT GPL 2014 version and make up some packages for it. Then it builds without problem.

GNAT_UTIL

This GNAT Util Library provides access to GNAT compiler internals for AdaCore utilities. It makes the previous effort of trying to mimic what Debian do redundant. In the build script, the source and build directory of the GCC compiler needs to be specified for it to work correctly.
I am using the 4.9.1 one from the sourceforge project. For there is no 4.9.2 version and there are basically no differences for the generation of gnat_util.

GTKAda

With the standard gtkada-gpl-3.8.2-src.tgz, there is nothing special, it builds without problem. One may have to note that currently it does not support GL.

Aunit

Aunit GPL 2014 also builds without too much problem.

GNATcoll

GNATcoll compile without problem with some patches.

GPS

Oh, finally, we could build the IDE. It depends on pygobject3, which could be build using SlackBuilds.org's supply script.

Florist, ASIS, GNATMEM, Ahven

The rests are relatively easy and build fine with the build script. For gnatmem, we need to rebuild the binutils and specified the directory in gnatmem build script.

Other experimental scripts

I have tried to build the Ada-RM, GNAT RM, GNAT user guide to integrated with GPS, it seems working for me.

Where are the build scripts

All build scripts are available at GitHub.

Monday, March 9, 2015

MIT open course for Ada

MIT offers one open course for Ada programming (it is based on Ada 95):
Unified Engineering I, II, III, & IV » Comps./Programming

Thursday, April 10, 2014

Internationalized application with Gtkada.Intl

Gtkada comes with Gtkada.Intl, which is a binding for libintl to provide support for string internationalization.

In this example, a very simple program is developed to illustrate the usage of Gtkada.Intl package.

We use the helloworld example, and modified it to support i18n. You could find all the source at GitHub.

The directory structure is similar as before, and we are using Makefile to automate some tasks.


|- Makefile.common
|- intl
|  |- obj/
|  |- src/
|  |- po/
|  |   |- Makefile
|  |   `- build_skeleton.pl
|  |- Makefile
|  `- generate_locale_path.sh
...
src/intl.adb:
...
with Gtkada.Intl;     use Gtkada.Intl;

with intl_cb; use intl_cb;
with intl_locale;

procedure Intl is
   Win    : Gtk_Window;
   Button : Gtk_Button;
   prgname : constant String := "intl";
begin
   -- procedure calls to setup Locale and text domain binding
   SetLocale;
   Text_Domain (prgname);
   Bind_Text_Domain (prgname, intl_locale.path);

...
   Win.Set_Title (-"Window");
...
   Gtk_New (Button, -"Hello World");
...
end Intl;
Here, we call SetLocale, Text_Domain and Bind_Text_Domain to initialize the locale data. The Bind_Text_Domain procedure needs a locale path as its second parameter, as the path may change on different system, we need a way to tell the program where is the location. A script generate_locale_path.sh was created. It is called from the Makefile, when ever there is changed in the locale path, it will generate a new src/intl_locale.ads file. A sample file is as follow:
-- This is an automatically generated file during compilation
-- Please don't edit it by hand.
-- Generated at 2014-04-09 23:45

package Intl_Locale is
   path : constant String := "/usr/local/share/locale";
end Intl_Locale;
Each string that is to be translated has a "-" signed in front. Messages in src/intl_cb.adb are also altered. We won't list the detail source here.

To generate the pot file, a modified version of build_skeleton.pl borrowed from the GtkAda project is used. There are a few modifications that make build_skeleton.pl works much better than the original one:

  • Use the perl Text::Balanced module to capture quoted string, which will work for a case like -("Hello world (from Ada)!");. The original script will get a partial string "Hello world (from Ada".
  • Use the perl Tie::IxHash module to preserve the order of message strings appear in the source file, which give translation a better view of the content near by.
  • Allow spaces in between '-', '(' and '"', for the case like -   (   "Hello world 2!");, it will not be captured by the original script.
  • Allow replace of Ada.Characters.Latin_1.LF to \n besides ASCII.LF
  • Add translation of quoted '"' in the string to \" in -("Ada said ""Hello World!");, which will allow gettext to work correctly.
  • Use command parameters for project specific information, so that it could be used on other projects without modifications.
  • Change the POT-Creation-Date format to include time and timezone information.
  • Only check src/ and its sub directories.

A Makefile is borrowed from the GtkAda project and simplified.

po/Makefile
LANGS:=zh_CN
CP:=cp
prgname := intl
## Refresh all translations by extracting the strings from the current sources
refresh:
 ./build_skeleton.pl > ${prgname}.pot
 ${foreach lang,${LANGS}, \
 if [ -f ${lang}.po ]; then true; else ${CP} ${prgname}.pot ${lang}.po; fi; \
 msgmerge --no-wrap --update ${lang}.po ${prgname}.pot}

## Install the translation files
$(LANGS): 
 -msgfmt -o $@.gmo $@.po --statistics --check-header --check-format --check-domain

clean:
 -$(RM) *.gmo
A simple run of it under the po directory will give us a simple pot file (intl.pot):
# Translation file for intl example app
# Copyright (C) 2014 Zhu Qun-Ying
#
msgid ""
msgstr ""
"Project-Id-Version: intl\n"
"Report-Msgid-Bugs-To: zhu.qunying@gmail.com\n"
"POT-Creation-Date: 2014-04-10 22:29-0700\n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: intl_cb.adb
msgid "button clicked"
msgstr ""

#: intl_cb.adb
msgid "button_quit is called"
msgstr ""

#: intl_cb.adb
msgid "Delete event encounter."
msgstr ""

#: intl.adb
msgid "Hello World"
msgstr ""

#: intl.adb
msgid "Window"
msgstr ""

We have defined a default translation language zh_CN in the Makefile, so after the make command, we also have zh_CN.po file for translation. If there is no existing zh_CN.po, it will be a copy of the intl.pot file. If there exist a zh_CN.po file and has translation, it will be merged with the intl.pot file if any changes have been made.

In order to better edit it, an editor that knows the format of the po file will ease your translation work. I am using vim with po.vim plugin.

Under po directory, run make zh_CN will compile the zh_CN.po file to zh_CN.gmo.

At the intl directory, we could issue the command:

make prefix=/usr
Which will generated the src/intl_locale.ads file and set the locale path to "/usr/share/locale" and compile the program.

Just for test, we copy the po/zh_CN.gmo to /usr/share/locale/zh_CN/LC_MESSAGES/intl.mo, then run the command:

LANG=zh_CN.utf8 obj/intl
The simple window with a button 世界你好 is shown. Click on the button, the program will exit, and you will get some output from command line:
按钮被点击
调用 button_quit

For advance usage of gettext utilities, please refer to the GNU gettext utilities document.

All the source is available at GitHub

Wednesday, March 26, 2014

Getting Started with GtkAda - Drawing

Many widgets, like buttons, do all their drawing themselves. You just tell them the label you want to see, and they figure out what font to use, draw the button outline and focus rectangle, etc. Sometimes, it is necessary to do some custom drawing. In that case, a Gtk_Drawing_Area might be the right widget to use. It offers a canvas on which you can draw by connecting to the "draw" signal.

The contents of a widget often need to be partially or fully redrawn, e.g. when another window is moved and uncovers part of the widget, or when tie window containing it is resized. It is also possible to explicitly cause part or all of the widget to be redrawn, by calling Gtk.Widget.Queue_Draw() or its variants. GtkAda takes care of most of the details by providing a ready-to-use cairo context to the ::draw signal handler.

The following example shows a ::draw signal handler. It is a bit more complicated than the previous examples, since it also demonstrates input event handling by means of ::button-press and ::motion-notify handlers.

src/draw.adb
with Gtk.Window;       use Gtk.Window;
with Gtk.Frame;        use Gtk.Frame;
with Gtk.Button;       use Gtk.Button;
with Gtk.Drawing_Area; use Gtk.Drawing_Area;
with Gtkada.Handlers;  use Gtkada.Handlers;
with Gdk.Event;        use Gdk.Event;
with draw_cb; use draw_cb;

with Gtk.Main;
with Gtk.Enums;

procedure Draw is
   Win   : Gtk_Window;
   Frame : Gtk_Frame;
   Da    : Gtk_Drawing_Area;
begin
   --  Initialize GtkAda.
   Gtk.Main.Init;

   -- create a top level window
   Gtk_New (Win);
   Win.Set_Title ("Drawing Area");
   -- set the border width of the window
   Win.Set_Border_Width (8);
   -- connect the "destroy" signal
   Win.On_Destroy (main_quit'Access);

   -- create a frame
   Gtk_New (Frame);
   Frame.Set_Shadow_Type (Gtk.Enums.Shadow_In);
   Win.Add (Frame);

   Gtk_New (Da);
   -- set a minimum size
   Da.Set_Size_Request (100, 100);
   Frame.Add (Da);

   -- Signals used to handle the backing surface
   Da.On_Draw (draw_cb.draw_cb'Access);
   Da.On_Configure_Event (configure_event_cb'Access);
   -- Event signals
   Da.On_Motion_Notify_Event (motion_notify_event_cb'Access);
   Da.On_Button_Press_Event (button_press_event_cb'Access);

   -- Ask to receive events the drawing area doesn't normally
   -- subscribe to. In particular, we need to ask for the
   -- button press and motion notify events that want to handle.
   Da.Set_Events (Da.Get_Events or Button_Press_Mask or Pointer_Motion_Mask);

   -- Now that we are done packing our widgets, we show them all
   -- in one go, by calling Win.Show_All.
   -- This call recursively calls Show on all widgets
   -- that are contained in the window, directly or indirectly.
   Win.Show_All;

   -- All GTK applications must have a Gtk.Main.Main. Control ends here
   -- and waits for an event to occur (like a key press or a mouse event),
   -- until Gtk.Main.Main_Quit is called.
   Gtk.Main.Main;
end Draw;
src/draw_cb.ads:
with Gtk.Widget;  use Gtk.Widget;
with Gtk.Button;  use Gtk.Button;
with Glib.Object;

with Gdk.Event;
with Cairo;

package draw_cb is
   procedure main_quit (Self : access Gtk_Widget_Record'Class);

   function draw_cb
     (Self : access Gtk_Widget_Record'Class;
      Cr   : Cairo.Cairo_Context)
      return Boolean;

   -- Create a new surface of the appropriate size to store our scribbles
   function configure_event_cb
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event_Configure)
      return  Boolean;

   -- Handle motion events by continuing to draw if button 1 is
   -- still held down. The ::motion-notify signal handler receives
   -- a GdkEventMotion struct which contains this information.
   function motion_notify_event_cb
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event_Motion)
      return  Boolean;

   -- Handle button press events by either drawing a rectangle
   -- or clearing the surface, depending on which button was pressed.
   -- The ::button-press signal handler receives a GdkEventButton
   -- struct which contains this information.
   function button_press_event_cb
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event_Button)
      return  Boolean;
end draw_cb;
src/draw_cb.adb:
with Glib;       use Glib;

with Gdk.Types;  use Gdk.Types;
with Gdk.Window;
with Gtk.Main;

package body draw_cb is

   -- Button defintions from gtk-3.0/gdk/gdkevents.h
   -- The primary button. This is typically the left button, or the
   -- right button in a left-handed setup.
   Gdk_Button_Primary : constant := 1;
   --  The secondary button. This is typically the right mouse button, or the
   -- left button in a left-handed setup.
   Gdk_Button_Secondary : constant := 3;

   surface : Cairo.Cairo_Surface;
   use type Cairo.Cairo_Surface;

   procedure main_quit (Self : access Gtk_Widget_Record'Class) is
   begin
      if surface /= Cairo.Null_Surface then
         Cairo.Surface_Destroy (surface);
      end if;

      Gtk.Main.Main_Quit;
   end main_quit;

   procedure Clear_Surface is
      Cr : Cairo.Cairo_Context;
   begin
      Cr := Cairo.Create (surface);
      Cairo.Set_Source_Rgb (Cr, 1.0, 1.0, 1.0);
      Cairo.Paint (Cr);
      Cairo.Destroy (Cr);
   end Clear_Surface;

   -- Redraw the screen from the surface. Note that the ::draw
   -- signal receives a ready-to-be-used cairo_t that is already
   -- clipped to only draw the exposed areas of the widget
   function draw_cb
     (Self : access Gtk_Widget_Record'Class;
      Cr   : Cairo.Cairo_Context)
      return Boolean
   is
   begin
      Cairo.Set_Source_Surface (Cr, surface, 0.0, 0.0);
      Cairo.Paint (Cr);
      return False;
   end draw_cb;

   function configure_event_cb
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event_Configure)
      return  Boolean
   is
   begin
      if surface /= Cairo.Null_Surface then
         Cairo.Surface_Destroy (surface);
      end if;
      surface :=
         Gdk.Window.Create_Similar_Surface
           (Self.Get_Window,
            Cairo.Cairo_Content_Color,
            Self.Get_Allocated_Width,
            Self.Get_Allocated_Height);
      -- Initialize the surface to white
      Clear_Surface;

      -- We've handled the configure event, no need for further processing.
      return True;
   end configure_event_cb;

   -- Draw a rectangle on the surface at the given position
   procedure draw_brush
     (Self : access Gtk_Widget_Record'Class;
      x    : Gdouble;
      y    : Gdouble)
   is
      Cr : Cairo.Cairo_Context;
   begin

      -- Paint to the surface, where we store our state
      Cr := Cairo.Create (surface);
      Cairo.Rectangle (Cr, x - 3.0, y - 3.0, 6.0, 6.0);
      Cairo.Fill (Cr);
      Cairo.Destroy (Cr);

      -- Now invalidate the affected region of the drawing area.
      Self.Queue_Draw_Area (Gint (x - 3.0), Gint (y - 3.0), 6, 6);
   end draw_brush;

   function motion_notify_event_cb
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event_Motion)
      return  Boolean
   is
   begin
      -- paranoia check, in case we haven't gotten a configure event
      if surface = Cairo.Null_Surface then
         return False;
      end if;

      if (Event.State and Gdk.Types.Button1_Mask) > 0 then
         draw_brush (Self, Event.X, Event.Y);
      end if;

      -- We've handled it, stop processing
      return True;
   end motion_notify_event_cb;

   function button_press_event_cb
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event_Button)
      return  Boolean
   is
   begin
      -- paranoia check, in case we haven't gotten a configure event
      if surface = Cairo.Null_Surface then
         return False;
      end if;

      if Event.Button = Gdk_Button_Primary then
         draw_brush (Self, Event.X, Event.Y);
      elsif Event.Button = Gdk_Button_Secondary then
         Clear_Surface;
         Self.Queue_Draw;
      end if;
      -- We've handled the event, stop processing
      return True;
   end button_press_event_cb;
end draw_cb;
draw.gpr:
with "gtkada";

project Draw is

   for Source_Dirs use ("src");
   for Object_Dir use "obj";
   for Main use ("draw.adb");

   --  Enable Ada 2005.
   package Compiler is
      for Default_Switches ("ada") use ("-gnat05");
   end Compiler;

end Draw;
To compile:
gprbuild -P draw

This program is more complicated and it shows a problem with the current GtkAda (3.8.3) binding.

There is no button definition for GDK_BUTTON_PRIMARY and GDK_BUTTON_SECONDARY, etc., which have to be defined in package draw_cb ourselves by taking the definition from the gtk-3.0/gdk/gdkevents.h. As of 2013-03-26, the SVN source tree has been updated after I reported the bug, it now contains the button definitions in gdk-event.ads as Button_Primary, Button_Secondary and Button_MIddle, if you are using the SVN source after the day, you may want to use that definition directly.

The cairo surface variable is defined in the draw_cb package body, it has the similar effect of the C static definition that only procedures and functions in the package body could see it.

We use use type Cairo.Cairo_Surface; in the draw_cb package body, so that /= and = could be used directly on the type without exposing other Cairo definitions.

Saturday, March 22, 2014

Getting started with GtkAda - Building user interfaces

When constructing a more complicated user interface, with dozens or hundreds of widgets, doing all the setup work in Ada code is cumbersome, and making changes becomes next to impossible.

Thankfully, GtkAda supports the separation of user interface layout from your business logic, by using UI descriptions in an XML format that can be parsed by the Gtk_Builder class.

This example is exactly like the grid example, but using the Gtk_Builder class.

src/builder.adb:
with Gtk.Builder; use Gtk.Builder;
with Gtk.Window; use Gtk.Window;
with Gtk.Button; use Gtk.Button;
with Gtkada.Handlers;  use Gtkada.Handlers;

with builder_cb; use builder_cb;
with Glib; use Glib;
with Glib.Error; use Glib.Error;
with Gtk.Main;

procedure Builder is
   Win   : Gtk_Window;
   Button : Gtk_Button;
   builder : Gtk_Builder;
   ret : GUint;
   error : aliased GError;
begin
   --  Initialize GtkAda.
   Gtk.Main.Init;

   -- construct a Gtk_Builder instance and load our UI description
   Gtk_New (Builder);
   ret := Builder.Add_From_File ("builder.ui", error'Access);

   -- connect signal handlers to the constructed widgets
   Win := Gtk_Window (Builder.Get_Object ("window"));
   -- connect the "destroy" signal
   Win.On_Destroy (main_quit'Access);

   button := Gtk_Button (Builder.Get_Object ("button1"));
   button.On_Clicked (button_clicked'Access);

   button := Gtk_Button (Builder.Get_Object ("button2"));
   button.On_Clicked (button_clicked'Access);

   button := Gtk_Button (Builder.Get_Object ("quit"));
   -- connect the "clicked" signal of the button to destroy function
   Widget_Callback.Object_Connect
     (Button,
      "clicked",
      Widget_Callback.To_Marshaller (button_quit'Access),
      Win);
   -- All GTK applications must have a Gtk.Main.Main. Control ends here
   -- and waits for an event to occur (like a key press or a mouse event),
   -- until Gtk.Main.Main_Quit is called.
   Gtk.Main.Main;
end Builder;
src/builder_cb.ads:
with Gtk.Widget;  use Gtk.Widget;
with Gtk.Button; use Gtk.Button;

package builder_cb is
   procedure main_quit (Self : access Gtk_Widget_Record'Class);

   procedure button_clicked (Self : access Gtk_Button_Record'Class);
   procedure button_quit (Self : access Gtk_Widget_Record'Class);
end builder_cb;
src/builder_cb.adb:
with Ada.Text_IO; use Ada.Text_IO;
with Gtk.Main;

package body builder_cb is

   procedure main_quit (Self : access Gtk_Widget_Record'Class) is
   begin
      Gtk.Main.Main_Quit;
   end main_quit;

   procedure button_clicked (Self : access Gtk_Button_Record'Class) is
   begin
      Put_Line ("Hello World!");
   end button_clicked;

   procedure button_quit (Self : access Gtk_Widget_Record'Class) is
   begin
      Put_Line ("buttion_quit is called");
      Destroy (Self);
   end button_quit;
end builder_cb;
builder.gpr:
with "gtkada";

project Builder is

   for Source_Dirs use ("src");
   for Object_Dir use "obj";
   for Main use ("builder.adb");

   -- Enable Ada 2005.
   package Compiler is
     for Default_Switches ("ada") use ("-gnat05");
   end Compiler;
end Builder
builder.ui:
<interface> 
  <object id="window" class="GtkWindow"> 
    <property name="visible">True</property> 
    <property name="title">Grid</property> 
    <property name="border-width">10</property> 
    <child> 
      <object id="grid" class="GtkGrid"> 
        <property name="visible">True</property> 
        <child> 
          <object id="button1" class="GtkButton"> 
            <property name="visible">True</property> 
            <property name="label">Button 1</property> 
          </object> 
          <packing> 
            <property name="left-attach">0</property> 
            <property name="top-attach">0</property> 
          </packing> 
        </child> 
        <child> 
          <object id="button2" class="GtkButton"> 
            <property name="visible">True</property> 
            <property name="label">Button 2</property> 
          </object> 
          <packing> 
            <property name="left-attach">1</property> 
            <property name="top-attach">0</property> 
          </packing> 
        </child> 
        <child> 
          <object id="quit" class="GtkButton"> 
            <property name="visible">True</property> 
            <property name="label">Quit</property> 
          </object> 
          <packing> 
            <property name="left-attach">0</property> 
            <property name="top-attach">1</property> 
            <property name="width">2</property> 
          </packing> 
        </child> 
      </object> 
      <packing> 
      </packing> 
    </child> 
  </object> 
</interface>
To compile:
gprbuild -P builder

Note that Gtk_Builder can also be used to construct objects that are not widgets, such as tree models, adjustments, etc. That is the reason the method we use here is called Get_Object and returns a Glib.Object.GObject instead of a Gtk_Widget. That is why we need to cast the return value of Get_Object to the corresponding type to pass the compilation.

Normally, you would pass a full path to Add_From_File to make the execution of your program independent of the current directory. A common location to install UI descriptions and similar data is /usr/share/appname.

It is also possible to embed the UI description in the source code as a string and use Add_From_String to load it. But keeping the UI description in a separate file has several advantages: It is then possible to make minor adjustments to the UI without recompiling your program, and, more importantly, graphical UI editors such as glade can load the file and allow you to create and modify your UI by point-and-click.

There is one thing in this example differ from the C version. In the C example, gtk_builder_add_from_file() is called with a NULL pointer for the error parameter, in GtkAda version, the Error is a must, otherwise, you will get constraint error:

raised CONSTRAINT_ERROR : gtk-builder.adb:151 access check failed

Friday, March 21, 2014

Getting Started with GtkAda - Packing

When creating an application, you'll want to put more than one widget inside a window. Our first helloworld example only used one widget so we could simply use Add call to "pack" the widget into the window. But when you want to put more than one widget into a window, it it becomes important to control how each widget is positioned and sized. This is where packing comes in.

GtkAda comes with a large variety of layout containers whose purpose it is to control the layout of the child widgets that are added to them. You could see the Gtk+ Layout Containers for an overview. GtkAda has all the containers and normally has the form of Gtk_<Container_Name>

The following example shows how the Gtk_Grid container lets you arrange several buttons. It follows the same directory structure in the helloworld example.

src/grid.adb:
with Gtk.Main;
with Gtk.Window;      use Gtk.Window;
with Gtk.Grid;        use Gtk.Grid;
with Gtk.Button;      use Gtk.Button;
with Gtkada.Handlers; use Gtkada.Handlers;

with grid_cb; use grid_cb;

procedure Grid is
   Win    : Gtk_Window;
   Grid   : Gtk_Grid;
   Button : Gtk_Button;
begin
   --  Initialize GtkAda.
   Gtk.Main.Init;

   -- create a top level window
   Gtk_New (Win);
   Win.Set_Title ("Grid");
   -- set the border width of the window
   Win.Set_Border_Width (10);
   -- connect the "destroy" signal
   Win.On_Destroy (main_quit'Access);

   -- Here we construct the container that is going pack our buttons
   Gtk_New (Grid);

   -- Packed the container in the Window
   Win.Add (Grid);

   -- create a button with label
   Gtk_New (Button, "Button 1");
   -- connect the click signal
   Button.On_Clicked (button_clicked'Access);

   -- Place the first button in the grid cell (0, 0), and make it fill
   -- just 1 cell horizontally and vertically (ie no spanning)
   Grid.Attach (Button, 0, 0, 1, 1);

   -- create another button with label
   Gtk_New (Button, "Button 2");
   Button.On_Clicked (button_clicked'Access);

   -- Place the second button in the grid cell (1, 0), and make it fill
   -- just 1 cell horizontally and vertically (ie no spanning)
   Grid.Attach (Button, 1, 0, 1, 1);

   -- create the quit button
   Gtk_New (Button, "Quit");
   -- connect the "clicked" signal of the button to destroy function
   Widget_Callback.Object_Connect
     (Button,
      "clicked",
      Widget_Callback.To_Marshaller (button_quit'Access),
      Win);
   -- Place the Quit button in the grid cell (0, 1), and make it
   -- span 2 columns.
   Grid.Attach (Button, 0, 1, 2, 1);

   -- Now that we are done packing our widgets, we show them all
   -- in one go, by calling Win.Show_All.
   -- This call recursively calls Show on all widgets
   -- that are contained in the window, directly or indirectly.
   Win.Show_All;

   -- All GTK applications must have a Gtk.Main.Main. Control ends here
   -- and waits for an event to occur (like a key press or a mouse event),
   -- until Gtk.Main.Main_Quit is called.
   Gtk.Main.Main;
end Grid;
src/grid_cb.ads:
with Gtk.Widget;  use Gtk.Widget;
with Gtk.Button;  use Gtk.Button;
with Glib.Object;

with Gdk.Event;

package grid_cb is
   function main_del
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event)
      return  Boolean;
   procedure main_quit (Self : access Gtk_Widget_Record'Class);
   procedure button_clicked (Self : access Gtk_Button_Record'Class);
   procedure button_quit (Self : access Gtk_Widget_Record'Class);
end grid_cb;
src/grid_cb.adb:
with Ada.Text_IO; use Ada.Text_IO;

with Gtk.Main;

package body grid_cb is
   -- If you return false in the "delete_event" signal handler,
   -- GTK will emit the "destroy" signal. Returning true means
   -- you don't want the window to be destroyed.
   --
   -- This is useful for popping up 'are you sure you want to quit?'
   -- type dialogs.
   function main_del
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event)
      return  Boolean
   is
   begin
      Put_Line ("Delete event encounter.");
      return True;
   end main_del;

   procedure main_quit (Self : access Gtk_Widget_Record'Class) is
   begin
      Gtk.Main.Main_Quit;
   end main_quit;

   procedure button_clicked (Self : access Gtk_Button_Record'Class) is
   begin
      Put_Line ("Hello clicked");
   end button_clicked;

   procedure button_quit (Self : access Gtk_Widget_Record'Class) is
   begin
      Put_Line ("buttion_quit is called");
      Destroy (Self);
   end button_quit;

end grid_cb;
grid.gpr
with "gtkada";

project Grid is

   for Source_Dirs use ("src");
   for Object_Dir use "obj";
   for Main use ("grid.adb");

   --  Enable Ada 2005.
   package Compiler is
      for Default_Switches ("ada") use ("-gnat05");
   end Compiler;

end Grid
To compile the program:
gprbuild -P grid

You may notice the callback functions are exactly the same as helloworld, you could reuse it by just rename the packages or simple using the same hello_cb package and in grid.adb with and use it with hello_cb; use hello_cb;, then you don't need to change anything on the call backs.

Thursday, March 20, 2014

Getting Started with GtkAda

There is not much resources on how to program with GtkAda. GtkAda User’s Guide provides some information but not enough to start from scratch. I tried to follow the Getting Started with GTK+ but writing it with GtkAda.

I will assume you have gnat gpl 2013 and GtkAda installed. I use the svn version of GtkAda (3.8.3) in stead of the GtkAda 2013 GPL 3.4.2.

Basics

We'll start with the simplest program possible. This program will create an empty window.
The source files are under the following directory structure:
example-top-dir
  |-- obj
  |-- src
  `-- hello.gpr
src/hello.adb:
with Gtk.Main;
with Gtk.Window;      use Gtk.Window;

with Main_quit;

procedure Hello is
   Win   : Gtk_Window;
begin
   --  Initialize GtkAda.
   Gtk.Main.Init;

   -- create a top level window
   Gtk_New (Win);
   Win.Set_Title ("Window");

   -- connect the "destroy" signal
   Win.On_Destroy (main_quit'Access);

   --  Show the window
   Win.Show_All;

   --  Start the Gtk+ main loop
   Gtk.Main.Main;
end Hello
src/main_quit.adb:
with Gtk.Widget;      use Gtk.Widget;
with Gtk.Main;

procedure main_quit (Self : access Gtk_Widget_Record'Class) is
begin
   Gtk.Main.Main_Quit;
end main_quit;
The project file hello.gpr:
with "gtkada";

project Hello is

   for Source_Dirs use ("src");
   for Object_Dir use "obj";
   for Main use ("hello.adb");

   --  Enable Ada 2005.
   package Compiler is
      for Default_Switches ("ada") use ("-gnat05");
   end Compiler;

end Hello;
You then could compile the program as
gprbuild -P hello

All GtkAda application will, of course, with the Gtk.Main and Gtk.Window packages. We'll need to add a call back to quit the program, so with the Main_quit procedure.

In the main procedure Hello, we declared a Win variable as of type Gtk_Window. Since we have use Gtk.Window, we does not need to give the full path as Gtk.Window.Gtk_Window.

The following line will call Gtk.Main.Init, which is the initialization function for GtkAda; it calls the underline gtk_init() function and will setup GtkAda, the type system, the the connection to the windowing environment, etc. The Gtk.Main.Init take care of passing command line arguments counter and string array to the C gtk_init() function; this allows GTK+ to parse specific command line arguments that control the behavior of GTK+ itself. The parsed arguments will be removed from the array, leaving the unrecognized ones for your application to parse.

For more information on which command line arguments GtkAda recognizes, please refer to the Running GTK+ Applications section.

A call to Gtk_New (Win) will create a new Gtk_Window and store it inside the Win variable. Gtk_New has Gtk.Enums.WindowToplevel as its default The_Type argument, so we don't need to specify it with the call. Which means the the Gtk_Window will be managed by the windowing system: it will have a frame, a title bar and window controls, depending on the platform.

In order to terminate the application when the Win is destroyed we connect the "destroy" signal to the Main_quit function by calling Win.On_Destroy (main_quit'Access);

This function will terminate the GtkAda main loop started by calling Gtk.Main.Main later. The "destroy" signal is emitted when a widget is destroyed, either by explicitly calling Gtk.Widget.Destroy or when the widget is unparented. Top-level Gtk_Windows are also destroyed when the Close window control button is clicked.

Gtk_Window are hidden by default. By calling Win.Show_All, we are asking GtkAda to set the visibility attribute so that it can be displayed. Al this work is done after the main loop has been started.

The last line of interest is the call to Gtk.Main.Main. This function will start the GtkAda main loop and will block the control flow of Hello until Gtk.Main.Main_Quit is called.

While the program is running, GtkAda is receiving events. These are typically input events caused by the user interacting with your program, but also things like messages from the window manager or other applications. GtkAda processes these and as a result, signals may by emitted on your widgets. Connecting handlers for these signals is how you normally make your program do somthing in response to user input.

In the main_quit procedure, Gtk.Main.Main_Quit is called to quit the program.

The following example is slightly more complex, and tries to showcase some of the capabilities of GtkAda.

In the long tradition of programming languages and libraries, it is called Hello, World.

Using the same project file of the first example, here are the sources:

src/hello.adb:
with Gtk.Main;
with Gtk.Window;      use Gtk.Window;
with Gtk.Button;      use Gtk.Button;
with Gtkada.Handlers; use Gtkada.Handlers;

with hello_cb; use hello_cb;

procedure Hello is
   Win    : Gtk_Window;
   Button : Gtk_Button;
begin
   --  Initialize GtkAda.
   Gtk.Main.Init;

   -- create a top level window
   Gtk_New (Win);
   Win.Set_Title ("Window");

   -- When the window emits the "delete-event" signal (which is emitted
   -- by GTK+ in response to an event coming from the window manager,
   -- usually as a result of clicking the "close" window control), we
   -- ask it to call the on_delete_event() function as defined above.
   Win.On_Delete_Event (main_del'Access);

   -- connect the "destroy" signal
   Win.On_Destroy (main_quit'Access);

   -- set the border width of the window
   Win.Set_Border_Width (10);

   -- create a button with label
   Gtk_New (Button, "Hello World");

   -- connect the click signal
   Button.On_Clicked (button_clicked'Access);

   -- connect the "clicked" signal of the button to destroy function,
   -- it will destroy the Win when button is clicked.
   Widget_Callback.Object_Connect
     (Button,
      "clicked",
      Widget_Callback.To_Marshaller (button_quit'Access),
      Win);

   -- This packs the button into the window. A Gtk_Window inherits from Gtk_Bin
   -- which is a special container that can only have one child.
   Win.Add (Button);

   --  Show all the window
   Win.Show_All;

   -- All GTK applications must have a Gtk.Main.Main. Control ends here
   -- and waits for an event to occur (like a key press or a mouse event),
   -- until Gtk.Main.Main_Quit is called.
   Gtk.Main.Main;
end Hello;
src/hello_cb.ads:
with Gtk.Widget;  use Gtk.Widget;
with Gtk.Button;  use Gtk.Button;
with Glib.Object;

with Gdk.Event;

package hello_cb is
   function main_del
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event)
      return  Boolean;
   procedure main_quit (Self : access Gtk_Widget_Record'Class);
   procedure button_clicked (Self : access Gtk_Button_Record'Class);
   procedure button_quit (Self : access Gtk_Widget_Record'Class);
end hello_cb;
src/hello_cb.adb:
with Ada.Text_IO; use Ada.Text_IO;
with Gtk.Main;

package body hello_cb is
   -- If you return false in the "delete_event" signal handler,
   -- GTK will emit the "destroy" signal. Returning true means
   -- you don't want the window to be destroyed.
   --
   -- This is useful for popping up 'are you sure you want to quit?'
   -- type dialogs.
   function main_del
     (Self  : access Gtk_Widget_Record'Class;
      Event : Gdk.Event.Gdk_Event)
      return  Boolean
   is
   begin
      Put_Line ("Delete event encounter.");
      return True;
   end main_del;

   procedure main_quit (Self : access Gtk_Widget_Record'Class) is
   begin
      Gtk.Main.Main_Quit;
   end main_quit;

   procedure button_clicked (Self : access Gtk_Button_Record'Class) is
   begin
      Put_Line ("Hello clicked");
   end button_clicked;

   procedure button_quit (Self : access Gtk_Widget_Record'Class) is
   begin
      Put_Line ("buttion_quit is called");
      Destroy (Self);
   end button_quit;

end hello_cb;
You compile the program with the same command:
gprbuild -P hello

This time, we defined a hello_cb package to host all the call back functions.

In GtkAda, there is no need for g_signal_connect_swapped(), we use the Widget_Callback.Object_Connect method.

P.S. All source code from these series of Getting Started with GtkAda are now at GitHub.

Friday, August 16, 2013

Automatic new line at program exit for Ada.Text_IO output.

For these two simple programs:

with Ada.Text_IO; use Ada.Text_IO;

procedure Hello is
begin
    Put("Hello");
    Put(" ");
    Put("World!");
end Hello;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Text_IO.Text_Streams; use Ada.Text_IO.Text_Streams;

procedure hello_stream is
    std_out   : Stream_Access;
begin
    std_out := Stream (Standard_Output);

    String'Write (std_out, "Hello");
    String'Write (std_out, " ");
    String'Write (std_out, "World!");
end hello_stream;

When executing the two program, you would have two different result, for hello program:

# ./hello
Hello World!
#

For hello_stream program:

# ./hello_stream
Hello World!# 

I have known this problem for some time, but did not dig into the root cause of it.  Recently I asked this question in comp.lang.ada group for a root cause of it. And Jeffrey gave me the answer.

The related references in ARM are A.7(6) and A.10.1(86).

For gnat compiler File_Type is defined at a-textio.ads:

...
type Text_AFCB;
type File_Type is access all Text_AFCB;

type Text_AFCB is new FCB.AFCB with record
...
end record
...
procedure AFCB_Close (File : not null access Text_AFCB);
Where FCB.AFCB is a tagged type defined in s-ficobl.ads.

In AFCB_Close() procedure, it calls Terminate_Line(), which will try to insert a new line if not terminate properly.

It seems, when program exit, gnat doing the finalization for Standard_Output call AFCB_Close(), which insert the new line if necessary.

p.s. This is tested under Slackware64 Linux with GCC gnat compiler.

Friday, September 28, 2012

AdaTutor - Advanced Topics (9)

Loose Ends and Pitfalls

This last section covers a few topics that were omitted earlier for simplicity.  We also mention some common errors made by Ada programmers.  Beginners aren't expected to understand every paragraph, so we won't ask questions here.

Some terminals and printers don't support the entire ASCII character set.  In an Ada program, the vertical bar | may be replaced with the exclamation mark !, as in when 3 ! 5 =>.  Also, a pair of sharp signs # may be replaced with a pair of colons :, as in 16:7C03:.  The quotation marks around a string constant may be replaced with percent signs if the string doesn't contain any quotation marks.  In that case, any percent signs within the string must be doubled.  For example, Put(%a 10%% increase%);.  These character replacements shouldn't be used in programs if the equipment will support the standard characters.

Section 4.9(2) of the Ada 95 RM gives a detailed definition of a static expression, but briefly, an expression is called static if it can be evaluated at compile time and isn't too complicated.  In almost every case where a constant normally appears, a static expression may also be used.  For example, an address representation clause normally takes a constant of type System.Address.  A static expression of this type is also acceptable, as in for Clock_Interrupt'Address use 16*16;.

The unary minus is always an operator and never part of a constant.  Thus -5 is actually a static expression and not a constant.  Normally, this doesn't concern the programmer, because, as we just said, static expressions can usually appear where a constant normally appears.  However, in a few special situations we can get into trouble.  For example, in Ada 83 we can write for I in 10 .. 20 loop and A : array(10 .. 20) of Float; but we can't omit the words Integer range in for I in Integer range -10 .. 10 loop and A : array(Integer range -10 .. 10) of Float;! (Ada 95 lets us write these without Integer range, however.)

Also, if a package P declares type Count is new Integer; then the unary minus operator for that type is part of the package.  If our program withs but doesn't use P, we can write A : P.Count := 1; but not B : P.Count := -1;.  We either have to use the package, rename P."-", or write B : P.Count := P."-"(1);.  Because we sometimes don't want to use the package except to avoid writing P."-"(1), Ada 95 lets us write

with P; use type P.Count;

This automatically uses only the infix operators belonging to the type P.Count.  Other operators belonging to P.Count, and other identifiers in the package P still require dot notation with the above use type clause.

The operators have precedence, so that 1 + 2 * 3 means 1 + (2 * 3).  The precedence of all the operators is given in section 4.5 of the Ada 95 RM.  A programmer should never have to look these up, because parentheses should be used for any cases that aren't obvious.  Unary minus has a low precedence, so -A mod B means -(A mod B).

If we write A, B : array(1 .. 5) of Float; then A and B have different anonymous types, and we can't write A := B;.  To fix this, write type Vector5 is array(1 .. 5) of Float; and then A, B : Vector5;, or write type Vector is array(Integer range <>) of Float; and A, B : Vector(1 .. 5);.

Ada 83 and Ada 95 will automatically convert from a universal type to a named type, but only Ada 95 will convert the other way.  For example,

   C1 : constant Integer := 1;  -- legal
   C2 : constant Integer := 2;  -- legal
   C3 : constant := C1 + C2;    -- legal only in Ada 95

When arrays are assigned, the subscripts don't have to match; only the lengths and types need match.  But in Ada 83 (not Ada 95), if a formal parameter ("dummy argument") of a subprogram is a constrained array, the subscripts in the call to the subprogram must match.  For example, the last line here will raise Constraint_Error in Ada 83:

   subtype Name is String(1 .. 30);
   John : Name;
   Line : String(1 .. 80);
   procedure Display(Person : in Name);
   ...
   John := Line(51 .. 80);   -- legal
   Display(Line( 1 .. 30));  -- legal
   Display(Line(51 .. 80));  -- Raises Constraint_Error in Ada 83

When a subprogram formal parameter is an unconstrained array, beginners often wrongly assume that the subscripts will start with one.  For example,

   Line : String(1 .. 80);
   procedure Display(S : in String) is
   begin
     for I in 1 .. S'Length loop
       ... S(I) ...
This will raise Constraint_Error if we call Display(Line(51 .. 80));.  The for statement should be changed to say for I in S'Range loop.

Remember that elaboration occurs at run time.  The following raises Program_Error by trying to activate a task before elaborating its body:

   task type T is ... end T;
   type P is access T;
   T1 : P := new T;
   task body T is ... end T;
The third line should be changed to T1 : P; and the statement T1 := new T; should be placed in the executable region.

Similarly, this procedure tries to activate a function before elaborating its body.  The initialization of J should be moved to the executable region:

   procedure Test is
   function X return Integer;
      J : Integer := X;  -- Raises Program_Error.
      function X return Integer is
      begin
         return 5;
      end X;
   begin
      null;
   end Test;

A return statement in a function is used with an object: return Answer;.  However, return may also appear without an object in a procedure; we simply write return;.  Normally, a procedure returns after executing its last statement, but an early return is possible by this method.  In the author's opinion, such early returns should be used rarely, if at all.

Many implementations of Ada allow us to insert machine code into a program. Ada 95 compilers that allow machine code insertions provide a package System.Machine_Code which usually contains a rather complex record definition representing the format of a machine instruction.  We can write a procedure or function that withs and uses that package.  In place of the usual Ada statements in the executable region, we write record aggregates, each one representing a machine code instruction.  Since the package System.Machine_Code varies greatly from one implementation to the next, you'll have to consult the compiler documentation.

Ada 83 compilers that allow machine code insertions sometimes provide a package Machine_Code and sometimes provide a pragma, such as pragma Native, which can be inserted in the middle of a procedure, function, etc.  Again, the details vary widely from one implementation to the next, and you'll have to consult the compiler documentation.

In the unusual case of a for loop index hiding an explicitly declared object of the same name, the explicitly declared object can be referenced inside the loop. Simply use dot notation with the name of the compilation unit (function, procedure, etc.) For example, the following is legal:

   procedure Main is
      Ix : Float;
      J  : Integer;
   begin
      Ix := 3.2;
      for Ix in 1 .. 10 loop
         Main.Ix := 6.0;
         J := Ix;
      end loop;
   end Main;
Inside the loop, Ix refers to the loop index, and the explicitly declared object can be referenced by writing Main.Ix.  Outside the loop, Ix refers to the explicitly declared object, and the loop index doesn't exist.

In the rare case of an aggregate containing just one element, we must use named notation rather than positional notation.  For example, the last line is illegal in the following program segment, because the right hand side is a Float rather than an array of one Float.

   type Vector is array(Integer range <>) of Float;
   A : Vector(1 .. 1);
   ...
   A := (1 => 2.3);  -- legal
   A := (2.3);  -- illegal

Of course, it's OK to use positional notation in calls to subprograms with only one parameter, for example, Put_Line("Hello");.

Annexes C through H of the Ada 95 RM describe optional features of Ada 95.  An Ada 95 compiler may implement any, all, or none of these.  Consult your compiler documentation for details.  The optional Ada 95 features are as follows:

  • ANNEX C. Systems Programming: Access to machine operations, interrupt support, shared variable control, task identification, etc.
  • ANNEX D. Real-Time Systems: Dynamic task priorities, scheduling, dispatching, queueing, monotonic time, etc.  An implementation that provides this must provide Systems Programming as well.
  • ANNEX E. Distributed Systems: Multiple partitions of an Ada program executing concurrently.
  • ANNEX F. Information Systems: Decimal types for handling monetary values. "Picture" strings to simplify output of monetary amounts.
  • ANNEX G. Numerics: Complex numbers, improved accuracy requirements for floating-point arithmetic, random number generation, etc.
  • ANNEX H. Safety and Security: Adds requirements on compilers for safety-critical systems.

Well, we haven't covered all there is to know about Ada, but this has been a very thorough course.  If you've come this far and completed the six Outside Assignments, you should be an excellent Ada programmer.  To continue learning, start doing all your casual programming in Ada.  If you need a simple program to balance your checkbook, write it in Ada!  At this point, switching to Ada for all your programming will do you much more good than further instruction from a tutorial program.

The best way to answer any remaining questions about Ada is to "ask the compiler" by writing a brief test program, especially if your compiler is validated.  You can also look in the Ada 95 RM, which, by definition, does cover all of the Ada language.  However, the RM isn't easy reading!

The best way to debug a short program is often to execute it by hand, with pencil and paper.  You can also add extra statements to the program to display intermediate results, and remove them later.

We wish you success with Ada, and welcome your comments and suggestions!

< prev

Wednesday, September 26, 2012

AdaTutor - Advanced Topics (8)

Pragmas

A pragma is a message to the compiler.  The pragmas that are predefined by Ada are all referenced in Annex L of the Ada 95 RM; we'll discuss the most important ones here.  A particular implementation of Ada need not honor all the predefined pragmas, and it may add some of its own.  (One implementation of Ada adds a pragma Time_Slice, used with tasking.)  Unlike representation clauses, unimplemented predefined pragmas do not cause error messages; the compiler simply ignores them.  This enhances program portability.  Any additional pragmas added by a particular implementation of Ada will be explained in the compiler documentation.  The most important predefined pragmas are these:

The statements pragma List(On); and pragma List(Off); turn on and off the compiler listing.  Also, pragma Page; will cause the compiler listing to start a new page, if the listing is turned on.  These pragmas are allowed almost anywhere in the program.

Within the declarative region we can write pragma Optimize(Time); or pragma Optimize(Space); to ask the compiler to optimize the program for minimum execution time or minimum memory usage.

We can write pragma Inline(...); with the name of a subprogram to ask the compiler to write inline code in place of every call to the subprogram.  Even implementations of Ada that honor this pragma will ignore it if the subprogram is recursive.

We can call a subprogram or reference an object written in another langauge with the Ada 95 pragma Import.  This pragma takes three parameters: the name of the language, the Ada name of the subprogram or object, and an optional external linker name for the subprogram or object.  Similarly, a program written in another language can call an Ada program with the Ada 95 pragma Export, taking the same three parameters.  The Ada 95 pragma Convention specifies that objects of a type should be stored using the conventions of another language.  Pragma Convention takes two parameters: the name of the language and the Ada name of the type whose storage convention is being specified.

Ada 83 provides only a pragma Interface.  It is similar to the Ada 95 pragma Import, but is useful only to call a subprogram written in another language, not to reference an object.  Pragma Interface takes only two parameters: the name of the language and the subprogram name.

We can ask the compiler to minimize memory occupied by a record or array by writing, after the type declaration, pragma Pack(...); with the name of the type.  Note that the specification for package Standard (in Annex A.1 of the Ada 95 RM) contains pragma Pack(String); after the definition of type String.

Package System defines a subtype of Integer called Priority.  We can assign a priority to a task by writing, in the specification, pragma Priority(...); with a parameter of subtype System.Priority.  Higher numbers denote greater urgency.

The pragma Suppress can be used to ask the compiler to turn off certain checks, such as Constraint_Error.  It's dangerous and shouldn't be used unless absolutely necessary because of time or memory constraints.

Question

In the author's opinion, which one of these is not dangerous?
  1. Ada.Unchecked_Deallocation
  2. pragma Pack
  3. pragma Suppress

< prev   next >

AdaTutor - Advanced Topics (7)

Unchecked Conversion and Unchecked Deallocation

Ada comes with a generic function Ada.Unchecked_Conversion and a generic procedure Ada.Unchecked_Deallocation.  (In Ada 83, the names don't contain Ada., and Ada 95 accepts the shorter names for compatibility.)  They can be instantiated for any type.  Both are somewhat dangerous to use, but we'll describe them briefly.  Their specifications (slightly simplified) are below; the Ada 83 specifications don't have (<>).

   generic
      type Source(<>) is limited private;
      type Target(<>) is limited private;
   function Ada.Unchecked_Conversion(S : Source) return Target;

   generic
      type Object(<>) is limited private;
      type Name       is access Object;
   procedure Ada.Unchecked_Deallocation(X : in out Name);

Ada.Unchecked_Conversion "converts" from one type to another without doing any arithmetic or bit manipulation, letting us look at an object of one type as if it were of another type.  (This is similar to using EQUIVALENCE in Fortran.)

The results of using Ada.Unchecked_Conversion may be unpredictable if the two types don't occupy the same amount of storage.

One use of Ada.Unchecked_Conversion might be to allow us to and two Integers.  (Ada 95 allows us to and two objects of a modular type, but not two Integers.) Some Ada compilers come with a package that enables us to and two Integers, but many compilers have no such package.  Suppose that types Integer and Boolean occupy the same amount of storage.  If our program says with Ada.Unchecked_Conversion; we could write

function Int_To_Bool is new Ada.Unchecked_Conversion(Integer, Boolean);
function Bool_To_Int is new Ada.Unchecked_Conversion(Boolean, Integer);
function "and"(Left, Right : in Integer) return Integer is
begin
   return Bool_To_Int(Int_To_Bool(Left) and Int_To_Bool(Right));
end "and";

Using Ada.Unchecked_Conversion usually destroys program portability.

Ada.Unchecked_Deallocation allows us to free the memory occupied by an object accessed through an access type.  Some systems automatically reclaim memory when it's needed.  However, the execution time for that so-called garbage collection tends to be long and unpredictable.  Suppose we have type P is access Link; and Ptr : P;.  Also suppose that we no longer need the object accessed by Ptr and we're sure that no other objects access the same object as Ptr.  If our program says with Ada.Unchecked_Deallocation; we can write

procedure Free is new Ada.Unchecked_Deallocation(Link, P);
...
Free(Ptr);
This will release the memory occupied by the object accessed by Ptr, and then set Ptr to null.  But there's a danger.  If there's another object that accessed what Ptr accessed, it now accesses released memory.  A reference through such an object will have unpredictable results.  In general, it's best to let the system handle the reclaiming of memory.  That way there's no danger of dangling references.

< prev   next >

AdaTutor - Advanced Topics (6)

Representation Clauses and System

Ada normally represents an enumeration type internally with successive integers starting at zero.  For example, if we write

type Command is (Left, Right, Forward, Back);
the compiler will normally represent Left with 0, Right with 1, etc.  Usually this doesn't concern the programmer.  However, after the above declaration, we can specify the internal representation with a representation clause like this:
for Command use (Left => 1, Right => 2, Forward => 4, Back => 8);
We might want to do that if, for example, we're sending a value of type Command to some hardware which will interpret the bit patterns.  The values must be assigned in increasing order with no duplications, but gaps are permitted.  The attributes 'Succ, 'Pred, 'Pos, and 'Val are not affected. Thus Command'Pos(Back) is still 3.

Ada lets us specify the Size, in bits, of the objects of a given type:

   type Num is range 0 .. 100;
   for Num'Size use 8;
Similarly, we can specify the attribute 'Small for a fixed point type:
   type Voltage is delta 0.01 range -20.0 .. 20.0;
   for Voltage'Small use 1.0/128.0;
These attributes can also be read:
   I : Integer := Num'Size;
   F : Float := Voltage'Small;

Before discussing the remaining types of representation clauses, we must briefly mention the package System that comes with Ada.  System contains implementation dependent specifications.

A brief outline of package System is in section 13.7 of the Ada 95 RM.  However, the full package specification should appear in the documentation that came with your compiler.  Of interest here is the type Address.  In our examples, we'll assume that System.Address is some integer type.  (On some PC implementations of Ada, type System.Address is a bit more complicated.)

We can specify the absolute address of a variable, a constant, a task entry, a procedure, or a package, with a constant of type System.Address.  In Ada 83 the program must with System.  This is useful for memory-mapped I/O, interrupt handlers, etc.  For example:

   Modem_Control : Integer;
   for Modem_Control'Address use 16#7C00#;
   task Interrupt_Handler is
      entry Clock_Interrupt;
      for Clock_Interrupt'Address use 16#100#;
   end Interrupt_Handler;
   procedure Keystroke;
   for Keystroke'Address use 16#200#;

In Ada 83, the syntax is for Keystroke use 16#200#, and Ada 95 accepts this older syntax for compatibility.

Finally, we can specify how records are stored.  This example forces A and B to be stored in bits 0 .. 3 and 4 .. 7 of byte 0 of the record, and C and D to be packed into byte 1.  The optional clause for Packed'Alignment use 2; specifies that all records of type Packed will begin at even addresses:

   type Nibble is range 0 .. 15;
   type Packed is record
       A, B, C, D : Nibble;
   end record;
   for Packed'Alignment use 2;
   for Packed use record
      A at 0 range 0 .. 3;
      B at 0 range 4 .. 7;
      C at 1 range 0 .. 3;
      D at 1 range 4 .. 7;
   end record;

In Ada 83, we use the clause record at mod 2; after for Packed use, rather than saying for Packed'Alignment use 2; before for Packed use:

   type Nibble is range 0 .. 15;
   type Packed is record
      A, B, C, D : Nibble;
   end record;
   for Packed use record at mod 2;
      A at 0 range 0 .. 3;
      B at 0 range 4 .. 7;
      C at 1 range 0 .. 3;
      D at 1 range 4 .. 7;
   end record;

Again, Ada 95 accepts this older syntax for compatibility.

An implementation of Ada need not accept most representation clauses to meet the standard.  If any clause is rejected, an error message will be displayed.

Question

   type Answer is (Yes, No, Maybe);
   for Answer use (Yes => 1, No => 2, Maybe => 4);
What is Answer'Val(20)?
  1. Answer'Val(2) is No.
  2. Answer'Val(2) is Maybe.

< prev   next >