Ogni programmatore, nel suo percorso di studio, prima o poi si pone questa domanda:
Come creo un linguggio di programmazione?
In questo articolo vi spiegherò quali sono i passaggi da seguire per crearne uno.
Per prima cosa dovete decidere che tipo di linguaggio volete creare: interpretato o compilato. La differenza sostanzialmente sta nel fatto che il primo tipo di linguaggio viene letto e interpretato da un programma chiamato intermprete, mentre un linguaggio compilato viene tradotto in linguaggio macchina ed eseguito direttamente dalla cpu(il programma che effettua la traduzione si chiama compilatore). C'è anche un altro tipo di linguaggi che è un ibrido tra i due,sto parlando dei linguaggi pseudocompilati, che sono tradotti in un linguaggio intermedio(bytecode), simile al linguaggio macchina, che viene interpretato ed eseguito non dalla cpu, bensì da un altro programma, un interprete.
Se scegliete un linguaggio compilato avete bisogno dei seguenti prerequisiti:
1) Conoscere approfonditamente l'architettura sulla quale il vostro linguaggio girerà. L 'architettura è l'insieme di hardware e software che consentono ai programmi di essere eseguiti. L'architettura ad oggi più diffusa è la 80x86 a 32/64 bit(hardware) accompagnata dal sistema operativo Windows(software).
2) Conoscere il linguaggio assembly destinato all'architettura scelta, nel nostro caso l'assembly 80x86.
3) Conoscere e saper utilizzare discretamente un linguaggio di programmazione qualsiasi. Molto importante è conoscere alla perfezione le funzioni di manipolazione delle stringhe di tale linguaggio e l'OOP.
Se scegliete un linguaggio interpretato avete invece avete bisogno di:
1) Conoscere e saper utilizzare discretamente un linguaggio di programmazione qualsiasi. Molto importante è conoscere alla perfezione le funzioni di manipolazione delle stringhe di tale linguaggio e l'OOP.
2) Conoscere il funzionamento di un elaboratore.
3) Avere una buona capacità nella creazione di algoritmi, puntando soprattutto ad ottimizzarne la velocità e l'utilizzo di memoria.
Se invece scegliete un linguaggio pseudocompilato, dovete saper:
1) Conoscere e saper utilizzare discretamente un linguaggio di programmazione qualsiasi. Molto importante è conoscere alla perfezione le funzioni di manipolazione delle stringhe di tale linguaggio e l'OOP.
2) Conoscere il funzionamento di un elaboratore e della struttura di un linguaggio macchina.
3) Avere una buona capacità nella creazione di algoritmi, puntando soprattutto ad ottimizzarne la velocità e l'utilizzo di memoria.
4) Saper creare un simulatore per un linguaggio macchina inventato, creando virtualmente componenti come la cpu e la ram.
Dopo aver scelto il tipo di linguaggio bisogna iniziare a definirne la sintassi.
E' possibile utilizzare, per questo compito, dei liguaggi descrittivi chiamati meta-linguaggi, il cui unico compito è descrivere la sintassi del linguaggio(uno di questi e il BNF). Essi però non sono essensiali, è possibile limitarsi a descrivere la sintassi utilizzando la lingua parlata(italiano o inglese) in un file txt, l'importante è essere precisi.
Una volta specificata la sintassi, si deve passare alla realizzazione pratica.
Nel caso di un linguaggio compilato:
Bisogna iniziare a scrivere il compilatore. Esso sarà diviso in 3 parti principali: lo scanner, il parser e il code generator.
Lo scanner ha il compito di leggere i vari token dal file di input(contenente le istruzioni scritte nel nostro nuovo linguaggio). Esso si basa su una semplice routine, che chiameremo Tokenize, che legge un carattere per volta dal file:
- Se è una lettera o un underscore(_), continua a leggere caratteri finchè sono lettere, underscore, o numeri e li accoda ad una stringa. Tale stringa viene poi aggiunta al risultato, che altro non è che una lista(o array) di stringhe che sarà passato al parser.
- Se è un numero, continua a leggere caratteri e se sono numeri li accoda ad una stringa, che alla fine viene aggiunta tra i risultati, come prima.
- Se è uno spazio, lo salta.
- Se è separatore di linea(ritorno a capo, ";", ecc...), aggiunge un tag, tipo "<eol>" tra i risultati, così da riconoscere la fine di una linea(o istruzione).
- Se è un " legge e accoda tutti i caratteri fino al prossimo ", e inserisce all'inizio di tale stringa, un tag <string>, per indicare appunto che si tratta di una stringa.
- Se è un simbolo, continua a leggere ed accoda ad una stringa finche i caratteri letti sono simboli, come prima, ma quì bisogna fare una distinzione: infatti è valido come simbolo "==", oppure "!=", ma non è valido ad esempio ",=", bisogna perciò effettuare un controllo sui simboli letti per capire se sono operatori o simboli di punteggiatura. il risultato viene aggiunto nell'array dei risultati, come prima.
- Tanti altri passaggi possono essere aggiunti a seconda della sintassi del linguaggio preso in considerazione...
Finito lo scanner, si deve creare il parser, che si occupa di analizzare l'array di stringhe creato dallo scanner e di produrre un albero di tokens.
L'albero di tokens è una struttura ad albero in grado di contenere informazioni sui token presenti nel file di input. Esso è una sequenza di strutture/classi(a seconda del linguaggio con cui si sta scrivendo il compilatore), una per ogni tipo di statement(istruzione), che conservano informazioni sulla sua struttura. Un esempio di parser e di struttura per un albero di token lo potete trovare qui(in C#):
http://msdn.microsoft.com/it-it/magazine/cc136756.aspxUna volta creato il parser, si deve procedere allla scrittura del code generator, che analizza l'albero di token prodotto dal parser e produce un output nel linguaggio assembly scelto, che verrà poi compilato da un assembler(ad esempio NASM).
Per avere la possibilità di gestire l'IO(scrittura a console, lettura da console, ecc...) si devono utilizzare le API del sistema operativo scelto.
Se si è scelto di creare un linguaggio interpretato, i passaggi sono gli stessi fino all'albero di token, che invece di essere passato al code generator, viene passato all'interpreter, una classe che legge l'albero di token e ne esegue le istruzioni in esso contenute, senza tradurre in nessun linguaggio assembly. In questo caso per l'IO si possono utilizzare le funzioni del linguaggio di programmazione utilizzato per scrivere l'interpreter.
Se si è scelto invece di creare un linguaggio pseudocompilato, si deve procedere esattamente come per il linguaggio compilato, solo che il code generator non creerà codice assembly ma codice bytecode, che sarà poi interpretato attraverso un altro programma, l'interprete di bytecode, o più spesso chiamato Virtual Machine, che è una specie di cpu virtuale.
Con questo articolo, che non vuole essere esaustivo, ho voluto presentare il processo di creazione di un linguaggio di programmazione. Invito chiunque voglia approfondire l'argomento a seguire i vari tutorial presenti sulla rete e a comprare un buon manuale sulla scrittura di parser, sull'architettura hardware e software e sui linguaggi assembly.
Manuel Furia(manvb.net)