/*
 *	Un simplu compilator pentru un limbaj minimal - Aer.
 *	(c) 2010, Vlad Dumitru
 *
 *	NOTE:	Limbajul (și compilatorul) au fost făcute doar ca un experiment,
 *			să pot vedea cât de mic poate fi un compilator pentru un limbaj
 *			destul de complet să se compileze singur.
 *		
*/

#include <stdio.h>
#include <stdlib.h>

FILE *fin, *fout;

int if_level = 0;
int line_count = 1;

void error ( char* s )
{
	printf ( "ac: %i: %s\n", line_count, s );
	exit ( 0 );
}

int main ( int argc, char* argv[] )
{
	int cc=0;		/*	cc ține caracterul curent al fișierului citit. */
	int ne=0;		/*	'ne' vine de la 'needs ending'; arată dacă e nevoie de
						cod suplimentar la sfârșitul unui cuvânt. */
	int m=0;		/*	m ține modul curent */
	int si=0;		/*	si ține indexul stringurilor, ca în assembly. */
	int ip=0, ii=0;	/*	pointerul către stivă */
	int wl=0;		/*	lungimea cuvântului */

	if ( argc < 3 )
	{
		printf ( "ac (c) Vlad Dumitru. usage: ac [input] [output]\n" );
		exit ( 0 );
	}
	
	 fin = fopen ( argv[1], "r" );
	fout = fopen ( argv[2], "w" );
	
	/*	Pentru că aer generează cod assembly pentru GAS, se folosește notația
		AT&T. */
		
	fprintf ( fout, "# ac output\n" );
	fprintf ( fout, ".text\n" );
	fprintf ( fout, "\t.globl main\n" );
	/* fprintf ( fout, "main:\n" ); */
	
	
	while ( feof ( fin ) == 0 )
	{
		
		cc = fgetc ( fin );
		
		if ( cc == EOF && if_level != 0 )
			error ( "Unbalanced parantheses." );
		
		if ( m == 0 )	/* modul normal */
		{
		
			if ( cc == '"' )
				/*	Acest caracter marchează începutul unui string; în assembly,
					trebuie delimitat spațiul pentru cod de cel pentru date */
			{
				/* modul 1 e modul pentru string-uri. */
				m = 1;
				/* începutul unui segment de date este marcat */
				fprintf ( fout,	".data\n\t_s%i:\t.asciz \"", si++ );
			}
			if ( cc == '{' )
				/*	Acest caracter marchează începutul unui bloc de cod
					assembly - conținutul său este copiat în fișierul
					compilat. */
			{
				/* modul 2 este modul assembly */
				m = 2;
				/* pentru a ajuta citirea, blocul este delimitat. */
				fprintf ( fout, "# inceput bloc asm\n" );
			}
			if ( cc == '(' )
				/* 	Acest caracter marchează începutul unui comentariu. */
			{
				/* Modul 4 este modul pentru comentarii. */
				m = 4;
			}
			
			if ( cc == '+' )
				/* Comandă care incrementează un integer. */
			{
				/* Are nevoie de un cuvânt (numele integer-ului) */
				m = 3;
				
				fprintf ( fout, "\tincl " );
			}
			if ( cc == '-' )
				/* Comandă care decrementează un integer. */
			{
				/* Are nevoie de un cuvânt (numele integer-ului) */
				m = 3;
				
				fprintf ( fout, "\tdecl " );
			}
			
			if ( cc == '[' )
				/*	Acest caracter marchează începutul unui bloc condițional.
					 */
			{
				fprintf ( fout, "\tcmpb $0, %%cl\n" );
				fprintf ( fout, "\tje %if\n", ii++ );
				if_level++;
			}
			if ( cc == ']' )
				/* Acest caracter marchează sfârșitul unui bloc condițional.
					 */
			{
				fprintf ( fout, "%i:\n" , --ii );
				if_level--;
			}
			
			/*	Următoarele 3 comenzi preiau elemente din stivă și le compară
				( a = b ) , ( a > b ), ( a < b ).	*/
			
			if ( cc == '=' )
			{
				fprintf ( fout, "\tpopl %%eax\n\tpopl %%ebx\n" );
				fprintf ( fout, "\tcmpl %%eax, %%ebx\n\tsete %%cl\n" );
			}
			if ( cc == '>' )
			{
				fprintf ( fout, "\tpopl %%eax\n\tpopl %%ebx\n" );
				fprintf ( fout, "\tcmpl %%eax, %%ebx\n\tseta %%cl\n" );
			}
			if ( cc == '<' )
			{
				fprintf ( fout, "\tpopl %%eax\n\tpopl %%ebx\n" );
				fprintf ( fout, "\tcmpl %%eax, %%ebx\n\tsetb %%cl\n" );
			}
			
			if ( cc == '!' )
				/*	Această comandă inversează valoarea din registrul %cl,
					registru folosit pentru a stoca valorile de adevăr ale
					operațiilor de comparare. */
				fprintf ( fout, "\tnot %%cl\n" );
			
			if ( cc == '$' )
				/*	Comandă care adaugă un număr în stivă. */
			{
				/*	Are nevoie de un cuvânt. Deși comanda este folosită pentru
					a adăuga numere în stivă, compilatorul nu verifică dacă
					cuvântul este număr întreg - astfel, orice poate fi scris
					în locul numărului. */
				m = 3;
				
				fprintf ( fout, "\tpushl $" );
			}
			
			if ( cc == '~' )
				/*	Comandă care atribuie registrului %eax o valoare. */
			{
				/* Are nevoie de un cuvânt și un sufix. */
				m = 3;
				ne = 3;
				
				fprintf ( fout, "\tmovl $" );
			}
			if ( cc == '`' )
				/*	Comandă care atribuie registrului %eax o valoare dintr-o
					variabilă definită înainte.	*/
			{
				/* Are nevoie de un cuvânt și un sufix. */
				m = 3;
				ne = 3;
				
				fprintf ( fout, "\tmovl " );
			}
			
			if ( cc == ':' )
				/*	Acest caracter marchează o etichetă - pentru salturi
					necondiționate */
			{
				/*	în asm, e nevoie de ':' după identificatorul etichetei. */
				ne = 1;
				m = 3;
			}
			if ( cc == ';' )
				/*	Comandă pentru salt necondiționat */
			{
				/* Are nevoie de un cuvânt (identificatorul etichetei) */
				m = 3;
				
				fprintf ( fout, "\tjmp " );
			}
			
			if ( cc == '@' )
				/* 	Comandă ce apelează la o funcție externă. */
			{
				/* Are nevoie de un cuvânt (identificatorul funcției) */
				m = 3;
				
				fprintf ( fout, "\tcall " );
			}
			
			if ( cc == '.' )
				/* Comandă pentru definirea unei variabile. */
			{
				/* Are nevoie de identificator și sufix. */
				m = 3;
				ne = 2;
				
				fprintf ( fout, ".data\n\t" );
			}
			if ( cc == ',' )
				/*	Comandă ce adaugă valoarea unei variabile în stivă. */
			{
				/* Are nevoie de un identificator. */
				m = 3;
				
				fprintf ( fout, "\tpushl " );
			}
			if ( cc == '^' )
				/*	Comandă ce mută valoarea registrului %eax într-o variabilă.
					*/
			{
				/* Are nevoie de identificator. */
				m = 3;
				
				fprintf ( fout, "\tmovl %%eax, " );
			}
			
			continue;
		}
		
		if ( m == 1 )	/* modul pentru string-uri */
		{
			if ( cc == '"' )
				/* Caracterul '"' marchează sfârșitul unui string. */
			{
				/* înapoi la modul 0 */
				m = 0;
				/* sfârșitul segmentului de date este marcat */
				fprintf ( fout, "\"\n.text\n" );
				/* pointer-ul string-ului este adăugat în stivă. */
				fprintf ( fout, "\tpushl $_s%i\n", si-1 );
			}
			else if ( cc >= 32 && cc <= 127 )
				/*	Orice alt caracter printabil este valid și astfel trece
					în segmentul de date al fișierului final. */
			{
				fprintf ( fout, "%c", cc );
			}
			
			continue;
		}
		
		if ( m == 2 )	/* modul assembly */
		{
			if ( cc == EOF )
				error ( "Expected '}'." );
			else if ( cc == '}' )
				/* Caracter ce marchează sfârșitul blocului. */
			{
				/* înapoi la modul 0 */
				m = 0;
				/* pentru a ajuta citirea, sfârșitul blocului este marcat */
				fprintf ( fout, "\n# asm block end.\n" );
			}
			else if ( cc >= 32 && cc <= 127 )
			{
				if ( cc == ';' )
					fprintf ( fout, "\n" );
				else
					fprintf ( fout, "%c", cc );
			}
			
			continue;
		}
		
		if ( m == 3 )	/* modul pentru cuvinte */
		{
			if ( cc == ' ' || cc == '\n' || cc == '\t' )
				/*	Orice caracter spațiu marchează sfârșitul unui cuvânt. */
			{
				if ( wl == 0 )
				{
					error ( "Zero length word." );
				}
				
				if ( cc == '\n' )
				{
					line_count++;
				}
				
				wl = 0;
			
				/* înapoi la modul 0, pentru cod normal */
				m = 0;
				
				/* Sufixuri */
				if ( ne == 1 )
					fprintf ( fout, ":\n" );
				else if ( ne == 2 )
					fprintf ( fout, ":\t.int 0\n.text\n" );
				else if ( ne == 3 )
					fprintf ( fout, ", %%eax\n" );
				else
					fprintf ( fout, "\n" );
					
				ne = 0;
			}
			else if ( cc >= 32 && cc <= 127 )
				/* Orice caracter printabil este valid. */
			{
				fprintf ( fout, "%c", cc );
				wl++;
			}

			continue;
		}
		
		if ( m == 4 )	/* modul pentru comentarii */
		{
			if ( cc == ')' )
				/*	Sfârșitul unui comentariu. */
			{
				m = 0;
			}
			continue;
		}
	}
	return 0;
}

