La traduzione in codice C è un'abitudine molto ben consolidata. La C originale con le classi (e le prime implementazioni C ++, allora chiamate Cfront ) lo fecero con successo. Lo stanno facendo diverse implementazioni di Lisp o Scheme, ad esempio Chicken Scheme , Scheme48 , Bigloo . Alcune persone tradotti Prolog a C . E così pure alcune versioni di Mozart (e ci sono stati tentativi di compilare il bytecode Ocaml in C ). Anche il sistema CAIA di intelligenza artificiale di J.Pitrat viene avviato e genera tutto il suo codice C. Vala si traduce anche in C, per il codice relativo a GTK. Il libro di Queinnec Lisp In Small Pieces avere qualche capitolo sulla traduzione in C.
Uno dei problemi quando si traduce in C sono le chiamate ricorsive della coda . Lo standard C non garantisce che un compilatore C li stia traducendo correttamente (in un "salto con argomenti", cioè senza mangiare stack di chiamate), anche se in alcuni casi, le versioni recenti di GCC (o di Clang / LLVM) effettuano tale ottimizzazione .
Un altro problema è la raccolta dei rifiuti . Diverse implementazioni usano semplicemente il garbage collector conservativo di Boehm (che è C amichevole ...). Se si desidera eseguire il garbage collection del codice (come fanno diverse implementazioni Lisp, ad esempio SBCL), questo potrebbe essere un incubo (si vorrebbe dlclose
su Posix).
Ancora un altro problema riguarda le continuazioni di prima classe e call / cc . Ma sono possibili trucchi intelligenti (guarda all'interno di Chicken Scheme). L'accesso allo stack di chiamate potrebbe richiedere molti trucchi (ma vedi backtrace GNU , ecc ....). La persistenza ortogonale di continuazioni (cioè di pile o fili) sarebbe difficile in C.
La gestione delle eccezioni è spesso una questione per emettere chiamate intelligenti a longjmp ecc ...
Potresti voler generare (nel tuo codice C emesso) #line
direttive appropriate . Questo è noioso e richiede molto lavoro (ti consigliamo di produrre, ad esempio, un gdb
codice più facilmente debuggabile).
Il mio linguaggio MELT specifico per il dominio lispy (per personalizzare o estendere GCC ) è tradotto in C (attualmente in C ++ scadente). Ha il suo generational copying garbage collector. (Potresti essere interessato da Qish o Ravenbrook MPS ). In realtà, il GC generazionale è più facile nel codice C generato dalla macchina che nel codice C scritto a mano (perché personalizzerai il tuo generatore di codice C per la tua barriera di scrittura e macchine GC).
Non conosco alcuna implementazione del linguaggio che si traduca in un codice C ++ originale, vale a dire usando una tecnica di "garbage collection" in fase di compilazione per emettere codice C ++ usando molti modelli STL e rispettando il linguaggio RAII . (per favore dì se ne conosci uno).
Ciò che è divertente oggi è che (sugli attuali desktop Linux) i compilatori C potrebbero essere abbastanza veloci da implementare un ciclo interattivo di lettura-eval-print di livello superiore tradotto in C: emetterai codice C (poche centinaia di righe) per ogni utente interazione, ne farai fork
una compilazione in un oggetto condiviso, che poi faresti dlopen
. (MELT lo sta facendo tutto pronto, e di solito è abbastanza veloce). Tutto ciò potrebbe richiedere alcuni decimi di secondo ed essere accettabile dagli utenti finali.
Quando possibile, consiglierei di tradurre in C, non in C ++, in particolare perché la compilazione C ++ è lenta.
Se stai implementando la tua lingua, potresti anche considerare (invece di emettere il codice C) alcune librerie JIT come libjit , GNU lightning , asmjit o persino LLVM o GCCJIT . Se vuoi tradurre in C, a volte potresti usare tinycc : compila molto rapidamente il codice C generato (anche in memoria) per rallentare il codice macchina. Ma in generale vuoi sfruttare le ottimizzazioni fatte da un vero compilatore C come GCC
Se traduci in C la tua lingua, assicurati di creare prima l'intero AST del codice C generato in memoria (ciò semplifica anche la generazione prima di tutte le dichiarazioni, quindi di tutte le definizioni e del codice funzione). Saresti in grado di fare alcune ottimizzazioni / normalizzazioni in questo modo. Inoltre, potresti essere interessato a diverse estensioni GCC (ad esempio goto calcolate). Probabilmente vorrai evitare di generare un tempo di compilazione di funzioni di grandi dimensioni è proporzionale al quadrato della dimensione del codice funzione). Quindi limitare la dimensione delle funzioni C generate a qualche migliaio di righe ciascuna. enormi funzioni C - ad es. Di centinaia di migliaia di righe di C generato (è meglio dividerle in pezzi più piccoli) poiché l'ottimizzazione dei compilatori C è molto insoddisfacente di funzioni C molto grandi (in pratica, e sperimentalmente,gcc -O
Si noti che entrambi i compilatori C & C ++ di Clang (attraverso LLVM ) e GCC (attraverso libgccjit ) offrono un modo per emettere alcune rappresentazioni interne adatte a questi compilatori, ma farlo potrebbe (o meno) essere più difficile dell'emissione di codice C (o C ++), ed è specifico per ciascun compilatore.
Se stai progettando una lingua da tradurre in C, probabilmente vorrai avere diversi trucchi (o costrutti) per generare una miscela di C con la tua lingua. Il mio documento DSL2011 MELT: un linguaggio specifico del dominio tradotto incorporato nel compilatore GCC dovrebbe darti suggerimenti utili.