[pipe] Brainfuck… reloaded!

Anche questa verifica riguarda BF-, un subset del linguaggio di programmazione esoterico Brainfuck. Rimandiamo alla pagina della verifica precedente per una descrizione dei comandi supportati da BF-.

Il programma fb contenuto nell’archivio (link) ripete per un numero casuale di volte le seguenti operazioni:

  • invia su una pipe un programma BF- terminato dal carattere #;
  • legge da una seconda pipe l’output prodotto dal programma inviato e lo confronta con quello atteso.

I nomi delle pipe da cui leggere e scrivere sono passati al programma fb come argomenti da riga di comando usando rispettivamente le opzioni -i e -o. Se l’opzione -o non viene specificata, il programma scrive su standard output; se l’opzione -i non viene specificata, il programma legge da standard input.

L’obiettivo della verifica è di realizzare un programma che legge dalla prima pipe il programma inviato da fb, lo interpreta e scrive sulla seconda pipe l’output del programma. Terminata l’esecuzione di un programma, dovete inviare sulla pipe il carattere # e riportare l’interprete allo stato iniziale (tutta la memoria deve essere inizializzata a 0 e il data pointer deve puntare alla prima variabile della memoria). Questa sequenza di operazioni deve essere ripetuta per tutti i programmi inviati sulla pipe da fb.

Il programma fb supporta altre opzioni:

  • -b attiva la modalità avanzata che produce programmi contenenti i comandi < e >;
  • -v attiva la modalità verbosa;
  • -h mostra le opzioni supportate.

Per usare le pipe, il programma può essere invocato come segue:

> ./fb -i /tmp/pipeIn -o /tmp/pipeOut

Soluzione

Ecco una possibile soluzione commentata della verifica:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define PIPEIN  "/tmp/pipeIn"
#define PIPEOUT "/tmp/pipeOut"
#define MEMSIZE 16

/*
 * La verifica consiste nell'implementazione di un programma in C che interagisca
 * tramite pipe con il programma 'fb' che ci è stato fornito. Il programma 'fb'
 * invia su PIPEOUT un programma nel linguaggio BF- terminato dal carattere '#' e
 * si aspetta di ricevere su PIPEIN l'output prodotto dal programma inviato.
 * Questa interazione deve essere ripetuta per tutti i programmi ricevuti su
 * PIPEOUT.
 *
 * Il programma che è stato realizzato supporta tutti i comandi previsti dal
 * linguaggio BF- ed utilizza una memoria di dimensione pari a 16 byte. Il
 * programma legge un carattere per volta del programma BF- da interpretare
 * ed esegue l'operazione corrispondente al comando letto. Quando viene letto
 * il carattere '#', viene a sua volta inviato sulla pipe per segnalare che
 * l'esecuzione del programma BF- è terminato: la memoria e il data pointer
 * vengono azzerati per preparare l'interprete all'esecuzione del programma
 * successivo.
 *
 * Dopo aver avviato questo programma, il programma di test va eseguito come
 * segue:
 *
 *     ./fb -i /tmp/pipeIn -o /tmp/pipeOut -b
 */

int main() {
	/* La memoria è modellata come un vettore di byte di dimensione 16. */
	unsigned char memory[MEMSIZE] = {0};
	int fr, fw, dp;
	char c;

	/* Crea le due pipe se non esistono già. */
	mkfifo(PIPEIN, 0666);
	mkfifo(PIPEOUT, 0666);

	/* Apri PIPEIN in scrittura e PIPEOUT in lettura. Stampa un messaggio e
	 * termina se si verifica un errore durante l'apertura. */
	fw = open(PIPEIN, O_WRONLY);
	if (fw < 0) {
		fprintf(stderr, "Errore nell'apertura della pipe '%s' in scrittura.\n", PIPEIN);
		return EXIT_FAILURE;
	}
	fr = open(PIPEOUT, O_RDONLY);
	if (fr < 0) {
		fprintf(stderr, "Errore nell'apertura della pipe '%s' in lettura.\n", PIPEOUT);
		return EXIT_FAILURE;
	}

	/* Inizialmente il data pointer punta alla prima cella della memoria. */
	dp = 0;
	/* Leggi ed interpreta un comando per volta. Il ciclo termina quando la
	 * funzione 'read' restituisce zero, ovvero PIPEOUT è vuota ed è stata chiusa
	 * in scrittura dal programma 'fb'. */
	while(read(fr, &c, sizeof(char))) {
		if (c == '>') {
			/* Incrementa il data pointer modulo MEMSIZE. */
			dp = (dp+1) % MEMSIZE;
		} else if (c == '<') {
			/* Decrementa il data pointer modulo MEMSIZE. */
			dp = (dp-1+MEMSIZE) % MEMSIZE;
		} else if (c == '+') {
			/* Incrementa la cella di memoria indicata dal data pointer. */
			memory[dp]++;
		} else if (c == '-') {
			/* Decrementa la cella di memoria indicata dal data pointer. */
			memory[dp]--;
		} else if (c == '.') {
			/* Invia sulla pipe il valore della cella di memoria indicata dal
			 * data pointer sotto forma di carattere. */
			write(fw, memory+dp, sizeof(char));
		} else if (c == '#') {
			/* Invia il carattere '#' per segnalare la terminazione dell'esecuzione
			 * del programma. */
			write(fw, "#", sizeof(char));
			/* Azzera la memoria e riporta il data pointer alla posizione
			 * iniziale. */
			memset(memory, 0, MEMSIZE);
			dp = 0;
		} else {
			fprintf(stderr, "Carattere '%c' non riconosciuto.\n", c);
			return EXIT_FAILURE;
		}
	}

	/* Chiudi le pipe e rimuovile dal filesystem. */
	close(fr);
	close(fw);
	unlink(PIPEIN);
	unlink(PIPEOUT);

	return EXIT_SUCCESS;
}