Att skriva ett Ada-program


Innehåll:


Strukturering av Adaprogram

Utveckling av Adaprogram sker på samma sätt som för andra kompilerande programmeringsspråk. Det som skiljer Ada från de flesta andra språk är att det är förberett för storskalig programutveckling med hjälp av ett modulbegrepp. Motsvarande kan till viss del åstadkommas i andra spåk såsom C genom att sammla kod tillhörande en "modul" i en fil, men i Ada har detta utvecklas ett steg längre.

Vidare så finns det definierat i standarden vad en progremmutvecklingsmiljö för ada skall innehålla. Bland komponenterna nämns ett avlusningssystem och en editor avsedd för Adaprogrammering.

"Paket" (moduler) i Ada

Ada är avsett att användas av många programmerare samtidigt och har i sig mekanismer för att deklarera programmoduler (PACKAGE). Ett antal fördefinierade moduler finns tillgängliga för hantering av bland annat filer och flyttalsfunktioner, och det är rekomenderat att man utvecklar sina egna program som nya moduler. Alla moduler kan separatkompileras.

För att använda en modul i sitt program använder man satserna WITH och eventuellt USE. Med WITH talar man om att man ämnar använda ett visst paket, och med USE anger man att man vill slippa skriva "paketnamn.funktion1" för att anropa funktion1 ur "paketnamn". Alla paket är dock inte färdiga att inkluderas direkt i ett program. Det kan vara ett paket som implementerar en datastruktur där man inte vet vilken typ av data som skall lagras. Sådana paket kallas generiska, eftersom de är genrellt användbara för en flertal olika typer av data.

För att använda ett generiskt paket så måste man skapa ett komplet paketet med den saknade informationen. Detta kallas för att instantiera ett paket. I exemplet nedan finns raden:

  PACKAGE I_io IS NEW Integer_io (INTEGER);
Med den raden skapas ett nytt paket kallat "I_io" genom att man anger att det generiska paketet "Integer_io" (som kan handskas med generella heltal, oavsett representation, godtyckligt stora) skall användas för tal av heltalstypen "INTEGER".

Här följer ett exempel som visar hur vi kan instantiera moduler för in- och utmatning av heltal och flyttal. I package standard som alltid skall finnas tillgängligt i en validerad Adakompilator finns fördefinierade moduler för att hantera detta.

WITH Text_io;         -- Deklarera att filhantering skall anvaendas.
USE Text_io;          -- Goer deklarationerna i Text_io synliga i programmet
PROCEDURE Main IS
  -- Instantiera packages för in- och utmatning av heltal och flyttal.
  PACKAGE I_io IS NEW Integer_io (INTEGER);
  PACKAGE F_io IS NEW Float_io (FLOAT);
  USE I_io, F_io;
  -- Deklarera och initiera naagra variabler.
  i : integer := 1;
  s : string(1..8) := "Hej Hopp";
  r : float := -5.2;
BEGIN
  -- Skriv ut variabler och konstanter
  Put(r);
  Put(i);
  Put(s);
  -- Skriv ut en vagnretur och `tvinga ut' utskriften.
  New_Line; Flush;
END;

WITH och USE deklarerar att filhanteringsmodulen Text_io skall användas i den kommande proceduren. Moduler för att hantera in- och utmatning av normala heltal och flyttal instantieras. Därefter öppnas de nya delmodulerna I_io och F_io så att deras in- och utmatningsrutiner blir tillgängliga. Vi testar delmodulerna genom att deklarera en variabel av varje typ och skriva ut dessa på terminalen.

Obs: En liten egenhet med gnat (åtminstone vissa versioner) är att I/O är buffrad, dvs systemet skriver inte ut något förränn utmatningsbufferten är full. För att tvinga fram utskrifter använder man proceduren Flush, lämpligen i samband med New_Line.

Procedurdeklarationer

Här följer ett exempel på hur procedurer deklareras i Ada. Exemplet visar hur anropsspårning kan implementeras med procedurerna TraceCall och TraceReturn. Observera att alla procedurer är deklarerade inne i proceduren main, den enda globala Ada-enheten i detta exempel.

WITH Text_io;
USE Text_io;        
PROCEDURE Main IS
  PACKAGE I_io IS NEW Integer_io (INTEGER);
  PACKAGE F_io IS NEW Float_io (FLOAT);
  USE I_io, F_io;
  indent : count := 1;
  -- count aer en datatyp som definieras i modulen Text_io
  PROCEDURE TraceCall(s : IN string) IS
  BEGIN
    Set_Col(indent); Put(s); Put(">"); New_Line; Flush;
    indent := indent + 3;
  END;
  PROCEDURE TraceReturn(s : IN string) IS
  BEGIN
    indent := indent - 3;
    Set_Col(indent); Put("<"); New_Line; Flush;
  END
  PROCEDURE foo IS
  BEGIN
    TraceCall("foo");
    -- koden foer foo
    TraceReturn("foo");
  END
  PROCEDURE fie IS
  BEGIN
    TraceCall("fie");
    -- koden foer fie med anrop till proceduren foo
    foo;
    TraceReturn("fie");
  END
BEGIN
  fie;
END;

Kompilering av detta program (givet att det är sparat i filen main.adb):

  gcc -gnatq -m10 -c main.adb
  gnatbl -o exempel main.ali

Det körbara programmet heter nu exempel.


Processer och processinteraktion i Ada

Inledning

Processer i Ada kallas för tasks och kommunicerar genom rendezvous. Innebörden av detta är att för varje task anges i dess specifikationsdel vilka ingångar (eng. entry) som den har. Ingångarna realiseras i processkroppen med satsen ACCEPT. Anrop av en ingång sker med punktnotation med processnamnet och ingången. Ingångarna till en task kan ha parametrar, vilka kan vara inparametrar, utparametrar eller kombinerade in- och utparametrar. Parametrarna deklareras på samma sätt som för procedurer.

Specifikation av processer

En process som saknar ingångar:

  TASK name;

En process med ingångar specificeras med:

  TASK name IS
    ENTRY ent1;
    ENTRY ent2(parameters);
  END name;

Processtyper

Det är också möjligt att specificera en processtyp, med eller utan ingångar:

  TASK TYPE name IS
    ENTRY ent1;
    ENTRY ent2(parameters);
  END name;

I det här fallet deklareras processerna som variabler av processtyp:

  pp : name;

eller

  pn : ARRAY (1..10) OF name;

När programmet exekveras kommer i det sista fallet en array med 10 identiska processer att exekveras parallellt. Ett rendezvous med någon av dessa sker genom att man som i en array av vilken typ som helst anger strukturens namn, det önskade elementets ordningsnummer samt ingångens namn. Observera punktnotationen.

  pn(3).ent2(parameters);

Variabeldeklarationer kommer efter typ- och processpecifikationer men före processernas implementeringsdelar (processkropparna).

Processernas implementeringsdelar

Strukturen för implementeringsdelen visas nedan:

  TASK BODY name IS
    -- deklarationer av lokal typer och variabler
  BEGIN
    LOOP
      SELECT -- rendezvous
        ACCEPT ent1 DO
          -- sekvens av satser under rendezvous
        END ent1;
      OR -- villkorlig rendezvous
        WHEN condition =>
          ACCEPT ent2(parameters) DO
            -- sekvens av satser under rendezvous
          END ent2;
      OR -- Avslutning om alla processer i omgivningen skall terminera
        TERMINATE;
      END SELECT;
    END LOOP;
  END name;