3. Grunder i Fortran 90

3.0 Inledning

I detta kapitel skall vi gå igenom de regler som gäller för alla program i Fortran 90, men koncentrera oss till de mest grundläggande reglerna. Efter studium av detta kapitel skall Du kunna skriva och köra enkla program i Fortran 90. I de följande kapitlen kommer sedan fler egenskaper i Fortran att introduceras.

3.1 Tillgängliga tecken vid skrivning av program i Fortran 90

Under utvecklingen av Fortran har antalet olika tecken som kan användas vid skrivning av program växt. Det är dock ofta så att de tecken som inte fanns med från början inte vunnit så allmän acceptans, varför jag här ger en successiv beskrivning av de som ingår. I Fortran 66 (FORTRAN IV) ingår de 26 engelska bokstäverna
        A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
de 10 siffrorna
        0 1 2 3 4 5 6 7 8 9 
samt de 10 specialtecknen
        = + - * / ( ) , . ¤
där den sista är en myntsymbol, som kan variera från land till land, till exempel £ i Storbritannien, $ i USA eller ¥ i Japan. Övriga specialtecken har en naturlig användning i Fortran. Dessutom ingår naturligtvis blank i teckenuppsättningen. Det låga antalet tecken (totalt 47) beror på att dåtidens utrustning (hålkortsstans) hade en mycket begränsad teckenrepertoar. Notera att de små bokstäverna inte fanns med. De kom "nästan" i Fortran 90, där standarden föreskriver att om implementeringen tillåter små bokstäver så skall dessa vara ekvivalenta med motsvarande stora bokstäver, utom i text-sammanhang. De flesta Fortran 77 implementationer har samma tolkning av små bokstäver. I Fortran 77 tillkom följande båda specialtecken
        ' :
I Fortran 90 tillkom ytterligare nio specialtecken
        _ ! " % & ; < > ?
De båda symbolerna $ och ? har ingen specificerad användning i Fortran 90, utan är tänkta främst för utmatning. Symbolen $ har dock haft en viss specificerad betydelse i vissa utvidgningar av Fortran 77, den har dessutom en viktig betydelse i UNIX. Mer om detta senare. Naturligtvis kommer jag även att förklara de övriga nytillkomna specialtecknen efter hand.

De nationella bokstäverna (av typ å ä ö é è ë ü ñ) kan oftast användas i text-sammanhang, om stöd för dem finns i implementeringen. De kan nästan aldrig användas i variabelnamn, och bör aldrig användas i variabelnamn om något system skulle råka tillåta det!

3.2 Variabler

Ett fundamentalt begrepp inom programmering är variabler, vilka kan tilldelas olika värden. För att kunna använda variabler måste sådana ges namn. Variabelnamn i Fortran 77 (och tidigare) inleds av en bokstav och fortsätter eventuellt med ytterligare bokstäver och siffror. Högst sex tecken var tillåtna. Exempel på variabelnamn är
    A ADAM        H2SO4   ILL     HEJSAN     H2J3A4     O
    J JAMEN       SLASK   TMP     ULI        LIU        MAI
I Fortran 90 har största tillåtna längden på ett variabelnamn ökats från 6 till 31, och understrykningstecknet _ får ingå inuti ett variabelnamn. Tillåtna variabelnamn, förutom de ovanstående, är
        T_1   AVSTAAND_TILL_MAANEN  DISTANCE_TO_THE_SUN_
        HEJSANSVEJSAN     O123456789ABCDEFHIJKLMNOPQRSTUV
De nya reglerna innebär dock inte att man bör välja krångliga namn. Avsikten med understrykningstecknet är att användas när det är lämpligt med namn bestående av mer än ett ord. Blanka är ju inte tillåtna inuti namn. Man bör vara försiktig med tecken som kan misstolkas, ofta blir det fel mellan bokstaven O och siffran 0, dessa är väldigt olika i vissa typsnitt, men ganska lika i typsnittet Courier, nämligen O respektive 0. Tyvärr finns det ingen bestämd regel mellan olika typsnitt om att just bokstaven skall vara "bredare" än siffran.

Övning.

(3.1) Vilka av följande variabelnamn är tillåtna under Fortran 77 respektive Fortran 90?
        A2     GUSTAVUS      ADOLFUS         GUSTAV_ADOLF
        2C     2_CESAR       ÅKE             $KE
        C-B    DOLLAR$       OOOOOO          DO
        K**2   HEJ_DU_GLADE  _STOCKHOLM_     GOETEBORG
        EIIR   Bettan        ABCDEFGHIJKLMNOPQRSTUVWXYZ

Lösning.

3.3 Deklaration av variabler

Eftersom allt i datorer lagras binärt, som ettor och nollor, måste varje variabel ha information om hur bitmönstret skall tolkas. Detta sker genom att tilldela varje variabel en datatyp, som heltal, flyttal, logisk, eller textsträng. Några ytterligare datatyper kommer att introduceras senare, men dessa fyra är de viktigaste. En variabel deklareras att ha en viss typ på följande sätt.
        REAL                    :: A, B, C
        INTEGER                 :: I, J, K, L, M, N
        LOGICAL                 :: BO
        CHARACTER (LEN=10)      :: TEXT1
        CHARACTER (10)          :: TEXT2
        CHARACTER*10            :: TEXT3
Dessa deklarationer talar om att variablerna A, B och C är flyttal, variablerna I, J, K, L, M och N är heltal, variabeln BO är logisk (boolsk), och variablerna TEXT1, TEXT2 och TEXT3 är textsträngar som rymmer 10 tecken. De båda första deklarationerna är strängt taget onödiga, eftersom Fortran har den regeln att odeklarerade variabler som börjar på någon av bokstäverna I, J, K, L, M eller N automatiskt blir heltal, och de som börjar på någon annan bokstav blir flyttal. Denna regel har gett upphov till mycket elände, eftersom kompilatorn då inte upptäcker felstavade variabler, liksom att felaktig användning av Fortran-kommandon i stället kan ge upphov till nya variabler. Det har därför införts ett helt nytt kommando i Fortran 90, nämligen IMPLICIT NONE. Detta kan placeras först i varje programenhet, och slår av regeln om initialbokstäverna. Jag kommer att försöka att i fortsättningen genomgående använda IMPLICIT NONE i denna bok, det är ett mycket bra verktyg för att få korrekta program. I textsträngsdeklarationen ingår en specifikation av hur många tecken som skall kunna rymmas i variabeln. Man kan här utelämna LEN= och direkt ange antalet tecken inom parentesen. Alternativt kan man i stället för parentesuttrycket använda den gamla (Fortran 77) beteckningen * följt av antalet tillåtna tecken. Alla tre varianterna har använts ovan. Om längdspecifikationen utelämnas helt blir längden 1.

I Fortran 77 användes inte beteckningen med dubbelkolon ::, att den behövs i Fortran 90 beror på att man infört attribut som tilläggsspecifikationer vid deklarationer.

3.4 Tilldelningar och beräkningar

När vi nu har lyckats deklarera variabler är det dags att använda dem. Normalt arbetar man med en programrad i taget. De fyra räknesätten anges med + - * respektive /. Vi upprepar att i matematik användes ofta en punkt eller ingenting för att markera multiplikation, som 2·3 eller 5x, men i Fortran måste asterisken * användas. Upphöjt till markeras med dubbelstjärna **. Tilldelning sker med ett likhetstecken =, varvid storheten till vänster får värdet av det till höger.
        IMPLICIT NONE
        INTEGER       :: I
        REAL          :: AREA, R
        LOGICAL       :: KLAR, OKLAR
        R = 2.0
        AREA = 3.141592654*R**2
        I = 0
        I = I + 1
        KLAR = .FALSE.
        OKLAR = .TRUE.
        END
I detta enkla program sättes cirkelns radie R till 2 enheter och dess yta AREA beräknas med den välkända formeln a = pi·r2. Därefter nollställes heltalet I, varefter det stegas upp med 1. De båda sista tilldelningssatserna sätter de båda logiska variablerna KLAR och OKLAR till värdena falskt respektive sant, vilka värden skrives på detta underliga sätt, där punkterna på båda sidor ingår i konstanterna. Det bör noteras att variabler i Fortran ej är automatiskt nollställda från början. Vid kompilering av ovanstående program kan det inträffa att kompilatorn varnar för att variablerna AREA, KLAR och OKLAR ej användes efter att de tilldelats sina värden. Detta är en korrekt iakttagelse, och en kompilator som noterar sådant är ett gott hjälpmedel för att skriva korrekta program. I ovanstående exempel vore det naturligt att lägga in en utmatning av variablerna, men jag har ju inte hunnit till det än (se sektion 3.7).

3.5 Inbyggda matematiska funktioner

De vanliga matematiska funktionerna finns inbyggda i Fortran. Att de är inbyggda betyder bland annat att de inte behöver deklareras, och ingen speciell åtgärd erfordras för att de skall länkas in i det färdiga programmet. De inkluderar funktioner som sin, cos, tan och motsvarande inversa eller hyperboliska (men ej inversa hyperboliska) samt kvadratrot, logaritm och exponentialfunktion. De finns alla specificerade i Appendix 5, avsnitt 3.

I Appendix 5, avsnitt 2, finns något som kallas numeriska funktioner, vilka innefattar sådant som absolutbelopp, realdel, heltalsdel och tecken. Där finns även funktioner för omvandling mellan olika precisioner, vilket vi återkommer till. De numeriska funktionerna är således av en mer maskinnära natur än de matematiska.

Vi ger nu ett enkelt exempel på användning av några av de inbyggda funktionerna.
        IMPLICIT NONE
        INTEGER       :: I, J, K
        REAL      :: AREA, R, X, Y
        AREA = 1.0
        R = SQRT(AREA/3.141592654)
        I = INT(-3.141592654)
        J = FLOOR(-3.141592654)
        K = CEILING(-3.141592654)
        X = REAL(I*J*K)
        Y = SIN(LOG10(ABS(X)))
        Y = ACOS(Y)
        END
I ovanstående lilla program beräknas först radien på en cirkel med ytan en enhet, varefter tre olika heltalsdelar av -pi beräknas, först den vanliga med INT som avrundar (trunkerar) mot noll och gav -3, sedan golvfunktionen FLOOR som avrundar nedåt mot - oändligheten och gav -4, och slutligen takfunktionen CEILING som avrundar uppåt mot + oändligheten och gav -3.

Därefter beräknas X som flyttalet svarande mot produkten av dessa tre heltal (-36) och man bildar Y som sin(10log |x|), varefter Y blir arcus cosinus av sig själv.

För arcustangenten finns två funktioner, dels den vanliga ATAN(X) som svarar mot arctan x, dels den ovanliga ATAN2(Y,X). Den senare har den uppgiften att den skall kunna klara av även de punkter där tangenten blir oändlig, nämligen udda multipler av pi/2. Resultatet av ATAN2 kan tolkas som principalvärdet för argumentet till det från noll skilda komplexa talet x + iy. Det är funktionen arctan (y/x) och ligger i intervallet -pi < ATAN2(Y,X) <= pi. Om y är positivt blir resultatet positivt, om y är noll blir resultatet noll om x > 0 och pi om x < 0. Om y är negativt blir resultatet negativt. Om x är noll blir resultatet pi/2 eller -pi/2, beroende på tecknet på y. Om både x och y är noll blir resultatet odefinierat.

Samtliga trigonometriska funktioner arbetar med radianer.

3.6 Kommentarer

En kommentar inleds med utropstecken ! och varar raden ut. Den kan starta redan i positionen längst till vänster (kolumn 1), och är då nästan i överensstämmelse med Fortran 77. I Fortran 77 inleddes dock kommentarer i stället med bokstaven C eller asterisk *. I Fortran 90 kan en kommentar även följa på samma rad som en sats. Kommentarer kan rymma nationella tecken, som de svenska tecknen åäöéü ÅÄÖÉÜ. Allmänt gäller att en kommentar finns i programlistningen, men den påverkar ej programmet vid körning. Från och med Fortran 77 kan även helt blanka rader användas som kommentarer (för att förbättra programmets utseende).

Ett kommando med en liknande uppgift som en kommentar är den programsats som kan inleda ett program (egentligen ett huvudprogram). Den har utseendet

        PROGRAM namn
och ger namnet namn på programmet. Detta programnamn utnyttjas av vissa operativsystem, om någon programsats ej är med får oftast programmet automatiskt namnet MAIN eller main. Programsatsen är frivillig, men jag rekommenderar den.

3.7 Enkel in- och utmatning

In- och utmatning under Fortran är mycket kraftfull. Vi kommer nu till att börja med enbart behandla de enklaste aspekterna av dem och begränsa oss till inmatning från tangentbordet och utmatning till skärmen. Senare tillkommer filhantering och speciella egenskaper i samband med utmatning på papper.

Inmatning sker med READ och utmatning med WRITE. Dessa operationer måste knytas till vissa fysiska enheter, det är lämpligt att använda * för dessa. Man kan även använda de historiska numren 5 för in-enheten och 6 för ut-enheten. Ett exempel följer.

        PROGRAM INUT_RI
        IMPLICIT NONE
        REAL    :: A
        INTEGER :: I
        WRITE(*,*) ' Ge värdena på flyttalet A och heltalet I'
        READ(*,*)  A, I
        WRITE(*,*) A, I
        WRITE(*,*) ' A = ', A, ' I = ', I
        END
Den första stjärnan i parentesen ovan talar om att standardenheten skall användas, den andra att list-styrd in- respektive utmatning skall användas. List-styrd innebär att data tolkas i enlighet med vilka tal som skall matas in eller ut, matar man ut ett flyttal skrivs det också ut som ett flyttal. Den första skrivsatsen ovan skriver ut texten inom apostroferna, dvs en uppmaning att ge värdet av ett flyttal A och av ett heltal I. Man ger värdena med mellanslag, komma eller vagnretur mellan värdena, och avslutar med en vagnretur. Observera att om heltalet är stort, till exempel 7 283 550 så får det inte skrivas så, man får inte ha några blanka inuti talen. Man kan ge flyttal som heltal, heltal med decimaler, eller heltal med decimaler och exponent. Man kan således ge det som 13 eller -5 eller 157.67 eller 2.38E7 eller 4.E-8 eller .2E5 (de båda sista varianterna är faktiskt tillåtna och tolkas som 4.0E-8 respektive 0.2E5). Däremot är det inte tillåtet att utelämna siffrorna både före och efter decimalpunkten!

Den följande skrivsatsen skriver ut värdena, den därpå följande talar även om vilka värden som skrivits ut.

Vi övergår nu till de båda datatyperna logisk och textsträng.

        PROGRAM INUT_LC
        IMPLICIT NONE
        LOGICAL                 :: B
        CHARACTER(LEN=8)        :: T
        WRITE(*,*) ' Ge den logiska variabeln B '
        READ(*,*) B
        WRITE(*,*) ' Den logiska variabeln B är ', B
        WRITE(*,*)
        WRITE(*,*) ' Ge textsträngsvariabeln T (högst 8 tecken)'
        WRITE(*,*) ' Observera att textsträngen måste ges inom'
        WRITE(*,*) ' apostrofer '' eller citattecken "'
        READ(*,*) T
        WRITE(*,*) ' Textsträngen T är ', T
        WRITE(*,*)
        WRITE(*,*) ' Ge textsträngsvariabeln T (högst 8 tecken)'
        WRITE(*,*) ' Observera att textsträngen nu skall ges'
        WRITE(*,*) ' utan extra tecken'
        READ(*,'(A8)') T
        WRITE(*,*) ' Textsträngen T är ', T
        END
När man skall mata in att en logisk variabel skall vara sann kan man välja en av representationerna T eller .T. eller .TRUE. När man skall mata in att en logisk variabel skall vara falsk kan man välja en av representationerna F eller .F. eller .FALSE.

List-styrd utmatning av en logisk variabel sker med ett ensamt T eller F.

Vid list-styrd inmatning av en textsträng måste den omges med apostrofer ' eller citattecken ". Man kan valfritt välja om man vill ange en textsträng inom apostrofer eller citattecken, om texten skall innehålla ett av dessa tecken är det praktiskt att omge texten med det andra tecknet, i annat fall måste det tecken som skall skrivas ut dubbelskrivas, dvs '' respektive "".

Det är oftast opraktiskt att behöva omge textsträngar med apostrofer, man måste ju då svara 'JA' i stället för JA på en JA/NEJ fråga. Man kommer ifrån det kravet genom att i stället för list-styrd inmatning använda format-styrd inmatning. Detta sker i den sista delen av ovanstående exempel, där den andra stjärnan utbytts mot '(A8)', vilket talar om för systemet att åtta tecken förväntas. Jag kommer i kapitel 7 utbreda mig mycket om format, allt för mycket enligt många, men det är ett mycket kraftfullt hjälpmedel, främst för att erhålla en prydlig utmatning.

3.8 Vektorer

Eftersom datorer är så snabba kan de behandla väldigt många element på en kort tid. Det vore då opraktiskt om varje variabel måste ha ett eget namn, man använder därför ofta indexerade variabler. Vi skall i detta avsnitt bara titta på variabler med ett index, kallade vektorer, för att i nästa kapitel titta på matriser och allmännare fält. Utrymme för vektorn ai, i = 1, 2, ... , 20, reserveras med satsen
        DIMENSION A(20)
I stället för ordet DIMENSION kan man använda någon av deklarationerna CHARACTER, INTEGER, LOGICAL, REAL.

Fältet får då angiven typ. I annat fall användes den implicita typen. Man kan även typdeklarera variabeln på vanligt sätt. Även de senare behandlade datatyperna COMPLEX och DOUBLE PRECISION kan naturligtvis bilda vektorer.

Från och med Fortran 77 behöver fältet ej längre börja i position 1, man anger då exempelvis

        DIMENSION C(-7:5)
för vektorn C(-7), C(-6), ..., C(-1), C(0), C(1), ..., C(5).

Många gamla program utnyttjar restriktionen att index måste börja på ett, varför det matematiska indexet ofta har kanat ett steg. Många gamla programmerare lever likaså kvar i den föreställningen att index börjar på ett.

I deklarationen av dimensionen kan normalt endast konstanter användas. I funktioner och subrutiner kan i vissa sammanhang vanliga heltalsvariabler, eller en asterisk, användas. Även till detta återkommer jag i nästa kapitel.

Deklarationen är ganska ändrad i Fortran 90 jämfört med tidigare (men de gamla möjligheterna finns kvar). I stället för att ge dimensioneringen som ett kommando ger man den nu som ett attribut. Det fanns tidigare följande möjligheter att deklarera en flyttalsvektor med 20 element:

        DIMENSION A(20)       ! Metod 1, med implicit
                              ! typdeklaration

        REAL A(20)            ! Metod 2 (vanligast)

        REAL A                ! Metod 3, med explicit 
        DIMENSION A(20)       ! typdeklation
Nu tillkommer den nya möjligheten, som är praktisk i det att alla egenskaper för en viss variabel samlats i en enda rad.
        REAL, DIMENSION(20)   :: A    ! Metod 4
Elementen i en vektor lagras i en entydigt bestämd ordning, om vektorn är deklarerad (j:k) finns elementet (s) i position 1+(s-j). Här användes således det första elementet som referensposition.
        DIMENSION A(-1:8), B(10:25)
        A(2) = B(21)
Här identifierar A(2) det fjärde elementet i vektorn A, och sättes till värdet av B(21), det tolfte elementet i vektorn B.

Vid användning av vektorer kan som index användas heltalskonstanter, heltalsvariabler, eller allmänna heltalsuttryck.

I Fortran användes regeln ovan om uträkning av position normalt även om indexet ligger utanför de givna indexgränserna. Detta innebär att tilldelningarna A(-2) = 17.0 och B(9) = A(9) är tillåtna, men kan få katastrofala följder. Den första av dessa placerar därvid flyttalet -17.0 i den minnescell som ligger närmast före den första i vektorn A. Den andra tilldelningen kan faktiskt tolkas som A(8) = B(10), om deklarationerna av A och B är sådana att vektorerna ligger i ordning, varvid elementet närmast efter A(8) blir B(10). Sådan ordning kan tvingas fram av programmeraren genom att använda COMMON och/eller SEQUENCE.

En del Fortran system innehåller möjlighet att slå på indexkontroll, varvid konstiga tilldelningar av ovanstående natur kan konstateras. Om tilldelningen sker med konstanter kan "felet" hittas redan vid kompileringen, om variabler användes som index kan "felet" normalt konstateras först vid exekveringen. Då måste oftast en avlusare (eng. debugger) användas.

3.9 Enkla slingor (DO)

När vi nu har ordnat tillgång till alla dessa tal som kan beskrivas med vektorer gäller det att finna motsvarande kommandon för att effektivt kunna utföra erforderliga beräkningar. Viktiga hjälpmedel är här slingor (DO-slingor) och alternativsatser (IF-satser, IF-konstruktioner och CASE-konstruktioner). Alternativsatserna behandlas i nästa avsnitt. En DO-slinga kan se ut på fyra olika sätt, först den gamla varianten
      DO 100 index = n1, n2, n3
             ! satser
100   CONTINUE
Siffrorna, i detta exempel 100, får väljas godtyckligt från 1 till 99 999, men det får inte finnas mer än en sats CONTINUE med aktuellt nummer. Numret i DO-sats och före CONTINUE måste naturligtvis vara samma. Det är (tyvärr) tillåtet för flera DO-slingor att sluta på samma CONTINUE.

Det är inte absolut nödvändigt att avsluta en gammaldags DO-slinga med en CONTINUE sats. Det går även med en vanlig sats, till exempel en vanlig tilldelningssats, som då har getts motsvarande nummer till vänster. En rekommendation i Fortran 90 och ett troligt krav nästa årtusende är att inte avsluta en gammaldags DO-slinga på annat sätt än med CONTINUE. Den nya och rekommenderade varianten är

        DO index = n1, n2, n3
           ! satser
        END DO
I båda fallen ovan gäller att storheterna n1, n2 och n3 ej får ändras under slingans utförande. De skall alltså betraktas som konstanter under exekveringen av slingan, men det är tillåtet att de är variabler eller variabeluttryck. Det viktiga är att delresultat under slingans gång, inklusive aktuellt värde av index, ej får påverka dem.

Båda dessa slingor startar med att sätta index till n1 och utföra satserna som följer. När exekveringen når CONTINUE (med rätt nummer) eller END DO hoppar exekveringen upp till DO-satsen igen och räknar upp index med n3. Om resultatet blir större än n2 avbrytes slingan med hopp till satsen efter CONTINUE eller END DO. Annars exekveras satserna ytterligare en gång. Det ovanstående förutsätter att n1 är högst lika med n2 och att n3 är positivt. Om n1 är större än n2 sker hopp direkt till satsen efter CONTINUE eller END DO.

Om ", n3" saknas ovan så fungerar satsen exakt som om n3 vore ett. Värdet av n3 får ej vara noll.

Om n3 är negativ så gäller att ingen exekvering av satserna sker om n1 är mindre än n2. Annars blir det i princip samma sak som om n3 är positiv, det blir nu en nedräkning av n1 till n2.

Den tredje möjligheten är att bara ha ordet DO på första raden. Man får då i princip en evig slinga, denna kan dock avbrytas med hjälp av de kommandon som beskrivs senare.
        DO 
                ! satser
        END DO
Den fjärde möjligheten är att ha en DO WHILE slinga. Man får då en slinga som utföres så länge som ett visst logiskt villkor är uppfyllt. Till skillnad från parametrarna i den vanliga varianten av DO-slingan kan (och bör) storheter i detta villkor ändras under slingans exekvering.
        DO WHILE (logiskt villkor)
                ! satser
        END DO
I Fortran 90 tillkom två helt nya kommandon att användas i DO-slingor, nämligen CYCLE och EXIT. Dessa kan läggas in bland de vanliga satserna inne i en DO-slinga. Om CYCLE påträffas fortsätter exekveringen direkt med nästa värde på indexet respektive direkt från början. Om EXIT påträffas fortsätter exekveringen direkt med nästa sats efter DO-slingan.

En DO-slinga kan tilldelas namn, vilket sker genom att före DO ge ett namn följt av ett kolon. Dessutom bör avslutande END DO följas av namnet. Kommandona CYCLE och EXIT kan utnyttja namnet för att upprepa eller avsluta specificerad slinga. Om inget namn ges i EXIT eller CYCLE avses automatiskt den innersta slinga man just befinner sig i. Dessa kommandon ersätter GO TO till den sats som avslutade den gammaldags DO-slingan (vilken oftast är en CONTINUE-sats).

En ny sats FORALL infördes i Fortran 95 för att till skillnad från en DO-slinga kunna utföras i godtycklig ordning (och därmed parallellt om den fysiska möjligheten finns). Den hämtades från tillägget HPF = Hög-Prestanda Fortran, vilket tillägg aldrig blivit någon succe. Ett par enkla exempel på FORALL-satser följer

        FORALL (I = 1:N, J = 1:N) H(I,J) = 1.0/REAL(I+J-1)
        FORALL (I = 1:N, J = 1:N, Y(I,J) .NE. 0.0) &
                        X(I,J) = 1.0/Y(I,J)
        FORALL (I = 1:N) A(I,I+1:N) = 4.0*ATAN(1.0)
Den första av dessa definierar Hilbertmatrisen av ordning N, den andra inverterar elementen i en matris med undvikande av eventuella nollor. I den tredje tilldelas alla element över huvuddiagonalen i matrisen A ett approximativt värde på pi.

I dessa satser kan FORALL sägas vara en dubbelslinga som kan utföras i godtycklig ordning. Det allmänna utseendet är

        FORALL ( v1 = l1:u1:s1, ... , vn = ln:un:sn, mask ) &
                a(e1, ... , em) = right_hand_side
och beräknas enligt väl definierade regler, i princip beräknas alla index först.

Dessutom finns en FORALL-konstruktion. Denna skall tolkas som om de ingående satserna i stället vore skrivna som FORALL-satser i samma ordning. Denna restriktion är väsentlig för att få ett entydigt beräkningsresultat. I en FORALL-konstruktion är anrop av funktioner och subrutiner tillåtna om dessa är rena (eng. pure). Med direktivet PURE kan man ange att så är fallet. Två enkla exempel på en FORALL-konstruktion:

        REAL, DIMENSION(N,N) :: A, B
        ...
        FORALL (I = 2:N-1, J = 2:N-1)
           A(I,J) = 0.25*(A(I,J-1)+A(I,J+1)+A(I-1,J)+A(I+1,J))
           B(I,J) = A(I,J)
        END FORALL
Efter dessa satser har A och B exakt samma värden i alla inre punkter, medan B har kvar sina eventuella gamla värden på ränderna.
        REAL, DIMENSION(N,N) :: H, B
        ...
        FORALL (I = 1:N, J = 1:N) 
	        H(I,J) = 1.0/REAL(I+J-1)
	        B(I,J) = H(I,J)**2
        END FORALL
Först tilldelas alla elementen i Hilbertmatrisen H sina värden, därefter tilldelas matrisen B dessa värden i kvadrat.

Även IF-konstruktioner och CASE-konstruktioner kan tilldelas namn i Fortran 90, liksom FORALL-konstruktionen i Fortran 95.

Anm. Det har tyvärr visat sig svårt att implementera FORALL på så sätt att den blir effektivare än en vanlig DO-sats.

Övning.

(3.2) Skriv en DO-slinga som successivt adderar kvadratroten ur elementen i en vektor om 100 element, men hoppar över negativa element och avslutar additionen om aktuellt element är noll. Kan denna uppgift skrivas enklare med en FORALL-konstruktion?
Lösning.

3.10 Alternativsatser (IF och CASE)

Alternativsatserna består av IF-satser, IF-konstruktioner och CASE-konstruktioner.

Den första av IF-satserna hör intimt samman med den satsnumrering som var nödvändig i vissa sammanhang upp till och med Fortran 77. Den går ut på att de satser man vill "hoppa" till ges ett nummer till vänster om den egentliga satsen, detta nummer skall vara mellan 1 och 99 999 (extremvärdena är tillåtna). Den första varianten av IF-satserna kallas aritmetisk, eftersom den tittar på tecknet hos den betraktade numeriska storheten (heltal eller flyttal). Om denna är negativ sker hopp till det första satsnumret, om den är noll till det andra, och om den är positiv till det tredje satsnumret.

        IF (X) 10, 20, 30
 10     ! Satser som utföres om X är negativt 
        GO TO 100
 20     ! Satser som utföres om X är noll 
        GO TO 100
 30     ! Satser som utföres om X är positivt
100     CONTINUE
        ! Satser som utföres i samtliga fall
I ovanstående exempel har jag även introducerat den ovillkorliga hoppsatsen GO TO, vilken gör det möjligt att helt särskilja exekveringen i de tre fallen. Om X är noll sker således först ett hopp till sats 20, där de satser som hör till detta fall utföres, varefter ett hopp sker till sats 100, där satser för samtliga fall kan följa. Den andra av IF-satserna är den logiska. Den är mycket enkel till sin natur.
        IF (logiskt uttryck) sats
Om det logiska uttrycket är sant så utföres satsen, annars exekveras nästa sats direkt. Som sats i en logisk IF-sats användes exempelvis en vanlig tilldelningssats, en ovillkorlig hoppsats eller ett subrutinanrop. Däremot är följande förbjudna i detta sammanhang: DO-slinga, IF-sats eller IF-konstruktion eller CASE-konstruktion. Vi övergår nu till den allmännare IF-konstruktionen. Den ser ut som följer.
        IF (logiskt uttryck) THEN
                ! Satser som utföres om det logiska 
                ! uttrycket är sant
        ELSE
                ! Satser som utföres om det logiska 
                ! uttrycket är falskt
        END IF
Här kan ELSE och efterföljande satser utelämnas, så att en förenklad variant erhålles.
        IF (logiskt uttryck) THEN
                ! Satser som utföres om det logiska 
                ! uttrycket är sant
        END IF
Alternativt kan ELSE utbytas mot ELSE IF, så att en utvidgad variant erhålles.
        IF (logiskt uttryck) THEN
                ! Satser som utföres om det logiska 
                ! uttrycket är sant
        ELSE IF (nytt logiskt uttryck) THEN
                ! Satser som utföres om det första logiska 
                ! uttrycket är falskt och det andra är sant
        ELSE 
                ! Satser som utföres om båda de logiska 
                ! uttrycken är falska
        END IF

Notera strukturen hos den allmänna IF-konstruktionen, på första raden finns efter det logiska uttrycket enbart THEN. Den logiska IF-satsen ser likadan ut men istället för THEN finns där en exekverbar sats. De nya raderna efter THEN och både före och efter ELSE är väsentliga för att konstruktionen skall kännas igen. Vi övergår nu till den helt nya CASE-konstruktionen. Den ser ut som följer, och väljer lämpligt fall efter ett skalärt argument av typ INTEGER, LOGICAL eller CHARACTER. Flyttalsargument är således inte tillåtna här. Ett enkelt exempel baserat på en heltalsvariabel IVAR:
        SELECT CASE (IVAR)
                CASE (:-1)      ! Alla negativa heltal)
                        WRITE (*,*) ' Negativt tal'
                CASE (0)                ! Nollfallet
                        WRITE (*,*) ' Noll'
                CASE (1:9)      ! Ental
                        WRITE (*,*) ' Siffran ', IVAR
                CASE (10:99)            ! Tvåsiffrigt tal
                        WRITE (*,*) ' Talet ',IVAR
                CASE DEFAULT            ! Alla resterande fall
                        WRITE (*,*) ' För stort tal'
        END SELECT
Det är inte tillåtet med överlappande argument i den meningen att ett enda argument satisfierar mer än ett fall av CASE. Fallet DEFAULT behöver inte finnas med, om inget giltigt alternativ finns så fortsätter exekveringen med första sats efter END SELECT. Jag rekommenderar dock att alltid ha med ett DEFAULT, som då bör ge en felutskrift om argumentet har ett otillåtet värde.

Dessutom finns två föråldrade villkorliga hoppsatser. Dessa presenteras i avsnitt 16.7 samt i Appendix 4, dvs. styrd hoppsats och tilldelad hoppsats.

Övning.

(3.3) Skriv en CASE-sats som utför olika beräkningar beroende på om styrvariabeln är negativ, noll, något av de första udda primtalen (3, 5, 7, 11, 13), och inget i övriga fall.
Lösning.

3.11 Variabler med startvärden (DATA)

Som jag nämnt tidigare nollställes inte variabler automatiskt från början i Fortran. I princip har det värde som föregående användare lämnade efter sig i aktuell minnescell legat kvar. Vissa kompilatorer har möjlighet till nollställning, eller ännu hellre till att sätta alla variabler till odefinierade vid start. Det finns ett kommando DATA i Fortran för att tilldela begynnelsevärden på variabler, denna tilldelning sker då vid inladdning av programmet. DATA satsen är därför ej exekverbar, det är meningslöst att hoppa tillbaks till en DATA sats för ny initiering. Ny initiering måste ske med omstart av programmet.
        REAL    :: A
        INTEGER :: I, J, K
        DATA A /17.0/, I, J, K /1, 2, 3 /   
Dessa satser tilldelar flyttalet A begynnelsevärdet 17.0 och heltalen I, J och K värdena 1, 2 respektive 3. Man kan även initiera dessa variabler ekvivalent utnyttjande de nya egenskaperna i Fortran 90, utan explicit DATA.
        REAL    :: A = 17.0
        INTEGER :: I = 1, J = 2, K = 3   
För vektorer kan man likaså använda både en gammal variant liksom en ny.
        REAL VEK1(10)
        DATA VEK1 /10*0.0/
	REAL, DIMENSION(1:10) :: VEK2 = (/ ( 0.0, I = 1, 10 ) /)
Likaså om det är fråga om olika värden som skall tilldelas kan både det gamla och det nya systemet användas.
        REAL, DIMENSION(3) :: B
        DATA B /12.0, 13.0, 14.0/

        REAL, DIMENSION(3) :: B = (/ 12.0, 13.0, 14.0 /)
I ovanstående konstruktion skall teckenkombinationerna (/ och /) tänkas som parenteser. Avsikten var från början att använda de raka parenteserna [ och ], men jag påpekade att dessa användes för Ä och Å i svensk 7-bits kod, varför kommittén ändrade till dessa tråkiga kombinationssymboler.

3.12 Konstanter (PARAMETER)

Ibland vill man i sitt program ha tillgång till variabler som i själva verket är konstanter. Sådana kallas i Fortran för parametrar, och de har den egenskapen att de ej kan ändra sitt värde under exekveringen.

        REAL, PARAMETER       :: PI = 3.141592653589793
        REAL, PARAMETER       :: PIHALF = PI/2.0
        REAL, PARAMETER       :: SQRT_E = 1.648721270700128
Den gamla varianten skrevs
        REAL PI, PIHALF, SQRT_E 
        PARAMETER (PI = 3.141592653589793)
        PARAMETER (PIHALF = PI/2.0)
        PARAMETER (SQRT_E = 1.648721270700128)
I båda fallen kan således en tidigare parameter utnyttjas vid definitionen av en senare.

3.13 Funktioner

Funktioner är en mycket viktig del i att göra ett beräkningsprogram överskådligt eller strukturerat. Genom att utnyttja en funktion kan man samla en ofta förekommande beräkning till en speciell programenhet. En funktion har ett antal indata och ger som resultat ett utdata "i funktionsnamnet". Ett naturligt exempel är den inbyggda funktionen SIN(X), som har X som indata och returnerar motsvarande sinus-värde i sitt namn. I detta avsnitt skall vi bara behandla externa funktioner med skalära utdata. Det finns även vektorvärda funktioner, och även funktioner som ändrar en del av sina indata, liksom rekursiva funktioner. Dessa behandlas senare.

Som första exempel tittar vi på en funktion f(x) som beräknar exp(-x)·cos x. Vi skriver då en programenhet av typ FUNCTION och med namnet F.

        REAL FUNCTION F(X)
        IMPLICIT NONE
        REAL :: X
        REAL, INTRINSIC :: EXP, COS
        F = EXP(-X) * COS(X)
        RETURN
        END FUNCTION F
I ovanstående funktion är faktiskt det mesta frivilligt. I den första raden behöver ordet REAL ej vara med på grund av reglerna om implicit typ-deklaration, jag rekommenderar att ordet är med. Den andra raden är likaså frivillig men jag rekommenderar den varmt. Notera att den måste upprepas i varje programenhet där den skall ha verkan. Den tredje raden blir nödvändig under dessa förutsättningar. Den fjärde raden med deklaration av de inbyggda funktionerna är onödig och jag rekommenderar faktiskt att den inte får vara med. Den är bara av värde i ett mer komplicerat fall. Den femte raden är den egentliga funktionsraden, som tilldelar funktionsvärdet. Notera att det här kallas F, dvs funktionsnamnet utan argument. Den sjätte raden talar om för systemet att exekveringen av programmet är slut och att exekveringen skall återgå till anropande programenhet (ofta huvudprogrammet). Den sista raden talar om för kompilatorn att funktionen är slut.

Att ha med orden FUNCTION F ger kompilatorn en möjlighet att klaga om man har strukturerat sitt program fel. Kompilatorn kontrollerar nämligen då att det är en funktion, och att namnet på denna stämmer. Om raden RETURN saknas så gäller att Fortran 77 och Fortran 90 fungerar så att END återsänder exekveringen till den anropande programenheten. Under Fortran 66 och tidigare fick man kompileringsfel. Jag upprepar att RETURN avslutar exekveringen och END avslutar kompileringen. Satsen END måste därför ligga sist, men satsen RETURN behöver inte alls ligga näst sist.

Nu gäller det att skriva ett huvudprogram för att utnyttja denna funktion.

        PROGRAM TVA
        IMPLICIT NONE
        REAL :: X, Y
        REAL, EXTERNAL      :: F
  1     READ(*,*) X
          Y = F(X)
          WRITE(*,*) X, Y
        GO TO 1
        END PROGRAM TVA
Om man vill köra detta exempel på en UNIX-maskin kan man antingen ha båda programmen i samma fil prog.f90, eller huvudprogrammet i filen tva.f90 och funktionen i filen f.f90.

Man kompilerar då med

        f90 prog.f90
respektive
        f90 tva.f90 f.f90
och kör med
        a.out
Programmet är skrivet så att det ständigt ber om mer indata. Man kan avbryta med Kontroll-z eller Kontroll-d eller ge ett sådant värde på X att programmet bryter på grund av spill i beräkningen av exp(-x ). På Sun gick det med -89. Ett "riktigt" program skall naturligtvis innehålla en mer användarvänlig utgång.

Nu följer ett mer meningsfullt exempel, baserad på ett exempel vid en kurs hos IBM 1967. Exemplet går ut på att skriva ett program för att beräkna kubikroten ur ett flyttal. Som bekant kan vi bara beräkna kvadratrötter ur icke-negativa flyttal (om vi håller oss till reella tal), men kubikroten blir en rent udda funktion, ty (-2)3 = -8.

Beräkningen sker nedan med hjälp av omskrivning med exp(x) och ln x. Man kan naturligtvis även använda upphöjt till med **, sedan man ordnat att basen (den som skall upphöjas) är positiv.

Vid kompilering av nedanstående program kommer åtminstone NAG:s kompilator att klaga på att den föråldrade aritmetiska IF-satsen användes. Jag tycker dock den passar utmärkt här. Man måste nämligen undvika att beräkna ln(0).

            PROGRAM KUBIKROT
            IMPLICIT NONE
            REAL                :: X, Y
            REAL, EXTERNAL  :: KUBROT
 1          READ(*,*) X
            Y = KUBROT(X)
            WRITE(*,*) X, Y
            GOTO 1
            END PROGRAM KUBIKROT

            REAL FUNCTION KUBROT(X)
            IMPLICIT NONE
            REAL :: X
            LOGICAL :: SKIFTE 
            SKIFTE = .FALSE.
            IF (X) 2, 1, 3
 1          KUBROT = 0.0
            RETURN
 2          SKIFTE = .TRUE.    

!           Alternativ 1
                X = ABS(X) 
 3              KUBROT = EXP(LOG(X)/3.0)

!                   Alternativ 2
!3              KUBROT = EXP(LOG(ABS(X))/3.0)

            IF (SKIFTE) KUBROT = -KUBROT
            RETURN
            END FUNCTION KUBROT
Beträffande ovanstående program kan man ge två viktiga kommentarer. Den första är att Alternativ 1 är klart olämpligt, eftersom det ändrar inargumentet X, dvs X ändras i det anropande programmet, huvudprogrammet. Man bör därför byta till Alternativ 2 (genom att ta bort utropstecknet framför den 3-an samt i stället sätta in utropstecken framför de båda satserna i Alternativ 1).

Den andra kommentaren avser att det kan vara frestande att ändra

        LOGICAL :: SKIFTE 
        SKIFTE = .FALSE.
till
        LOGICAL :: SKIFTE = .FALSE.
men då använder man en implicit DATA sats, vilket ger att SKIFTE blir initierat till falskt när programmet startas, men inte varje gång som subrutinen anropas, vilket faktiskt krävs.

Jag har ovan diskuterat den vanliga externa Fortran-funktionen samt något om de inbyggda funktionerna. Dessa båda bör deklareras med EXTERNAL respektive INTRINSIC då de användes som argument vid funktions- eller subrutinanrop.

Dessutom finns två typer av funktioner som ej kan användas som argument, nämligen satsfunktioner och interna funktioner. Till dessa lokala funktioner återkommer jag i avsnitten 11.3 respektive 11.4.

3.14 Subrutiner

Skillnaden mellan funktion och subrutin är att subrutinen i sig inte återför något värde, och därför inte heller har någon typ. En speciell egenskap i Fortran är att en subrutin anropas med CALL subrutinnamnet. Liksom funktioner har subrutiner normalt ett eller flera argument, men subrutiner utan argument är tillåtna. Syftet med en subrutin kan vara flera olika, vanliga fall är
  1. Beräkning av ett flertal värden, dvs ändring av en del av argumenten i anropslistan.
  2. Utföra in- eller utmatning.
  3. Öppna eller stänga filer.
  4. Utföra andra mer datorinriktade uppgifter, till exempel att blanka skärmen.
Liksom funktioner kan subrutiner ibland behöva deklareras som EXTERNAL (det finns däremot bara fem inbyggda subrutiner, varför deklarationen INTRINSIC är sällsynt för subrutiner).

Jag ger här ett enkelt exempel på en subrutin, nämligen för lösning av en andragradsekvation med koefficienten 1 för andragradstermen (vilket garanterar att det verkligen är en andra-gradare).

        SUBROUTINE ANDRA( A, B, X1, X2, NANTAL)
!       LÖS EKVATIONEN X^2 + A*X + B = 0
        IMPLICIT NONE
        REAL      :: A, B, X1, X2
        INTEGER       :: NANTAL  ! Antalet reella rötter
        REAL      :: DISK        ! Diskriminanten
        DISK = A*A - 4.0*B
        IF (DISK > 0.0 ) THEN
                DISK = SQRT(DISK)
                X1 = 0.5*(-A + DISK)
                X2 = 0.5*(-A - DISK)
                NANTAL = 2
                RETURN

        ELSE IF (DISK == 0.0 ) THEN
                X1 = -0.5*A
                NANTAL = 1
                RETURN

        ELSE
                NANTAL = 0
                RETURN
        END IF
        END
Subrutinen har således koefficienterna i andragradsekvationen som inparametrar och antalet reella rötter, samt deras eventuella värden, som utparametrar. Även om man använder principen att allt skall deklareras, genom att ge satsen IMPLICIT NONE först i programenheten, så är det fortfarande mycket vanligt i Fortran att använda konventionen om att allt som börjar på I till N är heltal. Därför har antalet rötter getts som NANTAL i stället för ANTAL. För att använda denna subrutin behövs en anropande programenhet, jag ger den här i form av ett huvudprogram.
        PROGRAM HUVUD
!       Huvudprogram för lösning av andragradsekvationen
!       X^2 + B*X + C = 0
        IMPLICIT NONE
        REAL :: B, C, ROT1, ROT2
        INTEGER :: N
        WRITE(*,*) " Ge koefficienterna B och C"
        READ(*,*) B, C
        CALL ANDRA( B, C, ROT1, ROT2, N)
        IF ( N == 2 ) THEN
                WRITE(*,*) " De två rötterna är ", ROT1, ROT2
        ELSE IF ( N == 1) THEN
                WRITE(*,*) " Den enda roten är ", ROT1
        ELSE
                WRITE(*,*) " Inga reella rötter "
        END IF
        STOP
        END
I detta program har jag valt att använda olika namn på de verkliga parametrarna i huvudprogrammet, nämligen B, C, ROT1, ROT2 och N, jämfört med de formella parametrarna i subrutinen A, B, X1, X2 och NANTAL. Observera speciellt att den verkliga parametern B svarar mot den formella parametern A och den verkliga parametern C svarar mot den formella parametern B. Detta är tillåtet, men naturligtvis förvirrande. Det enklaste är om man har samma namn på de verkliga och formella parametrarna, det näst enklaste om man har helt olika namn. Om de ovanstående programenheterna finns på filerna andra.f90 och huvud_an.f90 kompileras, länkas och körs programmet under UNIX med kommandona
        f90 huvud_andra.f90 andra.f90 -o program
        program
Om man använder MS-DOS blir aktuella kommandon i stället
        FTN90 HUVUD.F90
        FTN90 ANDRA.F90

        LINK77
        $LOAD HUVUD
        $LOAD ANDRA
        $FILE PROGRAM.EXE

        PROGRAM
Under MS-DOS genererar länkningsprogrammet LINK77 automatiskt dollarsymbolen först på varje rad, vilket avslutas då man ger kommandot FILE. Detaljerna ovan är från NAG:s system för Fortran under MS-DOS, och kan vara något annorlunda vid andra leverantörer.

3.15 Prioritetsregler

Prioritetsreglerna för de aritmetiska operationerna är ganska självklara, de aritmetiska operationerna indelas i tre grupper med fallande prioritet:
        **           (upphöjt till)
        * och /      (multiplikation och division)
        + och -      (addition och subtraktion)
  1. Först görs alla "upphöjt till", sedan alla multiplikationer och divisioner, slutligen alla additioner och subtraktioner
  2. Parenteser respekteras
  3. Operationer med samma prioritet utföres från vänster mot höger, utom vad gäller "upphöjt till", som utföres från höger mot vänster
  4. Operationer på variabler av olika typ sker så att den av lägsta typ konverteras till samma typ som den med högre typ
  5. En operator kan inte följa direkt efter en annan operator
Prioritetsreglerna för logiska operationer är något mer komplicerade. De logiska operationerna indelas i åtta grupper med fallande prioritet, där de fyra första avser operationer på aritmetiska storheter ingående i ett logiskt uttryck:
  **                             (upphöjt till)
  * och /                        (multiplikation och 
                                  division)
  + och -                        (addition och subtraktion)
  .LT. .LE. .EQ. .NE. .GE. .GT.  (jämförelse)
  .NOT.                          (negation)
  .AND.                          (skärning)
  .OR.                           (union)
  .EQV. .NEQV.                   (ekvivalens och 
                                  icke-ekvivalens)

3.16 Sammanfattning

I detta kapitel har de mest grundläggande kommandona i Fortran behandlats. Ett Fortran program består av ett huvudprogram och eventuella funktioner och/eller subrutiner (vi kommer senare att införa ytterligare två typer av programenheter). De variabeltyper som behandlats är bara vanliga flyttal (REAL), heltal (INTEGER) , logiska variabler (LOGICAL) samt ett par exempel på textsträngar (CHARACTER). I nästa kapitel införes det oerhört viktiga fältbegreppet, vilket är en generalisering av vektorer upp till högst sju dimensioner, och i de följande kapitlen utvidgas språket efter hand. Det som hittills har behandlats ger mycket begränsade möjligheter att skriva program i Fortran.

Övningar.

(3.4) Skriv en funktion i Fortran med tillhörande huvudprogram för beräkning av fakulteten. Använd heltal. Skriv gärna huvudprogrammet så att det ber om ett värde på det heltal för vilket fakulteten önskas beräknad. Provkör och beräkna 10! Prova även att beräkna 13!, 14! och 15!
Lösning.

(3.5) Upprepa föregående uppgift men använd nu flyttal. Provkör och beräkna 10! och 30! Prova även att beräkna 33!, 34! och 35!
Lösning.

(3.6) Skriv ett program för tabellering av sinus-funktionen. Skriv gärna huvudprogrammet så att det ber om det intervall för vilket funktionen önskas beräknad. Mata ut en tabell med x och sin x för x = 0.0, 0.1, ..., 1.0. Se till att utmatningen ser snygg ut! Provkör!
Lösning.

(3.7) Skriv ett program som utför en enkel kontroll av de trigonometriska funktionerna genom att beräkna den trigonometriska ettan, och skillnaden mellan denna och en exakt etta. För ett antal argument x skall således max(sin2 x + cos2 x -1) och min(sin2 x + cos2 x -1) beräknas.
Lösning.

(3.8) Skriv ett program i Fortran 90 för lösning av ett system av ordinära differentialekvationer med Runge-Kuttas klassiska metod. Givet är systemet

        y'(t) = z(t)
        z'(t) = 2*y(t)*(1+y(t)^2) + t*sin(z(t))

        y(0) = 1
        z(0) = 2 
Beräkna en approximation till y(0.2) genom att använda steglängden 0,04. Upprepa med steglängderna 0,02 och 0,01. Låt respektive derivata-funktioner finnas som externa (vanliga) funktioner. Startvärden på t, y och z bör ges interaktivt, liksom steglängden h och antalet steg N.
Lösning ges ej!

Ett andra program Innehåll Fält


Senast modifierad: 8 februari 2007
boein@nsc.liu.se