Szintaxis (programozási nyelvek)

A számítástechnikában a számítógépes nyelv szintaxisa az a szabálykészlet, amely szimbólumokkal megadott kombinációkat határoz meg, amiket az adott nyelven helyesen felépített dokumentumnak vagy töredéknek tekinthetünk. Ez vonatkozhat programozási nyelvekre, ahol a dokumentum a forráskódot képviseli és jelölőnyelvekre, ahol a dokumentum az adatokat képviseli. A nyelv szintaxisa meghatározza annak kinézetét is. A szöveg alapú számítógépes nyelvek karaktersorozatokon, míg a vizuális programozási nyelvek a térbeli elrendezésen és a szimbólumok közötti kapcsolatokon alapulnak (amelyek lehetnek szövegesek vagy grafikusak). A szintaktikailag érvénytelen dokumentumokat szintaxis hibának tekintjük. A nyelv szintaxisának megtervezésekor a tervező előfordulhat, hogy leírja mind a legális (legal strings), mind az illegális szövegeket (illegal strings), mielőtt megpróbálná kitalálni ezekből a példákból az általános szabályokat. A szintaxis tehát a formára utal, vele szemben pedig a jelentés áll. A számítógépes nyelvek feldolgozása során a jelsorozat jelentésbeli (azaz szemantikai) feldolgozása általában szintaktikai feldolgozás után következik be; néhány esetben a jelentésbeli feldolgozásra van szükség a teljes szintaktikai elemzéshez, és ezeket együtt vagy párhuzamosan végezzük el. Egy fordítóban a szintaktikai elemzés magába foglalja a frontend-et, míg a szemantikai elemzés magába foglalja a backend-et (és a középső véget, azaz a middle end-et, ha megkülönböztetjük ezt a fázist).

A szintaxis szintjei

[szerkesztés]

A számítógépes nyelv szintaxisát általában három szintre osztják:

  • Szavak - lexikai szint, meghatározva, hogy a karakterek hogyan formálják tokeneket, azaz a lexikális egységeket;
  • Kifejezések - a nyelvtani szint, szűk értelemben meghatározva, hogy a tokenek hogyan formálják a mondatokat;
  • Kontextus - meghatározza, hogy a különböző elnevezések milyen objektumokra vagy változókra utalnak, ha a típusok érvényesek, stb.

Az ilyen megkülönböztetés modulárisságot eredményez, lehetővé téve minden szint külön-külön és gyakran egymástól függetlenül való leírását és feldolgozását is. Először: egy lexer a karakterek lineáris sorozatát lineáris token sorozattá alakítja; ezt "lexikai elemzésnek" vagy "lexingnek" nevezik. Másodszor, az elemző a tokenek lineáris sorozatát hierarchikus szintaxis fává alakítja; ezt szűk értelemben "elemzés"-nek nevezzük. Harmadszor, a kontextuális elemzés megnevezi a neveket és ellenőrzi a típusokat. Ez a modularitás néha lehetséges, de sok valós nyelvben egy korábbi lépés egy későbbi lépéstől függ - például a lexer hack C-ben azért van, mert a tokenizálás a környezettől függ. Ezekben az esetekben a szintaktikai elemzés megközelítheti ezt az ideális modellt.

Maga az elemző szakasz két részre osztható: az elemző fa vagy a "konkrét szintaxis fa", amit a nyelvtan határoz meg, de általában túlságosan részletes a gyakorlati alkalmazáshoz, és az elvont szintaxis fa (AST), amely egyszerűsíti ezt egy használhatóbb formába. Az AST és a kontextus elemzési lépései a szemantikai elemzés egyik formájának tekinthetők, mivel jelentést és értelmezést adnak a szintaxishoz, vagy alternatívaként szintaktikai szabályok informális, manuális implementációjaként szolgálnak, amelyeket nehéz leírni vagy teljesen végrehajtani.

A szintek általában megfelelnek a Chomsky-hierarchiában előforduló szinteknek. A szavak egy szabályos nyelvben vannak meghatározva (regular grammar), amelyet a lexikai nyelvtan (lexical grammar) ad meg, általában szabályos kifejezések formájában. A mondatok kontextusmentes nyelven (CFL) és determinisztikus kontextus nélküli nyelven (DCFL) vannak, amelyet egy mondatszerkezeti nyelvtan (phrase structure grammar) határoz meg. A kifejezési nyelvtanokat (phrase grammar) gyakran sokkal korlátozottabb nyelvtanokban határozzák meg, mint a teljes kontextus nélküli grammatikákat, hogy könnyebbé váljon azok elemzése. Míg az LR parsel bármilyen DCFL-t elemezhet, az egyszerű LALR parsel és a még egyszerűbb LL parsell eszközök hatékonyabbak, de csak azokat a nyelvtanokat képesek elemezni, amelyek gyártási szabályai korlátozottak. Alapvetően a kontextuális struktúrát egy környezetérzékeny nyelvtan (context-sensitive grammar) írhatja le, és automatikusan elemzi, például attribútum-nyelvtanok segítségével, bár általában ezt a lépést manuálisan, névfeloldási szabályok és típusellenőrzés útján hajtják végre, egy szimbólumtáblán keresztül, amely tárolja az egyes hatókörök nevét és típusát.

Készítettek már olyan eszközöket, amelyek automatikusan generálnak egy lexer-t a reguláris kifejezésekben írt lexikai specifikációból és a BNF-ben írt nyelvtani kifejezés elemzőjéből: ez lehetővé teszi a deklaratív programozás használatát, ahelyett, hogy eljárási vagy funkcionális programozásra lenne szükség. Figyelemre méltó példa a lex-yacc pár. Ezek automatikusan létrehoznak egy konkrét szintaxis fát; az elemző írónak ezt követően manuálisan kell írni olyan kódot, amely leírja, hogyan konvertálódik ez absztrakt szintaxisfává. A kontextuális elemzést általában manuálisan is végrehajthatjuk, vagyis ezen eszközök megléte ellenére az elemzést gyakran manuálisan hajtjuk végre, különféle okok miatt - lehet, hogy a mondat felépítése nem megfelelő konntextus nélkül, vagy egy alternatív megvalósítás javítja a teljesítményt vagy a hibajelentést, vagy lehetővé teszi a nyelvtan megváltoztatását. Az elemeket gyakran funkcionális nyelveken, például Haskell, vagy szkriptnyelveken, például Python vagy Perl, vagy C vagy C ++ nyelven írják.

Hibafajták

[szerkesztés]

Például 1 és 1 összeadása egy szintaktikailag érvényes Lisp program (feltételezve, hogy az 'add' függvény létezik, egyébként a névfeloldás nem sikerül). A következők azonban érvénytelenek:

(_1 1)    lexical error: '_' is not valid 
(add 1 1   parsing error: missing closing ')'

Vegyük figyelembe, hogy a lexer nem tudja azonosítani az első hibát, csupán azt ismeri fel, hogy a program fennmaradó része érvénytelen, mivel egyetlen szó sem kezdődik a '_' jelöléssel. A második hibát az elemzési szakaszban észleli: Az elemző a '(') token miatt azonosította a „lista” előállítási szabályt (mint az egyetlen egyezést), és így hibaüzenetet adhat.

A típushibákat (type errors) és a nem deklarált változókból fakadó hibákat néha szintaxis hibáknak tekintik, amikor fordításkor észlelik őket (ez általában érvényes az erősen gépelt nyelvek fordításakor), de az előfordul, hogy az ilyen hibákat általában szemantikai hibákként osztályozzák.

Vegyük az alábbi Python kódot!

'a'+1

Típushibát tartalmaz, mert hozzáadunk egy karakterláncot egy egész számhoz. Az ilyen típusú hibákat a fordítás során lehet észlelni: az elemzés (frázis-elemzés) során észlelhetők, ha a fordító külön szabályokat használ, amelyek lehetővé teszik az „integerLiteral + integerLiteral” használatát, de a „stringLiteral + integerLiteral” -ét nem. Ha a fordító értelmezési szabályt alkalmaz, amely lehetővé teszi a "LiteralOrIdentifier + LiteralOrIdentifier" műveletet, akkor a hibát majd kontextuális elemzés során észleljük (amikor a típusellenőrzés történik).

Bizonyos esetekben az ilyen műveleteket nem a fordító hajtja végre, és ezek a hibák csak futás közben észlelhetők, például dinamikusan gépelt nyelvek esetén, ld. ez a Python-os példa:

a+b

Szintaktikailag érvényes a kifejezés szintjén, de az a és b típusok helyességét csak futásidejűleg lehet meghatározni, mivel a változóknak nincsenek típusai, csak értékei Pythonban. Míg nem derül ki, hogy a fordító által észlelt típushibát szintaxis hibának kell-e nevezni (nem statikus szemantikai hibának), addig a csak a program végrehajtásakor észlelhető típusú hibákat mindig szemantikai, mint szintaxis hibáknak tekintik.

Szintaxisok definiálása

[szerkesztés]

A szöveges programozási nyelvek szintaxisa általában a reguláris kifejezések (lexikai struktúra) és a Backus-Naur forma (grammatikai struktúra) kombinációjával határozható meg, úgy, hogy meghatározzuk a szintaktikai kategóriákat, a nem terminális és a terminális szimbólumokat. A szintaktikai kategóriákat a produkcióknak (productions) nevezett szabályok határozzák meg, amelyek megadják az adott szintaktikai kategóriához tartozó értékeket. A terminálszimbólumok azok a konkrét karakterek vagy karaktersorok (például kulcsszavak, a definiálás), amelyekből szintaktikailag érvényes programok készülnek.

Egy nyelvnek lehet eltérő, egymással egyenértékű nyelvtanai, például ekvivalens reguláris kifejezések (lexikai szinten), vagy különböző kifejezési szabályok, amelyek ugyanazt a nyelvet generálják. A nyelvtanok szélesebb kategóriájának, például az LR nyelvtannak a használata lehetővé teszi a rövidebb vagy egyszerűbb nyelvtanok használatát a korlátozottabb kategóriákhoz képest - ilyen például az LL nyelvtan, amely hosszabb, több szabályt igénylő nyelvtanokat igényelhet. A különféle, de ekvivalens kifejezésnyelvek eltérő fákat eredményeznek, bár az alapul szolgáló nyelv (az érvényes dokumentumok halmaza) ugyanaz.

Példa: Lisp S-kifejezések

[szerkesztés]

Az lejjebb látható egy egyszerű példa, amelyet a reguláris kifejezések és a kiterjesztett Backus–Naur forma határoz meg. Leírja az S-kifejezések szintaxisát, a Lisp programozási nyelv adatszintaxisát, amely meghatározza a kifejezés, atom, szám, szimbólum és lista szintaktikai kategóriáinak eredményeit:

kifejezés  =atom | lista
atom       =szam | szimbolum
szam       = [+-]?['0'-'9']+
symbol     = ['A'-'Z']['A'-'Z''0'-'9'].*
list       = '(', kifejezes*, ')'

Ez a nyelvtan (grammar) a következőket határozza meg:

  • egy kifejezés vagy atom vagy lista;
  • az atom szám vagy szimbólum;
  • egy szám egy vagy több tizedes számjegy tömör szekvenciája, amelyet opcionálisan plusz vagy mínusz jel előz meg;
  • egy szimbólum egy betű, amelyet nulla vagy annál több karakter követ (kivéve a szóközt); és
  • egy lista egyező pár zárójelek között, nulla vagy annál több kifejezéssel.

Itt a tizedes számjegyek, a kis- és nagybetűk, valamint a zárójelek terminális szimbólumok.

Azaz például ebben a nyelvtanban a token-szekvenciák: '12345', '()', '(A B C232 (1))'

Összetett nyelvtanok

[szerkesztés]

A programozási nyelv meghatározásához szükséges nyelvtan a nyelv Chomsky-hierarchiában elfoglalt helyének alapján osztályozható. A legtöbb programozási nyelv nyelvtani kifejezése a 2. típusú nyelvtan segítségével határozható meg, vagyis kontextusmentes nyelvtanok, bár az általános szintaxis kontextusérzékeny (változó deklarációk és beágyazott hatókörök miatt), azaz 1. típusú. Vannak kivételek, és néhány nyelvnél a nyelvtani kifejezés 0-as típusú (Turing-complete).

Néhány nyelvben, ilyen például a Perl és a Lisp, a nyelv specifikációja (vagy megvalósítása) lehetővé tesz olyan konstrukciókat, amelyek az elemzési szakaszban futnak. Ezenfelül ezeknek a nyelveknek olyan konstrukciói vannak, amelyek lehetővé teszik a programozó számára, hogy megváltoztassa az elemző viselkedését. Ez a kombináció hatékonyan elmossa a különbséget az elemzés és a végrehajtás között, és ezeknek a nyelveknek a szintaxis-elemzését meghatározhatatlan problémává teszi, ami azt jelenti, hogy az elemzési szakasz nem fejeződik be. Például Perl-ben a kód végrehajtása a BEGIN utasítás használatával történő elemzés során lehetséges, és a Perl-függvény prototípusai megváltoztathatják a fennmaradó kód szintaktikai értelmezését és esetleg szintaktikai érvényességét is. Beszélt nyelven erre így hivatkozhatunk: "csak a Perl képezi a Perl elemzését" (mivel a kódot az elemzés során kell végrehajtani, és a nyelvtan módosíthatja), illetve "még a Perl sem tudja elemezni a Perlt" (mert ez nem meghatározható). Hasonlóképpen, a defmacro szintaxis által bevezetett Lisp makrók szintén végrehajtják az elemzést, azaz a Lisp fordítónak teljes Lisp futásidejű rendszerrel kell rendelkeznie. Ezzel szemben a C makrók pusztán karakterlánc-cserék, és nem igényelnek kódfuttatást.

Szintaxis és szemantika

[szerkesztés]

A nyelv szintaxisa leírja az érvényes program formáját, de nem ad információt a program jelentéséről vagy a program végrehajtásának eredményéről. A szimbólumok kombinációjaként kapott jelentést a szemantika kezeli (formális vagy keményen kódolt referencia-megvalósításban). Nem minden szintaktikailag helyes program szemantikailag helyes. Számos szintaktikailag helyes program ennek ellenére rosszul alakult ki, a nyelv szabályaiból adódóan ( és a nyelv specifikációjától és a megvalósítás helyességétől függően) hibát okozhat a fordításban vagy a végrehajtásban. Egyes esetekben az ilyen programok viselkedése nem egyértelmű, még akkor sem, ha a program egy nyelven belül jól definiálható.

Például a mindennapi nyelv használatával előfordulhat, hogy a nyelvtanilag helyes mondatokhoz nem lehet értéket rendelni, vagy a mondat hamis lehet, mint ez a C-ben írt példa, amely elvégzi a szemantikusan nem meghatározott műveletet, de mivel p null pointer, a p-> real és p-> im műveleteknek nincs értelme:

complex*p = NULL 
complex abs_p = sqrt (p->real * p->real + p->im * p->im);

Vagy ez az egyszerűbb példa esetén:

int x;
printf("%d", x);

szintaktikailag helyes, de szemantikailag nem meghatározott, mivel nem inicializált változót használ. Annak ellenére, hogy egyes programozási nyelvek (például a Java és a C) fordítói rendszeresen észlelik az ilyen hibákat, szemantikai hibáknak, nem pedig szintaxis hibáknak kell őket tekinteni.

Fordítás

[szerkesztés]
  • Ez a szócikk részben vagy egészben a Syntax (programming languages) című angol Wikipédia-szócikk fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.