Ho scritto SLIC (System of Languages for Implementing Compilers) in sé. Quindi compilato a mano in assembly. SLIC ha molto in quanto era un singolo compilatore di cinque sotto-lingue:
- Linguaggio di programmazione parser SYNTAX PPL
- Linguaggio di generazione del codice PSEUDO a scansione di alberi basato su GENERATOR LISP 2
- ISO in sequenza, codice PSEUDO, linguaggio di ottimizzazione
- Macro PSEUDO come codice assembly che produce linguaggio.
- MACHOP Istruzioni di assemblaggio-macchina che definiscono la lingua.
SLIC è stato ispirato da CWIC (compilatore per la scrittura e l'implementazione di compilatori). A differenza della maggior parte dei pacchetti di sviluppo del compilatore, SLIC e CWIC hanno affrontato la generazione di codice con linguaggi specializzati, specifici per il dominio. SLIC estende la generazione del codice CWIC aggiungendo le sotto-lingue ISO, PSEUDO e MACHOP che separano le specifiche della macchina target dal linguaggio del generatore di scansione degli alberi.
LISP 2 alberi ed elenchi
Il sistema di gestione dinamica della memoria del linguaggio generatore basato su LISP 2 è un componente chiave. Gli elenchi sono espressi nella lingua racchiusa tra parentesi quadre, i suoi componenti sono separati da virgole, ovvero un elenco di tre elementi [a, b, c].
Alberi:
ADD
/ \
MPY 3
/ \
5 x
sono rappresentati da liste la cui prima voce è un oggetto nodo:
[ADD,[MPY,5,x],3]
Gli alberi sono comunemente visualizzati con il nodo separato che precede i rami:
ADD[MPY[5,x],3]
Annullamento dell'analisi con le funzioni del generatore basate su LISP 2
Una funzione del generatore è un insieme denominato di (unparse) => azione> coppie ...
<NAME>(<unparse>)=><action>;
(<unparse>)=><action>;
...
(<unparse>)=><action>;
Le espressioni non analisi sono test che abbinano i modelli di alberi e / o i tipi di oggetti suddividendoli e assegnando tali parti alla variabile locale da elaborare mediante la sua azione procedurale. Un po 'come una funzione sovraccarica che prende diversi tipi di argomenti. Tranne i test () => ... vengono tentati nell'ordine codificato. Il primo riuscito sparisce eseguendo la sua azione corrispondente. Le espressioni unparse sono test di smontaggio. ADD [x, y] corrisponde a un albero ADD a due rami che assegna i suoi rami alle variabili locali x e y. L'azione può essere un'espressione semplice o un blocco di codice limitato .BEGIN ... .END. Oggi userei i blocchi di stile {...}. La corrispondenza dell'albero, [], le regole unparse possono chiamare i generatori che passano i risultati restituiti all'azione:
expr_gen(ADD[expr_gen(x),expr_gen(y)])=> x+y;
In particolare il precedente expr_gen unparse corrisponde a un albero ADD a due rami. All'interno del modello di test verrà chiamato un singolo generatore di argomenti posizionato in un ramo di un albero con quel ramo. Il suo elenco di argomenti è costituito da variabili locali assegnate a oggetti restituiti. Al di sopra di unparse si specifica che un ramo è lo smontaggio dell'albero ADD, ricorsivo premendo ciascun ramo su expr_gen. Il ritorno del ramo sinistro inserito nelle variabili locali x. Allo stesso modo il ramo destro è passato a expr_gen con y l'oggetto return. Quanto sopra potrebbe far parte di un valutatore di espressioni numeriche. C'erano funzioni di scelta rapida chiamate vettori in cui sopra invece della stringa di nodo si poteva usare un vettore di nodi con un vettore di azioni corrispondenti:
expr_gen(#node[expr_gen(x),expr_gen(y)])=> #action;
node: ADD, SUB, MPY, DIV;
action: x+y, x-y, x*y, x/y;
(NUMBER(x))=> x;
(SYMBOL(x))=> val:(x);
Il valutatore di espressioni più completo sopra riportato assegna il ritorno dal ramo sinistro expr_gen a xe il ramo destro a y. Il vettore di azione corrispondente eseguito su xey restituito. Le ultime coppie azione =parse => corrispondono agli oggetti numerici e simbolici.
Simbolo e attributi simbolo
I simboli possono avere attributi con nome. val: (x) accede all'attributo val dell'oggetto simbolo contenuto in x. Una pila di tabelle di simboli generalizzata fa parte di SLIC. La tabella SIMBOLI può essere spinta e visualizzata fornendo simboli locali per le funzioni. I simboli appena creati sono catalogati nella tabella dei simboli in alto. La ricerca dei simboli cerca nello stack della tabella dei simboli dalla tabella superiore prima all'indietro nello stack.
Generazione di codice indipendente dalla macchina
Il linguaggio generatore di SLIC produce oggetti istruzione PSEUDO, aggiungendoli a un elenco di codici di sezioni. A .FLUSH fa eseguire il suo elenco di codici PSEUDO rimuovendo ogni istruzione PSEUDO dall'elenco e chiamandolo. Dopo l'esecuzione viene rilasciata una memoria degli oggetti PSEUDO. Gli organi procedurali delle azioni di PSEUDO e GENERATOR sono sostanzialmente la stessa lingua, tranne per il loro output. PSEUDO è pensato per fungere da macro di assemblaggio fornendo sequenzializzazione del codice indipendente dalla macchina. Forniscono una separazione della macchina target specifica dal linguaggio del generatore di scansione degli alberi. I PSEUDO chiamano le funzioni MACHOP per emettere il codice macchina. I MACHOP sono usati per definire pseudo-operazioni di assemblaggio (come dc, definire costanti ecc.) E istruzioni macchina o una famiglia di istruzioni simili formate usando l'immissione vettoriale. Trasformano semplicemente i loro parametri in una sequenza di campi di bit che compongono l'istruzione. Le chiamate MACHOP hanno lo scopo di assomigliare a assembly e fornire la formattazione di stampa dei campi per quando assembly viene mostrato nell'elenco di compilazione. Nel codice di esempio sto usando commenti in stile c che potrebbero essere facilmente aggiunti ma non nelle lingue originali. I MACHOP producono codice in una memoria indirizzabile a bit. Il linker SLIC gestisce l'output del compilatore. Un MACHOP per le istruzioni della modalità utente del DEC-10 usando una voce vettoriale: I MACHOP producono codice in una memoria indirizzabile a bit. Il linker SLIC gestisce l'output del compilatore. Un MACHOP per le istruzioni della modalità utente del DEC-10 usando una voce vettoriale: I MACHOP producono codice in una memoria indirizzabile a bit. Il linker SLIC gestisce l'output del compilatore. Un MACHOP per le istruzioni della modalità utente del DEC-10 usando una voce vettoriale:
.MACHOP #opnm register,@indirect offset (index): // Instruction's parameters.
.MORG 36, O(18): $/36; // Align to 36 bit boundary print format: 18 bit octal $/36
O(9): #opcd; // Op code 9 bit octal print out
(4): register; // 4 bit register field appended print
(1): indirect; // 1 bit appended print
(4): index; // 4 bit index register appended print
O(18): if (#opcd&&3==1) offset // immediate mode use value else
else offset/36; // memory address divide by 36
// to get word address.
// Vectored entry opcode table:
#opnm := MOVE, MOVEI, MOVEM, MOVES, MOVS, MOVSI, MOVSM, MOVSS,
MOVN, MOVNI, MOVNM, MOVNS, MOVM, MOVMI, MOVMM, MOVMS,
IMUL, IMULI, IMULM, IMULB, MUL, MULI, MULM, MULB,
...
TDO, TSO, TDOE, TSOE, TDOA, TSOA, TDON, TSON;
// corresponding opcode value:
#opcd := 0O200, 0O201, 0O202, 0O203, 0O204, 0O205, 0O206, 0O207,
0O210, 0O211, 0O212, 0O213, 0O214, 0O215, 0O216, 0O217,
0O220, 0O221, 0O222, 0O223, 0O224, 0O225, 0O226, 0O227,
...
0O670, 0O671, 0O672, 0O673, 0O674, 0O675, 0O676, 0O677;
Il .MORG 36, O (18): $ / 36; allinea la posizione a un limite di 36 bit stampando l'indirizzo $ / 36 word della posizione di 18 bit in ottale. L'opcd a 9 bit, il registro a 4 bit, il bit indiretto e il registro indice a 4 bit sono combinati e stampati come se fosse un singolo campo a 18 bit. L'indirizzo a 18 bit / 36 o il valore immediato viene emesso e stampato in ottale. Un esempio MOVEI stampato con r1 = 1 e r2 = 2:
400020 201082 000005 MOVEI r1,5(r2)
Con l'opzione assembly del compilatore si ottiene il codice assembly generato nell'elenco di compilazione.
Collegalo insieme
Il linker SLIC viene fornito come una libreria che gestisce le risoluzioni di collegamento e dei simboli. La formattazione del file di caricamento dell'output specifico di destinazione tuttavia deve essere scritta per i computer di destinazione e collegata alla libreria della libreria del linker.
Il linguaggio del generatore è in grado di scrivere alberi in un file e leggerli consentendo l'implementazione di un compilatore multipass.
Breve riepilogo della generazione e delle origini del codice
Ho esaminato prima la generazione del codice per assicurarmi che SLIC fosse un vero compilatore di compilatori. SLIC è stato ispirato da CWIC (compilatore per la scrittura e l'implementazione di compilatori) sviluppato presso la Systems Development Corporation alla fine degli anni '60. CWIC aveva solo i linguaggi SYNTAX e GENERATOR che producevano codice byte numerico fuori dal linguaggio GENERATOR. Il codice byte è stato inserito o inserito (il termine utilizzato nella documentazione CWIC) nei buffer di memoria associati alle sezioni denominate e scritti da un'istruzione .FLUSH. Un documento ACM su CWIC è disponibile negli archivi ACM.
Implementare con successo un importante linguaggio di programmazione
Alla fine degli anni '70, SLIC è stato utilizzato per scrivere un cross-compilatore COBOL. Completato in circa 3 mesi principalmente da un singolo programmatore. Ho lavorato un po 'con il programmatore, se necessario. Un altro programmatore ha scritto la libreria di runtime e i MACHOP per il target TI-990 mini-COMPUTER. Quel compilatore COBOL ha compilato sostanzialmente più righe al secondo rispetto al compilatore COBOL nativo DEC-10 scritto in assembly.
Più di un compilatore poi di solito parlava
Una grande parte della scrittura di un compilatore da zero è la libreria di runtime. Hai bisogno di una tabella dei simboli. Hai bisogno di input e output. Gestione dinamica della memoria ecc. Può essere più semplice scrivere la libreria di runtime per un compilatore e poi scrivere il compilatore. Ma con SLIC quella libreria di runtime è comune a tutti i compilatori sviluppati in SLIC. Nota: ci sono due librerie di runtime. Uno per la macchina target della lingua (COBOL per esempio). L'altra è la libreria di runtime dei compilatori di compilatori.
Penso di aver stabilito che questi non erano generatori di parser. Quindi ora con una piccola comprensione del back-end posso spiegare il linguaggio di programmazione del parser.
Linguaggio di programmazione parser
Il parser è scritto usando la formula scritta sotto forma di semplici equazioni.
<name> <formula type operator> <expression> ;
L'elemento linguaggio al livello più basso è il personaggio. I token sono formati da un sottoinsieme dei caratteri della lingua. Le classi di caratteri vengono utilizzate per nominare e definire quei sottoinsiemi di caratteri. L'operatore che definisce la classe di caratteri è i due punti (:). I personaggi membri della classe sono codificati sul lato destro della definizione. I caratteri stampabili sono racchiusi tra parentesi singole "stringhe". I caratteri non stampabili e speciali possono essere rappresentati dal loro ordinale numerico. I membri della classe sono separati da un'alternativa | operatore. Una formula di classe termina con un punto e virgola. Le classi di caratteri possono includere classi precedentemente definite:
/* Character Class Formula class_mask */
bin: '0'|'1'; // 0b00000010
oct: bin|'2'|'3'|'4'|'5'|'6'|'7'; // 0b00000110
dgt: oct|'8'|'9'; // 0b00001110
hex: dgt|'A'|'B'|'C'|'D'|'E'|'F'|'a'|'b'|'c'|'d'|'e'|'f'; // 0b00011110
upr: 'A'|'B'|'C'|'D'|'E'|'F'|'G'|'H'|'I'|'J'|'K'|'L'|'M'|
'N'|'O'|'P'|'Q'|'R'|'S'|'T'|'U'|'V'|'W'|'X'|'Y'|'Z'; // 0b00100000
lwr: 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|
'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'; // 0b01000000
alpha: upr|lwr; // 0b01100000
alphanum: alpha|dgt; // 0b01101110
Skip_class 0b00000001 è predefinito ma potrebbe essere fuori strada la definizione di skip_class.
In breve: una classe di caratteri è un elenco di alternative che può essere solo una costante di carattere, un carattere ordinale o una classe di caratteri precedentemente definita. Durante l'implementazione delle classi di caratteri: alla formula di classe viene assegnata una maschera di bit di classe. (Indicato nei commenti sopra) Qualsiasi formula di classe con caratteri letterali o ordinali determina l'allocazione di un bit di classe. Una maschera viene creata ordinando le maschere di classe delle classi incluse insieme al bit allocato (se presente). Una tabella di classi viene creata dalle classi di caratteri. Una voce indicizzata dall'ordinale di un personaggio contiene bit che indicano le appartenenze alla classe del personaggio. Il test di classe viene eseguito in linea. Un esempio di codice IA-86 con l'ordinale del personaggio in eax illustra i test di classe:
test byte ptr [eax+_classmap],dgt
Seguito da un:
jne <success>
o
je <failure>
Gli esempi di codice di istruzioni IA-86 sono usati perché penso che le istruzioni IA-86 siano oggi più conosciute. Il nome della classe che valuta la sua maschera di classe è AND non distruttivo con la tabella di classe indicizzata dai caratteri ordinali (in eax). Un risultato diverso da zero indica l'appartenenza alla classe. (EAX viene azzerato ad eccezione di al (gli 8 bit bassi di EAX) che contiene il carattere).
I token erano un po 'diversi in questi vecchi compilatori. Le parole chiave non sono state spiegate come token. Erano semplicemente abbinati alle costanti di stringa citate nel linguaggio parser. Le stringhe tra virgolette non vengono normalmente mantenute. I modificatori possono essere utilizzati. A + mantiene la stringa abbinata. (ie + '-' corrisponde a un carattere - mantenendo il carattere in caso di successo) L'operazione, (cioè, 'E') inserisce la stringa nel token. Lo spazio bianco viene gestito dalla formula token saltando i caratteri SKIP_CLASS principali fino a quando non viene effettuata una prima corrispondenza. Notare che una corrispondenza esplicita del carattere skip_class interromperà il salto consentendo a un token di iniziare con un carattere skip_class. La formula del token di stringa salta i caratteri skip_class iniziali che corrispondono a un carattere chiuso tra virgolette singole o una stringa tra virgolette doppie. Di interesse è la corrispondenza di un "carattere all'interno di" una stringa tra virgolette:
string .. (''' .ANY ''' | '"' $(-"""" .ANY | """""","""") '"') MAKSTR[];
La prima alternativa corrisponde a qualsiasi carattere tra virgolette singole. L'alternativa giusta corrisponde a una stringa tra virgolette doppie che può includere caratteri di virgolette doppie usando due caratteri "insieme per rappresentare un singolo" carattere. Questa formula definisce le stringhe utilizzate nella propria definizione. L'alternativa destra interna '"' $ (-" "" ".ANY |" "" "" "," "" ") '"' corrisponde a una stringa tra virgolette doppie. Possiamo usare un singolo carattere tra virgolette per abbinare un carattere tra virgolette doppie. Tuttavia all'interno della doppia stringa tra virgolette se vogliamo usare un carattere "dobbiamo usare due" caratteri per ottenerne uno. Ad esempio, nell'alternativa interna sinistra che corrisponde a qualsiasi carattere tranne una citazione:
-"""" .ANY
una sbirciatina negativa in avanti - "" "" viene utilizzato che quando ha esito positivo (non corrisponde a un "carattere) corrisponde quindi al carattere .ANY (che non può essere un" carattere perché - "" "" ha eliminato quella possibilità). L'alternativa giusta sta assumendo: "" "" abbinare un carattere "e fallire erano l'alternativa giusta:
"""""",""""
prova a far corrispondere due "caratteri sostituendoli con un singolo doppio" usando, "" "" per inserire il singolo carattere ". Entrambe le alternative interne che falliscono il carattere di citazione della stringa di chiusura vengono abbinate e MAKSTR [] ha chiamato per creare un oggetto stringa. sequenza, loop con esito positivo, l'operatore viene utilizzato nella corrispondenza di una sequenza. La formula token salta i caratteri della classe skip principale (con spazio). Una volta effettuata una prima corrispondenza skip_class il salto è disabilitato. È possibile chiamare le funzioni programmate in altre lingue utilizzando []. MAKSTR [], MAKBIN [], MAKOCT [], MAKHEX [], MAKFLOAT [] e MAKINT [] sono forniti con la funzione di libreria che converte una stringa di token corrispondente in un oggetto tipizzato. La formula numerica seguente mostra un riconoscimento token abbastanza complesso:
number .. "0B" bin $bin MAKBIN[] // binary integer
|"0O" oct $oct MAKOCT[] // octal integer
|("0H"|"0X") hex $hex MAKHEX[] // hexadecimal integer
// look for decimal number determining if integer or floating point.
| ('+'|+'-'|--) // only - matters
dgt $dgt // integer part
( +'.' $dgt // fractional part?
((+'E'|'e','E') // exponent part
('+'|+'-'|--) // Only negative matters
dgt(dgt(dgt|--)|--)|--) // 1 2 or 3 digit exponent
MAKFLOAT[] ) // floating point
MAKINT[]; // decimal integer
La formula del token numerico sopra riportata riconosce numeri interi e in virgola mobile. Le alternative - hanno sempre successo. Gli oggetti numerici possono essere utilizzati nei calcoli. Gli oggetti token vengono inseriti nello stack di analisi in caso di successo della formula. Il vantaggio dell'esponente in (+ 'E' | 'e', 'E') è interessante. Desideriamo avere sempre una E maiuscola per MAKEFLOAT []. Ma consentiamo una "e" minuscola che la sostituisce usando "E".
Potresti aver notato consistenze di classe di caratteri e formula di token. La formula di analisi prosegue aggiungendo alternative di backtracking e operatori di costruzione dell'albero. Gli operatori alternativi di backtracking e non di backtracking non possono essere mescolati all'interno di un livello di espressione. Potresti non avere (a | b \ c) che mescola senza backtrack | con alternativa \ backtracking. (a \ b \ c), (a | b | c) e ((a | b) \ c) sono validi. Un'alternativa \ backtracking salva lo stato di analisi prima di tentare la sua alternativa sinistra e in caso di errore ripristina lo stato di analisi prima di tentare la giusta alternativa. In una sequenza di alternative la prima alternativa di successo soddisfa il gruppo. Non vengono tentate ulteriori alternative. Il factoring e il raggruppamento prevedono un continuo avanzamento dell'analisi. L'alternativa backtrack crea uno stato salvato dell'analisi prima di tentare la sua alternativa sinistra. Il backtracking è necessario quando l'analisi può effettuare una corrispondenza parziale e quindi fallire:
(a b | c d)\ e
Quanto sopra in caso di errore di restituzione viene tentato il cd alternativo. Se poi c restituisce un errore, verrà tentata l'alternativa di backtrack. Se a ha esito positivo e b non riesce, l'analisi verrà retrocessa e verrà tentata. Allo stesso modo, un errore c ha esito positivo e b non riesce l'analisi viene ritracciata e l'alternativa e presa. Il backtracking non è limitato a una formula. Se una formula di analisi fa una corrispondenza parziale in qualsiasi momento e poi fallisce, l'analisi viene ripristinata al backtrack superiore e viene presa la sua alternativa. Un errore di compilazione può verificarsi se il codice è stato emesso nel senso che la backtrack è stata creata. Un backtrack viene impostato prima di iniziare la compilazione. Restituire un errore o tornare indietro è un errore del compilatore. I backtracks sono impilati. Possiamo usare il negativo - e il positivo? sbirciare / guardare avanti operatori per testare senza far avanzare l'analisi. essendo test delle stringhe è una sbirciatina in avanti che necessita solo dello stato di input salvato e ripristinato. Uno sguardo al futuro sarebbe un'espressione di analisi che crea una corrispondenza parziale prima di fallire. Uno sguardo al futuro è implementato usando il backtracking.
Il linguaggio parser non è né un parser LL né LR. Ma un linguaggio di programmazione per scrivere un parser decente ricorsivo in cui si programma la costruzione di alberi:
:<node name> creates a node object and pushes it onto the node stack.
.. Token formula create token objects and push them onto
the parse stack.
!<number> pops the top node object and top <number> of parstack
entries into a list representation of the tree. The
tree then pushed onto the parse stack.
+[ ... ]+ creates a list of the parse stack entries created
between them:
'(' +[argument $(',' argument]+ ')'
could parse an argument list. into a list.
Un esempio di analisi comunemente usato è un'espressione aritmetica:
Exp = Term $(('+':ADD|'-':SUB) Term!2);
Term = Factor $(('*':MPY|'/':DIV) Factor!2);
Factor = ( number
| id ( '(' +[Exp $(',' Exp)]+ ')' :FUN!2
| --)
| '(' Exp ')" )
(^' Factor:XPO!2 |--);
Exp e Term usando un loop crea un albero per mancini. Il fattore che utilizza la giusta ricorsione crea un albero per la mano destra:
d^(x+5)^3-a+b*c => ADD[SUB[EXP[EXP[d,ADD[x,5]],3],a],MPY[b,c]]
ADD
/ \
SUB MPY
/ \ / \
EXP a b c
/ \
d EXP
/ \
ADD 3
/ \
x 5
Ecco un po 'del compilatore cc, una versione aggiornata di SLIC con commenti in stile c. I tipi di funzione (grammatica, token, classe di caratteri, generatore, PSEUDO o MACHOP sono determinati dalla sintassi iniziale che segue il loro ID. Con questi parser top-down si inizia con una formula che definisce il programma:
program = $((declaration // A program is a sequence of
// declarations terminated by
|.EOF .STOP) // End Of File finish & stop compile
\ // Backtrack: .EOF failed or
// declaration long-failed.
(ERRORX["?Error?"] // report unknown error
// flagging furthest parse point.
$(-';' (.ANY // find a ';'. skiping .ANY
| .STOP)) // character: .ANY fails on end of file
// so .STOP ends the compile.
// (-';') failing breaks loop.
';')); // Match ';' and continue
declaration = "#" directive // Compiler directive.
| comment // skips comment text
| global DECLAR[*1] // Global linkage
|(id // functions starting with an id:
( formula PARSER[*1] // Parsing formula
| sequencer GENERATOR[*1] // Code generator
| optimizer ISO[*1] // Optimizer
| pseudo_op PRODUCTION[*1] // Pseudo instruction
| emitor_op MACHOP[*1] // Machine instruction
) // All the above start with an identifier
\ (ERRORX["Syntax error."]
garbol); // skip over error.
// Nota come ID viene scomposto e successivamente combinato durante la creazione dell'albero.
formula = ("==" syntax :BCKTRAK // backtrack grammar formula
|'=' syntax :SYNTAX // grammar formula.
|':' chclass :CLASS // character class define
|".." token :TOKEN // token formula
)';' !2 // Combine node name with id
// parsed in calling declaration
// formula and tree produced
// by the called syntax, token
// or character class formula.
$(-(.NL |"/*") (.ANY|.STOP)); Comment ; to line separator?
chclass = +[ letter $('|' letter) ]+;// a simple list of character codes
// except
letter = char | number | id; // when including another class
syntax = seq ('|' alt1|'\' alt2 |--);
alt1 = seq:ALT!2 ('|' alt1|--); Non-backtrack alternative sequence.
alt2 = seq:BKTK!2 ('\' alt2|--); backtrack alternative sequence
seq = +[oper $oper]+;
oper = test | action | '(' syntax ')' | comment;
test = string | id ('[' (arg_list| ,NILL) ']':GENCALL!2|.EMPTY);
action = ':' id:NODE!1
| '!' number:MAKTREE!1
| "+[" seq "]+" :MAKLST!1;
// C style comments
comment = "//" $(-.NL .ANY)
| "/*" $(-"*/" .ANY) "*/";
Da notare come il linguaggio del parser gestisce i commenti e il recupero degli errori.
Penso di aver risposto alla domanda. Dopo aver scritto gran parte del successore degli SLIC, il linguaggio cc in sé è qui. Non esiste ancora un compilatore. Ma posso compilarlo a mano in codice assembly, funzioni bare asm c o c ++.