#include <iostream> // cin, cout et cerr
#include <fstream>  // ifstream
#include <sstream>  // istringstream
#include <iomanip>  // setw(...) et setfill(...)
#include <string>   // Chaînes de caractères

using namespace std ;

// Conversion d'une chaîne de caractères en un entier naturel.
inline size_t
txt2size_t(const string & texte)
{
	istringstream lecture(texte) ;
	size_t i ; lecture >> i ; // Il faudrait vérifier qu'il n'y a pas d'erreurs.
	return i ;
}
// Erreur fatale : le message est affiché et le programme se termine avec un code de 
// retour égal à 1.
inline void
fatal(const string & message)
{
	cerr << "Erreur fatale, message : " << message << '.' << endl ;
	exit(1) ;
}

// Les variables suivantes sont globales, pour être vues depuis la fonction `afficher(...)'.
size_t rg ; // Registre général.
size_t sp ; // Stack pointer,  initialisé à 0.
size_t pc ; // Program counter, initialisé à 0.
size_t memoire[100] ; // Représentation de la mémoire, initialisée à 0.

int mem_debut = -1 ; // Adresse du premier emplacement de la mémoire à afficher.
size_t mem_fin ; // Adresse du dernier emplacement de la mémoire à afficher.

// Cette fonction permet d'obtenir les sorties du mode pas à pas.
void
afficher(
  size_t pas, // Pas du programme.
  const string & explication, // Libellé pour l'instruction en cours d'exécution.
  size_t code, // Code de base de l'instruction.
  int SdE, // Code du service appelé si appel du système d'exploitation.
  int adresse, // Adresse de l'opérande si l'instruction comporte un tel opérande.
  int deplacement) // Déplacement pour les instructions qui utilisent la pile.
{
	cerr << pas << '\t' << code ;
	// SdE, adresse et deplacement sont égales à -1 si elles ne sont pas pertinentes.
	if ( SdE != -1 ) {
		cerr << ' ' << SdE ; }
	else if ( adresse != -1 ) {
		cerr << ' ' << setw(2) << setfill('0') << adresse ; }
	else if ( deplacement != -1 ) {
		cerr << ' ' << deplacement ; }
	cerr << '\t' << explication << '\t' << rg << '\t' <<
	   setw(2) << setfill('0') << pc << '\t' << setw(2) << setfill('0') << sp ;
	// mem_debut est égale à -1 s'il n'est pas nécessaire d'afficher le contenu d'une région
	// de la mémoire.
	if ( mem_debut != -1 ) {
		for ( size_t i = mem_debut ; i <= mem_fin ; ++ i ) {
			cerr << '\t' << memoire[i] ; } }
	cerr << endl ;
}
int
main(int argc, char *argv[])
{
	if ( (argc != 2) && (argc != 4) ) {
		fatal("Appel invalide du programme `" + string(argv[0]) + "'") ; }

	// Lecture du fichier qui contient le programme exécutable.
	ifstream fichier(argv[1]) ;
	if ( ! fichier ) {
		fatal("Echec de l'ouverture en lecture du fichier `" + string(argv[1]) + "'") ; }
	while ( sp < 100 )
	{
		char tmp ;
		// Lecture d'un caractère dans le fichier ; l'expression est égale à non si l'opé- 
		// ration n'a pas abouti (fin de fichier ou erreur de lecture).
		if ( fichier >> tmp ) {
			// Un chiffre, OK ; le codage des chiffres, a priori, satisfait toujours la
			// règle selon laquelle ils sont codés consécutivement.
			if ( ('0' <= tmp) && (tmp <= '9') ) {
				memoire[sp++] = tmp - '0' ; }
			// Une fin de ligne ou un espace, à ignorer.
			else if ( (tmp == '\n') || (tmp == ' ') ) {
				; }
			else {
				fatal("Programme, contenant un caractère étrange, invalide") ; } }
		else {
			break ; }
	}
	if ( ! fichier.eof() ) {
		fatal("Programme, de trop grande taille, invalide") ; }
	if ( sp == 0 ) {
		fatal("Programme, de taille nulle, invalide") ; }

	// Lecture des deux paramètres facultatifs qui permettent d'afficher le contenu 
	// d'une région de la mémoire.
	if ( argc == 4 ) {
		mem_debut = txt2size_t(argv[2]) ; mem_fin = txt2size_t(argv[3]) ;
		if ( (mem_fin < mem_debut) || (mem_fin <= 0) || (mem_debut >= 99) ) {
			fatal("Valeurs invalides des paramètres de la région à afficher") ; } }

	// Première ligne des sorties du mode pas à pas.
	cerr << "Pas\tCode\tExplication\tRG\tPC\tSP" ;
	if ( mem_debut != -1 ) {
		for ( size_t i = mem_debut ; i <= mem_fin ; ++ i ) {
			cerr << '\t' << i ; } }
	cerr << endl ;

	size_t pas = 1 ;
	while ( true )
	{
		size_t code = memoire[pc] ;
		int SdE = -1 ;
		int adresse = -1 ;
		int deplacement = -1 ;
		string explication ;
		size_t tmp ;
		switch ( memoire[pc++] )
		{
			// Appeler le système d'exploitation.
			case 0 : explication = "Appeler le SdE" ;
			switch ( SdE = memoire[pc++] ) {

				// Fin du programme.
				case 0 : afficher(pas, explication, code, SdE, adresse, deplacement) ;
				exit(rg) ;

				// Lecture d'un entier naturel depuis std::cin.
				case 1 : cin >> rg ;
				if ( rg > 9 ) {
					fatal("Lecture d'un nombre invalide") ; }
				break ;

				// Ecriture d'un entier naturel vers std::cout.
				case 2 : cout << rg ;
				break ;
 
				// Ecriture d'un entier naturel vers std::cerr.
				case 3 : cerr << rg ;
				break ;

				default : fatal("Appel d'un service invalide du système d'exploitation") ;
				}
			break ;

			// Charger le registre général.
			case 1 : explication = "Charger le RG" ;
			adresse = memoire[pc]*10 + memoire[pc+1] ;
			rg = memoire[adresse] ;
			pc += 2 ;
			break ;

			// Stocker le contenu du registre général.
			case 2 : explication = "Stocker le RG" ;
			adresse = memoire[pc]*10 + memoire[pc+1] ;
			memoire[adresse] = rg ;
			pc += 2 ;
			break ;

			// Soustraire.
			case 3 : explication = "Soustraire" ;
			adresse = memoire[pc]*10 + memoire[pc+1] ;
			if ( rg < memoire[adresse] ) {
				fatal("Débordement inférieur de capacité du registre général") ; }
			rg -= memoire[adresse] ;
			pc += 2 ;
			break ;

			// Exécuter l'instruction d'adresse `adresse'.
			case 4 : explication = "Branchement inconditionnel" ;
			pc = adresse = memoire[pc]*10 + memoire[pc+1] ;
			break ;

			// Exécuter l'instruction d'adresse `adresse' si le registre général n'est pas
			// égal à zéro.
			case 5 : explication = "Branchement si RG != 0" ;
			adresse = memoire[pc]*10 + memoire[pc+1] ;
			pc = (rg != 0) ? adresse : pc+2 ;
			break ;

			// Charger le registre général depuis la pile.
			case 6 : explication = "Charger le RG depuis la pile" ;
			deplacement = memoire[pc++] ;
			if ( sp + deplacement > 99 ) {
				fatal("Adresse, trop grande, invalide") ; }
			rg = memoire[sp + deplacement] ;
			break ;

			// Stocker le contenu du registre général vers la pile.
			case 7 : explication = "Stocker le RG vers la pile" ;
			deplacement = memoire[pc++] ;
			if ( sp + deplacement > 99 ) {
				fatal("Adresse, trop grande, invalide -- la pile déborde") ; }
			memoire[sp + deplacement] = rg ;
			break ;

			// Appeler une fonction (elle ne comporte qu'un seul argument).
			case 8 : explication = "Appeler une fonction" ;
			if ( sp + rg + 3 > 99 ) {
				fatal("Débordement de la pile lors de l'appel d'une fonction") ; }
			// Sauvegarde de l'argument.
			tmp = memoire[sp + rg] ;
			// Ecriture dans la pile de l'adresse de retour.
			memoire[sp + rg] = pc / 10 ; memoire[sp + rg + 1] = pc % 10 ;
			// Ecriture dans la pile du nombre de locaux à ne pas écraser.
			memoire[sp + rg + 2] = rg ;
			// Ajustement du pointeur de la pile.
			sp += rg + 3 ;
			// Ecriture dans la pile de l'argument.
			memoire[sp] = tmp ;
			// Exécution de la première instruction de la fontion.
			pc = adresse = memoire[pc]*10 + memoire[pc+1] ;
			break ;

			// Retourner (depuis une fonction).
			case 9 : explication = "Retourner" ;
			tmp = memoire[sp-1] ;
			// Restauration de l'ancienne valeur du pointeur de pile.
			sp -= tmp + 3 ;
			if ( sp < 0 ) {
				fatal("Corruption de la mémoire, le retour est invalide") ; }
			// Reprise du flux d'instruction.
			pc = memoire[sp + tmp]*10 + memoire[sp + tmp + 1] + 2 ;
			break ;
		} // Fin du `switch ( memoire[pc++] )'.
		afficher(pas, explication, code, SdE, adresse, deplacement) ;
		++ pas ;
	}          // Fin du `while ( true )'.
	return 0 ; // Retour au système d'exploitation avec le code `0'.
}            // Fin de `main(...)'
