Grammatika ühesus

Arvutil ei ole mingit "oma arusaamist ", arvuti saab kõigest aru vaid talle esitatud märgi/bitijada struktuuri uurides. Ka programmilõikude semantika (tähenduse) saab leida ainult selle programmilõigu struktuuri abil. See struktuur kirjeldatakse grammatikaga ja programmilõigu tähenduset esitab selle programmilõigu süntaksi puu, s.t. derivatsioonipuu, mille põhjal see programmilõik saadakse grammatika algussümbolist.

Erinevad derivatsioonipuud annavad erineva tähenduse, näiteks aritmeetilisele avaldisele 1 + 2 * 3 saadakse erinevate puudega erinevad väärtused:

    

Esimese puu põhjal on kogu avaldise väärtus 7, aga teise puu põhjal 9. Loomulikult on selline erinevate interpretatsioonide võimalikkus lubamatu, kõik translaatorid peavad kõigis arvutites andma sama tulemuse.

Kõige olulisem nõue, mida süntaksit kirjeldavad grammatikad peavad rahuldama (sõltumata analüüsi meetodist) on grammatika ühesus (non-ambiquity) - igal grammatika abil genereeritud programmil (programmilõigul) võib olla vaid üks derivatsioonipuu, s.t. sama programmi ei või saada kahe erineva derivatsioonipuu abil. Programmikäsu või lõigu tähenduse määrab just derivatsioonipuu, seega ühesus on oluline, kuna see tagab programmi ühese tähenduse, semantika.

Aritmeetilise avaldise "otse"-defineerimine

avaldis avaldis op avaldis | arv
op + | - | * | /

ei võimalda määrata, kas 1+2*3 tuleb tõlgendada kui 1 + (2 * 3) (ülalesitatud vasakpoolne derivatsioonipuu) või kui (1 + 2) * 3 (parempoolne derivatsioonipuu), s.t. milline derivatsioonipuu on õige - kuid erinev tõlgendus (erinev semantika) annab avaldise arvutamisel erineva väärtuse.

Mitmesus on tavaliselt põhjustatud informatsiooni puudumisest grammatika mitteterminalide süsteemis. Mitteterminalid on grammatika poolt kujutatava struktuuri alamosad, üldmõisted, mis võtavad kokku mingi alamaosa. Matemaatilist avaldist ei kirjeldata koolimatemaatikas nii lihtsalt nagu ülal: avaldis avaldis op avaldis. Selline kirjeldus ei anna informatsiooni matemaatiliste operatsioonide prioriteetidest: korrutamine-jagamine on kõrgema prioriteediga ja tulevad sooritada enne kui liitmine-lahutamine. Sellepärast kirjeldatakse koolimatemaatikas matemaatiline avaldis kui hulkliige, mis koosneb üksliigetest; üksliikmed koosnevad korrutamiste-jagamistega seotud arvudest. Alammõiste üksliige lisab avaldise kirjeldusse puuduva informatsiooni ja teeb selle üheseks:

avaldis üksliige | avaldis + üksliige | avaldis - üksliige
üksliige arv | üksliige * arv | üksliige / arv

Sellega ekvivalentne on veidi "teaduslikum" grammatika:

avaldis üksliige | avaldis add_op üksliige
üksliige arv | üksliige mult_op arv
add_op + | -
mult_op * | /

mis selgemini esitab olulise: üksliikmes esinevad (vaid) kõrgema prioriteediga korrutamis-jagamistehted (tähistatud mitteterminaliga mult_op), üksliikmed liidetakse hulkliikmeks (avaldiseks) madalama prioriteediga liitmis-lahutamisoperatsioonide (tähistatud mitteterminaliga add_op) abil.

Klassikaline mitmese grammatika näide on if-lause ilma lõpetava (sulgeva) konstruktioonita, mis täpselt näitaks, kus then (ja else) järel täidetavad käsud lõpevad. Näiteks selline tingimuslause kirjeldamine (sellist skeemi kasutati esimeses FORTRAN-is ja Algol-is) on puuduliku infoga:

programm käsk | käsk programm
käsk omistamine | tingimuskäsk
omistamine identifikaator = avaldis
tingimuskäsk IF tingimus THEN programm
| IF tingimus THEN programm ELSE programm

Selle grammatika järgi saab kahte sisestatud if-lauset

IF a>b THEN IF c<d THEN x := 1 ELSE x := 0

tõlgendada kahel viisil (sisestatud osa on võetud sulgudesse): kas nii

IF a>b THEN (IF c<d THEN x := 1 ELSE x := 0)

või hoopis nii:

IF a>b THEN (IF c<d THEN x := 1) ELSE x := 0

See tähendab, et vastaval programmilõigul on kaks erinevat semantikat. Kui enne selle if-konstruktsiooni täitmist näiteks x = 2 ja a=1, b=0, c=3, d=2, saame pärast if-lausete täitmist esimese tõlgenduse järgi x = 0, aga teise tõlgenduse järgi x = 2.

Kaasaegsetes programmeerimiskeeltes kasutatakse sellise konstruktsiooni sulgemiseks spetsiaalset võtmesõna, näit ENDIF, then- ja else-osa tegemist eraldajatega piiratud blokiks (nagu C-s) või reataannet (nagu keeles Python).

Kui programmerimiskeele grammatika on mitmene (seda juhtub uut programmerimiskeelt defineerides sageli), tuleb see enne praktilist kasutamist (s.t. enne seda, kui selle põhjal koostatakse translaator) tingimata teisendada üheseks. See teisendus ei ole algoritmiseeritav, s.t. ei ole võimalik koostada programmi, mis teisendaks mitmese grammatika üheseks (s.t. vastav probleem on mittelahenduv).

Kui leksika- või süntaksianalüsaatorit genereeriv programm (Flex, Bison, Antlr jne) teatab konfliktist, on põhjuseks enamasti alati sisestatud grammatika mitmesus. Näiteks definitsioon

programm käsk | programm programm
käsk omistamine |tingimuskäsk

on mitmene, sest kolmekäsulise programmi võib saada kahel erineval viisil - kas pärast esimest produktsiooni programm programm programm arendades järgmisel sammul esimest mitteterminali programm või arendades teist mitteterminali programm.

Mitmene on ka järgnev kirjeldus:

exp Term | exp + term | exp - term
term prim | prim * term | prim / term
prim IDENT | CONST | funct
funct IDENT(args) | IDENT /* argumentideta funktsioon */

Selle järgi võib vaid ühest identifikaatorist koosnevat avaldist tõlgendada nii identifikaatorina: exp prim IDENT kui ka funktsiooni nimena: exp prim funct IDENT. Sellepärast nõutaksegi enamuses programmeerimiskeeltes, et funktsiooni tähistuses peavad pärast identifikaatorit kindlasti olema sulud ja identifikaatori ja sulgude vahel ei või olla tühikuid - tühiku järel algavaid sulge võivad skanner/süntaksianalüsaator tõlgendada sulgudesse võetud avaldisena.

Kindlasti teatavad Flex+Bison ja Antlr veast ka leksika/süntaksi kirjelduse puhul, milles esinevad read:

...
vektor '<' ident '>'
operaator aritm_op | loogika_op
aritm_op + | - | * | /
loogika_op = | /= | > | <
...
if_lause 'if' tingimus 'then käsud 'endif'
tingimus expr loogika_op expr
...
andmestruktuur IDENT | vect | struct
...

Kui analüüsitavas tekstis esineb näiteks konstruktsioon < Andmed >, siis skanner teisendab selle lekseemide jadaks loog_op IDENT loog_op ja leksikaanalüsaator ei suuda enam leida vektori definitsiooni, sest märke < , > kasutatakse ülalesitatud kirjelduses mitteüheselt.

Loomulikes keeltes (inglise, eesti,...) esineb mitteühesus väga sageli. Kui mitteühesusi - võimalusi juttu/teksti erinevalt tõlgendada - poleks, siis vist poleks ka vaja kõige enammakstud elukutset - juriste ("väänavad paragrahve nii et vähe pole ...."). Mõnikord on üldse arusaamatu, miks inimesed siiski (mõnikord) üksteist mõistavad, ja kuidas (näiteks) lapsed õpivad üksteisest aru saama. Näiteks lausel "Kõik hobused ei suutnud takistust ületada" (tõlgitud eesti keelde artiklist Vincenzo Moscati and Andrea Gualmini, "More Facts that Isomorphism Cannot Explain") on (vähemalt) kaks/rohkem? erinevat tõlgendust, sõltuvalt sellest, millele eitus "ei" on rakendatud; predikaatloogika modifitseeritud kirjaviisis oleks tõlgendused:

kõik hobused (ei (takistus.yletama()) //s.t. hobused ei suutnud, aga (näiteks) elevandid suutsid
ei(kõik hobused) (takistus.yletama() ) //s.t. kõik ei suutnud (seda) takistust ületada, aga mõni (võibolla) siiski sai sellest takistrusest üle
kõik hobused ( (ei (takistus)).yletama() ) // kõik hobused SEDA takistust ei ületanud, aga ületasid siiski mõne teise takistuse (mõned)

Kui inimeste käitumine põhineks (ainult) loogikal, oleks eelneva näite põhjal selge, miks näiteks muidu täiesti ratsionaalsed/mõistusega inimesed ei saa üksteisest aru - vt. Eesti parteide omavahelisest kaklemisest kuni maailmasõdadeni. Kahjuks juhivad inimeste käitumist veel palju rohkem isiklikud/kasusaamise/partei-riigipoliitelised jõud, seega sageli on täiesti arusaamatu, miks miski üldse toimib. Ja sellepärast juhtubki, et "tahtsime kõige paremat - 'eesti maailma viie rikkama riigi hulka!!!', aga välja tuli nagu alati ... või veel palju halvemini...".
Zhitt happenes, nagu tõdes kunagi kaheksakümnendatel Priit Pärn oma kuulsas karikatuuris.

Ja loogika on ju vaid (inimeste endi poolt loodud) abstraktsioon, s.t. matemaatiline mudel sellest, kuidas me (inimesed) arutleme. Kuid kõiki reaalsuse protsesse saab abstraheerida väga mitmel viisil, sest ükski abstraktsioon ei kirjelda ühtki protsessi 100%-lise täpsusega - antiikmaailmas olid väga ebamäärased ettekujutused selle kohta, misk õunad kukuvad õunapuu alla, Newton sai juba millesti aru, siis esitas Einstein, et asi on palju keerulisem; praegu ei ole füüsikud ka Einsteini teooriaga rahul ("stringid" jms); Darvini evolutsiooniteooriat ei ole tunnustanud suur osa USA elanikes, kes väidavad "see kõik on siiski liiga keeruline, et see oleks võinud ise tekkida... selle pidi keegi looma" (creationism).


Ülesandeid:
1. Rooma numbrid ühest kümneni on stringid

I II III IV V VI VII VIII IX X

Järgnev grammatika genereerib komaga eraldatud rooma numbrite jadasid:

Roman Number ( "," Number )*
Number StartI | StartV | StartX
StartI "I" ( "V" | "X" | [ "I" [ "I" ] ] )
StartV "V" [ "I" ] [ "I" ] [ "I" ]
StartX "X"

Kas see grammatika on ühene ? Kui ei, siis koosta ekvivalentne ühene grammatika!

2. Kas grammatika

programm   | käsk programm
käsk   |omistamine | if | tsykkel

on ühene (mõlemad produktsioonid algavad tühja alternatiiviga)? Kui ei, siis koosta ekvivalentne ühene grammatika.

3. Kas grammatika

loogiline True | False | loogiline AND loogiline | loogiline OR loogiline

on ühene? Kui ei, siis koosta ekvivalentne ühene grammatika.

4. Ada keele esimeses kirjelduses määrati mitteterminal name järgmiste produktsioonidega:

name simple_name | indexed_component | slice
simple_name identifier
indexed_component prefix(expression {,expression})
prefix name | function_call
slice prefix(discrete_range)
discrete_range subtype_indication | range
range range_attribute | expression..expression
range_attribute type_name'attribute
type_name Ident
attribute Ident
subtype_indication type_mark
type_mark Ident
expression relation relop relation
relop and | or | xor | and then | or else
relation simple_expression | [relational_operator simple_expression]
relational_operator = | /= | < | <= | > | >=
simple_expression term {addop term}
addop + | - | &
term factor {multop factor}
factor name

Leia selles grammatikas (vähemalt) kaks mitmeselt kirjeldatud objekti; ülaltoodud produktsioonide paremates pooltes sümbolid { } (kordub 0,1,2,... korda) ja [ ] (kas on või pole) on grammatika metasümbolid, kuid ( ) , . ' Ident on terminalid.

5. Kas järgnev lõik Ada manuaalis esitatud kirjeldusest on ühene ?

namespace-name original-namespace-name | class-name
original-namespace-name namespace-alias
namespace-alias identifier
class-name identifier | template-name
template-name identifier

6. Kas järgnev mitteterminali sentence kirjeldus on ühene?

sentence ::= expression
expression ::= term | expression + term
term ::= identifier | term * identifier | expression
identifier ::= X | Y | Z

7. Arvude kirjeldus programmeerimiskeeles Python on esitatud grammatikaga:

floatnumber pointfloat | exponentfloat
pointfloat [intpart] fraction | intpart "."
exponentfloat (intpart | pointfloat) exponent
intpart digit+
fraction "." digit+
exponent ("e" | "E") ["+" | "-"] digit+
digit [0..9]

Kas see kirjeldus on ühene? Kui ei, siis teisenda see (minimaalsete muudatustega) üheseks.
8. Kas järgnev lõik programmeerimiskeele Python grammatikast on ühene:

slicing simple_slicing | extended_slicing
simple_slicing identifier "[" short_slice "]"
extended_slicing primary "[" slice_list "]"
slice_list slice_item ("," slice_item)* [","]
slice_item expression | proper_slice
proper_slice short_slice | long_slice
short_slice [lower_bound] ":" [upper_bound]
long_slice short_slice ":" [stride]
lower_bound expression
upper_bound expression
stride expression

9. Kas järgnev lõik Python-i grammatikast on ühene:

primary atom | attributeref
attributeref primary "." identifier
atom identifier | literal
literal stringliteral | integer

10. Kas järgnev definitsioon on ühene:

stringliteral [stringprefix](shortstring | longstring)
stringprefix "r" | "u" | "ur" | "R" | "U" | "UR" | "Ur" | "uR"
shortstring "'" shortstringitem* "'" | '"' shortstringitem* '"'
longstring "'''" longstringitem* "'''" | '"""' longstringitem* '"""'
shortstringitem shortstringchar | escapeseq
longstringitem longstringchar | escapeseq
shortstringchar <any ASCII character except "\" or newline or the quote>
longstringchar <any ASCII character except "\">
escapeseq "\" <any ASCII character>

11. Sageli esitatakse programmeerimiskeele mõne osa kirjeldus mitteformaalselt, s.t. loomulikus keeles. Järgnev lõik kirjeldab (veidi lühendatult) programmeerimiskeeles Python kasutatavaid stringe:

"String literals can be enclosed in matching single quotes '...' or double quotes "...". They can also be enclosed in matching groups of three single or double quotes (these are generally referred to as triple-quoted strings). The backslash \ character is used to escape characters that otherwise have a special meaning, such as newline, backslash itself, or the quote character. String literals may optionally be prefixed with a letter 'r' or 'R'; such strings are called raw strings and use different rules for interpreting backslash escape sequences. In triple-quoted strings, unescaped newlines and quotes are allowed (and are retained), except that three unescaped quotes in a row terminate the string. (A “quote” is the character used to open the string, i.e. either ' or ".) Unless an 'r' or 'R' prefix is present, escape sequences in strings are interpreted according to rules similar to those used by Standard C. The recognized escape sequences are:
\\ (Backslash \) \’ (Single quote ') \" (Double quote ") \n (ASCII Linefeed LF)"

Esita Python-i stringide kirjeldus ühese grammatikaga.
12. Üks programmeerimiskeelte ühese kirjelduse formalisme on PEG (Parsing Expression Grammar) - kontekstivaba grammatika, milles produktsiooni paremal pool oleva avaldise võib koostada terminalidest, mitteterminalidest ja tühjast stringist ε kasutades regulaarseid avaldisi ja loogilisi operaatoreid & ("ja") ja ! ("ei"). Ühesuse tagamiseks nõutakse, et iga mitteterminali jaoks peab olema vaid üks produktsioon ja valikut e1 | e2 interpreteeritakse vasakult paremale esimese õnnestumiseni (s.t. kui e1 õnnestub, siis e2 enam ei kasutata). Koosta PEG-grammatika Pisi-Algoli kirjeldamiseks.

Küsimused, probleemid: ©2004-2013 Jaak Henno