Syntaxe (programovací jazyky) - Syntax (programming languages)

Zvýraznění syntaxe a styl odsazení se často používají k pomoci programátorům při rozpoznávání prvků zdrojového kódu. Tento kód Pythonu používá zvýraznění barevně odlišené.

Ve vědě o počítačích je syntax z počítačového jazyka je soubor pravidel, která určuje kombinace symbolů, které jsou považovány za být správně strukturované příkazy nebo výrazy v tomto jazyce. To platí jak pro programovací jazyky , kde dokument představuje zdrojový kód , tak pro značkovací jazyky , kde dokument představuje data.

Syntaxe jazyka definuje jeho povrchovou formu. Textové počítačové jazyky jsou založeny na posloupnostech znaků , zatímco vizuální programovací jazyky jsou založeny na prostorovém rozložení a spojení mezi symboly (které mohou být textové nebo grafické). Dokumenty, které jsou syntakticky neplatné, mají údajně chybu syntaxe . Při navrhování syntaxe jazyka může návrhář začít sepsáním příkladů legálních i nelegálních řetězců , než se pokusí zjistit obecná pravidla z těchto příkladů.

Syntax proto odkazuje na formu kódu a je v kontrastu se sémantikou - významem . Při zpracování počítačových jazyků přichází sémantické zpracování obvykle po syntaktickém zpracování; v některých případech je však pro úplnou syntaktickou analýzu nezbytné sémantické zpracování, které se provádí společně nebo souběžně . V kompilátoru syntaktická analýza zahrnuje frontend , zatímco sémantická analýza zahrnuje backend (a střední konec, pokud je tato fáze odlišena).

Úrovně syntaxe

Syntaxe počítačového jazyka se obecně rozlišuje do tří úrovní:

  • Slova - lexikální úroveň určující, jak postavy tvoří tokeny ;
  • Fráze - úroveň gramatiky, úzce řečeno, určující, jak tokeny tvoří fráze;
  • Kontext - určení, na co se vztahují názvy objektů nebo proměnných, zda jsou typy platné atd.

Rozlišování tímto způsobem přináší modularitu, která umožňuje každou úroveň popsat a zpracovat samostatně a často nezávisle. Za prvé, lexer změní lineární posloupnost znaků na lineární posloupnost tokenů; toto je známé jako „ lexikální analýza “ nebo „lexing“. Za druhé, analyzátor promění lineární posloupnost tokenů na hierarchický strom syntaxe; toto je známé jako " rozebrat " úzce řečeno. Za třetí, kontextová analýza rozlišuje názvy a typy kontrol. Tato modularita je někdy možná, ale v mnoha jazycích reálného světa předchozí krok závisí na pozdějším kroku-například lexer hack v C je proto, že tokenizace závisí na kontextu. I v těchto případech je syntaktická analýza často považována za aproximaci tohoto ideálního modelu.

Samotnou fázi analýzy lze rozdělit na dvě části: strom analýzy nebo „konkrétní syntaxový strom“, který je určen gramatikou, ale pro praktické použití je obecně příliš podrobný, a abstraktní syntaxový strom (AST), který zjednodušuje to do použitelné podoby. Kroky AST a kontextuální analýzy lze považovat za formu sémantické analýzy, protože přidávají syntaxi význam a interpretaci, nebo alternativně za neformální manuální implementaci syntaktických pravidel, které by bylo obtížné nebo nešikovné popsat nebo formálně implementovat.

Úrovně obecně odpovídají úrovním v Chomského hierarchii . Slova jsou v regulárním jazyce , uvedeném v lexikální gramatice , což je gramatika typu 3, obvykle uváděná jako regulární výrazy . Fráze jsou v bezkontextovém jazyce (CFL), obecně v deterministickém bezkontextovém jazyce (DCFL), specifikovaném v gramatice strukturní fráze , což je gramatika typu 2, obecně uváděná jako produkční pravidla ve formě Backus – Naur (BNF) ). Frázové gramatiky jsou často specifikovány v mnohem omezenějších gramatikách než úplné bezkontextové gramatiky , aby se snadněji analyzovaly; zatímco analyzátor LR může analyzovat jakýkoli DCFL v lineárním čase, jednoduchý analyzátor LALR a ještě jednodušší analyzátor LL jsou efektivnější, ale mohou analyzovat pouze gramatiky, jejichž pravidla produkce jsou omezena. Kontextovou strukturu lze v zásadě popsat pomocí kontextové gramatiky a automaticky ji analyzovat prostředky, jako jsou gramatiky atributů , ačkoli obecně se tento krok provádí ručně, pomocí pravidel rozlišení názvu a kontroly typu , a implementuje se pomocí tabulky symbolů který ukládá názvy a typy pro každý obor.

Byly napsány nástroje, které automaticky generují lexer z lexikální specifikace napsané v regulárních výrazech a analyzátoru z frázové gramatiky napsané v BNF: to umožňuje používat deklarativní programování , místo aby bylo nutné mít procedurální nebo funkční programování. Pozoruhodným příkladem je pár lex - yacc . Ty automaticky vytvářejí konkrétní strom syntaxe; zapisovatel analyzátoru pak musí ručně napsat kód popisující, jak je toto převedeno na abstraktní strom syntaxe. Kontextová analýza je také obecně implementována ručně. Navzdory existenci těchto automatických nástrojů je analýza často implementována ručně, a to z různých důvodů-struktura frází možná není bez kontextu, nebo alternativní implementace zlepšuje výkon nebo hlášení chyb nebo umožňuje snadnější změnu gramatiky. Analyzátory jsou často psány ve funkčních jazycích, jako je Haskell , nebo ve skriptovacích jazycích, jako je Python nebo Perl , nebo v jazyce C nebo C ++ .

Příklady chyb

Jako příklad lze uvést (add 1 1)syntakticky platný program Lisp (za předpokladu, že funkce 'přidat' existuje, jinak rozlišení názvu selže), přidání 1 a 1. Následující položky jsou neplatné:

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

Všimněte si, že lexer není schopen identifikovat první chybu - ví jen to, že po vytvoření tokenu LEFT_PAREN, '(' zbytek programu je neplatný, protože žádné slovo pravidlo nezačíná '_'. Druhá chyba je detekována ve fázi analýzy: Analyzátor identifikoval produkční pravidlo „seznam“ kvůli tokenu '(' (jako jediná shoda), a proto může poskytnout chybové hlášení; obecně může být nejednoznačné .

Chyby typu a nehlášené chyby proměnných jsou někdy považovány za chyby syntaxe, pokud jsou detekovány v době kompilace (což je obvykle případ při kompilaci silně typovaných jazyků), ačkoli je běžné tyto druhy chyb místo toho klasifikovat jako sémantické chyby.

Jako příklad lze uvést kód Pythonu

'a' + 1

obsahuje chybu typu, protože přidává řetězcový literál k celočíselnému literálu. Chyby typu tohoto druhu lze detekovat při kompilaci: Lze je zjistit během analýzy (analýza frází), pokud kompilátor používá samostatná pravidla, která umožňují „integerLiteral + integerLiteral“, ale nikoli „stringLiteral + integerLiteral“, ačkoli je pravděpodobnější, že kompilátor použije pravidlo analýzy, které umožňuje všechny výrazy ve tvaru „LiteralOrIdentifier + LiteralOrIdentifier“ a poté bude chyba detekována během kontextové analýzy (když dojde ke kontrole typu). V některých případech tuto validaci neprovádí kompilátor a tyto chyby jsou detekovány pouze za běhu.

V dynamicky zadávaném jazyce, kde lze typ určit pouze za běhu, lze mnoho chyb typu zjistit pouze za běhu. Například kód Pythonu

a + b

je syntakticky platný na úrovni frází, ale správnost typů a a b lze určit pouze za běhu, protože proměnné v Pythonu nemají typy, ale pouze hodnoty. Zatímco existuje neshoda ohledně toho, zda by chyba typu detekovaná kompilátorem měla být nazývána chybou syntaxe (spíše než statickou sémantickou chybou), chyby typu, které lze detekovat pouze v době provádění programu, jsou vždy považovány spíše za chyby sémantické než syntaktické.

Definice syntaxe

Analyzujte strom kódu Pythonu s vloženou tokenizací

Syntaxe textových programovacích jazyků je obvykle definována pomocí kombinace regulárních výrazů (pro lexikální strukturu) a formy Backus – Naur (pro gramatickou strukturu) pro indukční specifikaci syntaktických kategorií (neterminály) a koncových symbolů. Syntaktické kategorie jsou definovány pravidly nazývanými produkce , která určují hodnoty, které patří do konkrétní syntaktické kategorie. Terminální symboly jsou konkrétní znaky nebo řetězce znaků (například klíčová slova jako definovat , if , let nebo void ), ze kterých jsou konstruovány syntakticky platné programy.

Jazyk může mít různé ekvivalentní gramatiky, například ekvivalentní regulární výrazy (na lexikálních úrovních) nebo různá pravidla frází, která generují stejný jazyk. Použití širší kategorie gramatik, jako jsou LR gramatiky, může umožnit kratší nebo jednodušší gramatiky ve srovnání s omezenějšími kategoriemi, jako je LL gramatika, což může vyžadovat delší gramatiky s více pravidly. Různé, ale ekvivalentní frázové gramatiky poskytují různé stromy analýzy, ačkoli základní jazyk (sada platných dokumentů) je stejný.

Příklad: Lisp S-výrazy

Níže je jednoduchá gramatika definovaná pomocí notace regulárních výrazů a rozšířeného Backus – Naurova formuláře . Popisuje syntaxi výrazů S , datovou syntaxi programovacího jazyka Lisp , která definuje produkci pro výraz syntaktických kategorií , atom , číslo , symbol a seznam :

expression = atom   | list
atom       = number | symbol    
number     = [+-]?['0'-'9']+
symbol     = ['A'-'Z']['A'-'Z''0'-'9'].*
list       = '(', expression*, ')'

Tato gramatika určuje následující:

  • exprese je buď atom nebo seznamu ;
  • atom je buď číslo nebo symbol ;
  • číslo je neporušený sekvence jednoho nebo více desetinných míst, případně předchází plus nebo minus;
  • symbol je dopis následuje nula nebo více jiných znaků (bez mezery); a
  • seznam je uzavřeno dvojice závorek, s žádným nebo více výrazů uvnitř ní.

Zde jsou desetinné číslice, velká a malá písmena a závorky koncovými symboly.

Následují příklady dobře vytvořených sekvencí tokenů v této gramatice: ' 12345', ' ()', ' (A B C232 (1))'

Složité gramatiky

Gramatiku potřebnou k určení programovacího jazyka lze klasifikovat podle pozice v Chomského hierarchii . Frázovou gramatiku většiny programovacích jazyků lze specifikovat pomocí gramatiky typu 2, tj. Jedná se o bezkontextové gramatiky , ačkoli celková syntaxe je kontextová (kvůli deklaracím proměnných a vnořeným rozsahům), tedy Type-1. Existují však výjimky a u některých jazyků je frázová gramatika typu 0 (Turing-úplné).

V některých jazycích, jako je Perl a Lisp, umožňuje specifikace (nebo implementace) jazyka konstrukce, které se provádějí během fáze analýzy. Kromě toho mají tyto jazyky konstrukty, které umožňují programátorovi změnit chování analyzátoru. Tato kombinace efektivně stírá rozdíl mezi analýzou a prováděním a činí analýzu syntaxe v těchto jazycích nerozhodnutelným problémem , což znamená, že fáze analýzy nemusí být dokončena. Například v Perlu je možné spouštět kód při analýze pomocí BEGINpříkazu a prototypy funkcí Perlu mohou změnit syntaktickou interpretaci a případně i syntaktickou platnost zbývajícího kódu. Hovorově se tomu říká „pouze Perl může analyzovat Perl“ (protože kód musí být spuštěn během analýzy a může upravovat gramatiku), nebo silněji „ani Perl nemůže analyzovat Perl“ (protože je nerozhodnutelný). Podobně se makra Lisp zavedená defmacrosyntaxí provádějí také během analýzy, což znamená, že kompilátor Lisp musí mít k dispozici celý run-time systém Lisp. Naproti tomu makra C jsou pouze nahrazení řetězců a nevyžadují spuštění kódu.

Syntax versus sémantika

Syntaxe jazyka popisuje formu platného programu, ale neposkytuje žádné informace o významu programu ani o výsledcích provádění tohoto programu. Význam daný kombinaci symbolů řeší sémantika ( formální nebo pevně zakódovaná v referenční implementaci ). Ne všechny syntakticky správné programy jsou sémanticky správné. Mnoho syntakticky správných programů je nicméně špatně vytvořeno podle pravidel jazyka; a může (v závislosti na specifikaci jazyka a spolehlivosti implementace) vést k chybě při překladu nebo provádění. V některých případech mohou takové programy vykazovat nedefinované chování . I když je program v jazyce dobře definován, může mít stále význam, který není určen osobou, která jej napsala.

Při použití přirozeného jazyka jako příkladu nemusí být možné přiřadit význam gramaticky správné větě nebo může být věta nepravdivá:

  • Bezbarvé zelené nápady zuřivě spí .“ je gramaticky dobře formovaný, ale nemá žádný obecně přijímaný význam.
  • „John je ženatý mládenec.“ je gramaticky dobře formovaný, ale vyjadřuje význam, který nemůže být pravdivý.

Následující fragment jazyka C je syntakticky správný, ale provádí operaci, která není sémanticky definována (protože je to nulový ukazatel , operace a nemají žádný význam): pp->realp->im

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

Jako jednodušší příklad

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

je syntakticky platný, ale není sémanticky definován, protože používá neinicializovanou proměnnou . I když by kompilátory pro některé programovací jazyky (např. Java a C#) detekovaly neinicializované variabilní chyby tohoto druhu, měly by být považovány spíše za sémantické chyby než za chyby syntaxe.

Viz také

Chcete -li rychle porovnat syntaxi různých programovacích jazyků, podívejte se na seznam „Hello, World!“ ukázky programu :

Reference

externí odkazy