Cos'è un traduttore. Linguaggi di programmazione. compilatori e interpreti. L'algoritmo dell'interprete semplice

Gli esecutori concreti dei linguaggi di programmazione sono traduttori e interpreti.

Traduttoreè un programma sulla base del quale il computer converte i programmi inseriti in esso in linguaggio macchina, poiché può eseguire programmi scritti solo nella lingua del suo processore e gli algoritmi specificati in un'altra lingua devono essere tradotti in linguaggio macchina prima di essere eseguiti .

Traduttore- un programma o mezzo tecnico che esegue la trasmissione del programma.

Trasmissione del programma- trasformazione di un programma presentato in uno dei linguaggi di programmazione in un programma in un altro linguaggio, equivalente nei risultati del primo. Il traduttore di solito esegue anche la diagnostica degli errori, genera dizionari di identificatori, stampa testi di programmi, ecc.

Viene richiamata la lingua in cui viene presentato il programma di input originale lingua e il programma stesso: il codice sorgente. La lingua di output è chiamata lingua di destinazione o obbiettivo codice. Lo scopo della traduzione è convertire il testo da una lingua a un'altra che sia comprensibile al destinatario del testo. Nel caso dei programmi di traduzione, la destinazione è dispositivo tecnico(processore) o programma interprete.

I traduttori sono implementati come compilatori o interpreti. In termini di lavoro, un compilatore e un interprete sono molto diversi.

Il linguaggio del processore (codice macchina) è di basso livello. Viene chiamato un traduttore che converte i programmi in linguaggio macchina che viene accettato ed eseguito direttamente dal processore compilatore.

Compilatore(Inglese) compilatore- compiler, collector) legge l'intero programma, lo traduce e crea una versione completa del programma in linguaggio macchina, che viene poi eseguita. L'output del compilatore è un file binario eseguibile.

Vantaggio del compilatore: il programma viene compilato una volta e non sono necessarie ulteriori trasformazioni per ogni esecuzione. Di conseguenza, non è richiesto un compilatore sulla macchina di destinazione per la quale viene compilato il programma. Svantaggio: una fase di compilazione separata rallenta la scrittura e il debug e rende difficile l'esecuzione di programmi piccoli, semplici o monouso.

Se la lingua di partenza è un linguaggio assembly (un linguaggio di basso livello vicino al linguaggio macchina), viene chiamato il compilatore di tale linguaggio assemblatore.

Un altro metodo di implementazione è quando il programma viene eseguito con interprete nessuna traduzione.

Interprete(Inglese) interprete- interprete, interprete) traduce ed esegue il programma riga per riga.

L'interprete simula a livello di codice una macchina il cui ciclo fetch-execute opera su istruzioni in linguaggi di alto livello piuttosto che su istruzioni macchina. Tale modellazione software crea una macchina virtuale che implementa il linguaggio. Questo approccio è chiamato pura interpretazione. L'interpretazione pura viene solitamente utilizzata per linguaggi con una struttura semplice (ad esempio, APL o Lisp). Interpreti riga di comando elaborare i comandi in script su UNIX o in file batch (.bat) su MS-DOS, solitamente anche in modalità di pura interpretazione.

Il vantaggio di un interprete puro: l'assenza di azioni intermedie per la traduzione semplifica l'implementazione dell'interprete e ne rende più comodo l'utilizzo, anche nella modalità interattiva. Lo svantaggio è che l'interprete deve essere disponibile sulla macchina di destinazione su cui deve essere eseguito il programma. Inoltre, di norma, si verifica una perdita di velocità più o meno significativa. E la proprietà di un puro interprete, che gli errori nel programma interpretato vengono rilevati solo quando si tenta di eseguire un comando (o una riga) con un errore, può essere riconosciuta sia come svantaggio che come vantaggio.

Ci sono compromessi tra la compilazione e la pura interpretazione delle implementazioni del linguaggio di programmazione, quando l'interprete, prima di eseguire il programma, lo traduce in un linguaggio intermedio (ad esempio, in bytecode o p-code), più conveniente per l'interpretazione (ovvero, stiamo parlando di un interprete con traduttore integrato) . Tale metodo è chiamato implementazione mista. Perl è un esempio di implementazione di un linguaggio misto. Questo approccio combina sia i vantaggi di un compilatore e di un interprete (maggiore velocità di esecuzione e facilità d'uso) che svantaggi (sono necessarie risorse aggiuntive per tradurre e memorizzare il programma in una lingua intermedia; è necessario fornire un interprete per eseguire il programma sul macchina bersaglio). Proprio come nel caso di un compilatore, un'implementazione mista lo richiede prima dell'esecuzione fonte non conteneva errori (lessicali, sintattici e semantici).

Con l'aumento delle risorse informatiche e l'espansione di reti eterogenee (compresa Internet) che collegano computer di diversi tipi e architetture, è emerso un nuovo tipo di interpretazione, in cui il codice sorgente (o intermedio) viene compilato in codice macchina direttamente in fase di esecuzione , "al volo". Le sezioni di codice già compilate vengono memorizzate nella cache in modo che quando vi si accede nuovamente, ricevono immediatamente il controllo, senza ricompilazione. Questo approccio è stato chiamato compilazione dinamica.

Il vantaggio della compilazione dinamica è che la velocità di interpretazione dei programmi diventa paragonabile alla velocità di esecuzione dei programmi nei linguaggi compilati convenzionali, mentre il programma stesso viene archiviato e distribuito in un'unica forma, indipendente dalle piattaforme di destinazione. Lo svantaggio è una maggiore complessità di implementazione e maggiori requisiti di risorse rispetto al caso di semplici compilatori o puri interpreti.

Questo metodo è adatto per le applicazioni web. Di conseguenza, la compilazione dinamica è apparsa ed è supportata in una certa misura nelle implementazioni di Java, . NET Framework, Perl, Python.

Una volta che un programma è stato compilato, né il codice sorgente del programma né il compilatore sono necessari per eseguire il programma. Allo stesso tempo, il programma elaborato dall'interprete deve essere ritradotto in linguaggio macchina ogni volta che il programma viene eseguito. Cioè, il file sorgente è direttamente eseguibile.

I programmi compilati vengono eseguiti più velocemente, ma i programmi interpretati sono più facili da correggere e modificare.

Ogni lingua specifica è incentrata sulla compilazione o sull'interpretazione, a seconda dello scopo per cui è stata creata. Ad esempio, il C++ viene solitamente utilizzato per risolvere problemi piuttosto complessi in cui la velocità dei programmi è importante, quindi questo linguaggio viene implementato utilizzando un compilatore.

Per ottenere una maggiore velocità dei programmi nei linguaggi di programmazione interpretati, è possibile utilizzare la traduzione in un bytecode intermedio. I linguaggi che consentono questo trucco sono Java, Python e qualche altro linguaggio di programmazione.

L'algoritmo di un semplice interprete:

2. analizzare l'istruzione e determinare le azioni appropriate;

3. intraprendere azioni appropriate;

4. se la condizione di terminazione del programma non viene raggiunta, leggere l'istruzione successiva e passare al punto 2

I linguaggi di programmazione possono essere suddivisi in compilati e interpretati.

Un programma in un linguaggio compilato viene convertito (compilato) in un insieme di istruzioni per di questo tipo processore (codice macchina) e quindi scritto in un modulo eseguibile, che può essere avviato per l'esecuzione come programma separato. In altre parole, il compilatore traduce il codice sorgente del programma da un linguaggio di programmazione di alto livello in codici binari delle istruzioni del processore.

Se il programma è scritto in una lingua interpretata, l'interprete esegue (interpreta) direttamente il testo sorgente senza previa traduzione. Il programma rimane nella sua lingua originale e non può essere eseguito senza un interprete. Possiamo dire che il processore del computer è un interprete del codice macchina.

In breve, il compilatore traduce immediatamente e per intero il codice sorgente del programma in linguaggio macchina, creando un programma eseguibile separato, e l'interprete esegue il codice sorgente proprio durante l'esecuzione del programma.

La divisione in lingue compilate e interpretate è alquanto arbitraria. Quindi, per qualsiasi linguaggio compilato tradizionalmente, come Pascal, puoi scrivere un interprete. Inoltre, la maggior parte degli interpreti "puri" moderni non esegue direttamente i costrutti linguistici, ma li compila in una rappresentazione intermedia di alto livello (ad esempio, con dereferenza variabile ed espansione macro).

Per qualsiasi linguaggio interpretato, puoi creare un compilatore, ad esempio il linguaggio Lisp, originariamente interpretato, può essere compilato senza alcuna restrizione. Il codice generato in fase di esecuzione può anche essere compilato dinamicamente in fase di esecuzione.

Di norma, i programmi compilati vengono eseguiti più velocemente e non richiedono programmi aggiuntivi, poiché sono già stati tradotti in linguaggio macchina. Allo stesso tempo, ad ogni modifica del testo del programma, è richiesta la sua ricompilazione, il che crea difficoltà di sviluppo. Inoltre, un programma compilato può essere eseguito solo sullo stesso tipo di computer, e solitamente con lo stesso sistema operativo, per il quale è stato progettato il compilatore. Per creare un eseguibile per un diverso tipo di macchina, è necessaria una nuova compilazione.

Le lingue interpretate hanno alcune funzionalità aggiuntive specifiche (vedi sopra), inoltre, i programmi in esse contenuti possono essere eseguiti immediatamente dopo la modifica, il che facilita lo sviluppo. Un programma in linguaggio interpretato può spesso essere eseguito su molti diversi tipi di macchine e sistemi operativi senza ulteriori sforzi.

Tuttavia, i programmi interpretati vengono eseguiti notevolmente più lentamente dei programmi compilati e non possono essere eseguiti senza un programma interprete aggiuntivo.

Alcuni linguaggi, come Java e C#, rientrano tra il compilato e l'interpretato. Vale a dire, il programma non è compilato in linguaggio macchina, ma in codice bytecode indipendente dalla macchina di basso livello. Successivamente, viene eseguito il bytecode macchina virtuale. Per eseguire il bytecode, di solito viene utilizzata l'interpretazione, sebbene alcune delle sue parti possano essere tradotte in codice macchina direttamente durante l'esecuzione del programma utilizzando la compilazione Just-in-time (JIT) per velocizzare il programma. Per Java, il bytecode viene eseguito da un virtual Macchina Java(Java Virtual Machine, JVM), per C# - Common Language Runtime.

Questo approccio, in un certo senso, consente di utilizzare i vantaggi sia degli interpreti che dei compilatori. Va anche menzionata la lingua originale Forth, che ha sia un interprete che un compilatore.

Poiché il testo scritto in un linguaggio di programmazione è incomprensibile per un computer, è necessario tradurlo in codice macchina. Tale traduzione di un programma da un linguaggio di programmazione a un linguaggio in codice macchina è chiamata traduzione ed è eseguita da programmi speciali: traduttori.

Translator - un programma di utilità che converte il programma sorgente fornito nel linguaggio di programmazione di input in programma di lavoro presentato in un linguaggio oggetto.

Attualmente, i compilatori sono divisi in tre gruppi principali: assemblatori, compilatori e interpreti.

Assembler è un'utilità di sistema che converte i costrutti simbolici in istruzioni in linguaggio macchina. Una caratteristica specifica degli assemblatori è che traducono letteralmente un'istruzione simbolica in un'istruzione macchina. Pertanto, il linguaggio assembly (chiamato anche autocode) è progettato per facilitare la percezione del set di istruzioni del computer e velocizzare la programmazione in questo set di istruzioni. È molto più facile per un programmatore ricordare la designazione mnemonica delle istruzioni della macchina piuttosto che il loro codice binario.

Allo stesso tempo, il linguaggio assembly, oltre agli analoghi delle istruzioni macchina, contiene molte direttive aggiuntive che facilitano, in particolare, la gestione delle risorse del computer, la scrittura di frammenti ripetitivi e la costruzione di programmi multimodulo. Pertanto, l'espressività del linguaggio è molto più ricca di un semplice linguaggio di codifica simbolica, che aumenta notevolmente l'efficienza della programmazione.

Un compilatore è un programma di utilità che traduce in linguaggio macchina un programma scritto in un linguaggio di programmazione sorgente. Proprio come un assemblatore, un compilatore converte un programma da una lingua all'altra (il più delle volte, nella lingua di un particolare computer). Tuttavia, i comandi in lingua di origine differiscono in modo significativo nell'organizzazione e nella potenza dai comandi in linguaggio macchina. Esistono lingue in cui un'istruzione della lingua di partenza viene tradotta in 7-10 istruzioni della macchina. Esistono però anche linguaggi in cui ogni istruzione può corrispondere a 100 o più istruzioni macchina (ad esempio Prolog). Inoltre, nelle lingue di partenza viene spesso utilizzata una rigorosa tipizzazione dei dati, che viene eseguita attraverso la loro descrizione preliminare. La programmazione potrebbe non basarsi sulla codifica di un algoritmo, ma su un'attenta riflessione sulle strutture o sulle classi di dati. Il processo di traduzione da tali linguaggi è solitamente chiamato compilazione e i linguaggi di origine sono generalmente indicati come linguaggi di programmazione di alto livello (o linguaggi di alto livello). L'astrazione del linguaggio di programmazione dal sistema di comando del computer ha portato alla creazione indipendente di un'ampia varietà di linguaggi incentrati sulla risoluzione di problemi specifici. Le lingue sono apparse per calcoli scientifici, calcoli economici, accesso a database e altri.

Un interprete è un programma o un dispositivo che esegue la traduzione e l'esecuzione operatore per operatore del programma sorgente. A differenza di un compilatore, un interprete non produce un programma in linguaggio macchina come output. Dopo aver riconosciuto il comando della lingua di origine, lo esegue immediatamente. Sia i compilatori che gli interpreti utilizzano gli stessi metodi di analisi del codice sorgente di un programma. Ma l'interprete ti consente di iniziare a elaborare i dati dopo aver scritto anche un solo comando. Ciò rende più flessibile il processo di sviluppo e debug dei programmi. Inoltre, la mancanza di codice macchina in uscita consente di non "sporcare" dispositivi esterni file aggiuntivi, e l'interprete stesso può essere facilmente adattato a qualsiasi architettura di macchina sviluppandolo solo una volta in un linguaggio di programmazione ampiamente utilizzato. Pertanto, i linguaggi interpretati come Java Script, VB Script si sono diffusi. Lo svantaggio degli interpreti è la bassa velocità di esecuzione del programma. In genere, i programmi interpretati vengono eseguiti da 50 a 100 volte più lentamente dei programmi scritti in codice macchina.

Un emulatore è un programma o uno strumento software e hardware che offre la possibilità di eseguire un programma su un determinato computer senza riprogrammare, utilizzando codici o metodi per eseguire operazioni diverse da questo computer. Un emulatore è simile a un interprete in quanto esegue direttamente un programma scritto in una lingua. Tuttavia, molto spesso si tratta di linguaggio macchina o codice intermedio. Entrambi rappresentano istruzioni in codice binario che possono essere immediatamente eseguite dopo aver riconosciuto l'opcode. A differenza dei programmi di testo, non è necessario riconoscere la struttura del programma, per selezionare gli operandi.

Gli emulatori sono usati abbastanza spesso per una varietà di scopi. Ad esempio, quando si sviluppano nuovi sistemi informatici, viene prima creato un emulatore che esegue programmi sviluppati per computer che non esistono ancora. Ciò consente di valutare il sistema di comando e sviluppare una base Software anche prima che venga creata l'attrezzatura corrispondente.

Molto spesso, l'emulatore viene utilizzato per eseguire vecchi programmi su nuovi computer. In genere, i computer più recenti sono più veloci e dispongono di periferiche migliori. Ciò consente di emulare programmi meno recenti in modo più efficiente rispetto a eseguirli su computer meno recenti.

Transcoder - un programma o dispositivo software che traduce programmi scritti nel linguaggio macchina di un computer in programmi nel linguaggio macchina di un altro computer. Se l'emulatore è un analogo meno intelligente dell'interprete, allora il transcodificatore agisce nella stessa veste rispetto al compilatore. Allo stesso modo, il codice macchina sorgente (e solitamente binario) o una rappresentazione intermedia viene convertito in un altro codice simile in un'istruzione e senza alcuna analisi generale della struttura del programma sorgente. I transcodificatori sono utili durante il porting di programmi da un'architettura di computer a un'altra. Possono anche essere usati per ricostruire il testo di un programma in linguaggio di alto livello dal codice binario disponibile.

Un macroprocessore è un programma che sostituisce una sequenza di caratteri con un'altra. È una specie di compilatore. Genera il testo di output elaborando inserti speciali situati nel testo di origine. Questi inserti sono realizzati in modo speciale e appartengono ai costrutti linguistici, chiamati macrolinguaggio. I macroprocessori sono spesso usati come add-on per i linguaggi di programmazione, aumentando la funzionalità dei sistemi di programmazione. Quasi tutti gli assemblatori contengono un macroprocessore, che aumenta l'efficienza dello sviluppo di programmi macchina. Tali sistemi di programmazione sono comunemente indicati come macro assemblatori.

I macroprocessori sono utilizzati anche con linguaggi di alto livello. Aumentano la funzionalità di linguaggi come PL/1, C, C++. I macroprocessori sono particolarmente diffusi in C e C++, facilitando la scrittura di programmi. I macroprocessori aumentano l'efficienza della programmazione senza modificare la sintassi e la semantica del linguaggio.

Sintassi: un insieme di regole di un determinato linguaggio che determinano la formazione dei suoi elementi. In altre parole, è un insieme di regole per la formazione di sequenze di caratteri semanticamente significative in una data lingua. La sintassi viene specificata utilizzando regole che descrivono i concetti di un determinato linguaggio. Esempi di concetti sono: variabile, espressione, operatore, procedura. La sequenza dei concetti e il loro uso consentito nelle regole è determinata sintatticamente strutture corrette, formando programmi. È la gerarchia degli oggetti, non il modo in cui interagiscono tra loro, che viene definita attraverso la sintassi. Ad esempio, un operatore può essere presente solo in una procedura, mentre un'espressione in un operatore, una variabile può essere costituita da un nome e indici facoltativi e così via. La sintassi non è correlata a tali fenomeni nel programma come "saltare a un'etichetta inesistente" o "una variabile con il nome dato non è definita". Questo è ciò che fa la semantica.

Semantica: regole e condizioni che determinano la relazione tra gli elementi della lingua e i loro significati semantici, nonché l'interpretazione del significato significativo delle costruzioni sintattiche della lingua. Gli oggetti del linguaggio di programmazione non sono solo inseriti nel testo secondo una certa gerarchia, ma sono anche interconnessi tramite altri concetti che formano varie associazioni. Ad esempio, una variabile per la quale la sintassi definisce una posizione valida solo nelle dichiarazioni e in alcune istruzioni, ha un tipo specifico, può essere utilizzata con un insieme limitato di operazioni, ha un indirizzo, una dimensione e deve essere dichiarata prima di essere utilizzata in un programma.

Un parser è un componente del compilatore che controlla le istruzioni di origine per verificarne la conformità con le regole di sintassi e la semantica di un dato linguaggio di programmazione. Nonostante il nome, l'analizzatore controlla sia la sintassi che la semantica. Consiste di diversi blocchi, ognuno dei quali risolve i propri problemi. Sarà considerato in modo più dettagliato quando si descrive la struttura del traduttore. programmazione del linguaggio del compilatore traduttore

Qualsiasi traduttore svolge i seguenti compiti principali:

  • - analizza il programma trasmesso, in particolare, determina se contiene errori sintattici;
  • - genera un programma di output (spesso chiamato programma oggetto) nel linguaggio delle istruzioni macchina;
  • - alloca memoria per il programma oggetto. 1.1 Interpreti

Un vantaggio spesso citato dell'implementazione dell'interprete è che consente la "modalità immediata". La modalità immediata ti consente di chiedere al computer un'attività come PRINT 3.14159*3/2.1 e ti restituisce la risposta non appena premi INVIO (questo ti consente di utilizzare un computer da $ 3.000 come calcolatrice da $ 10). Inoltre, gli interpreti dispongono di attributi speciali che semplificano il debug. È possibile, ad esempio, interrompere l'elaborazione di un programma interprete, visualizzare il contenuto di determinate variabili, scorrere il programma e quindi continuare l'esecuzione.

Ciò che piace di più ai programmatori degli interpreti è la capacità di ottenere una risposta rapida. Non c'è bisogno di compilazione qui, poiché l'interprete è sempre pronto a intervenire nel tuo programma. Inserisci RUN e il risultato è tuo ultima modifica appare sullo schermo.

Tuttavia, le lingue degli interpreti presentano degli svantaggi. È necessario, ad esempio, avere sempre in memoria una copia dell'interprete, mentre molte delle caratteristiche dell'interprete, e quindi delle sue caratteristiche, potrebbero non essere necessarie per l'esecuzione di un particolare programma.

Un sottile svantaggio degli interpreti è che tendono a scoraggiare un buon stile di programmazione. Poiché i commenti e altri dettagli formalizzabili occupano molta memoria del programma, le persone tendono a non usarli. Il diavolo è meno furioso di un programmatore interprete BASIC che cerca di inserire un programma da 120K in una memoria da 60K. ma la cosa peggiore è che gli interpreti sono lenti.

Passano troppo tempo a capire cosa fare invece di fare la cosa reale. Quando esegue le istruzioni del programma, l'interprete deve prima scansionare ogni istruzione per leggerne il contenuto (cosa mi sta chiedendo di fare questa persona?) e quindi eseguire l'operazione richiesta. Le istruzioni nei cicli vengono scansionate troppo.

Considera il programma: sull'interprete BASIC 10 FOR N=1 TO 1000 20 PRINT N,SQR(N) 30 NEXT N alla prima transizione attraverso questo programma, l'interprete BASIC deve indovinare cosa significa la riga 20:

  • 1. convertire la variabile numerica N in stringa
  • 2. inviare una stringa allo schermo
  • 3. passare all'area di stampa successiva
  • 4. calcola la radice quadrata di N
  • 5. convertire il risultato in stringa
  • 6. invia una stringa allo schermo

Al secondo passaggio del ciclo, tutte queste supposizioni vengono ripetute di nuovo, poiché tutti i risultati dello studio di questa stringa qualche millisecondo fa vengono completamente dimenticati. E così in tutti i successivi 998 passaggi. Ovviamente, se potessi in qualche modo separare la fase di scansione/comprensione dalla fase di esecuzione, ne avresti di più programma veloce. Ed è esattamente a questo che servono i compilatori.


4. Principi di base della costruzione di traduttori. Traduttori, compilatori e interpreti: lo schema generale di lavoro. Compilatori e interpreti moderni.

Principi di base della costruzione di traduttori.

Traduttori, compilatori, interpreti: lo schema generale del lavoro.

Definizione di traduttore, compilatore, interprete

Per cominciare, diamo alcune definizioni: quali sono i traduttori e i compilatori già citati molte volte.

Definizione formale di traduttore

Traduttoreè un programma che traduce un programma di input nella lingua sorgente (input) in un programma di output equivalente nella lingua risultante (output). In questa definizione la parola "programma" ricorre tre volte, e non si tratta di un errore o di una tautologia. In effetti, tre programmi partecipano sempre al lavoro del traduttore.

In primo luogo, il traduttore stesso è un programma 1 - di solito è incluso nel software di sistema di un sistema informatico. Cioè, un traduttore è un pezzo di software (software), è un insieme di istruzioni e dati della macchina ed è eseguito da un computer, come tutti gli altri programmi all'interno di un sistema operativo (OS). Tutti i componenti del traduttore sono frammenti o moduli del programma con i propri dati di input e output.

In secondo luogo, i dati iniziali per il lavoro del traduttore sono il testo del programma di input, una sequenza di frasi del linguaggio di programmazione di input. Di solito si tratta di un file di caratteri, ma questo file deve contenere un testo di programma che soddisfi i requisiti sintattici e semantici della lingua di input. Inoltre, questo file ha un significato, determinato dalla semantica della lingua di input.

In terzo luogo, l'output del traduttore è il testo del programma risultante. Il programma risultante è costruito secondo le regole sintattiche specificate nella lingua di output del traduttore e il suo significato è determinato dalla semantica della lingua di output. Un requisito importante nella definizione di un traduttore è l'equivalenza dei programmi di input e output. L'equivalenza di due programmi significa la coincidenza del loro significato in termini di semantica del linguaggio di input (per il programma sorgente) e semantica del linguaggio di output (per il programma risultante). Senza soddisfare questo requisito, il traduttore stesso perde ogni significato pratico.

Quindi, per creare un traduttore, devi prima selezionare le lingue di input e output. Dal punto di vista della conversione delle frasi nella lingua di input in frasi equivalenti nella lingua di output, il traduttore funge da traduttore. Ad esempio, la traduzione di un programma da C in linguaggio assembly non è sostanzialmente diversa dalla traduzione, diciamo, dal russo all'inglese, con l'unica differenza che la complessità delle lingue è leggermente diversa (perché non ci sono traduttori dalle lingue naturali ​​- vedi la sezione "Classificazione dei linguaggi e delle grammatiche, capitolo 9). Pertanto, la stessa parola "traduttore" (inglese: traduttore) significa "traduttore".

Il risultato del lavoro del traduttore sarà il programma risultante, ma solo se il testo del programma sorgente è corretto - non contiene errori in termini di sintassi e semantica della lingua di input. Se il programma sorgente non è corretto (contiene almeno un errore), il risultato del lavoro del traduttore sarà un messaggio di errore (di norma, con spiegazioni aggiuntive e un'indicazione della posizione dell'errore nel programma sorgente). In questo senso, il traduttore è simile a un traduttore, ad esempio dall'inglese, a cui è stato inserito il testo sbagliato.

Teoricamente, è possibile implementare un traduttore utilizzando l'hardware. L'autore ha incontrato tali sviluppi, ma la loro ampia applicazione pratica non è nota. In questo caso, tutti i componenti del traduttore possono essere implementati sotto forma di hardware e dei loro frammenti - quindi il circuito di riconoscimento può ottenere un'implementazione completamente pratica!

definizione del compilatore.

La differenza tra un compilatore e un traduttore

Oltre al concetto di "traduttore", è ampiamente utilizzato anche il concetto di "compilatore", che gli è vicino nel significato.

Compilatore -è un traduttore che traduce un programma sorgente nel suo programma oggetto equivalente in linguaggio macchina o linguaggio assembly.

Pertanto, un compilatore differisce da un traduttore solo per il fatto che il suo programma risultante deve sempre essere scritto in codice macchina o in linguaggio assembly. Il programma di traduzione risultante, in generale, può essere scritto in qualsiasi lingua - è possibile, ad esempio, tradurre programmi da Pascal a C. Di conseguenza, ogni compilatore è un traduttore, ma non viceversa - non tutti i traduttori saranno un compilatore . Ad esempio, il traduttore da Pascal a C menzionato sopra non sarà 1 .

La stessa parola "compilatore" deriva da Termine inglese"compilatore" ("compilatore", "linker"). Apparentemente, il termine deve la sua origine alla capacità dei compilatori di creare programmi oggetto basati su programmi sorgente.

Il programma compilatore risultante è chiamato "programma oggetto" o "codice oggetto". Il file in cui è scritto è solitamente chiamato "file oggetto". Anche quando il programma risultante viene generato in un linguaggio macchina, c'è una differenza significativa tra un programma oggetto (file oggetto) e un programma eseguibile (file eseguibile). Il programma generato dal compilatore non può essere eseguito direttamente su un computer, in quanto non è legato ad un'area di memoria specifica dove dovrebbero trovarsi il suo codice e i suoi dati (per maggiori dettagli, vedere la sezione "Principi di funzionamento dei sistemi di programmazione", Capitolo 15 ) 2 .

I compilatori sono di gran lunga il tipo più comune di compilatore (molti li considerano l'unico tipo di compilatore, anche se non lo sono). Hanno la più ampia applicazione pratica, dovuta all'ampia distribuzione di tutti i tipi di linguaggi di programmazione. In quanto segue, parleremo sempre di compilatori, nel senso che il programma di output è scritto

Naturalmente, traduttori e compilatori, come tutti gli altri programmi, sono sviluppati da una persona (persone), di solito un gruppo di sviluppatori. In linea di principio, potrebbero crearlo direttamente nel linguaggio delle istruzioni della macchina, ma la quantità di codice e dati dei compilatori moderni è tale che la loro creazione nel linguaggio delle istruzioni della macchina è quasi impossibile in un tempo ragionevole con costi di manodopera ragionevoli. Pertanto, quasi tutti i compilatori moderni vengono creati anche utilizzando i compilatori (di solito, le versioni precedenti dei compilatori dello stesso produttore agiscono in questo ruolo). E in questa veste, il compilatore è già il programma di output per un altro compilatore, che non è né migliore né peggiore di tutti gli altri programmi di output generati 2 .

Definizione dell'interprete. La differenza tra interpreti e traduttori

Oltre ai concetti simili di "traduttore" e "compilatore", esiste un concetto fondamentalmente diverso di interprete.

Interprete -è un programma che prende un programma di input in una lingua sorgente e lo esegue.

A differenza dei traduttori, gli interpreti non generano il programma risultante (o qualsiasi codice risultante in generale) - e questa è la differenza fondamentale tra loro. L'interprete, come il traduttore, analizza il testo del programma sorgente. Tuttavia, non genera il programma risultante, ma esegue immediatamente il programma originale secondo il suo significato dato dalla semantica del linguaggio di input. Pertanto, il risultato dell'interprete sarà il risultato dato dal significato del programma sorgente, se questo programma è corretto, o un messaggio di errore se il programma sorgente non è corretto.

Naturalmente, per eseguire il programma sorgente, l'interprete deve in qualche modo convertirlo in linguaggio macchina, poiché altrimenti è impossibile eseguire programmi su un computer. Lo fa, ma i codici macchina risultanti non sono accessibili - non sono visibili all'utente dell'interprete. Questi codici macchina sono generati dall'interprete, eseguiti e distrutti

1 Va menzionato in particolare che ora nei moderni sistemi di programmazione hanno iniziato ad apparire compilatori in cui il programma risultante viene creato non nel linguaggio delle istruzioni macchina e non in linguaggio assembly, ma in un linguaggio intermedio. Di per sé, questo linguaggio intermedio non può essere eseguito direttamente su un computer, ma richiede uno speciale interprete intermedio per eseguire i programmi scritti in esso. Sebbene in questo caso il termine "traduttore" sarebbe probabilmente più corretto, il termine "compilatore" è usato in letteratura, poiché il linguaggio intermedio è un linguaggio di livello molto basso, essendo correlato alle istruzioni macchina e ai linguaggi assembly.

Questo solleva l'antica questione della gallina e dell'uovo. Naturalmente, nella prima generazione, i primissimi compilatori venivano scritti direttamente nelle istruzioni della macchina, ma poi, con l'avvento dei compilatori, questa pratica fu abbandonata. Anche le parti più critiche dei compilatori vengono create, come minimo, utilizzando il linguaggio assembly e vengono anche elaborate dal compilatore. tozhayutsya secondo necessità - come richiesto da un'implementazione specifica) dell'interprete. L'utente vede il risultato dell'esecuzione di questi codici - c'è il risultato dell'esecuzione del programma sorgente (il requisito per l'equivalenza del programma sorgente e dei codici macchina generati in questo caso, (condizionalmente, deve essere soddisfatto).

Più in dettaglio, le questioni relative all'implementazione degli interpreti e al loro allontanamento dai compilatori sono discusse più avanti nella sezione corrispondente.

Scopo di traduttori, compilatori e interpreti. Esempi di implementazione

I primi programmi creati per computer di prima generazione sono stati scritti direttamente nel linguaggio dei codici macchina. È stato davvero un lavoro infernale. È diventato subito chiaro che una persona non dovrebbe e non può parlare il linguaggio dei comandi della macchina, anche se è uno specialista di computer. DI; Tuttavia, tutti i tentativi di insegnare a un computer a parlare le lingue delle persone sono stati coronati da successo ed è improbabile che lo siano mai (per i quali ci sono alcuni motivi positivi discussi nel primo capitolo di questo manuale).

Da allora, l'intero sviluppo del software per computer è stato indissolubilmente legato all'emergere e allo sviluppo dei compilatori.

I primi compilatori erano compilatori di linguaggi assembly o, come venivano chiamati, codici mnemonici. I mnemocode hanno trasformato la "lettera di Filkin" dei comandi della macchina in un linguaggio più o meno comprensibile per uno specialista: le designazioni monic (principalmente inglesi) di questi comandi. (È già diventato molto più facile fornire programmi, ma nessun computer è in grado di eseguire mnem (linguaggio assembly), quindi era necessario creare compilatori. Questi compilatori sono elementari, ma continuano a svolgere un ruolo significativo in sistemi di programmazione oggi Maggiori dettagli sul linguaggio assembly e sui compilatori dalla sua storia: più avanti nella sezione corrispondente.

Il passo successivo è stata la creazione di linguaggi di alto livello. I linguaggi di alto livello (la maggior parte dei linguaggi di programmazione appartengono a loro) rappresentano un collegamento intermedio tra linguaggi puramente formali e linguaggi di comunicazione naturale delle persone. Dal primo hanno ottenuto una rigorosa malizzazione della struttura sintattica delle frasi della lingua, dal secondo - la parte significativa del vocabolario, la semantica delle principali strutture ed espressioni (con elementi di operazioni matematiche che provenivano dall'algebra).

L'avvento dei linguaggi di alto livello semplificò notevolmente il processo di programmazione, pur non riducendolo al "livello casalingo", come supplicarono con arroganza alcuni autori all'alba della nascita dei linguaggi di programmazione 1 . C'erano poche lingue del genere, poi dozzine, ora, probabilmente, ce ne sono più di cento. Non c'è fine in vista a questo processo. Tuttavia, i computer della tradizionale architettura "Neumann", che possono comprendere solo le istruzioni della macchina, prevalgono ancora, quindi la questione della creazione di compilatori continua ad essere rilevante.

Non appena ci fu un enorme bisogno di creare compilatori, iniziò a svilupparsi una teoria specializzata. Nel tempo, ha trovato un'applicazione pratica in una varietà di compilatori creati. I compilatori sono stati creati e continuano a essere creati non solo per linguaggi nuovi, ma anche conosciuti da tempo. Molti produttori, da aziende note e rispettabili (come Microsoft o Inprise) a team di autori poco conosciuti, rilasciano sempre più nuovi esempi di compilatori sul mercato. Ciò è dovuto a una serie di motivi, che verranno discussi di seguito.

Infine, poiché la maggior parte degli aspetti teorici nel campo dei compilatori ha ricevuto la loro implementazione pratica (e questo, va detto, è avvenuto abbastanza rapidamente, alla fine degli anni '60), lo sviluppo dei compilatori ha seguito la strada della loro cordialità verso una persona - un utente, uno sviluppatore di programmi in linguaggi di alto livello. La logica conclusione di questo processo è stata la creazione di sistemi di programmazione - sistemi software, che combinano, oltre ai compilatori diretti, molti componenti software correlati. Essendo apparsi, i sistemi di programmazione hanno rapidamente conquistato il mercato e ora lo dominano per la maggior parte (infatti, i compilatori separati sono una rarità tra i moderni strumenti software). Per informazioni su cosa sono i moderni sistemi di programmazione e come sono organizzati, vedere il capitolo Modern Programming Systems. Ora i compilatori sono parte integrante di qualsiasi sistema informatico. Senza la loro esistenza, la programmazione di qualsiasi compito applicato sarebbe difficile, se non impossibile. E la programmazione di attività di sistema specializzate, di norma, viene eseguita se non in un linguaggio di alto livello (C è attualmente utilizzato più spesso in questo ruolo), quindi in linguaggio assembly, quindi viene utilizzato un compilatore appropriato. La programmazione direttamente nei linguaggi del codice macchina è estremamente rara e solo per risolvere problemi molto ristretti. Qualche parola su esempi di implementazioni di compilatori e interpreti, nonché su come si relazionano ad altri esistenti strumenti software. I compilatori, come verrà mostrato più avanti, sono generalmente più facili da implementare rispetto agli interpreti. In termini di efficienza, li superano anche: è ovvio che il codice compilato verrà sempre eseguito più velocemente dell'interpretazione di un programma sorgente simile. Inoltre, non tutti i linguaggi di programmazione consentono la costruzione di un semplice interprete. Tuttavia, gli interpreti hanno un vantaggio significativo: il codice compilato è sempre legato all'architettura del sistema informatico su cui è orientato e il programma sorgente è legato solo alla semantica del linguaggio di programmazione, che è molto più facile da standardizzare. Questo aspetto è stato inizialmente ignorato. I primi compilatori erano compilatori mnemonici. I loro discendenti - moderni compilatori di linguaggi assembly - esistono per quasi tutti i sistemi informatici conosciuti. Sono estremamente rigidamente focalizzati sull'architettura. Poi c'erano compilatori di linguaggi come FORTRAN, ALGOL-68, PL/1. Erano focalizzati su computer mainframe con elaborazione batch di attività. Di quanto sopra, forse solo FORTRAN continua ad essere utilizzato fino ad oggi, poiché dispone di un numero enorme di biblioteche per vari scopi. Molte lingue, essendo nate, non si sono diffuse: ADA, Modula, Simula sono conosciute solo da una ristretta cerchia di specialisti. Allo stesso tempo, il mercato sistemi software dominato da compilatori di linguaggi che non avrebbero avuto un futuro brillante. Prima di tutto, ora è C e C ++. Il primo di loro è nato con sistemi operativi come UNIX, insieme ad esso ha conquistato il suo "posto al sole", per poi passare a sistemi operativi di altro tipo. Il secondo ha incarnato con successo in sé un esempio dell'implementazione delle idee della programmazione orientata agli oggetti su una base pratica ben consolidata 1 . Puoi anche citare il abbastanza comune Pascal, che, inaspettatamente per molti, è andato oltre lo scopo di un linguaggio puramente educativo per un ambiente universitario.

La storia degli interpreti non è così ricca (ancora!). Come già accennato, inizialmente non è stata data loro un'importanza significativa, poiché per quasi tutti gli aspetti sono inferiori ai compilatori. Dei ben noti linguaggi che implicavano l'interpretazione, si può solo citare Basic, sebbene la maggior parte ora conosca la sua implementazione compilata di Visual Basic, realizzata da Microsoft. Tuttavia, ora la situazione è in qualche modo cambiata, poiché la questione della portabilità del software e della loro indipendenza dalla piattaforma hardware sta diventando sempre più importante con lo sviluppo di Internet. L'esempio più noto oggi è il linguaggio Java (che a sua volta combina compilazione e interpretazione) e il relativo JavaScript. Dopo tutto, il linguaggio HTML, che è alla base del protocollo HTTP che ha dato origine a un così rapido sviluppo del World Wide Web, è anche un linguaggio interpretato. Secondo l'autore, le sorprese stanno ancora aspettando tutti nel campo dell'emergere di nuovi interpreti, e i primi sono già apparsi - ad esempio, il linguaggio C # ("C-sharp", ma il nome è ovunque come "Do diesis"), annunciato da Microsoft.

DI b la storia dei linguaggi di programmazione e lo stato attuale del mercato dei compilatori si può parlare a lungo e molto. L'autore ritiene di potersi limitare a quanto già detto, non essendo questo lo scopo del presente manuale. Chi lo desidera può fare riferimento alla letteratura.

fasi della traduzione. Schema generale del traduttore

Sulla fig. 13.1 mostra lo schema generale del compilatore. Da esso è chiaro che B In generale, il processo di compilazione consiste in due fasi principali: sintesi e analisi.

Nella fase di analisi viene riconosciuto il testo del programma sorgente, vengono create e compilate le tabelle degli identificatori. Il risultato del suo lavoro è una certa rappresentazione interna del programma, comprensibile al compilatore.

Nella fase di sintesi, sulla base della rappresentazione interna del programma e delle informazioni contenute nella tabella (tabelle) degli identificatori, viene generato il testo del programma risultante. Il risultato di questa fase è il codice oggetto.

Inoltre, il compilatore contiene una parte responsabile dell'analisi e della correzione degli errori, che, in caso di errore nel testo del programma sorgente, dovrebbe informare l'utente nel modo più completo possibile sul tipo di errore e sul luogo in cui si è verificato. Nella migliore delle ipotesi, il compilatore può offrire all'utente un'opzione per correggere l'errore.

Questi passaggi, a loro volta, consistono in passaggi più piccoli chiamati fasi di compilazione. La composizione delle fasi di compilazione è data nella forma più generale, la loro specifica attuazione e il processo di interazione

Innanzitutto, è un riconoscimento per la lingua del programma sorgente. Quindi deve ricevere in input una catena di caratteri della lingua di input, verificare l'appartenenza alla lingua e, inoltre, identificare le regole con cui è stata costruita questa catena (poiché la risposta alla domanda sull'appartenenza "sì" e "no" interessa poco). È interessante notare che l'utente, l'autore del programma di input, funge da generatore di catene linguistiche di input.

In secondo luogo, il compilatore è un generatore per la lingua del programma risultante. Deve costruire la catena della lingua di output da oprah all'output; determinate regole, linguaggio macchina previsto o linguaggio io campionatore. Il riconoscitore di questa catena sarà il sistema informatico in cui viene creato il programma risultante.

Analisi lessicale(scanner) è la parte del compilatore che legge i programmi letterali nella lingua di partenza e da essi costruisce parole (token) della lingua di partenza. L'input dell'analizzatore lessicale riceve il testo del programma sorgente e le informazioni di output vengono trasmesse per un'ulteriore elaborazione da parte del compilatore nella fase di analisi. Da un punto di vista teorico, l'analizzatore lessicale non è una parte obbligatoria e necessaria del compilatore. Tuttavia, ci sono motivi che ne determinano la presenza in quasi tutti i compilatori. Per maggiori dettagli si veda la sezione “Analizzatore lessicale (notatori). Principi di costruzione degli scanner”.

Analisiè la parte principale del compilatore nella fase di analisi. O esegue l'estrazione delle costruzioni sintattiche nel testo del programma sorgente, elaborate dall'analizzatore lessicale. Nella stessa fase della compilazione viene verificata la correttezza sintattica del programma. Il parser svolge un ruolo importante: il ruolo di riconoscimento del testo del linguaggio di programmazione di input (vedere la sezione "Parser. Traduzione sintatticamente controllata" di questo capitolo).

Analisi semanticaè la parte del compilatore che controlla il testo corretto* del programma sorgente in termini di semantica del linguaggio di input. CRS validazione direttamente, l'analisi semantica deve eseguire la conversione; Definizioni di testo richieste dalla semantica della lingua di origine (come l'aggiunta di funzioni di conversione di tipo implicite). In varie implementazioni del comp. Tori, l'analisi semantica può in parte entrare nella fase di analisi sintattica *, in parte - nella fase di preparazione per la generazione del codice.

Preparazione per la generazione del codiceè la fase in cui il compilatore esegue azioni preliminari che sono direttamente correlate alla sintesi del testo del programma risultante, ma non portano ancora alla generazione del testo nella lingua di output. Tipicamente, questa fase include attività relative all'identificazione degli elementi del linguaggio, allocazione della memoria, ecc. (vedi la sezione "Analisi semantica e preparazione per la generazione del codice", Capitolo 14).

Generazione del codice- questa è la fase direttamente correlata alla generazione del coma delle frasi costitutive della lingua di destinazione e del testo risultante nel suo insieme

Possono, ovviamente, variare a seconda della versione del compilatore. Tuttavia, in una forma o nell'altra, tutte le fasi presentate sono quasi sempre presenti in ogni particolare compilatore.

Il compilatore nel suo insieme, dal punto di vista della teoria dei linguaggi formali, agisce in “due ruoli”, svolge due funzioni principali. programmi. Questa è la fase principale nella fase di sintesi del programma risultante. Oltre alla generazione diretta del testo del programma risultante, la generazione di solito include anche l'ottimizzazione, un processo associato all'elaborazione del testo già generato. A volte l'ottimizzazione viene individuata come una fase separata della compilazione, poiché ha un impatto significativo sulla qualità e sull'efficienza del programma risultante (vedere le sezioni "Generazione del codice. Metodi di generazione del codice" e "Ottimizzazione del codice. Metodi di ottimizzazione di base", Capitolo 14 ).

Tabelle identificatori(a volte "tabelle di caratteri") sono insiemi di dati appositamente organizzati che servono a memorizzare informazioni sugli elementi del programma sorgente, che vengono poi utilizzati per generare il testo del programma risultante. Potrebbe esserci una tabella di identificatori in una particolare implementazione del compilatore o potrebbero esserci diverse tabelle di questo tipo. Gli elementi del programma sorgente, le cui informazioni devono essere memorizzate durante la compilazione, sono variabili, costanti, funzioni, ecc. - la composizione specifica dell'insieme di elementi dipende dal linguaggio di programmazione di input utilizzato. Il concetto di "tabella" non implica affatto che questo data warehouse debba essere organizzato proprio sotto forma di tabelle o altri array di informazioni - i possibili metodi per organizzarli sono discussi in dettaglio più avanti, nella sezione "Tabelle di identificazione". Organizzazione delle tabelle degli identificatori.

Mostrato in Fig. 13.1 La suddivisione del processo di compilazione in fasi serve a scopi piuttosto metodologici e nella pratica potrebbe non essere osservata in modo così rigoroso. Le successive sottosezioni di questo manuale discutono le varie opzioni per l'organizzazione tecnica delle fasi di compilazione presentate. Indica come possono essere collegati tra loro. Qui consideriamo solo gli aspetti generali di questo tipo di relazione.

Innanzitutto, durante la fase di analisi lessicale, i token vengono estratti dal testo del programma di input in quanto necessari per la successiva fase di parsing. In secondo luogo, come verrà mostrato di seguito, l'analisi e la generazione del codice possono essere eseguite contemporaneamente. Pertanto, queste tre fasi di compilazione possono funzionare in combinazione e insieme a esse può essere eseguita anche la preparazione per la generazione del codice. Successivamente, consideriamo le questioni tecniche dell'implementazione delle fasi principali della compilazione, che sono strettamente correlate al concetto passaggio.

Concetto di passaggio. Compilatori multi-pass e single-pass

Come già accennato, il processo di compilazione dei programmi si compone di diverse fasi. Nei compilatori reali, la composizione di queste fasi può differire in qualche modo da quella considerata sopra: alcune di esse possono essere suddivise in componenti, mentre altre, al contrario, sono combinate in un'unica fase. L'ordine in cui vengono eseguite le fasi di compilazione può anche variare tra le diverse varianti del compilatore. In un caso, il compilatore esamina il testo del programma sorgente, esegue immediatamente tutte le fasi della compilazione e riceve il risultato: il codice oggetto. In un'altra variante, esegue solo alcune delle fasi di compilazione sul testo sorgente e riceve non il risultato finale, ma un insieme di alcuni dati intermedi. Questi dati vengono quindi elaborati nuovamente e questo processo può essere ripetuto più volte.

I veri compilatori, di norma, traducono il testo del programma sorgente in più passaggi.

passaggio - questo è il processo di lettura sequenziale da parte del compilatore dei dati dalla memoria esterna, elaborandoli e collocando il risultato del lavoro nella memoria esterna. Molto spesso, un singolo passaggio comporta l'esecuzione di una o più fasi di compilazione. Il risultato dei passaggi intermedi è la rappresentazione interna del programma sorgente, il risultato dell'ultimo passaggio è il programma oggetto risultante.

COME memoria esterna qualsiasi supporto di informazioni può agire: RAM di un computer, unità su dischi magnetici, nastri magnetici, ecc. I compilatori moderni, di norma, si sforzano di sfruttare al massimo la RAM di un computer per l'archiviazione dei dati e solo quando manca di memoria disponibile, vengono utilizzate unità disco magnetiche. Altri supporti di memorizzazione non vengono utilizzati nei compilatori moderni a causa del basso tasso di scambio dei dati.

Durante ogni passaggio, il compilatore ha accesso alle informazioni ottenute da tutti i passaggi precedenti. Di norma tende ad utilizzare in primo luogo solo le informazioni ottenute sulla passata immediatamente precedente a quella attuale, ma in linea di principio può accedere ai dati delle passate precedenti fino al codice sorgente del programma. Le informazioni ottenute dal compilatore durante l'esecuzione dei passaggi non sono disponibili per l'utente. È memorizzato in memoria ad accesso casuale, che viene liberato dal compilatore dopo il completamento del processo di traduzione, oppure si trova sotto forma di file temporanei su disco, anch'essi distrutti dopo il completamento del compilatore. Pertanto, una persona che lavora con un compilatore potrebbe non sapere nemmeno quanti passaggi esegue il compilatore: vede sempre solo il testo del programma sorgente e il programma oggetto risultante. Ma il numero di passaggi eseguiti è importante specifiche tecniche compilatore, aziende rispettabili - gli sviluppatori di compilatori di solito lo indicano nella descrizione del loro prodotto.

È chiaro che gli sviluppatori stanno cercando di ridurre al minimo il numero di passaggi eseguiti dai compilatori. Ciò aumenta la velocità del compilatore, riduce la quantità di memoria di cui ha bisogno. Un compilatore one-pass che prende un programma sorgente come input e produce immediatamente il programma oggetto risultante è l'ideale.

Tuttavia, non è sempre possibile ridurre il numero di passaggi. Il numero di passaggi necessari è determinato principalmente dalle regole grammaticali e semantiche della lingua di partenza. Più complessa è la grammatica del linguaggio e più opzioni suggeriscono le regole semantiche, più passaggi eseguirà il compilatore (ovviamente, anche le qualifiche degli sviluppatori del compilatore giocano un ruolo). Ad esempio, questo è il motivo per cui i compilatori Pascal sono generalmente più veloci dei compilatori C: la grammatica di Pascal è più semplice e le regole semantiche sono più rigide. I compilatori single-pass sono rari, possibili solo per linguaggi molto semplici. I compilatori reali in genere eseguono da due a cinque passaggi. Quindi i veri compilatori sono multi-pass. I più comuni sono i compilatori a due e tre passaggi, ad esempio: il primo passaggio è l'analisi lessicale, il secondo è l'analisi e l'analisi semantica, il terzo è la generazione e l'ottimizzazione del codice (le opzioni di esecuzione, ovviamente, dipendono dallo sviluppatore). Nei moderni sistemi di programmazione, il primo passaggio del compilatore (analisi lessicale del codice) viene spesso eseguito parallelamente alla modifica del codice del programma sorgente (questa variante della costruzione dei compilatori viene discussa più avanti in questo capitolo).

Interpreti. Caratteristiche della costruzione di interpreti

Interpreteè un programma che prende un programma di input in una lingua sorgente e lo esegue. Come accennato in precedenza, la principale differenza tra interpreti e traduttori e compilatori è che l'interprete non genera il programma risultante, ma esegue semplicemente il programma sorgente.

Il termine 'interprete', come 'traduttore', significa 'traduttore'. Dal punto di vista della terminologia, questi concetti sono simili, ma dal punto di vista della teoria dei linguaggi formali e della compilazione c'è una grande differenza fondamentale tra loro. Se i concetti di "traduttore" e "compilatore" sono quasi indistinguibili, allora non possono essere confusi con il concetto di "interprete".

Il modo più semplice per implementare un interprete sarebbe quello di tradurre prima completamente il programma sorgente in istruzioni macchina e quindi eseguirlo immediatamente. In una tale implementazione, l'interprete, infatti, non differirebbe molto dal compilatore, con l'unica differenza che il programma risultante in esso sarebbe inaccessibile all'utente. Lo svantaggio di un tale interprete sarebbe che l'utente dovrebbe attendere la compilazione dell'intero programma sorgente prima che possa essere eseguito. In effetti, un tale interprete non avrebbe molto senso - non fornirebbe alcun vantaggio rispetto a un compilatore simile 1 . Pertanto, la stragrande maggioranza degli interpreti agisce in modo tale da eseguire il programma sorgente in sequenza, man mano che entra nell'input dell'interprete. Quindi l'utente non deve attendere il completamento della compilazione dell'intero programma sorgente. Inoltre, può entrare in sequenza nel programma sorgente e osservare immediatamente il risultato della sua esecuzione man mano che vengono immessi i comandi.

Con questo ordine di lavoro dell'interprete, si manifesta una caratteristica essenziale che lo distingue dal compilatore: se l'interprete esegue i comandi man mano che arrivano, non può eseguire l'ottimizzazione del programma sorgente. Di conseguenza, la fase di ottimizzazione nella struttura complessiva dell'interprete sarà assente. Altrimenti, differirà poco dalla struttura di un compilatore simile. Si dovrebbe solo tenere conto del fatto che nell'ultima fase - generazione del codice - le istruzioni della macchina non vengono scritte nel file oggetto, ma vengono eseguite man mano che vengono generate.

L'assenza di una fase di ottimizzazione definisce un'altra caratteristica tipica di molti interpreti: la notazione polacca inversa è molto spesso utilizzata come rappresentazione interna del programma (vedere la sezione "Generazione del codice. Metodi di generazione del codice", Capitolo 14). Questa comoda forma di rappresentazione delle operazioni ha solo un inconveniente significativo: è difficile da ottimizzare. Ma negli interpreti, questo è esattamente ciò che non è richiesto.

Non tutti i linguaggi di programmazione consentono la costruzione di interpreti che possano eseguire il programma sorgente all'arrivo dei comandi, per fare ciò il linguaggio deve consentire l'esistenza di un compilatore che parsing il programma sorgente in un solo passaggio. Inoltre, un linguaggio non può essere interpretato come vengono ricevuti i comandi se consente che le chiamate alle funzioni e alle strutture dati appaiano prima che vengano descritte direttamente. Pertanto, linguaggi come C e Pascal non possono essere interpretati con questo metodo.

L'assenza di una fase di ottimizzazione porta al fatto che l'esecuzione del programma con l'ausilio di un interprete è meno efficiente che con l'ausilio di un compilatore simile. Inoltre, durante l'interpretazione, il programma sorgente deve essere analizzato di nuovo ogni volta che viene eseguito, mentre durante la compilazione, viene analizzato solo una volta e successivamente viene sempre utilizzato il file oggetto. Pertanto, gli interpreti perdono sempre prestazioni rispetto ai compilatori.

Il vantaggio dell'interprete è l'indipendenza dell'esecuzione del programma dall'architettura del sistema informatico di destinazione. Come risultato della compilazione, si ottiene un codice oggetto, che è sempre orientato a una certa architettura. Per passare a un'altra architettura dei sistemi informatici di destinazione], il programma deve essere ricompilato. E per interpretare i programmi] è necessario avere solo il suo codice sorgente e un interprete dalla lingua appropriata.

Gli interpreti per lungo tempo hanno avuto una prevalenza significativamente inferiore Chi pelatrici. Di norma, esistevano interpreti per una gamma limitata di linguaggi di programmazione relativamente semplici (come, ad esempio, gli strumenti di sviluppo software professionale di base ad alte prestazioni erano costruiti sopra i compilatori.

Un nuovo impulso allo sviluppo degli interpreti è stato dato dalla diffusione del global reti di computer. Tali reti possono includere computer di un'architettura personale, e quindi il requisito per un'implementazione uniforme del testo del programma sorgente su ciascuno di essi diventa decisivo. Pertanto, con lo sviluppo delle reti globali e la diffusione di Internet in tutto il mondo,
Nei moderni sistemi di programmazione esistono implementazioni software che combinano sia le funzioni di un compilatore che le funzioni di un interprete: a seconda delle esigenze dell'utente, il programma sorgente viene compilato o eseguito (interpretato). Inoltre, alcuni linguaggi di programmazione moderni comportano due fasi di sviluppo: in primo luogo, il programma sorgente viene compilato in codice intermedio (un linguaggio di basso livello), quindi questo risultato della compilazione viene eseguito utilizzando l'interprete di questo linguaggio intermedio. Più in dettaglio, le opzioni per tali sistemi sono discusse nel capitolo "Sistemi di programmazione moderni".

Un esempio ampiamente utilizzato di linguaggio interpretato è l'HTML (Hypertext Markup Language), un linguaggio di descrizione ipertestuale. Quasi l'intera struttura di Internet funziona attualmente sulla sua base. Un altro esempio - linguaggi Java e JavaScript: combinano le funzioni di compilazione e interpretazione. Il testo del programma sorgente viene compilato in un codice binario intermedio che non dipende dall'architettura del sistema informatico di destinazione, questo codice viene distribuito sulla rete ed eseguito sul lato ricevente: viene interpretato.

Traduttori di lingue di assemblaggio ("assemblatori")

Linguaggio assembly -è un linguaggio di basso livello. La struttura e l'interconnessione delle catene di questo linguaggio sono vicine alle istruzioni della macchina del sistema informatico di destinazione, dove dovrebbe essere eseguito il programma risultante. L'uso del linguaggio assembly consente allo sviluppatore di gestire le risorse (processore, RAM, dispositivi esterni, ecc.) del sistema informatico di destinazione a livello di istruzioni macchina. Ogni istruzione del programma sorgente in linguaggio assembly viene convertita in un'istruzione macchina come risultato della compilazione.

Il traduttore dal linguaggio assembly sarà sempre, ovviamente, un compilatore, poiché il linguaggio del programma risultante è il codice macchina. Un traduttore in linguaggio assembly viene spesso indicato semplicemente come "assembler" o "programma assembler".

Implementazione di compilatori da linguaggio assembly

Il linguaggio assembly, di regola, contiene codici mnemonici per istruzioni macchina. Molto spesso vengono utilizzati i mnemonici dei comandi in lingua inglese, ma esistono altre varianti dei linguaggi di assemblaggio (comprese le versioni in lingua russa). Questo è il motivo per cui il linguaggio assembly era chiamato "linguaggio mnemocode" (ora questo nome non è praticamente più utilizzato). Tutte le possibili istruzioni in ogni linguaggio assembly possono essere suddivise in due gruppi: il primo gruppo comprende le istruzioni del linguaggio ordinario, che vengono convertite in istruzioni della macchina durante la traduzione; il secondo gruppo è costituito da istruzioni di linguaggio speciali che non vengono convertite in istruzioni macchina, ma vengono utilizzate dal compilatore per eseguire attività di compilazione (come, ad esempio, l'attività di allocazione della memoria). La sintassi del linguaggio è estremamente semplice. I comandi del programma sorgente sono generalmente scritti in modo tale che vi sia un comando per riga del programma. Ogni istruzione in linguaggio assembly, di regola, può essere suddivisa in tre componenti, uno dopo l'altro in sequenza: etichette, un codice operativo e un campo operando. Il compilatore da linguaggio assembly di solito prevede anche la possibilità di avere commenti nel programma di input separati dai comandi da un determinato delimitatore.

Il campo dell'etichetta contiene un identificatore che rappresenta l'etichetta oppure è vuoto. Ogni identificatore di etichetta può verificarsi solo una volta in un programma in linguaggio assembly. L'etichetta è considerata descritta dove essa A incontrato mediocre nel programma (è richiesta una descrizione preliminare delle etichette). Un'etichetta può essere utilizzata per trasferire il controllo al comando che invia. Non è raro che un'etichetta sia separata dal resto del comando da un carattere di separazione (più comunemente due punti ":").

Il codice operazione è sempre un mnemonico rigorosamente definito di uno dei possibili comandi del processore o anche un comando rigorosamente definito (del mio compilatore. Il codice operazione è scritto in caratteri alfabetici della lingua madre. Molto spesso, la sua lunghezza è 3-4, meno spesso - 5 o 6 caratteri.

Il campo dell'operando è vuoto o è un elenco di uno, due, meno spesso tre operandi. Il numero di operandi è rigorosamente definito e dipende dal codice dell'operazione: ogni operazione in linguaggio assembly fornisce un numero fisso di operandi. Di conseguenza, ciascuna di queste opzioni corrisponde a istruzioni senza indirizzo, uniindirizzo, due indirizzi o tre indirizzi (praticamente non viene utilizzato un numero maggiore di operandi; nei computer moderni, anche le istruzioni a tre indirizzi sono rare). Come opere; dov può essere identificatore o costante.

Una caratteristica del linguaggio assembly è che un numero di identificatori in n è allocato specificamente per designare i registri del processore. Tali ficatori, da un lato, non richiedono una descrizione preliminare, ma, con D1, non possono essere utilizzati dall'utente per altri scopi. L'insieme di questi identificatori è predefinito per ogni linguaggio assembly.

A volte il linguaggio assembly consente l'uso come operandi di determinate combinazioni limitate di designazioni di registro, identificatori e costanti, che sono combinate da alcuni segni di operatore. Tali combinazioni sono più spesso utilizzate per designare tipi di indirizzamento, ad esempio, nelle istruzioni macchina del sistema informatico di destinazione.

Ad esempio, la seguente sequenza di comandi

Questo è un esempio di una sequenza di comandi in linguaggio assembly n; processori della famiglia Intel 80x86. Qui c'è un comando per descrivere un set (dati (db), un'etichetta (loop), codici operativi (mov, dec e jnz). Gli operandi sono l'identificatore del set di dati (dati), le designazioni del registro di processo

(bx e cx), etichetta (loop) e costante (4). L'operando composto dei dati mappa l'indirizzamento indiretto dei dati al registro di base bx all'offset 4.

Questa sintassi della lingua può essere facilmente descritta utilizzando una grammatica regolare. Pertanto, la creazione di un riconoscitore per il linguaggio assembly non è difficile. Per lo stesso motivo, nei compilatori dal linguaggio assembly, l'analisi lessicale e sintattica, di regola, sono combinate in un unico riconoscitore.

La semantica del linguaggio assembly è completamente e completamente determinata dal sistema informatico di destinazione a cui è orientato questo linguaggio. La semantica del linguaggio assembly determina quale istruzione macchina corrisponde a ciascuna istruzione del linguaggio assembly, nonché quali operandi e quanti sono consentiti per un particolare codice operativo.

Pertanto, l'analisi semantica in un compilatore in linguaggio assembly è semplice quanto l'analisi sintattica. Il suo compito principale è verificare la validità degli operandi per ciascun codice operativo e anche verificare che tutti gli identificatori e le etichette incontrati nel programma di input siano descritti e gli identificatori che li denotano non corrispondano agli identificatori predefiniti utilizzati per designare codici operativi e registri del processore.

Gli schemi di analisi e semantica in un compilatore in linguaggio assembly possono quindi essere implementati sulla base di una macchina a stati convenzionale. È questa caratteristica che ha determinato il fatto che i compilatori in linguaggio assembly sono stati storicamente i primi compilatori creati per i computer. Esistono anche una serie di altre funzionalità specifiche dei linguaggi assembly e semplificano la creazione di compilatori per essi.

Innanzitutto, nei compilatori dal linguaggio assembly, non viene eseguita alcuna identificazione aggiuntiva delle variabili: tutte le variabili del linguaggio mantengono i nomi loro assegnati dall'utente. Lo sviluppatore del programma sorgente è responsabile dell'univocità dei nomi nel programma sorgente; la semantica del linguaggio non impone requisiti aggiuntivi a questo processo. In secondo luogo, nei compilatori dal linguaggio assembly, l'allocazione della memoria è estremamente semplificata. Il compilatore dal linguaggio assembly funziona solo con la memoria statica. Se viene utilizzata la memoria dinamica, per lavorarci è necessario utilizzare la libreria o le funzioni del sistema operativo appropriate e lo sviluppatore del programma sorgente è responsabile della sua allocazione. Lo sviluppatore del programma sorgente è anche responsabile del passaggio dei parametri e dell'organizzazione della visualizzazione in memoria di procedure e funzioni. Deve anche occuparsi di separare i dati dal codice del programma: un compilatore in linguaggio assembly, a differenza dei compilatori di linguaggi di alto livello, non esegue automaticamente tale separazione. E in terzo luogo, nella fase di generazione del codice nel compilatore dal linguaggio assembly, l'ottimizzazione non viene eseguita, poiché lo sviluppatore del programma sorgente è responsabile dell'organizzazione dei calcoli, della sequenza delle istruzioni della macchina e dell'allocazione dei registri del processore.

Fatta eccezione per queste caratteristiche, un compilatore in linguaggio assembly è un normale compilatore, ma molto semplificato rispetto a qualsiasi compilatore in linguaggio di alto livello. I compilatori dal linguaggio assembly sono implementati molto spesso in uno schema a due passaggi. Al primo passaggio, il compilatore analizza il programma sorgente, lo converte in codici macchina e contemporaneamente compila la tabella degli identificatori. Ma al primo passaggio nelle istruzioni della macchina, gli indirizzi di quegli operandi che si trovano nella RAM rimangono vuoti. Al secondo passaggio, il compilatore inserisce questi indirizzi e contemporaneamente rileva gli identificatori non dichiarati. Questo perché l'operando può essere dichiarato nel programma dopo che è stato utilizzato per la prima volta. Quindi il suo indirizzo non è ancora noto al momento della costruzione dell'istruzione macchina, e quindi è necessario un secondo passaggio. Un tipico esempio di tale operando è un'etichetta che salta in avanti nella sequenza di istruzioni.

Definizioni di macro e comandi di macro

Lo sviluppo di programmi in linguaggio assembly è un processo piuttosto laborioso che richiede spesso una semplice ripetizione delle stesse operazioni ripetute. Un esempio potrebbe essere una sequenza di istruzioni che vengono eseguite ogni volta per impostare una visualizzazione dello stack di memoria quando viene inserita una procedura o una funzione.

Per facilitare il lavoro dello sviluppatore, sono state create le cosiddette macro.

macroè una sostituzione di testo, durante la quale ogni identificatore di un certo tipo viene sostituito con una stringa di caratteri da un data store. Il processo di esecuzione di una macro è chiamato generazione di macro e la stringa di caratteri risultante dall'esecuzione di una macro è chiamata espansione di macro.

Il processo di esecuzione delle macro consiste nel rivedere in sequenza il testo del programma sorgente, rilevando in esso determinati identificatori e sostituendoli con le corrispondenti stringhe di caratteri. Inoltre, è la sostituzione testuale di una catena di caratteri (identificatore) con un'altra catena di caratteri (stringa) che viene eseguita. Tale sostituzione è chiamata macro sostituzione.

Le definizioni di macro vengono utilizzate per specificare quali identificatori devono essere sostituiti con quali righe. Le definizioni delle macro sono presenti direttamente nel testo del programma sorgente. Si distinguono per parole chiave o delimitatori speciali che non possono essere trovati altrove nel testo del programma. Durante l'elaborazione, tutte le definizioni di macro vengono completamente escluse dal testo del programma di input e le informazioni in esse contenute vengono memorizzate per l'elaborazione durante l'esecuzione di comandi macro.

Una macro può contenere parametri. Quindi ogni macro ad essa corrispondente deve, quando viene richiamata, contenere una stringa di caratteri invece di ogni parametro. Questa stringa viene sostituita durante l'esecuzione della macro in ogni punto in cui si verifica il parametro corrispondente nella definizione della macro. Il parametro della macro può essere un'altra macro, nel qual caso verrà chiamato in modo ricorsivo ogni volta che è necessario eseguire una sostituzione di parametro. In linea di principio, le macro possono formare una sequenza

Chiamate ricorsive, simili alla sequenza di procedure ricorsive e chiamate di funzioni, ma invece di calcolare e passare parametri, eseguono solo sostituzioni testuali 1 .

I comandi macro e le definizioni macro vengono elaborati da un modulo speciale chiamato processore macro o generatore di macro. Il macrogeneratore riceve in ingresso il testo del programma sorgente contenente definizioni macro e comandi macro, e in uscita appare il testo dell'estensione macro del programma sorgente, che non contiene definizioni macro e comandi macro. Entrambi i testi sono solo testi di programma, non viene eseguita alcuna altra elaborazione. È l'espansione macro del testo sorgente che entra nell'input del compilatore.

La sintassi dei comandi macro e delle definizioni macro non è definita in modo rigoroso. Può differire a seconda dell'implementazione del compilatore dal linguaggio assembly. Ma il principio stesso di eseguire macrosostituzioni nel testo del programma è invariato e non dipende dalla loro sintassi.

Il macrogeneratore molto spesso non esiste come modulo di programma separato, ma fa parte del compilatore dal linguaggio assembly. Un'estensione macro di un programma sorgente di solito non è disponibile per il suo sviluppatore. Inoltre, le sostituzioni di macro possono essere eseguite in sequenza durante l'analisi del codice sorgente al primo passaggio del compilatore, insieme all'analisi dell'intero testo del programma, e quindi l'espansione macro del programma sorgente nel suo insieme potrebbe non esistere affatto in quanto tale.

Ad esempio, il testo seguente definisce la macro push_0 nel linguaggio assembly di un processore di tipo Intel 8086:

Hog ah, ah ■ spingi l'ascia endm

La semantica di questa macro consiste nello scrivere il numero "0" nello stack tramite il registro del processore ax. Quindi ovunque nel testo del programma, dove la macro si incontrerà

Verrà sostituito a seguito della sostituzione di macro con una sequenza di comandi:

Hog ah, ah ■ spingere ascia

Questa è la definizione di macro più semplice. È possibile creare definizioni macro più complesse con parametri. Una di queste definizioni di macro è descritta di seguito:

La profondità di tale ricorsione è solitamente molto limitata. La sequenza di chiamate ricorsive alle macro è solitamente soggetta a restrizioni significativamente più rigorose rispetto alla sequenza di chiamate ricorsive a procedure e funzioni, che, con una visualizzazione della memoria impilata, è limitata solo dalla dimensione dello stack di trasferimento dei parametri. add_abx macro xl,x2

spingere l'ascia
finem

Quindi il macro comando deve essere specificato anche nel testo del programma con il corrispondente numero di parametri. In questo esempio, la macro

Add_abx4,8 verrà sostituito da una sequenza di comandi come risultato della sostituzione di macro:

Aggiungi ascia,4 aggiungi bx.4 aggiungi ex,8 spingi ascia

In molti compilatori dal linguaggio assembly sono possibili costrutti ancora più complessi, che possono contenere variabili ed etichette locali. Un esempio di tale costrutto è una definizione di macro:

Loop_ax macro xl,x2,yl

Hog bx.bx loopax: aggiungi bx.yl

Qui l'etichetta 1 oopax è locale, definita solo all'interno di questa macro. In questo caso non è più possibile eseguire una semplice sostituzione di testo del macro comando nel testo del programma, poiché se questo macro comando viene eseguito due volte, ciò porterà alla comparsa di due etichette identiche nel testo del programma. In questo caso, il macrogeneratore deve utilizzare metodi di sostituzione del testo più complessi, simili a quelli utilizzati dai compilatori per l'identificazione degli elementi lessicali del programma di input, in modo da assegnare a tutte le possibili variabili locali ed etichette di macro nomi univoci all'interno dell'intero programma. Le definizioni di macro e i comandi di macro hanno trovato impiego non solo nei linguaggi assembly, ma anche in molti linguaggi di alto livello. Lì vengono elaborati da un modulo speciale chiamato preprocessore del linguaggio (ad esempio, il preprocessore del linguaggio C è ampiamente noto). Il principio di elaborazione rimane lo stesso dei programmi in linguaggio assembly: il preprocessore esegue le sostituzioni di testo direttamente sulle righe del programma sorgente stesso. Nei linguaggi di alto livello, le definizioni delle macro devono essere separate dal testo del programma sorgente stesso, in modo che il preprocessore non possa confonderle con la sintassi del linguaggio di input. Per fare ciò, vengono utilizzati caratteri e comandi speciali (comandi del preprocessore), che non possono mai verificarsi nel testo del programma sorgente, oppure vengono trovate definizioni di macro

All'interno di una parte insignificante del programma sorgente - fanno parte dei commenti (tale implementazione esiste, ad esempio, nel compilatore Pascal creato da Borland). Le macro, d'altra parte, possono trovarsi ovunque nel codice sorgente del programma e sintatticamente la loro chiamata potrebbe non differire dalla chiamata delle funzioni nel linguaggio sorgente.

Va ricordato che, nonostante la somiglianza della sintassi della chiamata, i comandi macro sono fondamentalmente diversi dalle procedure e dalle funzioni, poiché non generano il codice risultante, ma sono una sostituzione del testo eseguita direttamente nel testo del programma sorgente. Il risultato della chiamata di una funzione e di una macro può essere molto diverso per questo motivo.

Si consideri un esempio in linguaggio C. Se viene descritta una funzione

Int fKint a) ( return a + a: ) e una macro simile

#define f2(a) ((a) + (a)) allora il risultato della loro chiamata non sarà sempre lo stesso.

Infatti, le chiamate j=fl(i) e j=f2(i) (dove i e j sono alcune variabili intere) porteranno allo stesso risultato. Ma le chiamate j=fl(++i) e j=f2(++i) daranno significati diversi variabile j. Il fatto è che poiché f2 è una definizione di macro, nel secondo caso verrà eseguita una sostituzione di testo, che porterà a una sequenza di operatori j=((++i) + (++i)). Si può vedere che in questa sequenza l'operazione ++i verrà eseguita due volte, a differenza della chiamata di funzione fl(++i), dove viene eseguita solo una volta.

Poiché il testo di un programma scritto in un linguaggio di programmazione non è comprensibile per un computer, è necessario tradurlo in linguaggio macchina. Viene chiamata la traduzione di un programma da un linguaggio di programmazione in un linguaggio di codice macchina trasmissione(traduzione - traduzione), ed è eseguita da programmi speciali - traduttori.

Esistono due tipi di traduttori: interpreti e compilatori.

Interpreteè chiamato un traduttore che esegue la traduzione operatore per operatore (comando per comando) e la successiva esecuzione dell'operatore tradotto del programma sorgente. Due svantaggi del metodo di interpretazione:

1. Il programma di interpretazione deve trovarsi nella memoria del computer durante l'intero processo di esecuzione del programma originale, ad es. occupare una certa quantità di memoria;

2. Il processo di traduzione della stessa istruzione viene ripetuto tante volte quante sono le volte che questo comando deve essere eseguito nel programma.

Compilatoreè un programma che converte (traduce) il programma sorgente in un programma (modulo) in linguaggio macchina. Successivamente, il programma viene scritto nella memoria del computer e solo successivamente eseguito.

Durante la compilazione, i processi di traduzione ed esecuzione sono separati nel tempo: prima il programma sorgente viene completamente tradotto in linguaggio macchina (dopodiché non è necessaria la presenza di un traduttore in RAM), quindi il programma tradotto può essere ripetutamente eseguito.

Qualsiasi traduttore risolve i seguenti compiti principali:

1. Analizza il programma trasmesso e determina se contiene errori di sintassi;

2. Genera un programma di output nel linguaggio di comando del computer;

3. Alloca memoria per il programma di output, ad es. ogni variabile, costante, array e altri oggetti ha una propria area di memoria.

Così, Compilatore(Inglese) compilatore- compiler, collector) legge l'intero programma interamente, lo traduce e crea una versione completa del programma in linguaggio macchina, che viene poi eseguito.

Interprete(Inglese) interprete- interprete, interprete) traduce ed esegue il programma linea per linea.

Una volta compilato il programma, non sono più necessari né il programma sorgente né il compilatore. Allo stesso tempo, il programma in fase di elaborazione da parte dell'interprete deve nuovamente trasferimento in linguaggio macchina ogni volta che il programma viene eseguito.

Ogni lingua specifica è incentrata sulla compilazione o sull'interpretazione, a seconda dello scopo per cui è stata creata. Per esempio, Pasquale solitamente utilizzato per risolvere problemi piuttosto complessi in cui la velocità dei programmi è importante. Pertanto, questo linguaggio viene solitamente implementato utilizzando compilatore. Dall'altro lato, DI BASEè stato creato come linguaggio per i programmatori alle prime armi, per i quali l'esecuzione del programma riga per riga presenta innegabili vantaggi. A volte per una lingua c'è e compilatore, e interprete. In questo caso, è possibile utilizzare un interprete per sviluppare e testare il programma, quindi compilare il programma sottoposto a debug per velocizzarne l'esecuzione.

Poiché il testo scritto in un linguaggio di programmazione è incomprensibile per un computer, è necessario tradurlo in codice macchina. Tale traduzione di un programma da un linguaggio di programmazione a un linguaggio in codice macchina è chiamata traduzione ed è eseguita da programmi speciali: traduttori.

Un traduttore è un programma di utilità che converte un programma sorgente fornito in un linguaggio di programmazione di input in un programma funzionante presentato in un linguaggio oggetto.

Attualmente, i compilatori sono divisi in tre gruppi principali: assemblatori, compilatori e interpreti.

Assembler è un'utilità di sistema che converte i costrutti simbolici in istruzioni in linguaggio macchina. Una caratteristica specifica degli assemblatori è che traducono letteralmente un'istruzione simbolica in un'istruzione macchina. Pertanto, il linguaggio assembly (chiamato anche autocode) è progettato per facilitare la percezione del set di istruzioni del computer e velocizzare la programmazione in questo set di istruzioni. È molto più facile per un programmatore ricordare la designazione mnemonica delle istruzioni della macchina piuttosto che il loro codice binario.

Allo stesso tempo, il linguaggio assembly, oltre agli analoghi delle istruzioni macchina, contiene molte direttive aggiuntive che facilitano, in particolare, la gestione delle risorse del computer, la scrittura di frammenti ripetitivi e la costruzione di programmi multimodulo. Pertanto, l'espressività del linguaggio è molto più ricca di un semplice linguaggio di codifica simbolica, che aumenta notevolmente l'efficienza della programmazione.

Un compilatore è un programma di utilità che traduce in linguaggio macchina un programma scritto in un linguaggio di programmazione sorgente. Proprio come un assemblatore, un compilatore converte un programma da una lingua all'altra (il più delle volte, nella lingua di un particolare computer). Tuttavia, i comandi in lingua di origine differiscono in modo significativo nell'organizzazione e nella potenza dai comandi in linguaggio macchina. Esistono lingue in cui un'istruzione della lingua di partenza viene tradotta in 7-10 istruzioni della macchina. Esistono però anche linguaggi in cui ogni istruzione può corrispondere a 100 o più istruzioni macchina (ad esempio Prolog). Inoltre, nelle lingue di partenza viene spesso utilizzata una rigorosa tipizzazione dei dati, che viene eseguita attraverso la loro descrizione preliminare. La programmazione potrebbe non basarsi sulla codifica di un algoritmo, ma su un'attenta riflessione sulle strutture o sulle classi di dati. Il processo di traduzione da tali linguaggi è solitamente chiamato compilazione e i linguaggi di origine sono generalmente indicati come linguaggi di programmazione di alto livello (o linguaggi di alto livello). L'astrazione del linguaggio di programmazione dal sistema di comando del computer ha portato alla creazione indipendente di un'ampia varietà di linguaggi incentrati sulla risoluzione di problemi specifici. Le lingue sono apparse per calcoli scientifici, calcoli economici, accesso a database e altri.

Un interprete è un programma o un dispositivo che esegue la traduzione e l'esecuzione operatore per operatore del programma sorgente. A differenza di un compilatore, un interprete non produce un programma in linguaggio macchina come output. Dopo aver riconosciuto il comando della lingua di origine, lo esegue immediatamente. Sia i compilatori che gli interpreti utilizzano gli stessi metodi di analisi del codice sorgente di un programma. Ma l'interprete ti consente di iniziare a elaborare i dati dopo aver scritto anche un solo comando. Ciò rende più flessibile il processo di sviluppo e debug dei programmi. Inoltre, l'assenza di codice macchina in uscita consente di non "ingombrare" i dispositivi esterni con file aggiuntivi e l'interprete stesso può essere adattato abbastanza facilmente a qualsiasi architettura di macchina, avendolo sviluppato una sola volta in un linguaggio di programmazione ampiamente utilizzato. Pertanto, i linguaggi interpretati come Java Script, VB Script si sono diffusi. Lo svantaggio degli interpreti è la bassa velocità di esecuzione del programma. In genere, i programmi interpretati vengono eseguiti da 50 a 100 volte più lentamente dei programmi scritti in codice macchina.

Un emulatore è un programma o uno strumento software e hardware che offre la possibilità di eseguire un programma su un determinato computer senza riprogrammare, utilizzando codici o metodi per eseguire operazioni diverse da questo computer. Un emulatore è simile a un interprete in quanto esegue direttamente un programma scritto in una lingua. Tuttavia, molto spesso si tratta di linguaggio macchina o codice intermedio. Entrambi rappresentano istruzioni in codice binario che possono essere immediatamente eseguite dopo aver riconosciuto l'opcode. A differenza dei programmi di testo, non è necessario riconoscere la struttura del programma, per selezionare gli operandi.

Gli emulatori sono usati abbastanza spesso per una varietà di scopi. Ad esempio, quando si sviluppano nuovi sistemi informatici, viene prima creato un emulatore che esegue programmi sviluppati per computer che non esistono ancora. Ciò consente di valutare il set di istruzioni e di sviluppare il software di base prima della creazione dell'hardware corrispondente.

Molto spesso, l'emulatore viene utilizzato per eseguire vecchi programmi su nuovi computer. In genere, i computer più recenti sono più veloci e dispongono di periferiche migliori. Ciò consente di emulare programmi meno recenti in modo più efficiente rispetto a eseguirli su computer meno recenti.

Transcoder - un programma o dispositivo software che traduce programmi scritti nel linguaggio macchina di un computer in programmi nel linguaggio macchina di un altro computer. Se l'emulatore è un analogo meno intelligente dell'interprete, allora il transcodificatore agisce nella stessa veste rispetto al compilatore. Allo stesso modo, il codice macchina sorgente (e solitamente binario) o una rappresentazione intermedia viene convertito in un altro codice simile in un'istruzione e senza alcuna analisi generale della struttura del programma sorgente. I transcodificatori sono utili durante il porting di programmi da un'architettura di computer a un'altra. Possono anche essere usati per ricostruire il testo di un programma in linguaggio di alto livello dal codice binario disponibile.

Un macroprocessore è un programma che sostituisce una sequenza di caratteri con un'altra. È una specie di compilatore. Genera il testo di output elaborando inserti speciali situati nel testo di origine. Questi inserti sono realizzati in modo speciale e appartengono ai costrutti linguistici, chiamati macrolinguaggio. I macroprocessori sono spesso usati come add-on per i linguaggi di programmazione, aumentando la funzionalità dei sistemi di programmazione. Quasi tutti gli assemblatori contengono un macroprocessore, che aumenta l'efficienza dello sviluppo di programmi macchina. Tali sistemi di programmazione sono comunemente indicati come macro assemblatori.

I macroprocessori sono utilizzati anche con linguaggi di alto livello. Aumentano la funzionalità di linguaggi come PL/1, C, C++. I macroprocessori sono particolarmente diffusi in C e C++, facilitando la scrittura di programmi. I macroprocessori aumentano l'efficienza della programmazione senza modificare la sintassi e la semantica del linguaggio.

Sintassi: un insieme di regole di un determinato linguaggio che determinano la formazione dei suoi elementi. In altre parole, è un insieme di regole per la formazione di sequenze di caratteri semanticamente significative in una data lingua. La sintassi viene specificata utilizzando regole che descrivono i concetti di un determinato linguaggio. Esempi di concetti sono: variabile, espressione, operatore, procedura. La sequenza dei concetti e il loro uso consentito nelle regole determina le strutture sintatticamente corrette che formano i programmi. È la gerarchia degli oggetti, non il modo in cui interagiscono tra loro, che viene definita attraverso la sintassi. Ad esempio, un operatore può essere presente solo in una procedura, mentre un'espressione in un operatore, una variabile può essere costituita da un nome e indici facoltativi e così via. La sintassi non è correlata a tali fenomeni nel programma come "saltare a un'etichetta inesistente" o "una variabile con il nome dato non è definita". Questo è ciò che fa la semantica.

Semantica: regole e condizioni che determinano la relazione tra gli elementi della lingua e i loro significati semantici, nonché l'interpretazione del significato significativo delle costruzioni sintattiche della lingua. Gli oggetti del linguaggio di programmazione non sono solo inseriti nel testo secondo una certa gerarchia, ma sono anche interconnessi tramite altri concetti che formano varie associazioni. Ad esempio, una variabile per la quale la sintassi definisce una posizione valida solo nelle dichiarazioni e in alcune istruzioni, ha un tipo specifico, può essere utilizzata con un insieme limitato di operazioni, ha un indirizzo, una dimensione e deve essere dichiarata prima di essere utilizzata in un programma.

Un parser è un componente del compilatore che controlla le istruzioni di origine per verificarne la conformità con le regole di sintassi e la semantica di un dato linguaggio di programmazione. Nonostante il nome, l'analizzatore controlla sia la sintassi che la semantica. Consiste di diversi blocchi, ognuno dei quali risolve i propri problemi. Sarà considerato in modo più dettagliato quando si descrive la struttura del traduttore.

Qualsiasi traduttore svolge i seguenti compiti principali:

Analizza il programma in corso di traduzione, in particolare, determina se contiene errori di sintassi;

Genera un programma di output (spesso chiamato programma oggetto) nel linguaggio delle istruzioni della macchina;

Alloca memoria per il programma oggetto.