Sintaxă (limbaje de programare) - Syntax (programming languages)
În informatică , sintaxa unui limbaj de calculator este setul de reguli care definește combinațiile de simboluri care sunt considerate a fi afirmații sau expresii structurate corect în acel limbaj. Acest lucru se aplică atât limbajelor de programare , unde documentul reprezintă codul sursă , cât și limbajelor de marcare , în care documentul reprezintă date.
Sintaxa unui limbaj definește forma sa de suprafață. Limbajele computerizate bazate pe text se bazează pe secvențe de caractere , în timp ce limbajele de programare vizuale se bazează pe aspectul spațial și pe conexiunile dintre simboluri (care pot fi textuale sau grafice). Se spune că documentele care sunt sintactic nevalide au o eroare de sintaxă . Atunci când proiectează sintaxa unui limbaj, un proiectant ar putea începe prin a scrie exemple atât de șiruri legale, cât și ilegale , înainte de a încerca să descopere regulile generale din aceste exemple.
Prin urmare, sintaxa se referă la forma codului și este în contrast cu semantica - sensul . În procesarea limbajelor computerizate, procesarea semantică vine în general după procesarea sintactică; cu toate acestea, în unele cazuri, procesarea semantică este necesară pentru o analiză sintactică completă, iar acestea se fac împreună sau concomitent . Într-un compilator , analiza sintactică cuprinde frontendul , în timp ce analiza semantică cuprinde backend - ul (și capătul mijlociu, dacă se distinge această fază).
Nivele de sintaxă
Sintaxa limbajului computerizat se distinge în general în trei niveluri:
- Cuvinte - nivelul lexical, determinând modul în care personajele formează jetoane ;
- Fraze - nivelul gramaticii, restrâns vorbind, care determină modul în care jetoanele formează fraze;
- Context - determinarea la ce se referă numele obiectelor sau variabilelor, dacă tipurile sunt valabile etc.
Distingerea în acest mod produce modularitate, permițând ca fiecare nivel să fie descris și procesat separat și adesea independent. În primul rând, un lexer transformă secvența liniară a caracterelor într-o secvență liniară de jetoane; aceasta este cunoscută sub numele de „ analiză lexicală ” sau „lexing”. În al doilea rând, analizorul transformă secvența liniară a jetoanelor într-un arbore ierarhic de sintaxă; acest lucru este cunoscut sub numele de „ analiză ” îngust vorbind. În al treilea rând, analiza contextuală rezolvă numele și verifică tipurile. Această modularitate este uneori posibilă, dar în multe limbaje din lumea reală un pas anterior depinde de un pas ulterior - de exemplu, hack-ul lexer din C se datorează faptului că tokenizarea depinde de context. Chiar și în aceste cazuri, analiza sintactică este adesea văzută ca o aproximare a acestui model ideal.
Etapa de analiză în sine poate fi împărțită în două părți: arborele de analiză sau „arborele de sintaxă concret”, care este determinat de gramatică, dar este, în general, mult prea detaliat pentru utilizare practică și arborele de sintaxă abstract (AST), care simplifică aceasta într-o formă utilizabilă. Etapele AST și analiza contextuală pot fi considerate o formă de analiză semantică, deoarece adaugă sens și interpretare sintaxei sau, alternativ, ca implementări manuale, informale, ale regulilor sintactice care ar fi dificil sau incomod de descris sau implementat formal.
Nivelurile corespund în general nivelurilor din ierarhia Chomsky . Cuvintele sunt într-un limbaj regulat , specificat în gramatica lexicală , care este o gramatică de tip 3, dată în general ca expresii regulate . Expresiile sunt într -un limbaj fără context (CFL), în general un limbaj determinist fără context (DCFL), specificat într-o gramatică de structură a frazelor , care este o gramatică de tip 2, dată în general ca reguli de producție în forma Backus-Naur (BNF ). Gramaticile de expresie sunt adesea specificate în gramatici mult mai restricționate decât gramaticile complete fără context , pentru a le face mai ușor de analizat; în timp ce analizorul LR poate analiza orice DCFL în timp liniar, analizorul LALR simplu și analizorul LL chiar mai simplu sunt mai eficiente, dar pot analiza doar gramaticile ale căror reguli de producție sunt constrânse. În principiu, structura contextuală poate fi descrisă printr-o gramatică sensibilă la context și analizată automat prin mijloace cum ar fi gramaticile atributelor , deși, în general, acest pas se face manual, prin reguli de rezoluție a numelor și verificarea tipului , și implementat printr-un tabel de simboluri care stochează nume și tipuri pentru fiecare domeniu.
Au fost scrise instrumente care generează automat un lexer dintr-o specificație lexicală scrisă în expresii regulate și un parser din sintagma gramatică scrisă în BNF: aceasta permite să folosiți programare declarativă , mai degrabă decât să aveți nevoie de programare procedurală sau funcțională. Un exemplu notabil este perechea lex - yacc . Acestea produc automat un arbore concret de sintaxă; scriitorul analizor trebuie să scrie manual cod care descrie modul în care acesta este convertit într-un arbore de sintaxă abstract . Analiza contextuală este de asemenea implementată manual. În ciuda existenței acestor instrumente automate, analiza este adesea implementată manual, din diverse motive - poate că structura expresiei nu este lipsită de context, sau o implementare alternativă îmbunătățește performanța sau raportarea erorilor sau permite schimbarea gramaticii mai ușor. Analizatorii sunt adesea scrise în limbaje funcționale, cum ar fi Haskell , sau în limbaje de scriptare, cum ar fi Python sau Perl , sau în C sau C ++ .
Exemple de erori
De exemplu, (add 1 1)este un program Lisp valid din punct de vedere sintactic (presupunând că funcția „adaugă” există, altfel rezoluția numelui eșuează), adăugând 1 și 1. Cu toate acestea, următoarele sunt nevalide:
(_ 1 1) lexical error: '_' is not valid (add 1 1 parsing error: missing closing ')'
Rețineți că lexerul nu este capabil să identifice prima eroare - tot ce știe este că, după producerea simbolului LEFT_PAREN, '(' restul programului este invalid, deoarece nicio regulă de cuvânt nu începe cu '_'. A doua eroare este detectată în etapa de analiză: Analizatorul a identificat regula de producție „listă” datorită simbolului '(' (singura potrivire) și, astfel, poate da un mesaj de eroare; în general, poate fi ambiguu .
Erorile de tip și variabilele nedeclarate sunt uneori considerate a fi erori de sintaxă atunci când sunt detectate la compilare (ceea ce se întâmplă de obicei la compilarea limbajelor puternic tastate), deși este obișnuit să clasificăm aceste tipuri de erori drept erori semantice .
De exemplu, codul Python
'a' + 1
conține o eroare de tip deoarece adaugă un literal șir la un literal întreg. Erorile de tip de acest tip pot fi detectate la compilare: pot fi detectate în timpul analizei (analiza frazelor) dacă compilatorul folosește reguli separate care permit „integerLiteral + integerLiteral”, dar nu „stringLiteral + integerLiteral”, deși este mai probabil ca compilatorul va folosi o regulă de analiză care permite toate expresiile formei „LiteralOrIdentifier + LiteralOrIdentifier” și apoi eroarea va fi detectată în timpul analizei contextuale (când are loc verificarea tipului). În unele cazuri, această validare nu este realizată de compilator, iar aceste erori sunt detectate numai în timpul rulării.
Într-un limbaj tastat dinamic, unde tipul poate fi determinat numai în timpul rulării, multe erori de tip pot fi detectate numai în timpul rulării. De exemplu, codul Python
a + b
este valabil din punct de vedere sintactic la nivel de frază, dar corectitudinea tipurilor de a și b poate fi determinată numai în timpul rulării, deoarece variabilele nu au tipuri în Python, doar valorile. În timp ce există un dezacord cu privire la faptul dacă o eroare de tip detectată de compilator ar trebui să fie numită eroare de sintaxă (mai degrabă decât o eroare semantică statică ), erorile de tip care pot fi detectate doar la momentul executării programului sunt întotdeauna considerate erori semantice, mai degrabă decât erori de sintaxă.
Definiția sintaxei
Sintaxa limbajelor de programare textuală este de obicei definită utilizând o combinație de expresii regulate (pentru structura lexicală ) și forma Backus – Naur (pentru structura gramaticală ) pentru a specifica inductiv categoriile sintactice (nonterminale) și simbolurile terminale . Categoriile sintactice sunt definite de reguli numite producții , care specifică valorile care aparțin unei anumite categorii sintactice. Simbolurile terminale sunt caracterele concrete sau șirurile de caractere (de exemplu, cuvinte cheie precum define , if , let sau void ) din care sunt construite programe valabile din punct de vedere sintactic.
Un limbaj poate avea diferite gramatici echivalente, cum ar fi expresii regulate echivalente (la niveluri lexicale) sau reguli de expresie diferite care generează același limbaj. Utilizarea unei categorii mai largi de gramatici, cum ar fi gramaticile LR, poate permite gramatici mai scurte sau mai simple în comparație cu categorii mai restrânse, cum ar fi gramatica LL, care poate necesita gramatică mai lungă, cu mai multe reguli. Gramaticile de expresii diferite, dar echivalente, produc arbori de analiză diferite, deși limbajul de bază (set de documente valide) este același.
Exemplu: Lisp S-expressions
Mai jos este o gramatică simplă, definită utilizând notația expresiilor regulate și a formei Extended Backus – Naur . Descrie sintaxa expresiilor S , o sintaxă de date a limbajului de programare Lisp , care definește producțiile pentru expresia categoriilor sintactice , atom , număr , simbol și listă :
expression = atom | list
atom = number | symbol
number = [+-]?['0'-'9']+
symbol = ['A'-'Z']['A'-'Z''0'-'9'].*
list = '(', expression*, ')'
Această gramatică specifică următoarele:
- o expresie este fie un atom, fie o listă ;
- un atom este fie un număr, fie un simbol ;
- un număr este o secvență neîntreruptă de una sau mai multe cifre zecimale, precedată opțional de un semn plus sau minus;
- un simbol este o literă urmată de zero sau mai multe caractere (cu excepția spațiului alb); și
- o listă este o pereche de paranteze potrivite, cu zero sau mai multe expresii în interiorul ei.
Aici cifrele zecimale, majusculele și minusculele și parantezele sunt simboluri terminale.
Următoarele sunt exemple de secvențe de jetoane bine formate în această gramatică: ' 12345', ' ()', ' (A B C232 (1))'
Gramatici complexe
Gramatica necesară pentru a specifica un limbaj de programare poate fi clasificată după poziția sa în ierarhia Chomsky . Expresia gramatică a majorității limbajelor de programare poate fi specificată folosind o gramatică de tip 2, adică sunt gramatici fără context , deși sintaxa generală este sensibilă la context (datorită declarațiilor variabile și domeniilor imbricate), deci de tip 1. Cu toate acestea, există excepții, iar pentru unele limbi fraza gramatică este de tip 0 (Turing-complet).
În unele limbi, cum ar fi Perl și Lisp, specificația (sau implementarea) limbajului permite constructele care se execută în timpul fazei de analiză. Mai mult, aceste limbaje au structuri care permit programatorului să modifice comportamentul parserului. Această combinație estompează efectiv distincția dintre analiză și execuție și face din analiza sintaxei o problemă indecidabilă în aceste limbi, ceea ce înseamnă că este posibil ca faza de analiză să nu se termine. De exemplu, în Perl este posibil să se execute cod în timpul analizei folosind o BEGINinstrucțiune, iar prototipurile funcției Perl pot modifica interpretarea sintactică și, eventual, chiar validitatea sintactică a codului rămas. În mod colocvial, aceasta este denumită „numai Perl poate analiza Perl” (deoarece codul trebuie executat în timpul analizei și poate modifica gramatica) sau mai puternic „chiar și Perl nu poate analiza Perl” (deoarece este indecidabil). În mod similar, macro-urile Lisp introduse de defmacrosintaxă se execută și în timpul analizei, ceea ce înseamnă că un compilator Lisp trebuie să aibă un întreg sistem de execuție Lisp prezent. În schimb, macro-urile C sunt doar înlocuiri de șiruri și nu necesită executarea codului.
Sintaxă versus semantică
Sintaxa unui limbaj descrie forma unui program valid, dar nu oferă nicio informație despre semnificația programului sau rezultatele executării acelui program. Înțelesul dat unei combinații de simboluri este tratat de semantică (fie formală, fie codificată într-o implementare de referință ). Nu toate programele corecte din punct de vedere sintactic sunt corecte din punct de vedere semantic. Multe programe sintactic corecte sunt totuși prost formate, conform regulilor limbii; și poate (în funcție de specificațiile de limbă și de soliditatea implementării) duce la o eroare la traducere sau la execuție. În unele cazuri, astfel de programe pot prezenta un comportament nedefinit . Chiar și atunci când un program este bine definit într-un limbaj, acesta poate avea totuși o semnificație care nu este intenționată de persoana care l-a scris.
Folosind limbajul natural ca exemplu, este posibil să nu fie posibilă atribuirea unui sens unei propoziții corecte din punct de vedere gramatical sau propoziția poate fi falsă:
- „ Ideile verzi incolore dorm furios ”. este bine format din punct de vedere gramatical, dar nu are un sens general acceptat.
- „Ioan este un burlac căsătorit”. este bine format din punct de vedere gramatical, dar exprimă un sens care nu poate fi adevărat.
Următorul fragment de limbaj C este corect din punct de vedere sintactic, dar efectuează o operație care nu este definită semantic (deoarece este un indicator nul , operațiunile și nu au nicio semnificație):
pp->realp->im
complex *p = NULL;
complex abs_p = sqrt (p->real * p->real + p->im * p->im);
Ca un exemplu mai simplu,
int x;
printf("%d", x);
este valabil din punct de vedere sintactic, dar nu este definit semantic, deoarece folosește o variabilă neinițializată . Chiar dacă compilatoarele pentru anumite limbaje de programare (de exemplu, Java și C #) ar detecta erori variabile neinițializate de acest fel, acestea ar trebui considerate mai degrabă ca erori semantice decât erori de sintaxă.
Vezi si
Pentru a compara rapid sintaxa diferitelor limbaje de programare, aruncați o privire la lista „Bună ziua, lume!” exemple de programe :
- Sintaxa și semantica Prolog
- Sintaxa Perl
- Sintaxa și semantica PHP
- Sintaxa C
- Sintaxa C ++
- Sintaxa Java
- Sintaxa JavaScript
- Sintaxa și semantica Python
- Sintaxa Lua
- Sintaxa Haskell
Referințe
linkuri externe
- Diverse construcții sintactice utilizate în limbaje de programare pentru computer
- Eroare Python „ImportError: Niciun modul numit” De ce? Cum? Linie de comanda? [Rezolvat2021]