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 + | - | * | /
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 * | /
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 niiIF 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
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).
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!
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.
loogiline True | False | loogiline AND loogiline | loogiline OR loogiline
on ühene? Kui ei, siis koosta ekvivalentne ühene grammatika.
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.
namespace-name
original-namespace-name | class-name
original-namespace-name namespace-alias
namespace-alias identifier
class-name identifier | template-name
template-name identifier
sentence ::= expression
expression ::= term | expression + term
term ::= identifier | term * identifier | expression
identifier ::= X | Y | Z
floatnumber
pointfloat | exponentfloat
pointfloat [intpart] fraction | intpart "."
exponentfloat (intpart | pointfloat)
exponent
intpart digit+
fraction "." digit+
exponent ("e" | "E") ["+" | "-"] digit+
digit [0..9]
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
primary atom |
attributeref
attributeref primary "." identifier
atom identifier | literal
literal stringliteral | integer
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>
"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)"