Skip to content
This repository was archived by the owner on Nov 4, 2023. It is now read-only.

interpreter : readCommand.c

sᴀʟᴠᴀᴛᴏʀᴇ ʙ edited this page Jul 23, 2017 · 1 revision

L’acquisizione del comando e il relativo parsing per distinguerne i campi è un passo fondamentale, in particolare non essendo possibile prevedere la lunghezza della stringa digitata è stato scelto di sfruttare la memoria dinamica.

/*
 * Restituisce il comando digitato dall'utente "trimmato" (senza spazi ne' all'inizio ne' alla fine)
 */
char* readCommand() {
	char *str;
	int ch, size = 21, len = 0;
str = realloc(NULL, sizeof(char) * size); //20 caratteri disponibili + '\0'
	if (str == NULL) //errore allocazione spazio
		return NULL;

while ((ch = fgetc(stdin)) != EOF && ch != '\n') { //lettura fino a EOF o '\n'
		str[len++] = ch;
		if (len == size) {
str = realloc(str, sizeof(char) * (size += 16)); //rialloco 15 carateri + '\0'
			if (str == NULL) //errore allocazione spazio
				return NULL;
		}
	}
	str[len++] = '\0'; //Terminazione stringa
	str = realloc(str, sizeof(char) * len); 
//Rialloco esattamente lo spazio occupato
	str = trim(str); // elimino spazi iniziali e finali
	return str;
}

La funzione readCommand() restituisce la linea di testo digitato dall’utente eseguendo il trim della stringa (operazione implementata in utilities.c). Inizialmente si allocano 20 caratteri + 1 (‘\0’), i quali vengono incrementalmente aumentati di 15 caratteri se necessario: la funzione fgetc() permette di acquisire un carattere alla volta e quindi contarne la quantità. La stringa ottenuta viene riallocata in base all’effettiva lunghezza, viene “trimmata” ed infine restituita. La liberazione dello spazio occupato dal puntatore “char* str” (istanziato dal padre ma copiato anche nel figlio) verrà effettivamente eseguita nella funzione parseCommand() esposta sotto.

/*
 * Restituisce una array di stringhe, il numero di righe è pari al numero dei campi
 * del comando passato piu' una riga NULL finale (necessaria per execvp)
 */
char** parseCommand(char *command) {
	int num_of_args = countFields(command, ' ');
	char** args = malloc(sizeof(char*) * num_of_args);
	str_split(command, ' ', args);
	free(command); //free del comando istanziato in readCommand
	return args;
}
/*
 * Determina il numero di campi del comando str piu' una riga NULL
 * es: ls -la -> (ls)+(-la)+NULL = 3
 */
int countFields(char* str, const char token_delimiter) {
	int token_counter = 0;

	/* Conteggio di quanti token troviamo, scorrendo la stringa carattere  */
	while (*str) {
		if (token_delimiter == *str) {
			token_counter++;
		}
		str++;
	}
	//+2 per il comando e per il NULL finale
	token_counter += 2;
	return token_counter;
}
/*
 * Associa ad ogni riga di args un campo del comando input_string
 */
void str_split(char* input_string, const char token_delimiter, char** args) {
	int id_tok = 0;
	char delim[] = { token_delimiter, '\0' };
	char* token = strtok(input_string, delim);
	/* Preso il token successivo (strtok), controllo se e' valido*/
	while (token != NULL) {
		args[id_tok] = strdup(token);
		token = strtok(0, delim);
		id_tok++;
	}
	args[id_tok] = NULL;
}

Come spiegato più avanti nella sezione sequentialExec.c e parallelExec.c, la decisione di separare l’acquisizione della stringa e il parsing deriva anche dal seguente fatto: la readCommand() è chiamata dal processo padre, mentre ogni parseCommand() viene richiamata dal figlio sulla stringa ottenuta. Come esposto in questi articoli:

La memoria dinamica allocata da un processo viene automaticamente riacquisita dal SO quando interviene una chiamata exec(); dato che i figli devono allocare un numero imprevedibile di stringhe (pari al numero dei campi che viene determinato dalla funzione countFields() ) è necessario allocare nuovamente memoria e farlo fare al processo padre sarebbe stato più complicato (oltre che più oneroso dato che la prerogativa è che esso possa tornare quanto prima a ricevere il nuovo comando dell’utente). Visitare il paragrafo successivo per un’ulteriore approfondimento di questa scelta implementativa.

La funzione str_split() si affida alla funzione strtok() per delimitare i campi del comando: ovviamente ogni campo è suddiviso da uno o più spazi ‘ ‘ e tale carattere viene passato come argomento “token_delimiter”. (Nota: il comando viene accettato anche con spazi).

Ad ogni sotto-stringa individuata da strtok() viene eseguita la funzione strdup(): quest’ultima consente di allocare una quantità esatta di memoria atta a contenere la stringa puntata (riguardo la liberazione di tale memoria vedere il paragrafo successivo).

Infine in parseCommand() è presente la free() del puntatore istanziato in readCommand().

Clone this wiki locally