Ukazni programski jezik
Vsebina
2. Ukazni programski jezik¶
Spoznali smo aritmetične izraze s spremenljivkami. Spremenljivke smo obravnavali po mačehovsko, saj se jim ni dalo nastavljati vrednosti in ni bilo možno definirati novih spremenljivk.
V tej lekciji bomo spoznali ukazni programski jezik, ki ima prave spremnljivke,
pogojne stavke in zanko while
.
Po vrsti bomo obravnvali:
sintakso jezika
operacijsko semantiko: kako se jezik izvaja
denotacijsko semantiko: kaj je matematični pomen jezika
prevajalnik v poenostavljeno strojno kodo
2.1. Sintaksa¶
V prejšnji lekciji smo spoznali aritmetične izraze. Dodali bomo še boolove izraze in ukaze. Podajmo abstraktna sintaksa jezika:
⟨aritmetični-izraz⟩ ::=
⟨spremenljivka⟩ |
⟨številka⟩
⟨aritmetični-izraz⟩ + ⟨aritmetični-izraz⟩ |
⟨aritmetični-izraz⟩ * ⟨aritmetični-izraz⟩
⟨boolov-izraz⟩ ::=
true | false |
⟨aritmetični-izraz⟩ = ⟨aritmetični-izraz⟩ |
⟨aritmetični-izraz⟩ < ⟨aritmetični-izraz⟩ |
⟨boolov-izraz⟩ and ⟨boolov-izraz⟩ |
⟨boolov-izraz⟩ or ⟨boolov-izraz⟩ |
not ⟨boolov-izraz⟩
⟨ukaz⟩ ::=
skip |
⟨spremenljivka⟩ := ⟨aritmetični-izraz⟩ |
⟨ukaz⟩ ; ⟨ukaz⟩ |
while ⟨boolov-izraz⟩ do ⟨ukaz⟩ done |
if ⟨boolov-izraz⟩ then ⟨ukaz⟩ else ⟨ukaz⟩ end
Da bi iz zgornjih pravil dobili konkretno sintakso, moramo podati še informacijo o prioriteti in asociativnosti operatrojev. Naštejmo operatorje od nižje do višje prioritete:
;
(levo)or
(levo)and
(levo)not
<
,=
+
(levo)*
(levo)
Na primer, or
je levo asociativen in ima prednost pred ;
. To še vedno ni dovolj za povsem konkretno sintakso, na
primer, dodati bi morali še pravila za pisanje oklepajev in pojasniti, kako se naredi leksično analizo (kakšna so
pravila za presledke, nove vrste, komentarje ipd.)
Primer
Program, ki sešteje števila od 1
do 100
in rezultat shrani v s
:
s := 0;
i := 0;
while i < 101 do
s := s + i;
i := i + 1
done
Zgornji program bi lahko zapisali v Javi takole:
s = 0 ;
i = 0 ;
while (i < 101) {
s = s + i ;
i = i + 1 ;
}
Abstraktna sintaksa obeh programov je enaka (vaja: narišite drevo, ki predstavlja ta program).
Tu in v nadaljevanju se ne bomo preveč posvečali podrobnostim konkretne sintakše. To ne pomeni, da je konkretna sintaksa nepomembna v praksi; navsezadnje so se pripravljeni programerji skregati že zaradi zamikanja kode. V zvezi s tem omenimo Wadlerjev zakon.
2.2. Operacijska semantika¶
Sedaj nadgradimo operacijsko semantiko izrazov še s pravili za boolove izraze in
ukaze. Še vedno imamo okolje η
, ki spremenljivkam priredi njihove vrednosti,
na primer
η = [x ↦ 4, y ↦ 10, u ↦ 1]
V našem jeziku bomo spremenljivke vedno hranile samo cela števila. Ker jim bomo tudi nastavljali vrednosti, potrebujemo
ustrezno operacijo, s katero to naredimo. Če je η
okolje, x
spremenljivka in n
celo število, potem zapis
η [x ↦ n]
pomeni okolje η
, v katerem je vrednost x
nastavljena na n
.
Primer
Če je η = [x ↦ 10, y ↦ 5]
, potem je η[x↦20]
enako [x ↦ 20, y ↦ 5]
.
2.2.1. Semantika malih korakov¶
Operacijska semantika aritmetičnih in boolovih izrazov¶
Pravila za aritmetične izraze smo že spoznali zapišimo jih še enkrat:
—————————-
η | n ↪ n
η(x) = n
————————––
η | x ↪ n
η | e₁ ↪ n₁ η | e₂ ↪ n₂
———————————————————————–———
η | e₁ + e₂ ↪ n₁ + n₂
η | e₁ ↪ n₁ η | e₂ ↪ n₂
———————————————————————–———
η | e₁ - e₂ ↪ n₁ - n₂
η | e₁ ↪ n₁ η | e₂ ↪ n₂
———————————————————————–———
η | e₁ * e₂ ↪ n₁ · n₂
Tudi Boolovi izrazi ne predstavljajo večje težave:
————————————————
η | true ↪ true
————————————————–
η | false ↪ false
η | b ↪ false
————————-––-—————
η | not b ↪ true
η | b ↪ true
—————————————————–
η | not b ↪ false
η | b₁ ↪ false
———————————-–—————————–
η | b₁ and b₂ ↪ false
η | b₁ ↪ true η | b₂ ↪ v₂
––———————————————————————————–
η | b₁ and b₂ ↪ v₂
η | b₁ ↪ true
————————————————————–
η | b₁ or b₂ ↪ true
η | b₁ ↪ false η | b₂ ↪ v₂
—————————————————————————————–
η | b₁ or b₂ ↪ v₂
η | e₁ ↪ n₁ η | e₂ ↪ n₂ n₁ < n₂
————————————————————–—————————————————
η | e₁ < e₂ ↪ true
η | e₁ ↪ n₁ η | e₂ ↪ n₂ n₁ ≥ n₂
————————————————————–———————————————
η | e₁ < e₂ ↪ false
Vaja
Dodajte pravila za =
, s katerim primerjamo dve celi števili in dobimo boolovo vrednost.
Vaja
Ko računamo boolove vrednosti, imamo pri račuanju b₁ and b₂
izbiro:
Izračuamo
b₁
inb₂
in nato vrednostb₁ and b₂
Najprej izračunamo
b₁
. Če dobimofalse
, je vrednnostb₁ and b₂
enakafalse
ne glede nab₂
, zato ga ne izračunamo.
Zgoraj smo uporabili drugo možnost. (Kako se to razbere iz pravil?) Podajte še pravila za prvo možnost.
Operacijska semantika ukazov¶
Semantika malih korakov je podana z dvema relacijama:
relacija
(η, c) ↦ η'
pomeni: v okoljuη
ukazc
v enem koraku konča v okoljuη'
.relacija
(η, c) ↦ (η', c')
: v okoljuη
ukazc
c enem koraku spremeni okolje vη'
in se nadaljuje z ukazomc'
.
Relaciji sta določeni z naslednjimi pravili:
—–————————————–
(η, skip) ↦ η
η | e ↪ n
————————————––—————————–
(η, (x := e)) ↦ η[x↦n]
(η, c₁) ↦ (η', c₁')
—————————————––—————————–——————————
(η, (c₁ ; c₂)) ↦ (η', (c₁' ; c₂))
(η, c₁) ↦ η'
—————————————––—————————–——————————
(η, (c₁ ; c₂)) ↦ (η', c₂)
η | b ↪ true
———————————————————————–—————————————————
(η, (if b then c₁ else c₂ end)) ↦ (η, c₁)
η | b ↪ false
———————————————————————–—————————————————
(η, (if b then c₁ else c₂ end)) ↦ (η, c₂)
η | b ↪ false
———————————––—————————–——————
(η, (while b do c done)) ↦ η
η | b ↪ true
—————————————––—————————–—————————–——————————————————————-
(η, (while b do c done)) ↦ (η, (c ; while b do c done))
2.3. Denotacijska semantika¶
Povejmo nekaj še o denotacijski semantiki, to je o matematičnem pomenu programov.
Kaj je matematični pomen posameznih komponent programa?
pomen aritmetičnega izraza
e
je neko celo število⟦ e ⟧ ∈ ℤ
pomen boolovega izraza
b
je neka resničnostna vrednost⟦ b ⟧ ∈ {⊥, ⊤}
.pomen programa
c
je nekaj preslikava⟦ c ⟧ : Env → Env
iz okolij v okolja.
Kot je v navadi v denotacijski semantiki, smo z dvojnimi oglatimi oklepaji označili matematični pomen.
Na primer, matematični pomen aritmetičnega izraza je celo število. Ko zapišemo
⟦ 42 ⟧ = 42
s tem povemo, da je pomen niza znakov "42"
število, ki mu po slovensko rečemo
»dvainštirideset«. Podobno podamo pomen znaka +
z enačbo:
⟦ e₁ + e₂ ⟧ = ⟦ e₁ ⟧ + ⟦ e₂ ⟧
To pomeni, da je pomen izraza oblike e₁ + e₂
(plus v oklepajih) enak vsoti (plus
kot matematična operacija) pomenov podizrazov e₁
in e₂
.
2.3.1. Pomen aritmetičnih in boolvih izrazov¶
Če smo povsem natančni, je pomen aritmetičnega izraza e
neka preslikava ⟦ e ⟧ : Env → ℤ
, saj se lahko v izrazu e
pojavljajo spremenljivke.
⟦ x ⟧(η) = η(x)
⟦ k ⟧(η) = k
⟦ e₁ + e₂ ⟧(η) = ⟦ e₁ ⟧(η) + ⟦ e₂ ⟧(η)
⟦ e₁ * e₂ ⟧(η) = ⟦ e₁ ⟧(η) · ⟦ e₂ ⟧(η)
Podobno je pomen boolovega izraza b
preslikava ⟦ b ⟧ : Env → {⊥, ⊤}
:
⟦false⟧(η) = ⊥
⟦true⟧(η) = ⊤
⟦ b₁ and b₂ ⟧(η) = ⟦b₁⟧(η) ∧ ⟦b₂⟧(η)
⟦ b₁ or b₂ ⟧(η) = ⟦b₁⟧(η) ∨ ⟦b₂⟧(η)
⟦ not b ⟧(η) = ¬ ⟦b⟧(η)
⟦ e₁ = e₂ ⟧(η) = (⟦e₁](η) = ⟦e₂⟧(η)
⟦ e₁ < e₂ ⟧(η) = (⟦e₁](η) < ⟦e₂⟧(η)
Ali razumete zadnji dve vrstici definicije?
2.3.2. Pomen programov¶
Pomen programa e
v ukaznem jeziku funkcija, ki slika okolja v okolja:
⟦ e ⟧ : Env → Env
Tu je Env
množica vseh okolij. Pomen definiramo takole, kjer smo predpostavili, da smo že definirali matematični pomen aritmetičnih in boolovih izrazov:
⟦ skip ⟧(η) = η
⟦ x := e ⟧(η) = η⟦x ↦ ⟦e⟧(η)⟧
⟦ c₁ ; c₂ ⟧(η) = ⟦c₂⟧(⟦c₁⟧(η))
⟦ if b then c₁ else c₂⟧(η) = ⟦c₁⟧(η) če ⟦b⟧(η) = ⊤
⟦ if b then c₁ else c₂⟧(η) = ⟦c₂⟧(η) če ⟦b⟧(η) = ⊥
⟦ while b do c ⟧(η) = ?
Pomen zanke while
ni očiten in ga bomo na tem mestu preskočili. Bi znali predlagati definicijo?
2.4. Ekvivalenca programov¶
Programa sta ekvivalenta, če se v vseh kontekstih obnašata enako. To pomeni, da lahko vedno enega zamenjamo z drugim.
Natančneje, evaluacijski kontekst C[ ]
je del programske kode C
, ki ima »luknjo« [ ]
.
Programska koda A
je ekvivalentna programski kodi B
, če za vse kontekste C[ ]
velja,
da imata C[A]
in C[B]
enak rezultata in enako spreminjata okolje.
Primer
Programa
x := x + 1 ;
x := x + 2
in
x := x + 3
sta ekvivalentna.
Primer
Programa
x := x + 1 ;
x := x + 2
in
y := y + 3
nista ekvivalentna, saj ju lahko razločimo s kontekstom in okoljem η = [x ↦ 0, y ↦ 0]
.
x := 0 ;
y := 0 ;
[ ]
Če vstavimo v luknjo prvi program, bo okolje spremenil v [x ↦ 3, y ↦ 0]
, drugi pa v [x ↦ 0, y ↦ 3]
.
Naloga
Ugotovite, ali je program, ki sešteje prvih 100 števil
i := 1 ;
s := 0 ;
while i < 101 do
s := s + i ;
i := i + 1
done
ekvivalentne programu
s := 5050
(Odgovor: nista ekvivalentna, ker drugi program ne nastavi vrednosti i
. Ekvivalentno bi bilo i := 101 ; s := 5050
.)
2.5. Prevajalnik¶
Implementiramo prevajalnik v strojno kodo. Ogledali si bomo implementacijo v OCamlu, glej programski jezik comm v Programming Languages Zoo. Implementirana je razširjena različica jezika, ki ima še ukaz za izpisovanje na zaslon in lokalne spremenljivke.
Jezik comm
vsebuje naslednje komponente:
aritmetične in boolove izraze
spremenljivke
deklaracija nove lokalne spremenljivke
let x := e in c
nastavljanje vrednosti
x := e
pogojni stavek
if b then c₁ else c₂ done
zanka
while b do c done
ukaz
skip
sestavljani ukaz
c₁ ; c₂
ukaz
print e
Ogledamo si sestavne dele implementacije:
abstraktna sintaksa je definirana s podatkovnimi tipi v
syntax.ml
konkretna sintaksa je opisana v
lexer.mll
inparser.mly
; uporabimo generator parserjev Menhirpreprost simulator procesorja z RAM in skladom najdemo v
machine.ml
prevajalnik iz
comm
v strojni jezik je vcompile.ml
glavni program je v
comm.ml
Prevajalnik neposredno pretvori program v strojno kodo, ker je comm
zelo preprost jezik. Prevajanje pravih programskih jezikov poteka preko večih stopenj, z vmesnimi jeziki. Vsak naslednji jezik je nekoliko bolj preprost in bližje strojni kodi.
2.5.1. Primeri¶
Na primerih preizkusimo, kako se prevajajo programi in kako hitro delujejo.
comm
:
# Print the sum of prime numbers up to n
new n := 1000000 in
print n ;
new s := 0 in
new k := 2 in
while k < n do
new i := 2 in
new isPrime := 1 in # 1 = true, 0 = false
while isPrime = 1 and i * i < k + 1 do
if k % i = 0 then isPrime := 0 else skip end ;
i := i + 1
done ;
if isPrime = 1 then s := s + k else skip end ;
k := k + 1
done ;
print s
Python
:
# The sum of primes below n
n = 1000000
print(n)
s = 0
k = 2
while k < n:
i = 2
isPrime = True
while isPrime and i * i < k + 1:
if k % i == 0:
isPrime = False
else:
pass
i = i + 1
if isPrime:
s = s + k
else:
pass
k = k + 1
print(s)
C
:
#include <stdio.h>
int main() {
/* The sum of primes below n */
const long n = 1000000 ;
printf("%ld\n", n);
long s = 0 ;
long k = 2 ;
int i ;
int isPrime ;
while (k < n) {
i = 2 ;
isPrime = 1 ;
while ((isPrime == 1) && (i * i < k + 1)) {
if (k % i == 0) {
isPrime = 0 ;
} else { }
i = i + 1 ;
}
if (isPrime == 1) {
s = s + k;
}
else { }
k = k + 1 ;
}
printf("%ld\n", s);
}
Rezultati zelo nestrokovno izvedene meritve, ki ji ne moremo zaupati:
Programski jezik |
Čas (s) |
---|---|
C |
|
Python |
|
comm |
|