Tipuri de traducători. lanțuri compatibile cu situația A:_b, pentru orice regulă A:b. Scopul acestui curs este de a dezvolta un traducător educațional dintr-un anumit limbaj text simplificat de nivel înalt

Traducător (traducător englez - traducător) este un program de traducere. Convertește un program scris într-una dintre limbile de nivel înalt într-un program format din instrucțiuni de mașină. De obicei, traducătorul diagnosticează și erorile, creează dicționare de identificatori, produce texte de program pentru tipărire etc. Limba în care este prezentat programul de intrare se numește limba sursă, iar programul în sine se numește cod sursă. Limba de ieșire se numește limba țintă sau cod obiect.

În general, conceptul de traducere se aplică nu numai limbajelor de programare, ci și altor limbi - atât limbaje formale de computer (cum ar fi limbaje de marcare precum HTML), cât și cele naturale (rusă, engleză etc.).

Tipuri de traducători

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

    Orientat sintactic (condus 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ă.

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

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

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

    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.

    Înapoi. Pentru un program în cod mașină, acesta produce un program echivalent în orice limbaj de programare (vezi: dezasamblator, decompilator).

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

Compilator (Compilatorul englez - compilator, colector) citește întregul program, îl traduce și creează o versiune completă a programului în limbajul mașinii, care este apoi executată. 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).

Tipuri de compilatoare

    Vectorizarea. Traduce codul sursă în cod mașină pe computerele echipate cu un procesor vectorial.

    Flexibil. Proiectat într-o manieră modulară, condus de tabele și programat într-un limbaj de nivel înalt sau implementat folosind un compilator de compilatoare.

    Dialog. Vezi: traducător de dialog.

    incremental. Retransmite fragmente de program și completări la acesta fără a recompila întregul program.

    Interpretativ (pas cu pas). Efectuează secvenţial o compilare independentă a fiecărei instrucţiuni individuale (comandă) a programului sursă.

    Compilator de compilatoare. Un traducător care acceptă o descriere formală a unui limbaj de programare și generează un compilator pentru acest limbaj.

    Depanați. Elimină anumite tipuri de erori de sintaxă.

    Rezident. Acesta rezidă permanent în RAM și este disponibil pentru reutilizare de multe sarcini.

    Autocompilare. Scris în aceeași limbă din care se realizează emisiunea.

    Universal. Bazat pe o descriere formală a sintaxei și semanticii limbajului de intrare. Componentele unui astfel de compilator sunt: ​​nucleul, încărcătoarele sintactice și semantice.

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 de procesor, OS, alt mediu), execută instrucțiunea tradusă (linia de program) și apoi trece la următoarea linie a textului programului. Interpretul nu generează fișiere executabile; el însuși efectuează toate acțiunile scrise în textul programului sursă.

Odată ce un program este compilat, nu mai sunt necesare nici programul sursă, nici compilatorul. Î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 este orientat fie spre compilare, fie spre interpretare – in functie 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ă. Prin urmare, acest limbaj este de obicei implementat 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 și apoi să compilați programul depanat pentru a îmbunătăți 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 cu un preprocesor în textul unui limbaj de programare standard. , care poate fi compilat de un compilator standard.

Emulator- software și/sau hardware care funcționează într-un anumit sistem de operare țintă și platformă hardware, concepute pentru a executa programe produse într-un alt sistem de operare sau care rulează pe un hardware diferit de țintă, dar permițând efectuarea acelorași operațiuni în mediul țintă ca și în sistem simulat.

Limbajele de emulare includ sisteme precum Java, .Net, Mono, în care, în etapa de creare a unui program, acesta este compilat într-un bytecode special și se obține un fișier binar, potrivit pentru execuție în orice mediu de operare și hardware, iar bytecode-ul rezultat este executat pe mașina țintă folosind un interpret simplu și rapid (mașină virtuală).

Reasamblator, dezasamblator- un instrument software conceput pentru a descifra codul binar și a-l prezenta sub formă de text de asamblare sau text dintr-un alt limbaj de programare, permițându-vă să analizați algoritmul programului sursă și să utilizați textul rezultat pentru modificarea necesară a programului, de exemplu , schimbarea adreselor dispozitivelor externe, accesarea resurselor sistemului și rețelei, identificarea funcțiilor ascunse ale codului binar (de exemplu, un virus de calculator 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 se realizează prin programe speciale - radiodifuzorii.

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

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

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, dezvoltată de Microsoft, include nu numai un mediu de dezvoltare în mai multe limbi numit Visual Studio .NET, ci și multe alte instrumente, cum ar fi suport pentru baze de date, suport prin e-mail și altele.

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 programului ș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ă .Cadru net.

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 - dezvoltatorii terți au creat zeci de compilatoare pentru .NET pentru limbajele 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 Unix-like Linux, Mac OS X și altele (Mono este un proiect software gratuit .NET Framework).

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 forțele armate britanice 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 limbi mai simple (în special, limba 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

Spala 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

Standardele familiei UNIX. Standardele limbajului de programare C. Definiția interfeței System V (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 sistemul de operare UNIX a fost că acesta 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ă în mod specific 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, standardul industrial pentru 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 diferite tipuri de SO. Astfel, pe lângă sintaxa și semantica limbajului C, standardul includea recomandări pentru conținutul bibliotecii standard. de suport pentru standardul ANSI C este indicat de numele simbolic predefinit _STDC.”

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

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

O altă direcție în dezvoltarea standardelor UNIX se datorează faptului că nu numai pasionații s-au gândit să creeze „standarde”. Principalii dezvoltatori ai sistemului, odată cu apariția multor „variante”, au decis să-și publice propriile documente. Astfel vin standardele produse de USG, organizația care documentează versiunile AT&T ale UNIX de când a fost formată acea subsidiară pentru a crea sistemul de operare. 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ă situația 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ță software a devenit baza documentului POSIX (Portable Operating System Interface for Computing Environment - 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ța de apel 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, în viitor, specificațiile standardelor POSIX sunt separate de acest sistem de operare particular. 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 adoptată o nouă versiune a standardului IEEE 1003.1 - 1990. Acesta a definit reguli generale pentru interfața software atât pentru apelurile de sistem, cât și pentru funcțiile de bibliotecă. Sunt aprobate completări ulterioare la acesta, definind servicii pentru sisteme în timp real, fire de execuție POSIX etc. Important este standardul POSIX 1003.2 - 1992 - definirea interpretului de comenzi și a utilitaților.

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ă”, partea a șaptea despre operațiunile directorului descrie trei funcții (opendir, readdir și closedir). Acestea sunt definite în patru puncte: „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 numele fișierului local NUL terminat cu caractere.

Readdir citește elementul director curent și setează indicatorul de poziție la următorul element. Directorul deschis este specificat de indicatorul dirp. Un element care conține 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 utilitățile, funcțiile, fișierele antet necesare cu comportamentul specificat în standard. Constanta _POSIX_VERSION are valoarea 200112L [ 49 ]".

În lumea tehnologiei computerelor există o astfel de expresie: „programare POSIX”. Acest lucru poate fi învățat din diferite tutoriale de programare a sistemului UNIX și sisteme de operare (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, un standard tehnic Open Group. și un 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 să fie procesat în modul prescris, fie să returneze o valoare fixă ​​a codului 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ă pe cât posibil continuitatea cu versiunile anterioare.Poate 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, există patru rezultate posibile, iar cerința pentru programul de aplicare este ca acesta să fie capabil să se ocupe de 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 a sistemelor 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 respectării soluțiilor standard care reflectă 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).

Fiecare calculator are propriul său limbaj de programare - limbaj de comandă sau limbaj de mașină - și poate executa programe scrise doar î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 care au o bună cunoaștere a structurii și funcționării computerului. Limbajele de nivel înalt (Fortran, PL/1, Pascal, C, Ada etc.) cu structuri de date dezvoltate și mijloace de procesare care nu depind de limbajul unui anumit computer au făcut posibilă depășirea acestei dificultăți.

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 introducere.

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 al unui program obiect în codul mașinii. În 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 interactivi, 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.

S-au înregistrat progrese semnificative în dezvoltarea compilatorului în ultimii ani. Primele compilatoare au folosit așa-numitele metode de difuzare în direct- Acestea sunt metode predominant euristice în care, pe baza unei idei generale, pentru fiecare construcție de limbă a fost dezvoltat propriul algoritm de traducere într-un echivalent de mașină. 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 (nivele) 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 limbaj natural sau 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 delimitatori, în special operatori aritmetici și logici, paranteze, virgule și alte simboluri. Vocabularul unei limbi, împreună cu o descriere a modurilor în care sunt reprezentate, constituie vocabular limba.

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ă limba. Î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 limba.

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. Figura 1.1 prezintă un model funcțional simplificat al translatorului.

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 un singur 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ă, controlul 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 implică diverse tipuri de procesare semantică. Un tip este verificarea convențiilor semantice într-un program. Exemple de astfel de convenții: descrierea unică a fiecărui identificator din program, definirea unei variabile se face înainte de a fi utilizată 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. Organizarea diferitelor faze de traducere și metodele practice asociate pentru descrierea lor matematică sunt discutate mai jos.

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 sursa. Se apelează limba de ieșire limbă ț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; pentru limbaje naturale, consultați: Traducere.

Tipuri de traducători

  • Abordare. Un dispozitiv funcțional care convertește o adresă virtuală într-o adresă de memorie reală.
  • 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ă.
  • Înapoi. 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 pe 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ărie). De asemenea, ca și în cazul unui compilator, o implementare mixtă necesită ca codul sursă să fie lipsit de erori (lexicale, sintactice și semantice) înainte de execuție.

Odată cu creșterea resurselor informatice și extinderea rețelelor eterogene (inclusiv Internetul) care conectează computere de 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. , "pe fuga." 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

Trimiteți-vă munca bună în baza de cunoștințe este simplu. Utilizați formularul de mai jos

Studenții, studenții absolvenți, tinerii oameni de știință care folosesc baza de cunoștințe în studiile și munca lor vă vor fi foarte recunoscători.

Postat pe http://www.allbest.ru

Introducere

1.1 Analiză de sus în jos

1.2 Analiză de jos în sus

1.2.1 LR(k) - gramatici

1.2.1.1 LR(0) - gramatici

1.2.2 LALR(1) - gramatici

2. Dezvoltarea traducătorului

2.1 Analiza cerințelor

2.2 Proiectare

2.2.1 Proiectarea unui analizor lexical

2.2.4 Implementarea software a parserului

2.3 Codificare

2.4 Testare

Concluzie

Lista surselor utilizate

Anexa A. Listarea textului programului de traducere

Anexa B. Rezultatele testelor

Anexa B. Diagrama programului Translator

Introducere

Au trecut de mult vremurile în care, înainte de a scrie un program, trebuia să înțelegi și să-ți amintești zeci de instrucțiuni ale mașinii. Un programator modern își formulează sarcinile în limbaje de programare de nivel înalt și folosește limbajul de asamblare doar în cazuri excepționale. După cum se știe, limbajele algoritmice devin disponibile pentru programator numai după crearea traducătorilor din aceste limbi.

Limbajul de programare este destul de diferit unul de celălalt ca scop, structură, complexitate semantică și metode de implementare. Acest lucru impune propriile caracteristici specifice dezvoltării unor traducători specifici.

Limbajele de programare sunt instrumente pentru rezolvarea problemelor din diferite domenii, ceea ce determină specificul organizării lor și diferențele de scop. Exemplele includ limbaje precum Fortran, care este orientat spre calcule științifice, C, care este destinat programării sistemelor, Prolog, care descrie în mod eficient problemele de inferență și Lisp, care este utilizat pentru procesarea recursivă a listelor. Aceste exemple pot fi continuate. Fiecare disciplină își pune propriile cerințe privind organizarea limbii în sine. Prin urmare, putem observa varietatea formelor de reprezentare a operatorilor și expresiilor, diferența în setul de operații de bază și scăderea eficienței programării la rezolvarea problemelor care nu țin de domeniul de studiu. Diferențele lingvistice se reflectă și în structura traducătorilor. Lisp și Prolog sunt cel mai adesea executate în modul de interpretare datorită faptului că folosesc generarea dinamică de tipuri de date în timpul calculelor. Traducătorii Fortran se caracterizează prin optimizarea agresivă a codului mașină rezultat, ceea ce devine posibil datorită semanticii relativ simple a constructelor de limbaj - în special, datorită absenței mecanismelor de denumire alternativă a variabilelor prin indicatori sau referințe. Prezența pointerilor în limbajul C impune cerințe specifice pentru alocarea dinamică a memoriei.

Structura unei limbi caracterizează relațiile ierarhice dintre conceptele sale, care sunt descrise prin reguli sintactice. Limbajele de programare pot diferi foarte mult unele de altele în organizarea conceptelor individuale și a relațiilor dintre ele. Limbajul de programare PL/1 permite imbricarea arbitrară a procedurilor și funcțiilor, în timp ce în C toate funcțiile trebuie să fie la nivelul de imbricare exterior. Limbajul C++ permite ca variabilele să fie declarate în orice moment al programului înainte de prima utilizare, în timp ce în Pascal variabilele trebuie definite într-o zonă specială de declarare. Luând acest lucru și mai departe este PL/1, care permite ca o variabilă să fie declarată după ce a fost utilizată. Sau puteți omite descrierea cu totul și utilizați regulile implicite. În funcție de decizia luată, traducătorul poate analiza programul într-una sau mai multe treceri, ceea ce afectează viteza de traducere.

Semantica limbajelor de programare variază foarte mult. Ele diferă nu numai prin caracteristicile de implementare ale operațiunilor individuale, ci și prin paradigmele de programare, care determină diferențe fundamentale în metodele de dezvoltare a programelor. Specificul implementării operațiunilor poate viza atât structura datelor în curs de prelucrare, cât și regulile de prelucrare a acelorași tipuri de date. Limbi precum PL/1 și APL acceptă operațiuni cu matrice și vectori. Majoritatea limbajelor funcționează în primul rând cu scalari, oferind proceduri și funcții scrise de programatori pentru procesarea tablourilor. Dar chiar și atunci când se efectuează operația de adăugare a două numere întregi, limbaje precum C și Pascal se pot comporta diferit.

Alături de programarea procedurală tradițională, numită și imperativă, există paradigme precum programarea funcțională, programarea logică și programarea orientată pe obiecte. Structura conceptelor și obiectelor limbilor depinde foarte mult de paradigma aleasă, care afectează și implementarea traducătorului.
Chiar și același limbaj poate fi implementat în mai multe moduri. Acest lucru se datorează faptului că teoria gramaticilor formale permite metode diferite de analiză a acelorași propoziții. În conformitate cu aceasta, traducătorii pot obține același rezultat (program obiect) din textul sursă original în moduri diferite.
În același timp, toate limbajele de programare au o serie de caracteristici și parametri comuni. Această caracteristică comună determină, de asemenea, principiile de organizare a traducătorilor care sunt similare pentru toate limbile.
Limbajele de programare sunt concepute pentru a ușura programarea. Prin urmare, operatorii și structurile lor de date sunt mai puternice decât cele din limbajele de mașină.
Pentru a crește vizibilitatea programelor, în locul codurilor numerice, se folosesc reprezentări simbolice sau grafice ale constructelor limbajului, care sunt mai convenabile pentru percepția umană.
Pentru orice limbă se definește:
- multe simboluri care pot fi folosite pentru a scrie programe corecte (alfabet), elemente de bază,
- multe programe corecte (sintaxă),
- „sensul” fiecărui program corect (semantică).
Indiferent de specificul limbajului, orice traducător poate fi considerat un convertor funcțional F, oferind o mapare unică de la X la Y, unde X este un program în limba sursă, Y este un program în limbajul de ieșire. Prin urmare, procesul de traducere în sine poate fi reprezentat formal destul de simplu și clar: Y = F(X).
Formal, fiecare program corect X este un lanț de simboluri dintr-un anumit alfabet A, transformat în lanțul său corespunzător Y, compus din simboluri ale alfabetului B.
Un limbaj de programare, ca orice sistem complex, este definit printr-o ierarhie de concepte care definește relațiile dintre elementele sale. Aceste concepte sunt interconectate în conformitate cu regulile sintactice. Fiecare program construit conform acestor reguli are o structură ierarhică corespunzătoare.
În acest sens, următoarele caracteristici comune pot fi distinse suplimentar pentru toate limbile și programele acestora: fiecare limbă trebuie să conțină reguli care să permită generarea de programe corespunzătoare acestei limbi sau recunoașterea corespondenței dintre programele scrise și un anumit limbaj.

O altă trăsătură caracteristică a tuturor limbilor este semantica lor. Determină semnificația operațiilor limbajului și corectitudinea operanzilor. Lanțurile care au aceeași structură sintactică în diferite limbaje de programare pot diferi în semantică (care, de exemplu, se observă în C++, Pascal, Basic). Cunoașterea semanticii unei limbi vă permite să o separați de sintaxa acesteia și să o utilizați pentru conversia într-o altă limbă (pentru a genera cod).

Scopul acestui curs este de a dezvolta un traducător educațional dintr-un anumit limbaj text simplificat de nivel înalt.

1. Metode de analiză gramaticală

Să ne uităm la metodele de bază ale analizei gramaticale.

1.1 Analiză de sus în jos

La analizarea de sus în jos, firele intermediare se deplasează de-a lungul copacului în direcția de la rădăcină la frunze. În acest caz, la vizualizarea lanțului de la stânga la dreapta, se vor obține în mod firesc concluzii pentru stângaci. În analiza deterministă, problema va fi ce regulă să se aplice pentru a rezolva cel mai din stânga nonterminal.

1.1.1 LL(k) - limbi și gramatici

Luați în considerare arborele de inferență în procesul de obținere a ieșirii din stânga lanțului. Lanțul intermediar în procesul de inferență constă dintr-un lanț de terminale w, cel mai din stânga non-terminal A, partea sub-inferită x:

-S--

/ \

/ -A-x-\

/ | \

-Wu----

figura 1

Pentru a continua analiza, este necesar să înlocuiți neterminalul A conform uneia dintre regulile formei A:y. Dacă doriți ca analizarea să fie deterministă (fără returnări), această regulă trebuie aleasă într-un mod special. Se spune că o gramatică are proprietatea LL(k) dacă, pentru a selecta o regulă, este suficient să luăm în considerare numai wax și primele k caractere ale șirului neexaminat u. Prima literă L (stânga) se referă la vizualizarea lanțului de intrare de la stânga la dreapta, a doua se referă la ieșirea din stânga utilizată.

Să definim două seturi de lanțuri:

a) FIRST(x) este mulțimea de șiruri terminale derivate din x, scurtate la k caractere.

b) FOLLOW(A) - un set de lanțuri terminale scurtate la k caractere, care pot urma imediat A în lanțurile de ieșire.

O gramatică are proprietatea LL(k) dacă, din existența a două lanțuri de inferențe stângi:

S:: ceară: wzx:: wu

S:: ceară: wtx:: wv

din condiția FIRST(u)=FIRST(v) rezultă z=t.

În cazul lui k=1, pentru a alege o regulă pentru A, este suficient să cunoaștem doar neterminalul A și a - primul caracter al lanțului u:

- regula A:x trebuie selectată dacă a este inclus în FIRST(x),

- regula A:e trebuie selectată dacă a este inclus în FOLLOW(A).

Proprietatea LL(k) impune restricții destul de puternice asupra gramaticii. De exemplu, LL(2) gramatica S: aS | a nu are proprietatea LL(1), deoarece FIRST(aS)=FIRST(a)=a. În acest caz, puteți reduce valoarea lui k folosind „factorizarea” (luând factorul din paranteze):

S: aA

A: S | e

Orice gramatică LL(k) nu este ambiguă. O gramatică recursiva la stânga nu aparține clasei LL(k) pentru orice k. Uneori este posibil să convertiți o gramatică non-LL(1) într-o gramatică LL(1) echivalentă prin eliminarea recursiunii stângi și a factorizării. Cu toate acestea, problema existenței unei gramatici LL(k) echivalente pentru o gramatică arbitrară non-LL(k) este indecidabilă.

1.1.2 Metoda coborârii recursive

Metoda coborârii recursive vizează acele cazuri în care compilatorul este programat într-unul din limbajele de nivel înalt, când este permisă utilizarea procedurilor recursive.

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. Aceste proceduri se apelează reciproc atunci când este necesar.

Descent recursiv poate fi folosit pentru orice gramatică LL(1). Fiecare non-terminal al gramaticii are o procedură corespunzătoare, care începe cu o tranziție la eticheta calculată și conține codul corespunzător fiecărei reguli pentru acest non-terminal. Pentru acele simboluri de intrare care aparțin setului de selecție al unei anumite reguli, tranziția calculată transferă controlul către codul corespunzător acelei reguli. Pentru simbolurile de intrare rămase, controlul este transferat la procedura de tratare a erorilor.

Codul oricărei reguli conține operații pentru fiecare caracter inclus în partea dreaptă a regulii. Operatiile sunt aranjate in ordinea in care simbolurile apar in regula. În urma ultimei operații, codul conține o retur din procedură.

Utilizarea descendenței recursive într-un limbaj de nivel înalt facilitează programarea și depanarea.

1.2 Analiză de jos în sus

Să luăm în considerare analizarea de jos în sus, în care știfturile intermediare sunt mutate de-a lungul copacului spre rădăcină. Dacă citiți caracterele din șir de la stânga la dreapta, arborele de analiză va arăta astfel:

-S--

/ \

/-X-\

/ | \

--w--b--u-

Figura 2

Ieșirea intermediară are forma xbu, unde x este un lanț de terminale și non-terminale, din care iese partea vizualizată a lanțului de terminale w, bu este partea nevizuită a lanțului de terminale, b este simbolul următor. Pentru a continua analiza, puteți fie să adăugați caracterul b la partea vizualizată a lanțului (efectuați așa-numita „schimbare”), fie să selectați la sfârșitul lui x un astfel de lanț z (x=yz) că unul dintre regulile gramaticii B:z pot fi aplicate lui z și înlocuite cu x lanțului yB (efectuați așa-numita „convoluție”):

-S-- -S--

/ \ / \

/-x-b-\ /yB-\

/ | \ / | \

--w--b--u- --w--b--u-

Figura 3 - După schimbare Figura 4 - După convoluție

Dacă convoluția este aplicată numai ultimelor caractere ale lui x, atunci vom obține rezultatele corecte ale lanțului. Această analiză se numește LR, unde simbolul L (Stânga, stânga) se referă la vizualizarea lanțului de la stânga la dreapta, iar R (Dreapta, dreapta) se referă la ieșirile rezultate.

Secvența operațiilor de schimbare și pliere este esențială. Prin urmare, analiza deterministă necesită alegerea între schimbare și convoluție (și reguli diferite de convoluție) în fiecare moment.

1.2.1 LR(k) - gramatici

Dacă, în procesul de parsare LR, este posibil să se ia o decizie deterministă cu privire la deplasare/reducere, luând în considerare doar șirul x și primele k caractere ale părții nevăzute a șirului de intrare u (aceste k caractere se numesc șirul de avans). ), se spune că gramatica are proprietatea LR(k).

-S--

/ \

/-X-\

--Wu--

Figura 5

Diferența dintre gramaticile LL(k) și LR(k) în ceea ce privește un arbore de inferență:

-S-

/ | \

/A\

/ / \ \

-w---v---u-

Figura 6

În cazul gramaticilor LL(k), regula aplicată lui A poate fi determinată în mod unic de w și primele k caractere ale lui vu, iar în cazul gramaticilor LR(k), de w,v și primul k personaje ale lui. Acest raționament neriguros arată că LL(k)-limbi< LR(k)-языки (при k > 0).

1.2.1.1 LR(0) - gramatici

Să luăm mai întâi în considerare cele mai simple gramatici ale acestei clase - LR(0). Când analizați un șir într-o limbă LR(0), nu trebuie să utilizați deloc lanțul de avans - alegerea între shift și fold se face pe baza lanțului x. Deoarece în timpul parsării se schimbă doar de la capătul din dreapta, se numește stivă. Să presupunem că nu există simboluri inutile în gramatică și simbolul inițial nu apare în partea dreaptă a regulilor - apoi convoluția la simbolul inițial semnalează finalizarea cu succes a parsării. Să încercăm să descriem setul de lanțuri de terminale și non-terminale care apar pe stivă în timpul tuturor analizei LR (cu alte cuvinte, toate inferențe din partea dreaptă din gramatică).

Să definim următoarele seturi:

L(A:v) - contextul stâng al regulii A:v - set de stări de stivă imediat înainte ca v să fie pliat în A în timpul tuturor analizelor LR de succes. Evident, fiecare lanț din L(A:v) se termină în v. Dacă coada v a tuturor acestor lanțuri este tăiată, atunci obținem setul de lanțuri care apar la stânga lui A în timpul tuturor inferențelor cu succes din partea dreaptă. Să notăm această mulțime ca L(A) - contextul din stânga al neterminalului A.

Să construim o gramatică pentru mulțimea L(A). Terminalele noii gramatici vor fi terminalele și non-terminele gramaticii originale; non-terminalele noii gramatici vor fi notate cu ,... - valorile lor vor fi contextele din stânga ale nonterminalelor gramaticii originale. Dacă S este caracterul inițial al gramaticii originale, atunci gramatica contextului din stânga va conține regula : e - contextul din stânga S conține un lanț gol Pentru fiecare regulă a gramaticii originale, de exemplu, A: B C d E

și adăugați reguli la noua gramatică:

: - L(B) include L(A)

: B - L(C) include L(A) B

: B C d - L(E) include L(A) B C d

Gramatica rezultată are o formă specială (astfel de gramatici se numesc liniar stânga), prin urmare, seturi de contexte din partea stângă

- regulat. Rezultă că dacă un șir aparține contextului stâng al unui nonterminal poate fi calculat inductiv folosind o mașină cu stări finite, scanând șirul de la stânga la dreapta. Să descriem acest proces în mod constructiv.

Să numim o situație LR(0) o regulă gramaticală cu o poziție marcată între simbolurile din partea dreaptă a regulii. De exemplu, pentru gramatica S:A; Aaaa; A:b există următoarele situații LR(0): S:_A; S:A_; Aaaa; Aaaa; Aaaa; Aaaa_; A:_b; A:b_. (poziția este indicată printr-o liniuță de subliniere).

Vom spune că lanțul x este în concordanță cu situația A:b_c dacă x=ab și a aparține lui L(A). (Cu alte cuvinte, ieșirea LR poate fi continuată x_... = ab_...:: abc_...:: aA_...:: S_.) În acești termeni, L(A:b) este mulțimea de șiruri în concordanță cu situația A:b_, L(A)

- lanțuri conforme cu situația A:_b, pentru orice regulă A:b.

Fie V(u) mulțimea situațiilor compatibile cu u. Să arătăm că funcția V este inductivă.

Dacă mulțimea V(u) include situația A:b_cd, atunci situația A:bc_d aparține lui V(uc). (c - terminale sau neterminale; b, d - secvențe (pot fi goale) de terminale și neterminale). Nu există alte situații de forma A:b_d, cu b nevid în V(uc). Rămâne să adăugați situații de forma C:_... la V(uc), pentru fiecare C neterminal al cărui context din stânga conține uc. Dacă situația A:..._C... (C-neterminal) aparține mulțimii V(uc), atunci uc aparține lui L(C) și V(uc) include situații de forma C:_... pentru toate C- regulile gramaticale.

V(e) conține situațiile S:_... (S-caracter de început), precum și situațiile A:_... dacă neterminalul A apare imediat după _ în situațiile deja incluse în V(e).

În cele din urmă, suntem gata să definim o gramatică LR(0). Fie u conținutul stivei în timpul parsării LR și V(u) să fie mulțimea situațiilor LR(0) compatibile cu u. Dacă V(u) conține o situație de forma A:x_ (x-secvență de terminale și neterminale), atunci u aparține lui L(A:x) și este permisă convoluția lui x în A. Dacă V(u ) conține situația A:..._a... (a-terminal), atunci este permisă o deplasare. Se spune că există un conflict deplasare-convoluție dacă atât deplasarea, cât și convoluția sunt permise pentru același șir u. Se spune că există un conflict de convoluție-reducere dacă sunt permise circumvoluții conform unor reguli diferite.

O gramatică se numește LR(0) dacă nu există conflicte shift-reduce sau fold-reduce pentru toate stările de stivă în timpul inferenței LR.

1.2.1.2 LR(k) - gramatici

Doar starea stivei este folosită pentru a decide între deplasare și pliere în analiza LR(0). Analiza LR(k) ia în considerare și primele k caractere ale părții nevăzute a lanțului de intrare (așa-numitul lanț de avans). Pentru a justifica metoda, trebuie să repetați cu atenție raționamentul paragrafului anterior, făcând modificări definițiilor.

Vom include, de asemenea, un lanț de avans în contextul din stânga al regulilor. Dacă ieșirea dreaptă folosește ieșirea wAu:wvu, atunci perechea wv,FIRSTk(u) aparține lui Lk(A:v), iar perechea w,FIRSTk(u) aparține lui Lk(A). Setul de contexte din stânga, ca și în cazul lui LR(0), poate fi calculat folosind inducția pe lanțul din stânga. Să numim o situație LR(k) o pereche: o regulă gramaticală cu o poziție marcată și un lanț avansat de lungime nu mai mare de k. Vom separa regula de lanțul de avans cu o linie verticală.

Vom spune că lanțul x este în concordanță cu situația A:b_c|t dacă există o ieșire LR: x_yz = ab_yz:: abc_z:: aA_z:: S_, iar FIRSTk(z) = t. Regulile pentru calcularea inductivă a mulțimii de stări Vk sunt următoarele:

Vk(e) conține situațiile S:_a|e pentru toate regulile S:a, unde S este caracterul de început. Pentru fiecare situație A:_Ba|u din Vk(e), fiecare regulă B:b și lanț x aparținând FIRSTk(au), este necesar să se adauge situația B:_b|x la Vk(e).

Dacă Vк(w) include situația A:b_cd|u, atunci situația A:bc_d|u va aparține lui Vk(wc). Pentru fiecare situație A:b_Cd|u din Vk(wc), fiecare regulă C:f și lanț x aparținând FIRSTk(du) este necesar să se adauge situația C:_f|x la Vk(wc).

Folosim seturile construite de stări LR(k) pentru a rezolva problema deplasării-convoluție. Să fie u conținutul stivei și x să fie upchain-ul. Evident, convoluția conform regulii A:b poate fi efectuată dacă Vk(u) conține situația A:b_|x. A decide dacă o schimbare este permisă necesită atenție dacă gramatica conține reguli electronice. În situația A:b_c|t (c nu este gol), o deplasare este posibilă dacă c începe de la un terminal și x aparține lui FIRSTk(ct). Informal vorbind, puteți împinge simbolul din stânga din partea dreaptă a regulii pe stivă, pregătind convoluția ulterioară. Dacă c începe cu un nonterminal (situația arată ca A:b_Cd|t), atunci împingerea unui simbol pe stivă în pregătirea convoluției în C este posibilă numai dacă C nu generează un lanț gol. De exemplu, în starea V(e)= S:_A|e; A:_AaAb|e,a, A:_|e,a nu există ture permise, deoarece La derivarea lanțurilor terminale din A, la un pas este necesar să se aplice regula A:e la non-terminalul A situat la capătul stâng al lanțului.

Să definim mulțimea EFFk(x), constând din toate elementele mulțimii FIRSTk(x), în timpul derivării căreia neterminalul de la capătul stâng al lui x (dacă există unul) nu este înlocuit cu un lanț gol. În acești termeni, o deplasare este permisă dacă în mulțimea Vk(u) există o situație A:b_c|t, c nu este gol și x aparține lui EFFk(ct).

O gramatică se numește gramatică LR(k) dacă nicio stare LR(k) nu conține două situații A:b_|u și B:c_d|v astfel încât u aparține lui EFFk(dv). O astfel de pereche corespunde unui conflict fold-reduce dacă d este gol și unui conflict shift-fold dacă d nu este gol.

În practică, gramaticile LR(k) nu sunt folosite pentru k>1. Există două motive pentru aceasta. În primul rând: un număr foarte mare de stări LR(k). În al doilea rând: pentru orice limbă definită de o gramatică LR(k), există o gramatică LR(1); Mai mult, pentru orice limbaj determinist KS există o gramatică LR(1).

Numărul de stări LR(1) pentru gramaticile practic interesante este, de asemenea, destul de mare. Astfel de gramatici au rareori proprietatea LR(0). În practică, o metodă intermediară între LR(0) și LR(1), cunoscută sub numele de LALR(1), este mai des utilizată.

1.2.2 LALR(1) - gramatici

Aceste două metode se bazează pe aceeași idee. Să construim un set de stări canonice LR(0) ale gramaticii. Dacă acest set nu conține conflicte, atunci poate fi utilizat analizatorul LR(0). În caz contrar, vom încerca să rezolvăm conflictele apărute luând în considerare un lanț de avans cu un singur caracter. Cu alte cuvinte, să încercăm să construim un parser LR(1) cu multe stări LR(0).

Metoda LALR(1) (Look Ahead) este următoarea. Să introducem o relație de echivalență pe mulțimea de situații LR(1): vom considera două situații ca fiind echivalente dacă diferă doar în lanțurile lor conducătoare. De exemplu, situațiile A:Aa_Ab|e și A:Aa_Ab|a sunt echivalente. Să construim o mulțime canonică de stări LR(1) și să combinăm stările constând dintr-un set de situații echivalente.

Dacă setul de stări rezultat nu conține conflicte LR(1) și, prin urmare, permite construirea unui parser LR(1), atunci se spune că gramatica are proprietatea LALR(1).

2. Dezvoltarea traducătorului

2.1 Analiza cerințelor

În acest curs, este necesar să se dezvolte un traducător educațional sub forma unui interpret dintr-o limbă definită de gramatica formală corespunzătoare. Există patru etape principale în dezvoltarea unui interpret:

Proiectarea unui analizor lexical;

Proiectarea unui distribuitor automat;

Implementarea software a parserului;

Dezvoltarea unui modul de interpretare.

Dezvoltarea va fi realizată folosind sistemul de operare Windows XP pe un computer personal IBM PC cu procesor Intel Pentium IV.

Pe baza tendințelor de dezvoltare software, a fost ales limbajul de programare C# în mediul Visual Studio 2010 pentru a implementa traducătorul educațional.

2.2 Proiectare

2.1.1 Proiectarea unui analizor lexical

Lexical analiza presupune scanarea programului tradus (sursa) si recunoasterea lexemelor care alcatuiesc propozitiile textului sursa. Jetoanele includ, în special, cuvinte cheie, semne de operare, identificatori, constante, caractere speciale etc.

Rezultatul muncii unui analizor lexical (scaner) este o secvență de jetoane, fiecare token fiind de obicei reprezentat de un cod cu lungime fixă ​​(de exemplu, un număr întreg), precum și generarea de mesaje despre erorile sintactice (lexicale). , dacă este cazul. Dacă tokenul este, de exemplu, un cuvânt cheie, atunci codul său oferă toate informațiile necesare. În cazul, de exemplu, a unui identificator, este necesară suplimentar numele identificatorului recunoscut, care este de obicei înregistrat într-un tabel de identificatori, organizat, de regulă, folosind liste. Un tabel similar este necesar pentru constante.

Un lexem poate fi descris prin două caracteristici principale. Una dintre ele este că lexemul aparține unei anumite clase (variabile, constante, operații etc.) Al doilea atribut definește un element specific al acestei clase.

Forma specifică a tabelului de simboluri (structura de date) nu contează pentru lexer sau parser. Ambele trebuie doar să ofere capacitatea de a obține un index care identifică în mod unic, de exemplu, o anumită variabilă și să returneze valoarea indexului pentru a completa informații despre un anumit nume de variabilă în tabelul cu simboluri.

Vizualizarea tabelului ID îndeplinește două funcții principale:

a) înregistrarea unui nou nume în tabel la procesarea descrierilor de variabile;

b) căutarea unui nume înregistrat anterior în tabel.

Acest lucru vă permite să identificați situații eronate, cum ar fi descrieri multiple ale unei variabile și prezența unei variabile nedescrise.

Dezvoltarea unui analizator lexical constă în parte în modelarea diferitelor automate pentru a recunoaște identificatori, constante, cuvinte rezervate etc. Dacă jetoanele de diferite tipuri încep cu același caracter sau aceeași secvență de caractere, poate fi necesară combinarea recunoașterii lor.

Prin rularea analizorului lexical, ne împărțim programul în token-uri, după care fiecare jeton trece o verificare a lungimii (un jeton nu poate avea mai mult de 11 caractere). După ce am finalizat cu succes această etapă, verificăm locația corectă a jetoanelor (cuvintele cheie var, begin, end, for, to, do, end_for). Apoi analizăm lexemele variabile - nu trebuie să conțină numere în descrierea lor și nu trebuie repetate. În ultima etapă, verificăm ortografia corectă a lexemelor (cuvinte cheie, identificatori necunoscuți). Dacă cel puțin una dintre verificări eșuează, analizorul lexical imprimă o eroare.

Diagrama programului analizor lexical este prezentată în Anexa B din Figura B.1.

2.2.2 Proiectarea unui automat

Să definim următoarea gramatică:

G: (Vt, Va, I, R),

unde Vt este multimea simbolurilor terminale, Va este multimea simbolurilor neterminale, I este starea initiala a gramaticii, R este multimea regulilor gramaticale.

Pentru această gramatică, definim seturi de simboluri terminale și non-terminale:

Să compunem regulile pentru gramatica noastră G și să le prezentăm în tabelul 1.

Tabelul 1 - Reguli gramaticale

Regula nr.

Partea stângă a regulii

Partea dreaptă a regulii

f ID = EX t EX d LE n;

Continuarea tabelului 1.

Regula nr.

Partea stângă a regulii

Partea dreaptă a regulii

Denumirile lexemelor, traducerea lexemelor în coduri și o listă a denumirilor gramaticale sunt date în tabelele 2, 3, 4, respectiv.

Tabelul 2 - Denumirile lexemelor

Desemnarea jetonului

cuvânt cheie „begin” (începutul descrierii acțiunilor)

cuvântul cheie „sfârșit” (descrierea sfârșitului acțiunii)

cuvânt cheie „var” (descrierea variabilei)

cuvânt cheie „citește” (operator de introducere a datelor)

cuvânt cheie „scrie” (operator de ieșire a datelor)

cuvânt cheie „for” (instrucțiune de buclă)

cuvânt cheie „la”

cuvânt cheie „a face”

cuvânt cheie „end_case” (instrucțiunea de sfârșit a buclei)

tip variabilă „întreg”

operatie de adaugare

operația de scădere

operația de înmulțire

caracter separator „:”

caracter separator „;”

caracter separator „(”

caracter separator ")"

caracter separator ","

Desemnarea jetonului

caracter separator „="

Tabelul 3 - Traducerea lexemelor în coduri

<цифра>

<буква>

Tabelul 4 - Lista simbolurilor gramaticale

Desemnare

Explicaţie

Program

Descrierea calculelor

Descrierea variabilelor

Lista de variabile

Operator

Misiune

Expresie

Subexpresie

Operații binare

Operații unare

Lista sarcinilor

Identificator

Constant

Să construim un detector determinist de jos în sus.

Luați în considerare următoarele relații pentru a construi un recunoaștetor determinist de jos în sus:

a) Dacă există un simbol al grupului B astfel încât o regulă de gramatică să includă lanțul AB și există un simbol xFIRST"(B), atunci vom presupune că relațiile x DUPĂ A sunt determinate între simbolurile x și A

b) Dacă într-o gramatică dată există o regulă B -> bAb A, BV a, b atunci între A și x se determină relația A ACOPERIRE x.

Toată gramatica noastră rămâne aceeași, adică:

G: (Vt, Va, I, R),

iar regulile gramaticii G sunt date în tabelul 5.

Tabelul 5 - Reguli gramaticale

Regula nr.

Partea stângă a regulii

Partea dreaptă a regulii

f ID = EX t EX d LE n;?

Continuarea tabelului 5.

Regula nr.

Partea stângă a regulii

Partea dreaptă a regulii

Unde? - marker pentru capătul lanțului.

Să definim câteva cazuri:

a) ID-ul este format din multe litere ale alfabetului latin, adică vom presupune că u = ( a, b, c, d, e, f, g, h, i, j, k, l, m, n , o, p, q, r, s, t, u, v, w, x, y, z)

b) Constanta CO este formată din numere, adică vom presupune că k = (0,1,2,3,4,5,6,7,8,9)

Pentru ca gramatica noastră să fie o strategie de precedență mixtă, trebuie îndeplinite următoarele condiții:

a) Lipsa regulilor electronice

b) Există reguli conform cărora, x DUPĂ A? A VERT x = ?

c) A -> bYg

si este necesar ca IN DUPA x? B VERT x = ?

adică în gramatică se vor executa ÎN DUPĂ x ​​sau A DUPĂ x, unde x este simbolul predicat al lanțului b.

a) FIRST"(PG)=(PG?)

FIRST"(RG) = FIRST(DE) = (RG, v,:, i,;)

FIRST" (AL) = FIRST (b LE e)= (AL, b, e)

FIRST" (DE) = FIRST (v LV: i;) = (DE, v,:, i,;)

FIRST" (LV) = FIRST (ID, LV) = (LV, ID)

FIRST" (OP) =(OP, ID, CO)

FIRST" (EQ) = FIRST(ID = EX;) = (EQ, =,;)

FIRST" (EX) = (EX, SB, -)

PRIMUL" (BO) =(B0, +,*,-)

FIRST" (SB) = FIRST((EX)SB) ? FIRST(OP) ? FIRST(BO)=(SB, (,), OP, BO);

FIRST" (LE) = FIRST(EQ) = (LE, (,), =,;, f, t, d, n, w, r)

FIRST" (UO) = (UO,-)

FIRST" (ID)= FIRST" (u) = (u)

FIRST" (CO) = FIRST" (k) = (k)FIRST" (e) =( e)

PRIMUL" (b) =( b)

PRIMUL" (e) =( e)

PRIMUL" (v) =( v)

PRIMUL" (w) =(w)

PRIMUL" (r) =( r)

PRIMUL" (i) =( i)

PRIMUL" (f) =( f)

PRIMUL" (d) =(d)

PRIMUL" (n) =( n)

PRIMUL" (c) =( c)

PRIMUL" (+) =( +)

PRIMUL" (*) =( *)

PRIMUL" (-) =( -)

PRIMUL" (,) =(,)

PRIMUL" (;) =(;)

PRIMUL" (:) =(:)

PRIMUL" (=) = ( = )

PRIMUL" (() =( ()

PRIMUL" ()) =() )

PRIMUL" (u) =(u)

PRIMUL" (k) =(k)

b) TRACE `(AL) = (?)? TRACE"(PG)=(?,b,e)

NEXT ` (DE) = (?)?PRIMUL"(AL)= (?, b, e)

NEXT ` (LV) = (?)?PRIMUL"(:)= (?,:)

NEXT ` (OP) = (?)?PRIMUL"(SB)= (?,;,), d, t, +, -, *)

TRACK ` (EQ) = (?)?FIRST"(LE)=(?, (,),;, f, =, t, d, n,w,r )

TRACK ` (EX) = (?)?PRIMUL"(t)?PRIMUL"(d)?PRIMUL"(;)?PRIMUL"())=(?, t,d,;,))

NEXT ` (BO) = (?)?PRIMUL"(SB)= (?, (,), OP, BO)

NEXT ` (UO) = (?)?PRIMUL"(SB)= (?, (,), OP, BO)

TRACE ` (SB) = (?)? TRACE"(EX)= (?, t,d,;,), +, *, -)

TRACK ` (LE) = (?) ?PRIMUL"(e) ?PRIMUL"(n) = (?, e, n)

TRACE `(ID)= (?)? NEXT" (OP) ? FIRST" (=) =(?,;,), d, t, +, -, *, =)

TRACE `(CO) = (?)? TRACE" (OP)= (?,;,), d, t, +, -, *, =)

NEXT ` (b) =(?)?PRIMUL"(LE)= (?, u, =,;)

TRACE ` (e) =(?)? TRACE"(AL)= (?, b)

NEXT ` (v) =(?)?PRIMUL"(LV)= (?, u)

NEXT ` (w) =(?)?PRIMUL"(()= (?, ()

NEXT ` (r) =(?)?PRIMUL"(() = (?, ()

NEXT ` (i) =(?)?PRIMUL"(;)= (?,; )

NEXT ` (f) =(?)?PRIMUL"(ID) = (?, u)

NEXT ` (d) =(?)?PRIMUL"(LE)= (?, u, =,;)

NEXT ` (n) =(?)?PRIMUL"(i) = (?, i )

TRACE ` (+) =(?)? TRACE"(IN) = (?, +,*,-)

TRACE ` (-) =(?)? TRACE"(IN) = (?, +,*,-)

TRACE ` (*) =(?)? TRACE"(IN) = (?, +,*,-)

TRACK ` (;) =(?)?TRACK" (DE)?TRACK `(LE1)?TRACK" (EQ) = (?, b, e, l, u)

NEXT ` (:) =(?)?PRIMUL"(i)= (?, i )

NEXT ` (=) = (?)?PRIMUL"(EX) = (? (,), u, k, +, -, *)

NEXT ` (() =(?)?PRIMUL"(DE)= (?, v,:, i,;)

TRACE ` ()) =(?)? FIRST"(;) = (?,; )

TRACE ` (,) =(?)? FIRST"(LV) = (?, u)

TRACE `(u) =(?)? FIRST" (ID)= (u, ?)

TRACE `(k) =(?)? PRIMUL (CO)= (?, k)

c) PG ->DE AL

AL DUPĂ DE = (b,e) DUPĂ DE = ((b DE), (e DE) )

e DUPĂ LE = ((e LE))

LE DUPĂ b = ((,), =,;, f, t, d, n, w, r) DUPĂ b = (((b), ()b), (=b), (;b), ( f b), (t b), (d b), (n b), (w b), (r b))

;DUPĂ i = ((; i))

i DUPĂ: = ( (i:) )

: DUPĂ LV = ( (: LV) )

LV DUPĂ v = ( (ID, v) )

LV DUPĂ, = ((ID,))

DUPĂ ID = ((,u))

LE AFTER EQ = ((,), =,;, f, t, d, n, w, r ) AFTER EQ = (((EQ), () EQ), (= EQ), (; EQ), ( f EQ), (t EQ), (d EQ), (n EQ), (w EQ), (r EQ))

LE -> r (DE);

; DUPĂ) = ((;)))

) DUPA DE = (((DE))

DE DUPA (= (= ((v)), (:)), (i)), (;)), (e)))

(DUPĂ r = (((r))

LE -> w (DE);

; DUPĂ) = ((;)))

) LAST DE = (((DE))

DE DUPĂ (= ((v)), (:)), (i)), (;)), (e)))

(DUPĂ w = (((w))

LE -> f ID = EX t EX d LE n;

; DUPĂ n = ((;n))

n DUPĂ LE = ( (n, LE))

LE DUPĂ d = (((,), =,;, f, t, d, n, w, r)) ? DUPĂ d = (((d), ()d), (;d), (f d), (t d), (d d), (n d), (w d), (r d))

d DUPĂ EX = ((d, EX))

EX DUPĂ t = (BO, -) ? DUPĂ t = ((BO t), (- t))

t DUPĂ EX = ( t EX)

EX DUPA = = ((BO, -) ? DUPA = = ((BO =), (- =))

AFTER ID = ((= ID))

ID DUPĂ f = ((ID f))

EQ -> ID = EX;

; DUPĂ EX = ((; EX )

EX DUPA = = (BO, -) ? DUPĂ = = ((BO =), (- =))

DUPĂ u = ( (=u))

SB DUPĂ UO = ( (,), OP, BO ) DUPĂ UO = (((UO), (OP UO), (BO UO) )

) DUPĂ EX = ( ()EX) )

EX DUPĂ (= (BO, -) ? DUPĂ (= ((BO (), (- ())

SB->SB BO SB

SB DUPĂ BO = ((,), OP, BO) DUPĂ BO = (((BO), ()BO), (OP BO), (BO BO))

BO DUPĂ SB = (+,*,-) DUPĂ SB = ((+SB), (*SB), (-SB))

ID DUPĂ u = ((u, u))

G) PG ->DE AL

AL COLLECTION PG = AL COLLECTION TRACE" (PG) = ((AL ?))

e COLECȚIE AL = e COLECȚIE TRACE"(AL)= ((eb), (e?))

=; PISTA DE COLECȚIE"(DE) = ((;b), (;?))

LV COLLECTION LV = LV COLLECTION TRAIL" (LV) = ((LV:), (LV?))

ID COLLECTION LV = ID COLLECTION TRACK" (LV) = ((ID:), (ID ?))

; COLLAPSE LE =; PISTA DE COLECTARE" (LE) = ((; e), (;?), (; n))

LE -> f ID = EX t EX d LE n;

; COLLAPSE LE =; PISTA DE COLECTARE" (LE) = ((; e), (;?), (; n))

EQ COLLECTION LE = EQ COVER TRACE" (LE) = ((EQ e), (EQ?), (EQ n))

EQ -> ID = EX;

; COLLAPSE EQ =; PISTA DE COLECȚIE” (EQ) = ((; (), (;)), (;;), (;f), (;?), (;=), (;t), (;d), (;; n), (;w), (;r))

SB COLLECTION EX = SB COVER TRACE" (EX) = ((SB t), (SB?), (SB d), (SB)), (SB;), (SB(), (SB=), (SBf ), (SBn), (SBw), (SBr) )

) COLECȚIE SB = SB COLECȚIE TRACE" (SB) = (() t), ()?), () d), ())), ();))

OP COLLECTION SB = OP COLLECTION TRACE" (SB) = ((OP t), (OP ?), (OP d), (OP)), (OP;))

SB->SB BO SB

SB COLLECTION SB = SB COVER TRACE" (SB) = ((SB, t), (SBd), (SB;). (SB)), (SB+), (SB-), (SB*), (SB? ) }

COLECȚIE UO = - PISTA DE COLECȚIE" (UO) = ( (-?), (--))

COLECȚIE BO = + COLECȚIE TRACK" (BO) = ((++), (+?), (+*), (+-))

* COLECȚIE BO = * COLECȚIE TRACK" (BO) = ((*+), (*?), (**), (*-))

COLECȚIE BO = - PISTA DE COLECȚIE" (BO) = ((-+), (-?), (-*), (--))

COLEGERE ID-uri OP = ID-ACOPERIRE TRACE" (OP) = ((ID+), (ID?), (ID*), (ID-))

CO COVER OP = CO COPER TRACE" (OP) = ((CO+), (CO?), (CO*), (CO-), (CO;), (COd), (COt), (CO)))

ID COLECTARE ID = ID COLLECTION TRACK" (ID) = ((ID)), (ID ?), (ID k), (ID+), (ID-), (ID*), (ID=), (IDt) , (IDd)))

u ID COLECȚIE = l PISTA DE COLECȚIE" (ID) = ((u)), (u?), (uk), (u+), (u-), (u*), (u=), (ut), (ud)))

CO COVER CO = CO COPER TRACE" (CO) = (CO+), (CO?), (CO*), (CO-), (CO;), (COd), (COt), (CO)))

k COLECȚIE CO = k COLECȚIE TRASE" (CO) = (k+), (k?), (k*), (k-), (k;), (kd), (kt), (k)))

O situație conflictuală a fost detectată la prăbușirea regulilor

OP ->ID și ID -> u ID

Introducem ID1 -> ID, prin urmare rescriem regula ID1 -> u ID

Prin urmare, vom efectua operații de convoluție.

ID1 COLECȚIE ID1 = ID1 COLLECTION TRACK" (ID) = ((ID1)), (ID1 ?), (ID1 k), (ID1+), (ID1-), (ID1*), (ID1=), (ID1t) , (ID1d)))

Pentru fiecare pereche (x, A)? x DUPĂ A construim o funcție de tranziție care determină acțiunea de transfer??(S 0 , x, A) = (S 0 , A)

? (S0, b, DE) = (S0, DEb)

? (S0, e, DE) = (S0, DEe)

? (S0, e, LE) = (S0, LEe)

? (S0,), b) = (S0, b))

? (S0,;, b) = (S0, b;)

? (S0, (, b) = (S0, b()

? (S0, =, b) = (S0, b=)

? (S0, f, b) = (S0, bf)

? (S0, t, b) = (S0, bt)

? (S0, d, b) = (S0, bd)

? (S0, n, b) = (S0, bn)

? (S0, w, b) = (S0, bw)

? (S0, r, b) = (S0, br)

? (S0,;, i) = (S0, i;)

? (S0, i,:) = (S0, i:)

? (S0,:LV) = (S0,LV:)

? (S0, ID, v) = (S0, vID)

? (S0,ID,) = (S0,ID)

? (S0, u) = (S0, u,)

? (S0, (, EQ)= (S0, EQ()

? (S0,), EQ)= (S0, EQ))

? (S0, =, EQ)= (S0, EQ=)

? (S0,;, EQ)= (S0, EQ;)

? (S0, f, EQ)= (S0, EQf)

? (S0, t, EQ)= (S0, EQt)

? (S0, d, EQ)= (S0, EQd)

? (S0, n, EQ)= (S0, EQn)

? (S0, w, EQ)= (S0, EQw)

? (S0, r, EQ)= (S0, EQr)

? (S0,;,)) = (S0,);)

? (S0, (, DE) = (S0, DE()

? (S0, v,)) = (S0,)v)

? (S0,;,)) = (S0,);)

? (S0, i,)) = (S0,)i)

? (S0,:,)) = (S0,):)

? (S0, e,)) = (S0,)e)

? (S0, (, r) = (S0, r()

? (S0, (, w) = (S0, w()

? (S0,;, n) = (S0, n;)

? (S0, n, LE) = (S0, LEn)

? (S0, (, d) = (S0, d()

? (S0,), d) = (S0, d))

? (S0,;, d) = (S0, d;)

? (S0, f, d) = (S0, df)

? (S0, t, d) = (S0, dt)

? (S0, d, d) = (S0, dd)

? (S0, n, d) = (S0, dn)

? (S0, w, d) = (S0, dw)

? (S0, r, d) = (S0, dr)

? (S0, d, EX) = (S0, EXd)

? (S0, BO, t) = (S0, tBO)

? (S0, -, t) = (S0, t-)

? (S0, t, EX) = (S0, EXt)

? (S0, BO, =) = (S0, =BO)

? (S0, -, =) = (S0, =-)

? (S0, =, ID) = (S0, ID=)

? (S0, ID, f) = (S0, fID)

? (S0,;, EX) = (S0, EX;)

? (S0, =, u) = (S0, u=)

? (S0, (, UO) = (S0, UO()

? (S0, OP, UO) = (S0, UO OP)

? (S0, BO, UO) = (S0, UO BO)

? (S0,), EX) = (S0, EX))

? (S0, BO, () = (S0, (BO)

? (S0, BO, -) = (S0, -BO)

? (S0, (, BO) = (S0, BO()

? (S0,),BO) = (S0,)BO)

? (S0, OP, BO) = (S0, BOOP)

? (S0, +, SB) = (S0, SB+)

? (S0, *, SB) = (S0, SB*)

? (S0, -, SB) = (S0, SB-)

? (S0, u, u) = (S0, uu)

Pentru fiecare pereche (x, A)? Și CONVERT x construim o funcție de tranziție care determină acțiunea de convoluție?? * (S0, x, bA) = (S0, B), unde B->bA

? * (S 0 , AL, ?) = (S 0 , PG)

? * (S 0 , e, b) = (S 0 , AL)

? * (S 0 , n, ?) = (S 0 , AL)

? * (S 0 ,;, b) = (S 0 , DE)

? * (S 0 ,;, ?) = (S 0 , DE)

? * (S 0 ,;, e) = (S 0 , DE)

? * (S 0 , LV,:) = (S 0 , LV)

? * (S 0 , LV, ?) = (S 0 , LV)

? * (S0, ID, ?) = (S0, LV)

? * (S 0 , ID, e) = (S 0 , LV)

? * (S 0 ,;, e) = (S 0 , LE)

? * (S 0 ,;, ?) = (S 0 , LE)

? * (S 0 ,;, n) = (S 0 , LE)

? * (S 0 , EQ, n) = (S 0 , LE)

? * (S 0 , EQ, e) = (S 0 , LE)

? * (S 0 , EQ, ?) = (S 0 , LE)

? * (S 0 ,;, e) = (S 0 , LE)

? * (S 0 ,;, ?) = (S 0 , LE)

? * (S 0 ,;, () = (S 0 , EQ)

? * (S 0 ,;,)) = (S 0 , EQ)

? * (S 0 ,;, f) = (S 0 , EQ)

? * (S 0 ,;, =) = (S 0 , EQ)

? * (S 0 ,;, t) = (S 0 , EQ)

? * (S 0 ,;, d) = (S 0 , EQ)

? * (S 0 ,;, n) = (S 0 , EQ)

? * (S 0 ,;, w) = (S 0 , EQ)

? * (S 0 ,;, r) = (S 0 , EQ)

? * (S0, SB, ?) = (S0, EX)

? * (S 0 , SB, d) = (S 0 , EX)

? * (S 0 , SB,)) = (S 0 , EX)

? * (S 0 , SB,;) = (S 0 , EX)

? * (S 0 , SB, w) = (S 0 , EX)

? * (S 0 , SB, r) = (S 0 , EX)

? * (S 0 , SB, f) = (S 0 , EX)

? * (S 0 , SB, =) = (S 0 , EX)

? * (S 0 , SB, t) = (S 0 , EX)

? * (S 0 , SB, ?) = (S 0 , SB)

? * (S 0 , SB, () = (S 0 , SB)

? * (S 0 , SB,)) = (S 0 , SB)

? * (S 0 , SB, u) = (S 0 , SB)

? * (S 0 , SB, k) = (S 0 , SB)

? * (S 0 , SB, +) = (S 0 , SB)

? * (S 0 , SB, -) = (S 0 , SB)

? * (S 0 , SB, *) = (S 0 , SB)

? * (S 0 , SB, e) = (S 0 , SB)

? * (S 0 ,), t) = (S 0 , SB)

? * (S 0 ,), ?) = (S 0 , SB)

? * (S 0 ,), t) = (S 0 , SB)

(S 0 ,),)) = (S 0 , SB)

? * (S 0 ,),;) = (S 0 , SB)

? * (S 0 , -, ?) = (S 0 , UO)

? * (S 0 , -, -) = (S 0 , UO)

? * (S 0 , +, +) = (S 0 , BO)

? * (S 0 , +, ?) = (S 0 , BO)

? * (S 0 , +, *) = (S 0 , BO)

? * (S 0 , -, +) = (S 0 , BO)

? * (S 0 , -, ?) = (S 0 , BO)

? * (S 0 , -, *) = (S 0 , BO)

? * (S 0 , -, -)) = (S 0 , BO)

? * (S 0 , *, +) = (S 0 , BO)

? * (S 0 , *, ?) = (S 0 , BO)

? * (S 0 , *, *) = (S 0 , BO)

? * (S 0 , *, -)) = (S 0 , BO)

? * (S 0 , u, +) = (S 0 , BO)

? * (S 0 , u, ?)= (S 0 , BO)

? * (S 0 , u, *) = (S 0 , BO)

? * (S 0 , u, -)) = (S 0 , BO)

? * (S 0 , k, +) = (S 0 , BO)

? * (S 0 , k, ?) = (S 0 , BO)

? * (S 0 , k, *) = (S 0 , BO)

? * (S 0 , k, -)) = (S 0 , BO)

? * (S 0 , CO, ?) = (S 0 , OP)

? * (S 0 , CO, +) = (S 0 , OP)

? * (S 0 , CO, *) = (S 0 , OP)

? * (S 0 , CO, -) = (S 0 , OP)

? * (S 0 , CO,;) = (S 0 , OP)

? * (S 0 , CO, d) = (S 0 , OP)

? * (S 0 , CO, t) = (S 0 , OP)

? * (S 0 , ID, -) = (S 0 , OP)

? * (S 0 , ID, *) = (S 0 , OP)

? * (S 0 , ID, ?) = (S 0 , OP)

? * (S 0 , ID, () = (S 0 , OP)

? * (S 0 , ID,)) = (S 0 , OP)

? * (S 0 , ID, u) = (S 0 , OP)

? * (S 0 , ID, k) = (S 0 , OP)

? * (S 0 , ID, -) = (S 0 , OP)

? * (S 0 , ID, +) = (S 0 , OP)

? * (S 0 , u,)) = (S 0 , I OP)

? * (S 0 , ID1, *) = (S 0 , ID)

? * (S0, ID1, ?) = (S0, ID)

? * (S 0 , ID1, () = (S 0 , ID)

? * (S 0 , ID1,)) = (S 0 , ID)

? * (S 0 , ID1, u) = (S 0 , ID)

? * (S0, ID1, k) = (S0, ID)

? * (S 0 , ID1, -) = (S 0 , ID)

? * (S 0 , ID1, +) = (S 0 , ID)

? * (S 0 , u,)) = (S 0 , ID)

? * (S 0 , u, ?) = (S 0 , BO)

? * (S 0 , u, k) = (S 0 , ID)

? * (S 0 , u, *)) = (S 0 , ID)

? * (S 0 , u, -)) = (S 0 , ID)

? * (S 0 , u, +)) = (S 0 , ID)

? * (S 0 , u, d)) = (S 0 , ID)

? * (S 0 , u, t)) = (S 0 , ID)

? * (S 0 , u, =)) = (S 0 , ID)

? * (S 0 , CO, ?) = (S 0 , CO)

? * (S 0 , CO, +) = (S 0 , CO)

? * (S 0 , CO, -) = (S 0 , CO)

? * (S 0 , CO, *) = (S 0 , CO)

? * (S 0 , CO,;) = (S 0 , CO)

? * (S 0 , CO, d) = (S 0 , CO)

? * (S 0 , CO, t) = (S 0 , CO)

? * (S 0 , CO,)) = (S 0 , CO)

? * (S 0 , k, +) = (S 0 , CO)

? * (S 0 , k, -) = (S 0 , CO)

? * (S 0 , k, *) = (S 0 , CO)

? * (S 0 , k,;) = (S 0 , CO)

?? * (S 0 , k, d) = (S 0 , CO)

? * (S 0 , k, t) = (S 0 , CO)

? * (S 0 , k,)) = (S 0 , CO)

? * (S 0 , k, () = (S 0 , CO)

2.2.3 Implementarea software a parserului

Analizatorul sintactic (parser) citește fișierul lexem generat de analizatorul lexical, efectuează analiza gramaticală, afișează mesaje despre erori de sintaxă, dacă există, și creează o formă intermediară de înregistrare a programului sursă. Baza dezvoltării unui parser este proiectarea și implementarea mașinii de reviste corespunzătoare.

Pentru analiza gramaticală de jos în sus pentru un recunoaștetor determinist de jos în sus, după ce îl aduceți la forma dorită, este necesar să proiectați o mașină de stocare cu o descriere detaliată a tuturor tranzițiilor din cadrul funcției de tranziție folosind funcțiile AFTER și COVER.

Când dezvoltam un automat, am construit funcții de tranziție care vor sta la baza parserului. Toate funcțiile de tranziție pot fi împărțite în două tipuri:

Ciclul de funcționare al unei mașini de magazie fără citirea simbolului de intrare (ciclu gol);

Tact de funcționare a unei mașini de revistă cu citirea simbolului de intrare.

La implementarea analizorului lexical, am împărțit programul în lexeme și le-am scris într-o listă. Apoi procesăm această listă în parser. Trimitem programul nostru (lista), simbolul inițial (PG) și markerul de jos al mașinii de stocare (h0) la intrare, după care se selectează funcția de tranziție dorită și se efectuează un apel recursiv.

Diagrama programului parser este prezentată în Anexa B din Figura B.2.

2.2.4 Dezvoltarea modulului de interpretare

La dezvoltarea unui modul de interpretare ca formă intermediară a programului original Cel mai des este folosită forma de notație postfixă, ceea ce face destul de ușoară implementarea procesului de execuție (interpretare) a programului tradus.

Să luăm în considerare principiile de bază ale formării și executării formei postfixe de scriere a expresiilor.

Regulile de bază pentru transformarea unei expresii infixe într-una postfixă sunt următoarele.

Operanzii citiți sunt adăugați la notația postfixă, iar operațiile sunt scrise în stivă.

Dacă operația din partea de sus a stivei are o prioritate mai mare (sau egală) decât operația de citit curent, atunci operația de pe stivă este adăugată la intrarea postfix, iar operația curentă este împinsă pe stivă. În caz contrar (la prioritate mai mică), numai operația curentă este împinsă pe stivă.

Paranteza de deschidere citită este împinsă pe stivă.

După ce acoladele de închidere sunt citite, toate operațiunile până la prima acoladă de deschidere sunt scoase din stivă și adăugate la intrarea postfix, după care atât bretele de deschidere, cât și cele de închidere sunt aruncate, de exemplu. nu sunt plasate pe o înregistrare postfix sau pe stivă.

După ce întreaga expresie este citită, operațiunile rămase pe stivă sunt adăugate la intrarea postfix.

Notarea postfix a unei expresii permite calcularea acesteia după cum urmează.

Dacă jetonul este un operand, acesta este scris în stivă. Dacă jetonul este o operație, atunci operația specificată este efectuată asupra ultimelor elemente (ultimul element) scrise în stivă, iar acele elemente (element) sunt înlocuite pe stivă de rezultatul operației.

Dacă analizele lexicale și sintactice au fost finalizate cu succes, atunci trecem la interpretare. Mai întâi, facem propoziții din cuvinte, apoi traducem expresiile în notație postfixă și calculăm.

Diagrama de funcționare a interpretului este prezentată în Anexa B din Figura B.3.

2.3 Codificare

Programul este implementat în limbajul C# în mediul de programare Visual Studio 2010. Textul programului este prezentat în Anexa A.

Programul conține cinci clase. Interfața cu utilizatorul este implementată folosind clasa MainForn. Folosind clasa LexAnalysis, este implementat un modul de analiză lexicală, SynAnalysis este un modul de analiză sintactică, Intepreter este un modul de interpretare, ProgramisciJakPolska este o clasă auxiliară pentru traducerea expresiilor în notație poloneză inversă (postfix).

Scopul procedurilor și funcțiilor implementate în program este descris în tabelele 6,7,8.

Tabelul 6 - Scopul procedurilor și funcțiilor analizei lexicale

Documente similare

    Translator ca program sau mijloc tehnic care traduce un program. Luarea în considerare a principalelor caracteristici ale construcției unui analizator lexical. Introducere în etapele dezvoltării unui traducător dintr-un subset limitat al unei limbi de nivel înalt.

    lucrare curs, adăugată 08.06.2013

    Proiectarea analizatoarelor lexicale și sintactice pentru limbajul educațional. Reguli pentru conversia expresiilor logice în POLYZ. Formarea triadelor, optimizarea listei lor. Structura logică a programului. Testarea modulelor traducător-interpret.

    lucrare curs, adaugat 28.05.2013

    Caracteristici generale și evaluarea capacităților limbajului de programare C-Sharp, caracteristicile sale similare și distinctive față de C++ și Java. Dezvoltarea unui analizator lexical și sintactic folosind acest limbaj de programare. Compilarea tabelelor de analiză.

    lucrare de curs, adăugată 06.11.2010

    Proiectarea unui program de analiză format din două părți: un analizor lexical care descompune textul sursă al programului în lexeme și completează un tabel de nume; un parser care verifică conformitatea textului cu o anumită gramatică.

    lucrare curs, adaugat 14.06.2010

    Scrierea unui program care realizează analiza lexicală și sintactică a limbajului de programare de intrare, generează un tabel de lexeme indicând tipurile și valorile acestora și, de asemenea, construiește un arbore de sintaxă; textul limbii de introducere este introdus de la tastatură.

    lucrare de curs, adăugată 23.02.2012

    Metodologie pentru dezvoltarea și implementarea parțială a unui traducător pentru limbajul C folosind limbajul C++, care împarte lanțul original de caractere în constructe minime indivizibile de limbaj bazate pe vocabularul limbii. Analiza functionarii programului.

    lucrare curs, adaugat 19.03.2012

    Structura, clasificarea și cerințele pentru implementarea compilatorului. Proiectarea și implementarea părții de analiză a compilatorului limbajului C++. Metode de implementare a analizei lexicale. Algoritm pentru funcționarea parserului. Principii de implementare a software-ului.

    lucrare curs, adaugat 26.01.2013

    Crearea unui traducător care procesează codul programului în Pascal și, folosind operatori echivalenti, generează un program în C. Caracteristici ale specificației externe și funcționării analizorului lexical. Structura programului, afișarea rezultatelor pe ecran.

    lucru curs, adăugat 07.02.2011

    Metode de analiză gramaticală. Dezvoltarea structurii unui traducător educațional în limbajul de programare de bază Object Pascal în mediul de programare vizuală orientată pe obiecte Borland DELPHI 6.0 folosind sistemul de operare Windows XP.

    lucrare curs, adaugat 05.12.2013

    Implementarea software a unei aplicații desktop folosind limbajul de programare C#. Designul și structura interfeței cu utilizatorul, cerințele pentru aceasta și evaluarea funcționalității. Elaborarea unui manual de utilizare și utilizarea acestuia.