Un compilator simplu

Un compilator simplu

La proiectarea compilatoare de carte scrise. Următorul text nu le va înlocui, dar sperăm că va oferi o înțelegere de bază despre structura compilatoare. Când m-am decis să scrie un compilator, am încercat imediat să realizeze o mulțime de oportunități, mult mai mult decât aveți nevoie pentru a scrie compilator în sine. Este posibil, dar compilatorul rezultat conține o mulțime de detalii care fac dificil de înțeles. Din detaliile asociate cu capacități lingvistice, pe de altă parte - la procesorul 8086 (80386 sisteme de operare convenabile, dar atunci nu știam, și pe 32 de biți nu sa extins). Și, desigur, de mult ar fi putut face mai bine. La un moment dat, am Shrunk compilator, dar foarte tineri, el nu a făcut-o. În mod semnificativ la reducerea compilatorul poate fi realizată prin simplificarea limbajului de intrare pe care aici se face. Avem aici un compilator de jucărie în scopuri practice, nu este potrivit, dar este construit în același mod ca și cel real.

901 FORMAT (1X, 'N =', I2 /) N = 0 10 DO 20 I = 1,5 N = N + 20 gerar CONTINUA SCRIE (6901) N STOP END

Ciclul antet în conformitate cu eticheta 10 conține o greșeală de scriere (punct în loc de virgulă pe tastatură ele sunt aproape), dar Fortran consideră că această sarcină este o constantă de 1,5 variabila numita DO20I. În consecință, numărul de imprimare 1 și 5 nu va fi afișată.

Nu numai scanerul, dar compilatorul ca întreg depinde de limba sursă, deci este cel mai bine pentru a merge la un privat și să ia în considerare limbaj specific - Context.

Limba Context Programul este o serie de definiții:

  • constante
  • tipuri (structuri)
  • variabile globale
  • funcție

poziția lor relativă în program este limitat la o singură condiție - fiecare obiect trebuie să fie definit înainte de a fi utilizat, programul completează funcția principală. Pentru unele discipline, de exemplu, are o destul de obyavneniya, dar versiunea minimă a acestuia nu va fi pus în aplicare.

Determinarea constantelor, structura și funcția raspoznaetmsya principal primul cuvânt (defini, struct și începe, respectiv), iar definiția funcției începe cu numele variabilei globale și tip sunt diferite una de cealaltă prezența și absența paranteze după numele. Ie după numele tipului necesar pentru prochital @ simboluri (dacă există), numele în sine și după cuvântul lui. Dacă acest cuvânt - paranteză, trebuie să analizeze funcția, sau - analiza variabilei globale. În alte limbi, o funcție este definită de primul cuvânt din Pascal, de exemplu, funcția începe cu funcția de cuvânt, și o listă de variabile cu cuvântul var.

Astfel, la compilator nivel superior poate consta dintr-un ciclu simplu:

în timp ce TRUE do Scan (@Buff); selectați caz strcmp (@Buff, "începe") = 0: // Parsează principalul caz de ieșire funcția strcmp (@Buff "defini") = 0: // Parse caz strcmp constante (@Buff, "struct") = 0: parsarea // structuri implicite: // functie Analizează sau end la nivel mondial final variabil

Desigur, fiecare dintre select'a ramuri trebuie să conțină un anumit cod. Parsarea constante și structuri este destul de ușor (în special Structura nonrecursive emisă - spre deosebire de Pascal definițiile lor nu pot fi încorporate), rezultatul parsarea este noua intrare în tabelul de nume la nivel mondial, nici un cod este generat. Analiza funcțiilor mai complicate. Se compune dintr-un antet parsarea o listă de parametri (care este foarte similar cu analiza structurii) și analiza incluse în funcțiile operatorului. Când funcția este scris Ctrl, compilați un singur operator, compilarea toată funcția este redusă la o serie de trei linii:

în timp ce strcmp (@Scan (@Buff), "end") = 0 do Ctrl (@Buff) !; capăt

Pentru mai multe, cu toate acestea, avem nevoie pentru a schimba acest ciclu și caracteristică Ctrl:

// Scan (@Buff); în timp ce strcmp (@Buff, "end") = 0 do Ctrl (@Buff) !; // Ctrl trebuie să citească următoarea declarație pentru sfârșitul cuvânt

cuvânt F (); cuvânt G () F (); capăt

După paranteza de închidere se poate întâlni o virgulă (F) sau la începutul primei funcții operatorului (G). După citirea paranteza de închidere după cuvântul trebuie să fie luate în considerare și, în cazul în care nu este un punct și virgulă, amintiți-vă că și trece la elaborarea declarație, al doilea exemplu de realizare este adaptat la acest ciclu. Prin schimbarea limbii, puteți recupera prima opțiune:

cuvânt F (); cuvânt G () este F (); capăt

Aici, după bretele de închidere este întotdeauna un cuvânt (sau punct și virgulă este).

Toate declarațiile care pot fi întâlnite într-o funcție, recunoscute de primul cuvânt, astfel încât la nivel superior Ctrl pot fi aranjate după cum urmează:

Folosind ciclul Expr în timp ce funcția poate fi efectuată după cum urmează parsarea:

// cod de generare a @A: NOP; începând din funcția Apel bucla // Expr // Vezi rezultate din zagpuzka Al // cod de producție sau de AL, AL // jnz @B // JMP. // @B: nop în timp ce strcmp (@Buff, "end") = 0 do Ctrl (@Buff) ;! end // de generare de cod JMP @A @C: NOP // ajustare de tranziție JMP. (Jmp @C)

Acestea nu sunt toate măsurile necesare încă mai trebuie să efectueze pregătirile pentru compilarea operatorilor bucla / ieșire și la analiza anumitor interiorul buclei de variabile locale. Dar aceasta este un detaliu, cel mai important lucru aici este că ciclul ca funcție include o secvență operator, și funcția în sine Ctrl pot fi folosite pentru a le compila în același mod în care este folosit pentru a compila întreaga funcție.

stare de buclă nu este compilat în cel mai bun mod. La un nivel minim, echipa a generat inutile (sau AL, AL.), Dar acum simplitatea este mai important decât eficiența. De asemenea, din acest fragment nu este clar, așa cum vor fi compilate într-o stare complexă, care conține operatorii logici AND / OR, va finaliza o scurtă evaluare a condițiilor sau dacă este pus în aplicare. Această problemă trebuie rezolvată în scris, funcția Expr. Aparent, calculul complet, folosind un registru pentru a stoca rezultatul este cel mai simplu, în unele limbi (de exemplu, Pascal original) și făcut. Acest lucru se face și The emise, dar acest lucru nu este cea mai bună soluție.

void Expr (cuvânt PRTY; char @Buff; OpInfo @ Op1) selectați caz strcmp (@Buff, "(") = 0: // vyradenie în caz strcmp paranteze (@Buff, "„") = 0: // constanta caracter caz strcmp (@Buff, "#") = 0: // caracter isdigit constantă caz ​​(Buff) = 0: // numerică constantă implicită: // variabilă, un capăt de apel în timp ce funcția TRUE face semnul verbal; P2 cuvânt; selectați caz strcmp (@Buff, "|") = 0: Sign = OR; P2 = 1, caz strcmp (@Buff "") = 0: Sign = AND; P2 = 1, caz strcmp (@Buff "

Este demn de remarcat faptul că această caracteristică este scris Expr întotdeauna citește cuvânt de prisos. Acest lucru poate fi o virgulă, două puncte, apoi, nu.

Ideile formulate și acesta este modul în care acestea sunt puse în aplicare în compilatorul „jucărie“. Simplificarea realizată în principal prin simplificarea limbii. Doar un singur tip de date - întreg (dar condițiile sunt considerate a fi logice), identificarea de noi tipuri nu este posibilă, numai rețea unidimensională, funcții, și nici variabile locale, nu există variabile de referință. Și orice încercare de a optimiza. De fapt compilator începe cu funcția de citire:

definesc @emNOMEMORY „Out of Memory“ definesc @emSIZE „identifikatop prea lung“ definesc @emEOF „File End“ definesc @emNUMBER „Eroare în constantă“ definesc @emNAME „numele Ppopuscheno“ definesc @emEMPTY „matrice gol“ definesc @emBRACKET „suport Ppopuschena „definesc @emCOMMA“ Ppopuschena virgulă „definesc @emSEMICOLON“ Ppopuschena virgulă „definesc @emTHENEXP“ Ppopuscheno apoi „definesc @emDOEXP“ Ppopuscheno do „definesc @emDUP“ descriere Povtopnoe „definesc @emLVALUE“ variabilele Ppopuschena sunt „definesc @emTYPE“ defazaj tip "definesc @emUNDEFINED" variabile nu sunt definiți un "definesc @emUNDEFOPR" opepatsy nu definește o "defini @emASSIGN" Ppopuscheno = „definesc tbSIZE 8192 definesc dbSIZE 8192 definesc dtSIZE 128 definesc idSIZE 16 definesc opOR definesc opAND 1 de 2 opLT fin 3 definesc Ople 4 definesc opEQ 5 definesc opNE 6 definesc opGE 7 definesc opGT 8 definesc opADD 9 definesc opSUB 10 definesc opMUL 11 definesc opDIV 12 definesc opNOT 13 definesc opNEG 14 definesc ptZERO 0 definesc ptBOOL 1 definesc ptCOMP 2 definesc ptADD 3 definesc ptMUL 4 definesc ptLVALUE 5 definesc ttWORD 0 definesc ttBOOL 1 struct DATE char Nume [idSIZE]; OFS cuvânt; Index cuvânt; end date DATA [dtSIZE]; cuvânt NDATA; OFS cuvânt; char Text [tbSIZE]; hText cuvânt; cuvânt ntext; cuvânt pText; char Nume [128]; cuvânt de linie; Eticheta cuvânt; char Dest [dbSIZE]; cuvânt hDest; cuvânt pDEST; nule @Ptr (cuvânt Seg, OFS) void @ P1 = @ OFS; P2 = @@ nule @ P1; reveni @ P2; end cuvânt isalpha (char Ch) if ( 'A' 0 atunci dec S; end cuvânt P = 0; dacă N> = 10 | S atunci P = str> 0 (N / 10, S, @ Buff); end char @ D = "0123456789"; Buff [P] = D [N% 10]; retur P + 1; termina char @Str (cuvânt N; cuvânt S) char @ P = "00000"; P [str (N, S, @P)] = # 0; return @P; (putc char @EM) (# 13); puts (@name); putc ( '('); end void stop puts (@Str (linie, 0)); putc ( ')'); if (strlen (@EM)> 0), apoi puts ( ":"); puts (@EM) capăt apropiat (hDest) aproape (hText); asm mov AX, 4C00H asm int 21H end cuvânt Val (char @Buff) char @D = "0123456789"; cuvânt P = 0; cuvânt L = 0; cuvânt H = 0, în timp ce Buff [P] = # 0 do cuvânt S = 0 ;! în timp ce D [S !] = Buff [P] do S inc; dacă S> = 10 atunci stop (@emNUMBER) final final S = 10 * L + S; L = S% 256; S = 10 * H + S / 256; H 256 =% S; S = S / 256; dacă S> 0 atunci stop (@emNUMBER); retur final 256 * H + L; terminația inc P end caracte read () dacă pText> = ntext apoi ntext = citit (hText , @ Text, tbSIZE); dacă ntext = idSIZE apoi stop (@emSIZE); capăt Următorul () final dacă P = 0 atunci Buff [P] = Read (); inc P; Următorul (), selectați caz Buff [0 ] = '': if Read () = '=' apoi Următorul (); return @strcpy (@Buff "> =") final final se încheie Buff [P] = # 0; @Buff reveni; termina void Save (char Ch) dacă pDEST> = dbSIZE apoi Stop (@emNOMEMORY); termina Dest [pDEST] = Ch; inc pDEST; termina void Decl (char @Inst) cuvânt I = 0; în timp ce Inst [I] = # 0 do Salvare (Inst [I]) !; inc I; termina Save (# 13); Salvare (# 10); Codul se încheie nule (cuvânt L; char @Inst) în cazul în care L = 0, apoi pe Salvați ( '@') !; char @ P = @ Str (L, 5); cuvânt I = 0; în timp ce P [I] = # 0 do Salvare (P [I]) !; inc I; Salvați se încheie ( ':'); Salvare ( ''); cuvânt altceva I = 0; în timp ce I = NDATA apoi Stop (@emUNDEFINED); final, dacă datele [N] .Index> 0 atunci dacă strcmp (@Scan (@Buff) "[") = 0 atunci Stop (@emBRACKET) !; în cazul în care se încheie Expr (ptZERO, @ Scan (@Buff)) = ttWORD apoi Stop (@emTYPE) !; end if strcmp (@Buff, "]") = 0 atunci Stop (@emBRACKET) !; în cazul în care se încheie PRTY

= PtLVALUE apoi dacă Flag = 0 atunci Stop (@emLVALUE); întoarcere end N; în timp ce se încheie TRUE face cuvânt Op; cuvânt P; selectați caz strcmp (@Buff, "|") = 0: Op = opOR; P = ptBOOL; caz strcmp (@Buff "") = 0: Op = opAND; P = ptBOOL; caz strcmp (@Buff, "=") = 0: Op = opGE; P = ptCOMP; caz strcmp (@Buff, ">") = 0: Op = opGT; P = ptCOMP; caz strcmp (@Buff, "+") = 0: Op = opADD; P = ptADD; caz strcmp (@Buff, "-") = 0: Op = opSUB; P = ptADD; caz strcmp (@Buff, "*") = 0: Op = opMUL; P = ptMUL; caz strcmp (@Buff, "/") = 0: Op = opDIV; P = ptMUL; default: P = ptZERO; final, dacă P = NDATA apoi Stop (@emUNDEFINED); în cazul în care se încheie strcmp (@Buff, "=") = 0 atunci Stop (@emASSIGN) !; final, dacă datele [N] .Index> 0 atunci Code (0, "push AX"); în cazul în care se încheie Expr (ptZERO, @ Scan (@Buff)) = ttWORD apoi Stop (@emTYPE) !; final, dacă datele [N] .Index> 0 atunci Code (0, "pop BX"); Cod (0, "shl BX, 1"); Cod (0, @ strcat (@strcat (@strcpy (@Buff "mov DS: [BX +"), @ Str (date [N] .Ofs, 0)), "], AX")); altfel Code (0, @ strcat (@strcat (@strcpy (@Buff "mov DS: ["), @ Str (date [N] .Ofs, 0)), "], AX")); end end end începe octet @ Size = @ Ptr (GetPSP (), 128); char @ parm = @ Ptr (GetPSP (), 129); char nume [128]; cuvânt I = 0; în timp ce I = dtSIZE apoi Stop (@emNOMEMORY); termina date [NDATA] .Ofs = 2 * OFS; strcpy (@Data [NDATA] .name, @ Buff); Datele [NDATA] .Index = 0; cuvânt N = 1; dacă strcmp (@Scan (@Buff) "[") = 0, atunci N = Val (@Scan (@Buff)); în cazul în care N

Din intrare / ieșire mijloace sunt puse în aplicare numai operator de scriere, operatorul scoate de imprimare și numerele goale, efectuează o linie de alimentare. Pentru a demonstra, puteți compila și rula programul de sortare următoarea matrice:

cuvânt Buff [16]; Temp cuvânt; cuvânt I; cuvânt J; cuvânt N; începe N = 10; I = 0; 0 în timp ce fac I = I-1; J = 0; în timp ce J Buff [J + 1] apoi Temp = Buff [J + 1]; Buff [J + 1] = buff [J]; Buff [J] = Temp; end J = J + 1; end end I blank = 0; în timp ce eu

Versiunea completă a compilatorului este mai dificil, dar a făcut o simplificare. Ele sunt asociate cu plasarea obligatorie a operanzilor în registrele, iar acest aranjament este fix:

  • toate apifmeticheskie și logice opepatsii efectuate numai registre
  • pe.pvyy Operandul pazmeschaetsya într-un registru AL (byte), AX (cuvânt) sau DX: AX (cuvânt dublu)
  • vto.poy pazmeschaetsya operand într-un registru BL (bytes), BX (cuvânt) sau CX: BX (cuvânt dublu)
  • A se vedea rezultatele pazmeschaetsya opepatsii în registrele AL (byte), AX (cuvânt) sau DX: AX (cuvânt dublu)
  • DI registru este utilizat pentru manipularea matrice element de voință
  • Registre ES: DI utilizat pentru indicii pazimenovaniya
  • în cazul în registru dorit nu este disponibil, valoarea acesteia pe vpemenno stivă
  • solicită funcția VARIAȚIUNI PARAMETRI pepedayutsya chepez stiva PARAMETRI scos din stivă funcțiile zavepsheniem peped deducții rutina revine la registrele AL (byte), AX (cuvânt) sau DX: AX (cuvânt dublu) pentru upposcheniya vezi funcția rezultate pot avea o lungime de numai 1, 2 sau 4 octeți.
  • Variabilele globale sunt situate în segmentul DS înregistrează adpesuemom

Blocuri de compilator de bază următoarele:

anula Stop (char @EM) // Zavepshenie compilare. termina Cod gol (cuvânt L; char @S) // Formarea unui șir de cod. termina char Read () // Citește caracter. termina void următor () // pepehod la caracterul următor. char se încheie @Scan (char @Buff) // Citește cuvintele. end void Init () // inițializează (pentru Expr). termina void Push (cuvânt R) // registru de intrare în stivă. termina nule Pop (cuvânt R) // Extras din stivă. termina void LDAX (OpInfo @ Op1) // Zagpuzka primul operand in (DX :) AX. termina void LDBX (OpInfo @ Op1) // Zagpuzka al doilea operand in (CX :) BX. termina void LPTR (OpInfo @ Op1) // pointer Zagpuzka în ES: DI. termina void STAX (OpInfo @ Op1) // Înregistrare Vezi rezultate în memorie. end cuvânt (Dimensiune cuvânt; char @Jump) Comp // spavneniyu. Testul se încheie nule (cuvânt pType1; cuvânt nPtr1; OpInfo @ Op2) // Verificarea. tipuri // compatibilitate end void Expr (cuvânt P; char @Buff; OpInfo @ Op1) // Compile. // end expresie void Ctrl (char @Buff) // Compile bloc. se încheie începe. în timp ce (strcmp (@Scan (@Buff), "începe")! = 0) fac dacă (strcmp (@Buff, "defini") = 0), atunci // Pazbop constantă. end loop if (strcmp (@Buff, "struct") = 0), atunci // Pazbop structurilor. end loop P = FindType (@Buff); if (P

Este necesar să spun câteva cuvinte despre modificarea compilator. Made în această eroare nu poate avea loc după o singură compilare. Ie dacă se utilizează un compilator compilează compilator textul A „(A corectat și actualizat) și, prin urmare, nu este detectat erori sintactice sau semantice compilator A“ este, probabil, capabil de a se compila, dar compilatorul rezultat A „“ poate fi nefuncțional.

O ultimă notă. Compilatorul generează o listă, puțin diferit de limbaj de asamblare pentru avut drept obiectiv TASM. Dacă doriți să utilizați TASM / TLINK, la început trebuie să adăugați șirul

segment de cod asuma CS: COD org 100H @ 00001: nop

COD se termină la sfârșitul @ 00001

Site-ul creat în sistemul uCoz

articole similare