Ce este un compilator, interpret, traducător. Arhitectura compilatorului: componente. Ideea principală a coborârii recursive este că fiecare nonterminal al gramaticii are o procedură corespunzătoare care recunoaște orice lanț generat de acest nonterminal.

Fiecare computer are propriul său limba proprie programare – limbaj de comandă sau limbajul mașinii– și poate executa programe scrise numai în acest limbaj. În principiu, orice algoritm poate fi descris folosind limbajul mașinii, dar costurile de programare vor fi extrem de mari. Acest lucru se datorează faptului că limbajul mașină vă permite să descrieți și să procesați doar structuri de date primitive - biți, octeți, cuvinte. Programarea în coduri de mașină necesită detalii excesive ale programului și este accesibilă doar programatorilor, ei bine cunoștințe despre dispozitiv si functionarea calculatorului. Limbile ne-au permis să depășim această dificultate nivel înalt(Fortran, PL/1, Pascal, C, Ada etc.) cu structuri de date dezvoltate și mijloace de procesare a acestora, independent de limbajul unui anumit computer.

Limbajele algoritmice de nivel înalt permit programatorului să descrie destul de simplu și convenabil algoritmi pentru rezolvarea multor probleme aplicate. Această descriere se numește programul original, iar limbajul de nivel înalt este limba de intrare.

Procesor de limbaj este un program în limbajul mașinii care permite unui computer să înțeleagă și să execute programe în limbajul de intrare. Există două tipuri principale de procesoare de limbă: interpreți și traducători.

Interpret este un program care acceptă un program în limbajul de intrare ca intrare și, pe măsură ce sunt recunoscute constructele limbajului de intrare, le implementează, producând rezultatele calculelor prescrise de programul sursă.

Traducător este un program care acceptă un program original ca intrare și generează la ieșire un program care este echivalent funcțional cu cel original, numit obiect. Un program obiect este scris într-un limbaj obiect. Într-un caz particular, limbajul mașină poate servi ca limbaj obiect, iar în acest caz, programul obținut la ieșirea traducătorului poate fi executat imediat pe un computer (interpretat). În acest caz, computerul este un interpret program obiectîn codurile mașinilor. ÎN caz general un limbaj obiect nu trebuie să fie o mașină sau ceva apropiat de el (autocod). Un limbaj obiect poate servi ca limbaj intermediar– o limbă situată între limbajul de intrare și limbajul mașină.

Dacă un limbaj intermediar este folosit ca limbaj obiect, atunci sunt posibile două opțiuni pentru construirea unui traducător.

Prima opțiune este că pentru limba intermediară există (sau este în curs de dezvoltare) un alt traducător din limbajul intermediar în limbajul mașină și este folosit ca ultim bloc al traducătorului proiectat.

A doua opțiune pentru construirea unui traducător folosind o limbă intermediară este de a construi un interpret pentru comenzile de limbaj intermediar și de a-l folosi ca ultimul bloc al traducătorului. Avantajul interpreților se manifestă în debugging și traducători de dialog, care asigură că utilizatorul poate lucra într-un mod interactiv, până la a face modificări în program fără a-l retraduce complet.

Interpreții sunt folosiți și în emularea programelor - execuția pe o mașină tehnologică a programelor compilate pentru o altă mașină (obiect). Această opțiune, în special, este utilizată la depanarea programelor pe un computer de uz general care va fi executat pe un computer specializat.

Un traducător care folosește o limbă apropiată de limbajul mașinii (autocod sau asamblare) ca limbă de intrare este denumit în mod tradițional asamblator. Este chemat un traducător pentru o limbă de nivel înalt compilator.

În construirea unui compilator pentru ultimii ani S-au făcut progrese semnificative. Primele compilatoare au folosit așa-numitele metode de difuzare în direct- Acestea sunt metode predominant euristice, în care, pe baza idee generală Pentru fiecare construct de limbă, a fost dezvoltat propriul algoritm de traducere într-o mașină echivalentă. Aceste metode au fost lente și nestructurate.

Metodologia de proiectare pentru compilatoarele moderne se bazează pe metoda compozițională condusă sintactic prelucrarea limbajului. Compozițional în sensul că procesul de conversie a unui program sursă într-un program obiect este implementat prin alcătuirea de mapări independente funcțional cu structuri de date de intrare și de ieșire identificate în mod explicit. Aceste mapări sunt construite din luarea în considerare a programului sursă ca o compoziție a principalelor aspecte (niveluri) descriere a limbajului de intrare: vocabular, sintaxă, semantică și pragmatică, și identificarea acestor aspecte din programul sursă în timpul compilării acestuia. Să luăm în considerare aceste aspecte pentru a obține un model de compilator simplificat.

Baza oricărui natural sau limbaj artificial este alfabet– un set de caractere elementare permise în limbă (litere, cifre și caractere de serviciu). Semnele pot fi combinate în cuvinte– construcții elementare ale limbajului, considerate în text (program) ca simboluri indivizibile care au un anumit sens.


Un cuvânt poate fi, de asemenea, un singur caracter. De exemplu, în limbajul Pascal, cuvintele sunt identificatori, cuvinte cheie, constante și separatori, în special simboluri aritmetice și logice, paranteze, virgule și alte simboluri. Vocabularul unei limbi, împreună cu o descriere a modurilor în care sunt reprezentate, constituie vocabular limbă.

Cuvintele dintr-o limbă sunt combinate în structuri mai complexe - propoziții. În limbajele de programare, cea mai simplă propoziție este un operator. Propozițiile sunt construite din cuvinte și propoziții mai simple după regulile de sintaxă. Sintaxă limbajul este o descriere a propozițiilor corecte. Descrierea sensului propozițiilor, de ex. semnificațiile cuvintelor și conexiunile lor interne, este semantică limbă. În plus, observăm că un anumit program are un anumit impact asupra traducătorului - pragmatism. Luate împreună, sintaxa, semantica și pragmatismul limbajului formează semiotica limbă.

Traducerea unui program dintr-o limbă în alta, în general, constă în schimbarea alfabetului, vocabularului și sintaxei limbajului programului, păstrând în același timp semantica acestuia. Procesul de traducere a unui program sursă într-un program obiect este de obicei împărțit în mai multe subprocese independente (faze de traducere), care sunt implementate de blocurile translator corespunzătoare. Este convenabil să se ia în considerare analiza lexicală, analiza sintactică, analiza semantică și

sinteza programului obiect. Cu toate acestea, în multe compilatoare reale, aceste faze sunt împărțite în mai multe subfaze și pot exista și alte faze (de exemplu, optimizarea codului obiect). În fig. 1.1 prezintă o simplificare model functional traducător.

Conform acestui model, programul de intrare este supus în primul rând procesării lexicale. Scopul analizei lexicale este de a traduce programul sursă în limbajul intern al compilatorului, în care cuvintele cheie, identificatorii, etichetele și constantele sunt reduse la același format și înlocuite cu coduri condiționate: numerice sau simbolice, care se numesc descriptori. Fiecare descriptor constă din două părți: clasa (tipul) jetonului și un pointer către adresa de memorie unde sunt stocate informații despre tokenul specific. De obicei, aceste informații sunt organizate în tabele. Concomitent cu traducerea programului sursă în limbajul intern, la etapa analizei lexicale, controlul lexical- identificarea cuvintelor inacceptabile în program.

Analizorul preia rezultatul analizorului lexical și traduce secvența de imagini simbol în forma unui program intermediar. Un program intermediar este în esență o reprezentare a arborelui de sintaxă a unui program. Acesta din urmă reflectă structura programului original, adică. ordinea și conexiunile dintre operatorii săi. În timpul construcției unui arbore de sintaxă, control sintactic– identificarea erorilor de sintaxă din program.

Ieșirea reală a parserului poate fi secvența de comenzi necesare pentru a construi middleware-ul, pentru a accesa tabelele de directoare și pentru a emite un mesaj de diagnosticare atunci când este necesar.

Orez. 1.1. Model funcțional simplificat al traducătorului

Sinteza unui program obiect începe, de regulă, cu distribuirea și alocarea memoriei pentru obiectele principale ale programului. Fiecare propoziție din programul sursă este apoi examinată și sunt generate propoziții echivalente semantic în limbajul obiect. Informațiile de intrare aici sunt arborele de sintaxă al programului și tabelele de ieșire ale analizorului lexical - un tabel de identificatori, un tabel de constante și altele. Analiza arborelui ne permite să identificăm secvența comenzilor generate ale unui program obiect, iar folosind tabelul de identificatori, determinăm tipurile de comenzi care sunt valabile pentru valorile operanzilor din comenzile generate (de exemplu, ce comenzi trebuie generate: fixă ​​sau virgulă mobilă etc.).

Generarea efectivă a unui program obiect este adesea precedată de analiza semantică care include diverse tipuri prelucrare semantică. Un tip este verificarea convențiilor semantice într-un program. Exemple de astfel de convenții: unicitatea descrierii fiecărui identificator din program, definirea unei variabile se face înainte de utilizarea acesteia etc. Analiza semantică poate fi efectuată în fazele ulterioare ale traducerii, de exemplu, în faza de optimizare a programului, care poate fi inclusă și în traducător. Scopul optimizării este de a reduce resursele de timp sau resursele RAM necesare pentru a executa un program obiect.

Acestea sunt principalele aspecte ale procesului de traducere din limbi de nivel înalt. Mai multe detalii despre organizarea diferitelor faze ale difuzării și problemele conexe moduri practice descrierile lor matematice sunt discutate mai jos.

Traducător (îng. translator - translator) este un program de traducere. Convertește un program scris într-unul dintre limbajele de programare într-un fișier binar al unui program format din instrucțiuni de mașină sau execută direct acțiunile programului.

Traducătorii sunt implementați sub formă de compilatoare, interpreți, preprocesoare și emulatori. În ceea ce privește realizarea muncii, compilatorul și interpretul sunt semnificativ diferite.

Compilator (eng. compilator - compilator, colector)- citește întregul program, îl traduce și creează o versiune completă a programului în limbajul mașinii, adică un fișier binar care conține o listă de instrucțiuni ale mașinii. Un fișier binar poate fi executabil, bibliotecă, obiect), este executat de sistemul de operare fără participarea unui compilator.

Interpret (ing. interpret - interpret, traducător)— traduce programul linie cu linie (o instrucțiune la un moment dat) în codul mașinii (instrucțiuni pentru procesor, OS, alt mediu), execută instrucțiunea tradusă (linia de program) și apoi trece la rândul următor textul programului. Interpretul nu generează fișiere executabile, el însuși realizează toate acțiunile scrise în textul programului sursă.

După ce programul este compilat, nici unul programul original, nici compilatorul nu mai sunt necesare. În același timp, programul procesat de interpret trebuie retradus în limbajul mașinii de fiecare dată când programul este lansat.

Programele compilate rulează mai repede, dar cele interpretate sunt mai ușor de reparat și schimbat.

Fiecare limbaj specific orientată fie spre compilare, fie spre interpretare – în funcţie de scopul pentru care a fost creat. De exemplu, Pascal este de obicei folosit pentru a rezolva probleme destul de complexe în care viteza programului este importantă. De aceea limba dată implementat de obicei folosind un compilator.

Pe de altă parte, BASIC a fost creat ca un limbaj pentru programatorii începători, pentru care execuția linie cu linie a unui program are avantaje incontestabile.

Uneori există atât un compilator, cât și un interpret pentru aceeași limbă. În acest caz, puteți utiliza un interpret pentru a dezvolta și testa programul, apoi puteți compila programul depanat pentru a crește viteza de execuție.

Preprocesor este un traducător dintr-un limbaj de programare în altul fără a crea un fișier executabil sau a executa un program.

Preprocesoarele sunt convenabile pentru a extinde capacitățile unui limbaj și comoditatea programării prin utilizarea, în etapa de scriere a unui program, a unui dialect al unui limbaj de programare mai prietenos cu oamenii și traducerea acestuia de către un preprocesor în textul unui limbaj de programare standard. , care poate fi compilat de un compilator standard.

Emulator- software și/sau operare într-un sistem de operare și platformă hardware țintă hardware, conceput pentru a executa programe produse într-un sistem de operare diferit sau care rulează pe un hardware diferit de cel țintă, dar permițând să fie efectuate aceleași operațiuni în mediul țintă ca și în sistemul simulat.

Limbajele de emulare includ sisteme precum Java, .Net, Mono, în care, în etapa de creare a unui program, acesta este compilat în bytecode special și obținut fișier binar, potrivit pentru execuție în orice sală de operație și sală de feronerie mediu și execuție bytecode rezultat este produs pe mașina țintă folosind un interpret simplu și rapid (mașină virtuală).

Reasamblator, dezasamblator- un instrument software conceput pentru a decripta cod binar cu prezentarea sa sub formă de text de asamblare sau text dintr-un alt limbaj de programare, care vă permite să analizați algoritmul programului sursă și să utilizați textul rezultat pentru modificarea necesară a programului, de exemplu, modificarea adreselor dispozitive externe, accesarea resurselor sistemului și rețelei, identificarea funcțiilor ascunse ale codului binar (de exemplu, virus informatic sau alt program rău intenționat: troian, vierme, keylogger etc.).

la algoritmi de algoritmizare, structuri de date și programare DBMS Ya&MP 3GL 4GL 5GL tehnologii prog.

Știați Ce abstractizare prin parametrizare este o tehnică de programare care permite, folosind parametri, să se reprezinte un set practic nelimitat de calcule diferite cu un singur program, care este o abstractizare a acestor mulțimi.

Traducători

Deoarece textul unui program scris în Pascal nu este de înțeles de un computer, acesta trebuie tradus în limbajul mașinii. Această traducere a unui program dintr-un limbaj de programare într-un limbaj de cod de mașină este numită difuzat (traducere - traducere), și este realizat prin programe speciale - radiodifuzorii.

Există trei tipuri de traducători: interpreți, compilatori și asamblatori.

Interpret se numește translator care realizează procesarea și execuția operator cu operator (instrucțiune cu instrucțiune) și execuția programului sursă.

Compilator convertește (traduce) întregul program într-un modul în limbaj mașină, după care programul este scris în memoria computerului și abia apoi executat.

Asambleri traduce un program scris în limbaj de asamblare (autocode) într-un program în limbaj mașină.

Orice traducător rezolvă următoarele sarcini principale:

Analizează programul tradus, în special determină dacă acesta conține erori de sintaxă;

Generează un program de ieșire (numit adesea obiect sau program de lucru) într-un limbaj de comandă de calculator (în unele cazuri, traducătorul generează un program de ieșire într-un limbaj intermediar, de exemplu, limbaj de asamblare);

Alocă memorie pentru programul de ieșire (în cel mai simplu caz, aceasta constă în atribuirea fiecărui fragment de program, variabile, constante, tablouri și alte obiecte cu propriile adrese de memorie).

Introducere în .Net și Sharp

Programatorul scrie un program într-un limbaj pe care programatorul îl înțelege, iar computerul execută doar programe scrise în limbajul codului mașinii. Setul de instrumente pentru scrierea, editarea și convertirea unui program în cod de mașină și executarea acestuia se numește mediu de dezvoltare.

Mediul de dezvoltare conține:

    Editor de text pentru introducerea și editarea textului programului

    Compilator pentru traducerea unui program în limbajul de comandă al mașinii

    Instrumente pentru depanare și lansare de programe pentru execuție

    Biblioteci partajate cu elemente software reutilizabile

    Sistem de ajutor etc.

Platforma .NET ("dot net"), dezvoltată de de către Microsoft, include nu numai un mediu de dezvoltare în mai multe limbi numit Visual Studio .NET, ci și multe alte instrumente, cum ar fi instrumente pentru baze de date, e-mail etc.

Cele mai importante sarcini în dezvoltarea software-ului modern sunt:

    Portabilitate - capacitatea de a rula pe diferite tipuri de computere

    Securitate – imposibilitatea acțiunilor neautorizate

    Fiabilitate – funcționare fără defecțiuni în condiții date

    Utilizarea componentelor disponibile pentru a accelera dezvoltarea

    Interacțiune interlingvă – utilizarea mai multor limbaje de programare.

Toate aceste sarcini sunt rezolvate în cadrul platformei .NET.

Pentru a asigura portabilitatea, compilatorii platformei traduc programul nu în codul mașinii, ci în limbajul intermediar MSIL (Microsoft Intermediate Language) sau pur și simplu în IL. IL nu conține comenzi specifice unui sistem de operare sau tip computer. Programul IL este executat de CLR (Common Language Runtime), care este deja specific fiecărui tip de computer. Traducerea unui program IL în coduri mașină ale unui anumit computer este efectuată de un compilator JIT (Just In Time).

Diagrama de execuție a programului pe platforma .NET este prezentată în Fig. 1.

Compilatorul creează un ansamblu de program - un fișier cu extensia . exe sau . dll, care conține codul IL. Execuția programului este organizată de mediul CRL, care monitorizează validitatea operațiunilor, realizează alocarea și curățarea memoriei și gestionează erorile de execuție. Acest lucru asigură siguranța și fiabilitatea programelor.

Prețul acestor avantaje este o scădere a performanței programelor și necesitatea instalării .NET pe computer pentru a executa programe gata făcute.

Deci, .NET este o platformă de programare.

C# (Sea Sharp) este unul dintre limbajele de programare ale platformei .NET. Este inclus cu Visual Studio - Visual Studio.NET (versiunile 2008, 2010, 2012). Pe lângă C#, Visual Studio.NET include Visual Basic.NET și Visual C++.

Unul dintre motivele pentru care Microsoft dezvoltă un nou limbaj este crearea unui limbaj orientat pe componente pentru platformă .NET Framework.

Fig.1 Diagrama de execuție a programului în .NET

NET Framework este format din două părți:

    În primul rând, include o bibliotecă imensă de clase care pot fi apelate din programele C#. Există o mulțime de clase (aproximativ câteva mii). Acest lucru elimină nevoia de a scrie totul singur. Prin urmare, programarea în C# implică scrierea propriului cod care apelează clasele stocate în .NET Framework după cum este necesar.

    În al doilea rând, include mediul .NET Runtime, care controlează lansarea și funcționarea programelor gata făcute.

Platforma .NET este un mediu deschis – dezvoltatori terți a creat zeci de compilatoare pentru .NET pentru limbile Ada, COBOL, Fortran, Lisp, Oberon, Perl, Python etc.

Platforma .NET se dezvoltă activ - sunt lansate noi versiuni ale acestei platforme. Folosind meniul Proiect Proprietăți Aflați versiunea platformei .NET pe care o utilizați.

În teorie, un program .NET poate rula pe orice sistem de operare pe care este instalat .NET. Dar, în practică, singura platformă oficială pentru aceasta este sistemul de operare Windows. Cu toate acestea, există implementări neoficiale .NET pentru Linux asemănător Unix, Mac OS X și altele (Mono este un proiect .NET Framework bazat pe programul gratuit). software).

Cuvântul rapieră

Cuvântul rapier în litere engleze (transliterat) - rapira

Cuvântul rapier este format din 6 litere: a a și p r r

Înțelesuri ale cuvântului rapier. Ce este o rapieră?

Rapier (germană Rapier, din franceză rapière, inițial spadas roperas spaniolă - literalmente, „sabie pentru îmbrăcăminte” (adică nu pentru armură), distorsionată în franceză la rapiere) - o armă cu tăiș predominant, un tip de sabie...

en.wikipedia.org

Rapieră (germană Rapier, din franceză rapière), o armă de piercing sportivă, constă dintr-o lamă elastică din oțel și un mâner (o protecție și un mâner de protecție în formă de cupă).

TSB. - 1969-1978

RAPIER (germană Rapier, din franceză rapiere). Armă de piercing sportivă. Constă dintr-o lamă flexibilă din oțel și mâner (apărătoare și mâner de protecție în formă de cupă). Lama are o secțiune transversală dreptunghiulară, care se îngustează spre partea de sus...

Enciclopedia Olimpică. - 2006

RAPIRA, Extended Adapted Poplan-Interpreter, Editor, Archive, este un limbaj de programare educațional și industrial. Dezvoltat la începutul anilor 80 în URSS. Pinza este un mijloc...

Enciclopedia limbajelor de programare

Rapieră (SAM)

Rapier este un sistem de rachete sol-aer dezvoltat de britanici forţelor armate pentru Royal Air Force. Este în serviciu cu armatele din Australia, Marea Britanie, Indonezia, Singapore, Turcia, Malaezia și Elveția.

en.wikipedia.org

Luptă cu spade

Rapieră de luptă - (din franceză rapiere) - piercing, piercing-tăiere cu lamă lungă X.0. cu mâner, cunoscut în Europa din a 2-a jumătate a secolului al XVII-lea. Constă dintr-o lamă dreaptă, plată sau fațetată din oțel, cu un ascuțit (pentru duelul R.)...

Penză sportivă

SPORTS RAPIRA - o armă cu tăiș sport, constând dintr-o lamă dreptunghiulară flexibilă în secțiune transversală și un mâner detașabil cu o protecție rotundă în formă de cupă.

weapon.slovaronline.com

O rapieră sport este o armă cu tăiș pentru sport, constând dintr-o lamă dreptunghiulară flexibilă și un mâner detașabil cu o protecție rotundă în formă de cupă.

Petrov A. Dicționar de arme cu lamă și armuri

Rapier (limbaj de programare)

RAPIRA - Extended Adapted Poplan-Interpreter, Editor, Archive - limbaj de programare procedural. Dezvoltat la începutul anilor 80 în URSS ca mijloc de tranziție de la mai mult limbaje simple(în special, limbaj educațional Robik)...

en.wikipedia.org

Scrimă la Jocurile Olimpice de vară din 1896 - floretă

Competiția masculină de scrimă cu floretă de la Jocurile Olimpice de vară din 1896 a avut loc pe 7 aprilie.

Traducător, compilator, interpret

Au participat opt ​​sportivi din două țări. Mai întâi au concurat în două grupe de patru sportivi...

en.wikipedia.org

Scrimă la Jocurile Olimpice de vară din 1900 - floretă

Competiția masculină de scrimă cu floretă de la Jocurile Olimpice de vară din 1900 a avut loc în perioada 14-19 și 21 mai. Au participat 54 de sportivi din zece țări.

en.wikipedia.org

limba rusă

Spadă.

Dicționar morfem-ortografic. - 2002

Scrimă la Jocurile Olimpice de vară din 1900 - floretă printre maeștri

Competiția de scrimă cu floretă între maeștrii de sex masculin la Jocurile Olimpice de vară din 1900 a avut loc în perioada 22 - 25 și 27 - 28 mai.

Au participat 59 de sportivi din șapte țări.

en.wikipedia.org

Exemple de utilizare pentru rapier

Rapa este făcută în așa fel încât să nu poată intra înăuntru maximul care poate rămâne este o vânătaie.

Pe baza marcajelor de pe mânerul spatei, operatorii merg la clubul de scrimă și află că de acolo a fost furată spatul în urmă cu un an.

Curs: Standarde și licențe software

Standarde familia UNIX. Standardele limbajului de programare C System V Interface Definition (SVID). comitetele POSIX. X/Open, OSF și Open Group. Licențe pentru software și documentație.
Conţinut

  • 3.1. Standardele familiei UNIX
    • C Standardele limbajului de programare
    • Definiția interfeței System V (SVID)
    • comitetele POSIX
    • X/Open, OSF și Open Group
  • 3.2. Licențe software și documentație

3.1. Standardele familiei UNIX

Motivul apariției standardelor pentru sala de operație sistem UNIX ceea ce s-a întâmplat a fost că a fost portat pe multe platforme hardware. Primele sale versiuni rulau pe hardware PDP, dar în 1976 și 1978 sistemul a fost portat la Interdata și VAX. Din 1977 până în 1981, au luat forma două ramuri concurente: AT&T UNIX și BSD. Probabil, obiectivele pentru dezvoltarea standardelor au fost diferite. Una dintre ele este de a legitima primatul versiunii sale, iar celălalt este de a asigura portabilitatea sistemului și a programelor de aplicație între diferite platforme hardware. În acest sens, se vorbește despre mobilitatea programelor. Astfel de proprietăți sunt relevante atât pentru codul sursă al programelor, cât și pentru programele executabile.

Următorul material este prezentat în ordinea cronologică a apariției standardelor.

C Standardele limbajului de programare

Acest standard nu se aplică direct UNIX. Dar întrucât C este baza atât pentru această familie, cât și pentru alte sisteme de operare, vom menționa standardul acestui limbaj de programare. A început cu publicarea în 1978 a primei ediții a cărții de B. Kernighan și D. Ritchie. Acest standard este adesea numit K&R. Programatorii care au scris această lucrare au lucrat pe UNIX cu Ken Thompson. Mai mult, primul dintre ei a sugerat numele sistemului, iar al doilea a inventat acest limbaj de programare. Textul corespunzător este disponibil pe Internet [ 45 ].

Cu toate acestea standard industrial Limbajul de programare C a fost lansat în 1989 de ANSI și a fost numit X3. 159 – 1989. Iată ce se scrie despre acest standard [ 46 ]:

„Standardul a fost adoptat pentru a îmbunătăți portabilitatea programelor scrise în limbajul C între diverse tipuri OS. Astfel, standardul, pe lângă sintaxa și semantica limbajului C, include recomandări privind conținutul bibliotecă standard. Suportul pentru standardul ANSI C este indicat de numele simbolic predefinit _STDC."

În 1988, pe baza acestui standard de limbaj de programare, a fost lansată a doua ediție a cărții lui Kernighan și Ritchie despre C. Rețineți că companiile produc produse software pentru a dezvolta programe în limbajul C, își pot forma propriile biblioteci și chiar pot extinde ușor compoziția altor instrumente de limbaj.

^ Definiția interfeței System V (SVID)

O altă direcție în dezvoltarea standardelor UNIX se datorează faptului că nu numai entuziaștii s-au gândit la crearea de „standarde”. Principalii dezvoltatori ai sistemului, odată cu apariția multor „variante”, au decis să-și publice propriile documente. Acesta este modul în care standardele sunt produse de USG, organizația care a dezvoltat documentația pentru versiunile UNIX ale AT&T de când a fost creat sistemul de operare. filială. Primul document a apărut în 1984 pe baza SVR2. Se numea SVID (System V Interface Definition). O descriere în patru volume a fost lansată după lansarea SVR4. Aceste standarde au fost completate de un set de programe de testare SVVS (System V Verification Suite). Scopul principal al acestor instrumente a fost acela de a permite dezvoltatorilor să judece dacă sistemul lor se poate califica pentru numele System V [ 14 ].

Rețineți că starea de fapt cu standardul SVID este oarecum similară cu standardul limbajului de programare C. Cartea publicată de autorii acestui limbaj de programare este unul dintre standarde, dar nu singurul. Standardul C, lansat ulterior, este rezultatul muncii colective, a depășit stadiul de discuție de către publicul larg și, aparent, poate pretinde un rol de lider în lista standardelor. La fel, SVVS este un set de teste care vă permite să judecați dacă un sistem este demn de numele System V, doar una dintre versiunile UNIX. Acest lucru nu ia în considerare toată experiența în dezvoltarea sistemelor de operare de la diferiți producători.

comitetele POSIX

Lucrările la proiectarea standardelor UNIX au început de către un grup de entuziaști în 1980. Scopul a fost formulat pentru a defini în mod formal serviciile pe care sistemele de operare le oferă aplicațiilor. Acest standard de interfață de programare a devenit baza documentului POSIX (Portable Sistem de operare Interfață pentru mediu de calcul - o interfață portabilă a sistemului de operare pentru un mediu de calcul) [ 14 ]. Primul grup de lucru POSIX a fost format în 1985 din comitetul de standarde orientat spre UNIX /usr/group, numit și UniForum [ 47 ]. Numele POSIX a fost propus de fondatorul GNU Richard Stallman.

Versiunile timpurii ale POSIX definesc multe servicii de sistem necesare pentru funcționarea programelor de aplicație, care sunt descrise în interfața specificată pentru limbajul C (interfață apeluri de sistem). Ideile conținute în acesta au fost folosite de comitetul ANSI (American National Standards Institute) la crearea standardului de limbaj C menționat mai devreme. Setul inițial de funcții inclus în primele versiuni se baza pe AT&T UNIX (versiunea SVR4 [ 48 ]). Dar mai târziu are loc o întrerupere a specificațiilor Standardele POSIX din acel OS anume. Abordarea organizării unui sistem bazat pe multe funcții de bază ale sistemului a fost aplicată nu numai în UNIX (de exemplu, WinAPI de la Microsoft).

În 1988, a fost publicat standardul 1003.1 - 1988, care definește API (Application Programming Interface). Doi ani mai târziu a fost acceptat noua optiune Standardul IEEE 1003.1 - 1990 A definit regulile generale ale interfeței programului atât pentru apelurile de sistem, cât și pentru funcțiile de bibliotecă. Sunt aprobate completări suplimentare la acesta, definind servicii pentru sisteme în timp real, fire POSIX etc. Definiția standardului POSIX 1003.2 – 1992 – este importantă interpret de comenzi si utilitati.

Există o traducere [ 1 ] aceste două grupuri de documente, care se numesc: POSIX.1 (interfața programului aplicației) și POSIX.2 (interpretul de comenzi și utilitare - interfață utilizator). Traducerea menționată conține trei capitole: concepte de bază, servicii de sistem și utilități. capitolul " Servicii de sistem" este împărțit în mai multe părți, fiecare grupând servicii cu funcții similare. De exemplu, într-una dintre secțiunile "I/O de bază", a șaptea parte, dedicată operațiunilor directoare, descrie trei funcții (opendir, readdir și closedir) Sunt definite în patru paragrafe: „Sintaxă”, „Descriere”, „Valoare returnată” și „Erori”.

Pentru cei care sunt familiarizați cu limbajul de programare algoritmică C, iată un exemplu de fragmente de descriere.

Limbaje de programare, traducători, compilatori și interpreți

De fapt, această descriere oferă o idee despre cum este specificată „Interfața de apel de sistem”. În secțiunea „Sintaxă” despre funcția readdir, sunt date următoarele linii:

#include

#include

struct dirent *readdir(DIR *dirp);

Al doilea paragraf („Descriere”) conține următorul text:

„Tipurile și structurile de date utilizate în definițiile directoarelor sunt definite în fișierul dirent.h. Compoziția internă a directoarelor este definită de implementare. Când se citește cu ajutorul funcției readdir, se formează un obiect de tip struct dirent, care conține ca câmp matrice de caractere d_name, care conține NUL terminat cu caractere nume local fişier.

Readdir citește elementul director curent și setează indicatorul de poziție la următorul element. Directorul deschis este specificat de indicatorul dirp. Element care contine nume goale, este omis."

Și iată ce este dat în paragraful „Valoarea de returnare”:

„Readdir, la finalizarea cu succes, returnează un pointer către un obiect de tip struct dirent care conține elementul de director citit. Elementul de citit poate fi stocat în memoria statică și este suprascris de următorul astfel de apel aplicat aceluiași director deschis. Apelarea readdir pentru diferite directoare deschise nu se suprapun cu informațiile citite În Dacă apare o eroare sau se ajunge la sfârșitul fișierului, este returnat un pointer nul.”

Paragraful „Erori în standard” prevede următoarele:

"Readdir și closedir au întâmpinat o eroare. Dirp nu este un pointer către un director deschis."

Acest exemplu arată cum sunt descrise serviciile oferite de o aplicație. Cerințele pentru sistemul de operare (implementare) sunt că „...trebuie să suporte toate cele necesare utilitati, funcții, fișiere antet care asigură comportamentul specificat în standard. Constanta _POSIX_VERSION are valoarea 200112L [ 49 ]".

în lume tehnologie informatică Există o astfel de expresie: „programare POSIX”. Acest lucru poate fi învățat folosind diverse manuale pe sistemele de programare și operare UNIX (de exemplu, [ 5 ]). Există o carte separată cu acest titlu [ 3 ]. Rețineți că prefața acestei cărți afirmă că descrie „... un standard triplu...”, deoarece se bazează pe cea mai recentă versiune POSIX din 2003, care se bazează pe trei standarde: IEEE Std 1003.1, standard tehnic Grup deschis și ISO/IEC 9945.

Cum puteți verifica dacă un anumit sistem respectă standardul POSIX? Formalizarea unei astfel de întrebări nu este atât de simplă pe cât pare la prima vedere. Versiunile moderne oferă 4 tipuri de conformitate (patru semnificații semantice ale cuvântului „conformitate”: complet, internațional, național, extins).

Documentele luate în considerare oferă liste cu două tipuri de instrumente de interfață: obligatorii (dacă este posibil, se presupune că sunt compacte) și opționale. Acesta din urmă trebuie fie procesat în modul prescris, fie returnat valoare fixă Cod ENOSYS care indică faptul că funcția nu este implementată.

Rețineți că setul de documente POSIX se schimbă de mulți ani. Dar dezvoltatorii de versiuni noi încearcă întotdeauna să mențină continuitatea pe cât posibil cu versiunile anterioare, Este posibil să apară ceva nou în edițiile mai recente. De exemplu, documentul din 2004 a combinat patru părți [ 50 ]:

  • Volumul Base Definitions (XBD) – definirea termenilor, conceptelor și interfețelor comune tuturor volumelor acestui standard;
  • System Interfaces volume (XSH) – interfețe la nivel de sistem și legarea acestora la limbajul C, care descrie interfețele obligatorii dintre programele de aplicație și sistemul de operare, în special – specificațiile apelului de sistem;
  • Shell and Utilities volume (XCU) – definirea interfețelor standard de interpret de comandă (așa-numita shell POSIX), precum și funcționalitatea de bază a utilităților Unix;
  • Volumul justificativ (informativ) (XRAT) – informații suplimentare, inclusiv istorice, despre standard.

La fel ca primele ediții, documentul în partea sa principală descrie grupurile de servicii prestate. Fiecare element este descris acolo în următoarele paragrafe: NUME (Nume), SINOPSIS (Sintaxă), DESCRIERE (Descriere), VALOARE RETURNĂ (Valoare returnată), ERORI (Erori) și în final EXEMPLU (Exemple).

Versiunile moderne ale standardului definesc cerințele atât pentru sistemul de operare, cât și pentru programele de aplicație. Să dăm un exemplu [ 51 ].

Funcția readdir() trebuie să returneze un pointer către structura corespunzătoare următorului element de director. Dacă elementele de director numite „punct” și „punct la punct” sunt returnate nu este specificat de standard. În acest exemplu, sunt posibile patru rezultate și cerința pentru programul de aplicare este că trebuie să fie conceput pentru oricare dintre ele.

Și în încheiere, vă prezentăm un fragment din cursul prelegerilor lui Sukhomlinov („INTRODUCERE LA ANALIZA TEHNOLOGIILOR INFORMAȚIONALE”, Sukhomlinov V.A. Partea V. Metodologia și sistemul standardelor POSIX OSE), dedicat domeniului de aplicabilitate al standardelor [ 52 ]:

„Scopul de aplicabilitate al standardelor POSIX OSE (Open System Environment) este de a oferi următoarele capabilități (numite și proprietăți de deschidere) pentru sistemele informatice dezvoltate:

  • Portabilitatea aplicației la nivel de cod sursă, de ex. oferind posibilitatea de a transfera programe și date prezentate în codul sursă al limbajelor de programare de la o platformă la alta.
  • Interoperabilitatea sistemului, de ex. susținerea interconectivității între sisteme.
  • Portabilitatea utilizatorului, de ex. oferind posibilitatea utilizatorilor de a lucra pe diferite platforme fără recalificare.
  • Adaptabilitate la noile standarde (Acomodarea Standardelor) legate de atingerea obiectivelor sistemelor deschise.
  • Adaptabilitate la noile tehnologii informaționale (Accomodation of new System Technology) bazată pe universalitatea structurii de clasificare a serviciilor și independența modelului față de mecanismele de implementare.
  • Scalabilitate a platformelor de aplicații (Application Platform Scalability), reflectând capacitatea de a transfera și reutiliza software-ul aplicației în raport cu diferite tipuri și configurații de platforme de aplicații.
  • Scalabilitate sisteme distribuite(Distributed System Scalability), reflectând capacitatea aplicației software de a funcționa indiferent de dezvoltarea topologiei și a resurselor sistemelor distribuite.
  • Transparența implementării, de ex. ascunderea caracteristicilor implementării lor de utilizatori în spatele interfețelor de sistem.
  • Specificații sistematice și precise ale cerințelor funcționale ale utilizatorului (cerințe funcționale ale utilizatorului), care asigură completitatea și claritatea în determinarea nevoilor utilizatorilor, inclusiv în stabilirea compoziției standardelor aplicabile."

Acest lucru vă permite să rezolvați următoarele probleme:

  • integrarea sistemelor informatice din componente de la diverși producători;
  • eficiența implementărilor și dezvoltărilor, datorită acurateței specificațiilor și conformității soluții standard, reflectând nivelul științific și tehnic avansat;
  • eficiența transferului aplicației software, datorită utilizării interfețelor standardizate și transparenței mecanismelor de implementare a serviciilor de sistem.

De asemenea, standardele definesc formal următoarele concepte importante ale sistemelor de operare: utilizator; fişier; proces; Terminal; gazdă; nod de rețea; timp; mediul lingvistic și cultural. Formularea unei astfel de definiții nu este dată acolo, ci sunt introduse operațiile aplicate acestora și atributele inerente acestora.

În total, există mai mult de trei duzini de elemente în lista standardelor POSIX. Numele lor încep în mod tradițional cu litera „P”, urmată de un număr din patru cifre cu simboluri suplimentare.

Există, de asemenea, nume de grup pentru standardele POSIX1, POSIX2 etc. De exemplu, POSIX1 este asociat cu standarde pentru interfețele de bază ale sistemului de operare (P1003.1x, unde x este fie gol, fie caractere de la a la g; astfel, există 7 documente în acest grup), iar POSIX3 este legat de metodele de testare (două documente). - P2003 și P2003n).

De obicei, traducătorul diagnosticează erorile, alcătuiește dicționare de identificare, produce texte de program pentru tipărire etc.

Difuzarea programului- transformarea unui program prezentat într-unul dintre limbajele de programare într-un program într-o altă limbă și, într-un anumit sens, echivalent cu primul.

Se apelează limba în care este prezentat programul de intrare limba originală, și programul în sine - cod sursă. Se apelează limba de ieșire limba țintă sau cod obiect.

Conceptul de traducere se aplică nu numai limbajelor de programare, ci și altor limbaje de calculator, cum ar fi limbaje de marcare, similare cu HTML, și limbi naturale, cum ar fi engleza sau rusă. Cu toate acestea, acest articol este doar despre limbaje de programare, vezi: Traducere;

Tipuri de traducători

  • Adresa. Un dispozitiv funcțional care convertește o adresă virtuală în adresa reală Adresa de memorie.
  • Dialog. Oferă utilizarea unui limbaj de programare în modul de partajare a timpului.
  • Multi-pass. Formează un modul obiect peste mai multe vederi ale programului sursă.
  • Spate. La fel ca detraducatorul. Vezi și: decompilator, dezasamblator.
  • O singură trecere. Formează un modul obiect într-o vizualizare secvențială a programului sursă.
  • Optimizarea. Efectuează optimizarea codului în modulul obiect generat.
  • Orientat sintactic (condus pe sintactic). Primește ca intrare o descriere a sintaxei și semanticii limbii și a textului în limba descrisă, care este tradusă în conformitate cu descrierea dată.
  • Test. Un set de macrocomenzi în limbaj de asamblare care vă permit să setați diverse proceduri de depanare în programele scrise în limbaj de asamblare.

Implementări

Scopul traducerii este de a converti textul dintr-o limbă în alta, ceea ce este de înțeles destinatarului textului. În cazul programelor de traducere, destinatarul este un dispozitiv tehnic (procesor) sau un program de interpret.

Există o serie de alte exemple în care arhitectura seriei dezvoltate de calculatoare s-a bazat sau a depins puternic de un anumit model de structură a programului. Astfel, seria GE/Honeywell Multics s-a bazat pe un model semantic de executare a programelor scrise în limbajul PL/1. În Template:Not translated B5500, B6700 ... B7800 s-a bazat pe un model de program de rulare scris în limbajul ALGOL extins. ...

Procesorul i432, ca și aceste arhitecturi anterioare, se bazează, de asemenea, pe un model semantic al structurii programului. Cu toate acestea, spre deosebire de predecesorii săi, i432 nu se bazează pe un model de limbaj de programare specific. În schimb, scopul principal al dezvoltatorilor a fost să ofere suport direct de rulare pentru ambele date abstracte(adică programarea cu tipuri de date abstracte) și pentru sisteme de operare specifice domeniului. …

Avantajul compilatorului: programul este compilat o singură dată și nu sunt necesare transformări suplimentare de fiecare dată când este executat. În consecință, nu este necesar un compilator pe mașina țintă pentru care este compilat programul. Dezavantaj: Un pas separat de compilare încetinește scrierea și depanarea și îngreunează rularea unor programe mici, simple sau unice.

Dacă limba sursă este un limbaj de asamblare (un limbaj de nivel scăzut apropiat de limbajul mașinii), atunci compilatorul unui astfel de limbaj se numește asamblator.

Metoda opusă de implementare este atunci când programul este executat folosind interpret nicio emisiune deloc. Software-ul interpretor modelează o mașină al cărei ciclu de preluare-execuție funcționează pe instrucțiuni în limbaje de nivel înalt, mai degrabă decât pe instrucțiuni ale mașinii. Această simulare software creează o mașină virtuală care implementează limbajul. Această abordare se numește interpretare pură. Interpretarea pură este de obicei folosită pentru limbile cu o structură simplă (de exemplu, APL sau Lisp). Interpreții de linie de comandă procesează comenzi în scripturi în UNIX sau în fișiere batch (.bat) în MS-DOS, de obicei, de asemenea, în modul pur de interpretare.

Avantajul unui interpret pur: absența acțiunilor intermediare pentru traducere simplifică implementarea interpretului și îl face mai convenabil de utilizat, inclusiv în modul de dialog. Dezavantajul este că un interpret trebuie să fie prezent pe mașina țintă unde urmează să fie executat programul. Iar proprietatea unui interpret pur, că erorile din programul interpretat sunt detectate doar atunci când se încearcă executarea unei comenzi (sau linie) cu o eroare, poate fi considerată atât un dezavantaj, cât și un avantaj.

Există compromisuri între compilare și interpretare pură în implementarea limbajelor de programare, atunci când interpretul, înainte de a executa programul, îl traduce într-un limbaj intermediar (de exemplu, în bytecode sau p-code), mai convenabil pentru interpretare (adică, vorbim despre un interpret cu traducător încorporat) . Această metodă se numește implementare mixtă. Un exemplu de implementare în limbaj mixt este Perl. Această abordare combină atât avantajele unui compilator și interpret (viteză de execuție mai mare și ușurință în utilizare), cât și dezavantaje (sunt necesare resurse suplimentare pentru a traduce și stoca un program într-un limbaj intermediar; trebuie furnizat un interpret pentru a executa programul pe țintă). maşină). De asemenea, ca și în cazul compilatorului, o implementare mixtă necesită asta înainte de a fi executată cod sursă nu conținea erori (lexicale, sintactice și semantice).

Pe măsură ce resursele computerelor cresc și se extind rețelele eterogene (inclusiv internetul) care conectează computerele diferite tipuriși arhitecturi, a apărut un nou tip de interpretare, în care codul sursă (sau intermediar) este compilat în codul mașinii direct în timpul rulării, „din mers”. Secțiunile de cod deja compilate sunt stocate în cache, astfel încât atunci când sunt accesate din nou, ele primesc imediat controlul, fără recompilare. Această abordare se numește compilare dinamică.

Avantajul compilației dinamice este că viteza de interpretare a programului devine comparabilă cu viteza de execuție a programului în limbaje compilate convenționale, în timp ce programul în sine este stocat și distribuit într-o singură formă, independent de platformele țintă. Dezavantajul este o complexitate mai mare a implementării și cerințe mai mari de resurse decât în ​​cazul compilatoarelor simple sau a interpreților puri.

Această metodă funcționează bine pentru

Programele, ca și oamenii, necesită un traducător sau un traducător pentru a traduce dintr-o limbă în alta.

Concepte de bază

Programul este o reprezentare lingvistică a calculelor: i → P → P(i). Un interpret este un program a cărui intrare este un program P și unele date de intrare x. Efectuează P pe x: I(P, x) = P(x). Faptul că există un singur traducător capabil să facă totul programe posibile(care poate fi reprezentat într-un sistem formal) este descoperirea foarte profundă și semnificativă a lui Turing.

Procesorul este un interpret al programelor în limbajul mașinii. În general, este prea costisitor să scrieți interpreți pentru limbi de nivel înalt, astfel încât aceștia sunt traduși într-o formă mai ușor de interpretat.

Unele tipuri de traducători au nume foarte ciudate:

  • Un asamblator traduce programe în limbaj de asamblare în limbaj mașină.
  • Compilatorul traduce dintr-un limbaj de nivel înalt într-un limbaj de nivel inferior.

Un traducător este un program care ia ca intrare un program într-o limbă S și scoate un program în limbajul T astfel încât ambele să aibă aceeași semantică: P → X → Q. Adică ∀x. P(x) = Q(x).

Traducerea unui întreg program în ceva interpretat se numește compilare înainte de execuție sau compilare AOT. Compilatoarele AOT pot fi utilizate secvenţial, ultimul dintre acestea fiind adesea un asamblator, de exemplu:

Cod sursă → Compilator (traducător) → Cod de asamblare → Asamblator (traducător) → Cod mașină → CPU (interpret).

Compilarea online sau dinamică are loc atunci când o parte a unui program este tradusă în timp ce alte părți compilate anterior sunt executate. Traducătorii JIT își amintesc ceea ce au făcut deja, astfel încât să nu fie nevoiți să repete codul sursă iar și iar. Ei pot realiza chiar și compilare adaptivă și recompilare pe baza comportamentului mediului de rulare al programului.

Multe limbi permit ca codul să fie executat în timpul difuzării și compilat cod nouîn timpul executării programului.

Etape de difuzare

Traducerea constă din etapele de analiză și sinteză:

Cod sursă → Analizor → Vedere conceptuală → Generator (sintetizator) → Cod țintă.

Acest lucru se datorează următoarelor motive:

  • Orice altă metodă nu este potrivită. Traducerea cuvânt cu cuvânt pur și simplu nu funcționează.
  • O soluție de inginerie bună: dacă trebuie să scrieți traducători pentru M limbi sursă și N limbi țintă, trebuie să scrieți doar M + N programe simple (semi-compilatoare), și nu M × N programe complexe (traducători completi).

Cu toate acestea, în practică, o reprezentare conceptuală este foarte rar expresivă și suficient de puternică pentru a acoperi toate limbile sursă și țintă imaginabile. Deși unii au putut să se apropie de asta.

Compilatorii adevărați trec prin multe etape. Când vă creați propriul compilator, nu trebuie să repetați toată munca grea pe care oamenii au făcut-o deja atunci când au creat vizualizări și generatoare. Vă puteți traduce limba direct în JavaScript sau C și puteți utiliza motoarele JavaScript existente și compilatoarele C pentru a face restul. De asemenea, puteți utiliza vizualizări intermediare existente și

Înregistrare cu traducător

Un traducător este un program sau mijloace tehnice, care implică trei limbi: sursă, țintă și bază. Ele pot fi scrise în formă de T cu sursa în stânga, ținta în dreapta și baza dedesubt.

Există trei tipuri de compilatoare:

  • Un traducător este un auto-compilator dacă limba sa sursă se potrivește cu limba de bază.
  • Un compilator a cărui limbă țintă este egală cu limba de bază se numește auto-rezident.
  • Un traducător este un compilator încrucișat dacă limbile țintă și de bază sunt diferite.

De ce este acest lucru important?

Chiar dacă nu faci niciodată un compilator adevărat, este bine să știi despre tehnologia din spatele lui, deoarece conceptele folosite pentru el sunt folosite peste tot, de exemplu în:

  • formatarea textului;
  • la baze de date;
  • arhitecturi avansate de calculatoare;
  • generalizat;
  • interfețe grafice;
  • limbaje de scripting;
  • controlere;
  • mașini virtuale;
  • traduceri automate.

În plus, dacă doriți să scrieți preprocesoare, asamblatoare, încărcătoare, depanatoare sau profilere, trebuie să parcurgeți aceiași pași ca atunci când scrieți un compilator.

De asemenea, puteți învăța cum să scrieți mai bine programe, deoarece crearea unui traducător pentru o limbă înseamnă să înțelegeți mai bine complexitățile și ambiguitățile acesteia. Studiind principii generale traducerea vă permite, de asemenea, să deveniți un bun designer de limbi. Chiar contează cât de cool este o limbă dacă nu poate fi implementată eficient?

Tehnologie cuprinzătoare

Tehnologia compilatorului acoperă multe domenii diferite ale informaticii:

  • teoria formală a limbajului: gramatica, analizarea, calculabilitatea;
  • arhitectura computerului: seturi de instrucțiuni, RISC sau CISC, pipelining, nuclee, cicluri de ceas etc.;
  • concepte de limbaj de programare: de exemplu, controlul secvenței, execuția condiționată, iterațiile, recursiunile, descompunerea funcțională, modularitatea, sincronizarea, metaprogramarea, domeniul de aplicare, constantele, subtipurile, șabloanele, tipul de ieșire, prototipurile, adnotările, fluxurile, monade, cutii poştale, continuări, metacaractere, expresii regulate, memorie tranzacțională, moștenire, polimorfism, moduri de parametri etc.;
  • limbaje abstracte și mașini virtuale;
  • algoritmi și expresii regulate, algoritmi de analiză, algoritmi grafici, antrenament;
  • limbaje de programare: sintaxă, semantică (statică și dinamică), suport pentru paradigme (structurale, POO, funcționale, logice, stivă, paralelism, metaprogramare);
  • crearea de software (compilatoarele sunt de obicei mari și complexe): localizare, cache, componentizare, API-uri, reutilizare, sincronizare.

Proiectarea compilatorului

Câteva probleme care apar la dezvoltarea unui traducător real:

  • Probleme cu limba sursă. Este ușor de compilat? Există un preprocesor? Cum sunt procesate tipurile? Există biblioteci?
  • Gruparea trecerii compilatorului: cu o singură trecere sau cu mai multe treceri?
  • Gradul de optimizare dorit. O difuzare rapidă și murdară a unui program cu optimizare redusă sau deloc poate fi normală. Supraoptimizarea va încetini compilatorul, dar un cod mai bun în timpul execuției poate merita.
  • Gradul necesar de detectare a erorilor. Se poate opri traducătorul la prima eroare? Când ar trebui să se oprească? Ar trebui să se acorde încredere compilatorului pentru a corecta erorile?
  • Disponibilitatea instrumentelor. Cu excepția cazului în care limba sursă este foarte mică, un scanner și un generator de parser sunt obligatorii. Există și generatoare de cod, dar nu sunt la fel de comune.
  • Tipul de cod țintă de generat. Ar trebui să alegeți dintre codul de mașină pur, augmentat sau virtual. Sau pur și simplu scrieți o parte de intrare care creează vederi intermediare populare, cum ar fi LLVM, RTL sau JVM. Sau faceți o traducere din codul sursă în codul sursă în C sau JavaScript.
  • Format cod țintă. Puteți selecta o imagine de memorie portabilă.
  • Retargeting. Cu generatoare multiple, este bine să aveți o parte comună de intrare. Din același motiv, este mai bine să aveți un generator pentru mai multe părți de intrare.

Arhitectura compilatorului: componente

Acestea sunt principalele componente funcționale ale traducătorului care generează codul mașinii (dacă programul de ieșire este un program C sau o mașină virtuală, atunci nu vor fi necesari mulți pași):

  • Programul de intrare (un flux de caractere) intră într-un scanner (analizor lexical), care îl convertește într-un flux de jetoane.
  • Analizorul (parserul) construiește un arbore de sintaxă abstractă din ele.
  • Analizorul semantic descompune informațiile semantice și verifică nodurile arborelui pentru erori. Ca rezultat, este construit un grafic semantic - un arbore sintactic abstract cu proprietăți suplimentareși legături stabilite.
  • Generatorul de cod intermediar construiește un grafic de flux (tuplurile sunt grupate în blocuri principale).
  • Optimizatorul de cod independent de mașină efectuează atât optimizarea locală (în cadrul blocului de bază) cât și globală (în toate blocurile), rămânând în principal în cadrul subrutinelor. Reduce codul redundant și simplifică calculele. Rezultatul este un grafic de flux modificat.
  • Generatorul de cod țintă înlănțuiește blocurile de bază într-un cod de transfer de control simplu, creând un fișier obiect în limbaj de asamblare cu registre virtuale (posibil ineficiente).
  • Optimizatorul de linker dependent de mașină alocă memorie între registre și efectuează programarea instrucțiunilor. Convertește un program de asamblare în asamblator real cu buna utilizare prelucrarea transportoarelor.

În plus, sunt utilizate subsisteme de detectare a erorilor și un manager de tabel de simboluri.

Analiză lexicală (scanare)

Scanerul convertește un flux de caractere de cod sursă într-un flux de jetoane, eliminând spații, comentarii și extinzând macrocomenzi.

Scanerele întâmpină adesea probleme dacă respectă sau nu majuscule, indentări, întreruperi de rând și comentarii imbricate.

Erorile care pot fi întâlnite în timpul scanării se numesc erori lexicale și includ:

  • caractere care lipsesc din alfabet;
  • depășirea numărului de caractere dintr-un cuvânt sau dintr-un rând;
  • nu un caracter privat sau un șir literal;
  • sfârșitul fișierului în comentariu.

Analizare (parsare)

Analizorul convertește o secvență de jetoane într-un arbore de sintaxă abstractă. Fiecare nod de arbore este stocat ca un obiect cu câmpuri numite, multe dintre ele fiind ele însele noduri de arbore. Nu există cicluri în această etapă. Când creați un parser, trebuie să acordați atenție nivelului de complexitate al gramaticii (LL sau LR) și să aflați dacă există reguli de dezambiguizare. Unele limbi necesită de fapt analiză semantică.

Erorile întâlnite în această etapă se numesc erori sintactice. De exemplu:

  • k = 5 * (7 - y;
  • j = /5;
  • 56 = x * 4.

Analiza semantică

În timpul execuției, este necesar să se verifice regulile de validitate și să se lege părți ale arborelui de sintaxă (rezolvarea referințelor de nume, operații de inserare pentru turnarea implicită a tipului etc.) pentru a forma un grafic semantic.

Evident, setul de reguli de admisibilitate variază de la limbă la limbă. Dacă sunt compilate limbaje asemănătoare Java, traducătorii pot găsi:

  • declarații multiple ale unei variabile în domeniul ei;
  • referiri la o variabilă înainte de declararea acesteia;
  • referiri la un nume nedeclarat;
  • încălcarea regulilor de accesibilitate;
  • numar prea multe sau insuficient de argumente la apelarea unei metode;
  • nepotrivire de tip.

Generaţie

Generarea de cod intermediar produce un grafic de flux compus din tupluri grupate în blocuri de bază.

Generarea codului produce cod de mașină real. În compilatoarele tradiționale pentru mașinile RISC, prima etapă este crearea unui asamblator cu număr infinit registre virtuale. Pentru mașinile CISC probabil că acest lucru nu se va întâmpla.

SECȚIUNEA 7. Traducere, compilare și interpretare

Un program este o secvență de instrucțiuni concepute pentru a fi executate de un computer. În prezent, programele sunt formatate ca text, care este scris în fișiere. Acest text este rezultatul activităților programatorului și, în ciuda specificului limbajului formal, rămâne program pentru un programator.

Procesul de creare a unui program presupune mai multe etape. Etapa de proiectare a programului este urmată de etapa de programare. În această etapă se scrie programul. Pentru programatori, acest text este mai ușor de înțeles decât codul binar, deoarece diferitele mnemonice și nume conțin informații suplimentare.

Fișier cu textul original programul (numit și modul sursă) este procesat traducător , care traduce un program dintr-un limbaj de programare într-o secvență de coduri care poate fi citită de mașină.

Traducător - program sau instrument tehnic care efectuează difuzarea programului. Un program de mașină care traduce dintr-o limbă în alta și, în special, dintr-un limbaj de programare în altul. Un program de procesare conceput pentru a transforma un program sursă într-un modul obiect.

De obicei, traducătorul diagnosticează și erorile, creează dicționare de identificatori, produce texte de program pentru tipărire etc.

Difuzarea programului - transformarea unui program prezentat într-unul dintre limbajele de programare într-un program într-o altă limbă și, într-un anumit sens, echivalent cu primul.

Se apelează limba în care este prezentat programul de intrare limba originală, și programul în sine - cod sursă. Se apelează limba de ieșire limba țintă sau cod obiect.

Tipuri de traducători

Traducătorii sunt împărțiți în:

· Adresa. Un dispozitiv funcțional care convertește o adresă virtuală Adresă virtuală) la o adresă reală (engleză) Adresa de memorie).

· Dialog. Oferă utilizarea unui limbaj de programare în modul de partajare a timpului.

· Multi-pass. Formează un modul obiect peste mai multe vederi ale programului sursă.

· Spate. La fel ca detraducatorul. Vezi și: decompilator, dezasamblator.

· O singură trecere. Formează un modul obiect într-o vizualizare secvențială a programului sursă.

· Optimizarea. Efectuează optimizarea codului în modulul obiect generat.

· Orientat sintactic (condus pe sintactic). Primește ca intrare o descriere a sintaxei și semanticii limbii și a textului în limba descrisă, care este tradusă în conformitate cu descrierea dată.

· Test. Un set de comenzi macro în limbaj de asamblare care vă permit să setați diverse proceduri de depanare în programele scrise în limbaj de asamblare.



Traducătorii sunt implementați în formular compilatoare sau interpreți . În ceea ce privește realizarea muncii, compilatorul și interpretul sunt semnificativ diferite.

Compilator(engleză) compilator- compilator, colector) - un traducător care convertește un program scris în limbajul sursă într-un modul obiect. Un program care traduce textul programului într-o limbă de nivel înalt într-un program echivalent în limbaj mașină.

· Un program conceput pentru a traduce limbajul de nivel înalt în cod absolut sau, uneori, în limbaj de asamblare. Informațiile de intrare către compilator (codul sursă) sunt o descriere a algoritmului sau programului într-un limbaj orientat spre probleme, iar rezultatul compilatorului este o descriere echivalentă a algoritmului într-un limbaj orientat către mașină (cod obiect).

Compilare-traducerea unui program scris în limba sursă într-un modul obiect. Efectuat de compilator.

Compilează - traduce un program de mașină dintr-un limbaj orientat spre probleme într-un limbaj orientat către mașină.

Compilatorul citește întregul program în întregime, îl traduce și creează o versiune completă a programului în limbajul mașinii, care este apoi executată.

Interpret(engleză) interpret- interpret, interpret) traduce și execută programul rând cu rând. Interpretul preia următorul operator de limbă din textul programului, îi analizează structura și apoi îl execută imediat (de obicei, după analiză, operatorul este tradus într-o reprezentare intermediară sau chiar cod mașină pentru o execuție ulterioară mai eficientă). Numai după ce instrucțiunea curentă a fost executată cu succes, interpretul va trece la următoarea. Mai mult, dacă același operator este executat în program de mai multe ori, interpretul îl va executa ca și cum ar fi fost întâlnit pentru prima dată. Ca urmare, programe care necesită implementare volum mare calculele vor fi lente. În plus, pentru a rula un program pe alt computer, trebuie să existe și un interpret - la urma urmei, fără el, textul este doar un set de caractere.



Într-un alt mod, putem spune că interpretul simulează unele calcule mașină virtuală, pentru care instructiuni de baza Nu comenzile elementare ale procesorului servesc, ci operatorii limbajului de programare.

Diferențeleîntre compilare şi interpretare.

1. Odată ce un program este compilat, nu mai sunt necesare nici programul sursă, nici compilatorul. În același timp, programul procesat de interpret trebuie din nou transferîn limbajul mașinii de fiecare dată când programul este lansat.

2. Programele compilate rulează mai repede, dar cele interpretate sunt mai ușor de reparat și schimbat.

3. Fiecare limbaj specific este orientat fie spre compilare, fie spre interpretare – in functie de scopul pentru care a fost creat. De exemplu, Pascal folosit de obicei pentru a rezolva probleme destul de complexe în care viteza programului este importantă. Prin urmare, acest limbaj este de obicei implementat folosind compilator.

Pe de alta parte, DE BAZĂ a fost creat ca un limbaj pentru programatorii începători, pentru care execuția linie cu linie a unui program are avantaje incontestabile.

Aproape toate limbajele de programare de nivel scăzut și de a treia generație, cum ar fi asamblarea, C sau Modula-2, sunt compilate, în timp ce limbajele de nivel superior, cum ar fi Python sau SQL, sunt interpretate.

Uneori pentru o singură limbă există și compilator, și interpret. În acest caz, puteți utiliza un interpret pentru a dezvolta și testa programul și apoi să compilați programul depanat pentru a îmbunătăți viteza de execuție. Există o întrepătrundere a proceselor de traducere și interpretare: interpreții pot compila (inclusiv compilarea dinamică), iar traducătorii pot necesita interpretare pentru constructele de metaprogramare (de exemplu, pentru macrocomenzi în limbaj de asamblare, compilare condiționată în C sau pentru șabloane în C++).

4. Traducerea și interpretarea sunt procese diferite: traducerea se ocupă cu traducerea programelor dintr-o limbă în alta, iar interpretarea este responsabilă de execuția programelor. Cu toate acestea, deoarece scopul traducerii este de obicei pregătirea programului pentru interpretare, aceste procese sunt de obicei luate în considerare împreună.

Concluzie: Dezavantajul compilatorului este laboriozitatea traducerii limbajelor de programare axate pe prelucrarea datelor structurilor complexe, adesea necunoscute în avans sau care se schimbă dinamic în timpul rulării programului. Apoi trebuie să introduceți o mulțime de verificări suplimentare, analizează disponibilitatea resurselor sistemului de operare, le confiscă și eliberează în mod dinamic, formează și procesează obiecte complexe în memoria computerului, ceea ce este destul de dificil de implementat la nivelul instrucțiunilor mașinii codificate greu și este aproape imposibil pentru sarcină.

Cu ajutorul unui interpret, dimpotrivă, este posibil să opriți programul în orice moment, să examinați conținutul memoriei, să organizați un dialog cu utilizatorul, să efectuați transformări arbitrar complexe și, în același timp, să monitorizați constant starea mediul software și hardware din jur, obținând astfel o fiabilitate ridicată a funcționării. La executarea fiecărei instrucțiuni, interpretul verifică multe caracteristici ale sistemului de operare și, dacă este necesar, informează dezvoltatorul cât mai detaliat cu privire la problemele apărute. În plus, interpretul este foarte convenabil de utilizat ca instrument de învățare a programării, deoarece vă permite să înțelegeți principiile de funcționare ale oricărui operator individual în limbaj.


Procesul de compilare este împărțit în mai multe etape:

1. Preprocesor. Programul sursă este procesat prin înlocuirea macrocomenzilor și fișierelor antet existente.

2. Analiza lexicală și sintactică. Programul este convertit într-un lanț de jetoane și apoi într-o reprezentare internă de arbore.

3. Optimizare globală. Reprezentarea internă a programului este transformată în mod repetat pentru a reduce dimensiunea și timpul de execuție a programului.

4. Generarea codului. Reprezentarea internă este convertită în blocuri de instrucțiuni ale procesorului, care sunt convertite în text de asamblare sau cod obiect.

5. Asamblare. Dacă este generat textul de asamblare, acesta este asamblat pentru a obține codul obiect.

6. Asamblare. Asamblatorul concatenează mai multe fișiere obiect în fișier executabil sau bibliotecă.

În fază analiza lexicala (LA) programul de introducere, care este un flux de caractere, este împărțit în lexeme - cuvinte în conformitate cu definițiile limbajului. Principalul formalism care stă la baza implementării analizatoarelor lexicale este mașinile cu stări finite și expresiile regulate. Analizatorul lexical poate funcționa în două moduri principale: fie ca subrutină numită de parser după fiecare token, fie ca trecere completă, al cărei rezultat este un fișier de token-uri. În procesul de selectare a lexemelor, LA poate construi independent tabele de nume și constante și poate furniza valori pentru fiecare lexem data viitoare când este accesat. În acest caz, tabelul de nume este construit în fazele ulterioare (de exemplu, în timpul procesului de analiză).

În stadiul LA sunt detectate unele erori (simple) (caractere nevalide, înregistrare incorectă a numerelor, identificatoare etc.).

Să aruncăm o privire mai atentă asupra stadiului analizei lexicale.

Sarcina principală a analizei lexicale - împărțiți textul introdus, constând dintr-o secvență de caractere individuale, într-o secvență de cuvinte, sau lexeme, de ex. selectați aceste cuvinte dintr-o secvență continuă de caractere. Din acest punct de vedere, toate caracterele secvenței de intrare sunt împărțite în caractere care aparțin unor lexeme și caractere care separă lexeme (delimitatoare). În unele cazuri, este posibil să nu existe separatori între jetoane. Pe de altă parte, în unele limbi, jetoanele pot conține caractere nesemnificative (de exemplu, caracterul spațiu în Fortran). În C, valoarea de separare a caracterelor delimitare poate fi blocată ("\" la sfârșitul unui rând în interiorul unui "...").

De obicei, toate lexemele sunt împărțite în clase. Exemple de astfel de clase sunt numerele (întreg, octal, hexazecimal, real etc.), identificatorii, șirurile de caractere. Cuvintele cheie și simbolurile de punctuație (uneori numite caractere delimitare) sunt evidențiate separat. De obicei, cuvintele cheie sunt un subset finit de identificatori. În unele limbi (de exemplu, PL/1), semnificația unui lexem poate depinde de contextul său și este imposibil să se efectueze o analiză lexicală izolat de analiza sintactică.

Din punctul de vedere al fazelor ulterioare de analiză, analizatorul lexical produce informații de două tipuri: pentru un analizator sintactic, care lucrează după cel lexical, informațiile despre succesiunea claselor de lexeme, delimitatori și cuvinte cheie sunt esențiale, iar pentru analiza contextuală. , lucrând după cel sintactic, informații despre semnificațiile specifice ale lexemelor individuale (identificatori, numere etc.).

Astfel, schema generală de funcționare a analizorului lexical este următoarea. Mai întâi, este extras un singur token (poate folosind caractere delimitare). Cuvintele cheie sunt recunoscute fie prin selecția explicită direct din text, fie prin extragerea mai întâi a unui identificator și apoi verificarea dacă acesta aparține unui set de cuvinte cheie.

Dacă lexemul selectat este un delimitator, atunci acesta (mai precis, o parte din atributul său) este emis ca rezultat al analizei lexicale. Dacă lexemul selectat este un cuvânt cheie, atunci un semn al corespondentului cuvânt cheie. Dacă jetonul selectat este un identificator, se emite atributul de identificare, iar identificatorul însuși este stocat separat. În cele din urmă, dacă jetonul selectat aparține oricăreia dintre celelalte clase de jeton (de exemplu, jetonul este un număr, un șir etc.), atunci este returnat un atribut al clasei corespunzătoare, iar valoarea jetonului este stocată separat .

Analizorul lexical poate fi fie o fază independentă de traducere, fie o subrutină care funcționează pe principiul „dați un simbol”. În primul caz (Fig. 3.1, a) ieșirea analizorului este un fișier lexem, în al doilea (Fig. 3.1, b) lexemul este emis de fiecare dată când se accesează analizorul (în acest caz, de regulă, atributul clasei lexeme este returnat ca rezultat al funcției „analizator lexical”, iar valoarea jetonului este transmisă printr-o variabilă globală). În ceea ce privește procesarea valorilor jetonelor, analizatorul poate fie pur și simplu să scoată valoarea fiecărui jeton, caz în care construcția tabelelor de obiecte (identificatori, șiruri de caractere, numere etc.) este amânată pentru fazele ulterioare, fie poate construi tabelele de obiecte. în sine. În acest caz, valoarea jetonului este un pointer către intrarea în tabelul corespunzător.

Orez. 3.1:

Funcționarea analizorului lexical este specificată de o mașină cu stări finite. Cu toate acestea, o descriere directă a unei mașini cu stări finite este incomodă din punct de vedere practic. Prin urmare, pentru a specifica un analizor lexical, de regulă, se folosește fie o expresie regulată, fie o gramatică liniară dreapta. Toate cele trei formalisme (mașini cu stări finite, expresii regulate și gramaticile drept-liniare) au aceeași putere expresivă. În special, conform expresie regulată sau se poate construi gramatica liniară dreapta mașină de stat, recunoscând aceeași limbă.

Sarcina principală a analizei - analiza structurii programului. De regulă, structura este înțeleasă ca un arbore care corespunde analizei în gramatica fără context a limbii. În prezent, fie analiza LL(1) (și varianta sa - coborâre recursivă), fie analiza LR(1) și variantele sale (LR(0), SLR(1), LALR(1) și altele) sunt utilizate cel mai des. Coborârea recursiv este mai des folosită la programarea manuală a unui parser, LR(1) - atunci când se utilizează sisteme de automatizare pentru construirea parserelor.

Rezultatul analizei este un arbore de sintaxă cu legături către un tabel de nume. Procesul de analiză dezvăluie și erori legate de structura programului.

În stadiul analizei contextuale dependențele sunt identificate între părți ale programului care nu pot fi descrise prin sintaxa fără context. Practic sunt conexiuni" descriere - utilizare„, în special, analiza tipurilor de obiecte, analiza domeniilor, corespondența parametrilor, etichetele și altele. În procesul de analiză contextuală se construiește un tabel de simboluri, care poate fi considerat ca un tabel de nume, completat cu informații despre descrierile (proprietățile) obiectelor.

Principalul formalism utilizat în analiza contextuală este gramaticile atributelor. Rezultatul fazei de analiză a contextului este un arbore de program atribuit. Informațiile despre obiecte pot fi fie dispersate în copac, fie concentrate în el mese separate personaje. În timpul procesului de analiză a contextului pot fi detectate și erori legate de utilizarea incorectă a obiectelor.

Atunci programul poate fi transferat la reprezentarea internă . Acest lucru se face în scopuri de optimizare și/sau ușurință în generarea codului. Un alt scop al conversiei unui program într-o reprezentare internă este dorința de a avea compilator portabil. Atunci numai ultima fază (generarea codului) depinde de mașină. O notație prefixă sau postfixă, un grafic direcționat, triple, cvadruple și altele pot fi utilizate ca reprezentare internă.

Pot exista mai multe faze de optimizare . Optimizări de obicei împărțit în dependent de mașină și independent de mașină, local și global. Unele optimizări dependente de mașină sunt efectuate în timpul fazei de generare a codului. Optimizare globalăîncearcă să ia în considerare structura întregului program, local - doar micile sale fragmente. Optimizarea globală se bazează pe analiza globală a fluxului, care este efectuată pe graficul programului și reprezintă în esență o transformare a acestui grafic. În acest caz, pot fi luate în considerare proprietățile programului precum analiza interprocedurală, analiza intermodulară, analiza zonelor de viață a variabilelor etc.

In sfarsit, generarea codului- ultima fază a emisiunii. Rezultatul este fie un modul de asamblare, fie un modul de obiect (sau încărcare). În timpul procesului de generare a codului, pot fi efectuate unele optimizări locale, cum ar fi alocarea registrului, selectarea ramurilor lungi sau scurte și luarea în considerare a costurilor de instrucțiuni atunci când alegeți o anumită secvență de instrucțiuni. Au fost dezvoltate diferite tehnici pentru generarea codului, cum ar fi tabelele de decizie, potrivirea modelelor, inclusiv programarea dinamică, diverse tehnici sintactice.

Desigur, anumite faze ale traducătorului pot fi fie complet absente, fie combinate. În cel mai simplu caz al unui traducător cu o singură trecere, nu există o fază explicită de generare a unei reprezentări intermediare și de optimizare, fazele rămase sunt combinate într-una singură și nu există un arbore de sintaxă construit în mod explicit.