Interessante, solo un paio di giorni fa, stavo scrivendo un interprete brainf * ck in Java.
Uno dei problemi riscontrati era che la spiegazione dei comandi su official page non era sufficiente e non menzionava la parte relativa ai cicli nidificati. Il Wikipedia page on Brainf*ck ha una sottosezione Commands che descrive il comportamento corretto.
Fondamentalmente per riepilogare il problema, la pagina ufficiale indica quando un'istruzione è una [
e la posizione di memoria corrente è 0
, quindi passare al successivo ]
. Il comportamento corretto è quello di passare alla corrispondente]
, non alla successiva.
Un modo per ottenere questo comportamento è di tenere traccia del livello di nidificazione. Ho finito per implementare questo con un contatore che teneva traccia del livello di nidificazione.
Quello che segue è parte del ciclo principale dell'interprete:
do {
if (inst[pc] == '>') { ... }
else if (inst[pc] == '<') { ... }
else if (inst[pc] == '+') { ... }
else if (inst[pc] == '-') { ... }
else if (inst[pc] == '.') { ... }
else if (inst[pc] == ',') { ... }
else if (inst[pc] == '[') {
if (memory[p] == 0) {
int nesting = 0;
while (true) {
++pc;
if (inst[pc] == '[') {
++nesting;
continue;
} else if (nesting > 0 && inst[pc] == ']') {
--nesting;
continue;
} else if (inst[pc] == ']' && nesting == 0) {
break;
}
}
}
}
else if (inst[pc] == ']') {
if (memory[p] != 0) {
int nesting = 0;
while (true) {
--pc;
if (inst[pc] == ']') {
++nesting;
continue;
} else if (nesting > 0 && inst[pc] == '[') {
--nesting;
continue;
} else if (inst[pc] == '[' && nesting == 0) {
break;
}
}
}
}
} while (++pc < inst.length);
Ecco la leggenda per i nomi delle variabili:
memory
- celle di memoria per i dati.
p
- puntatore alla posizione corrente della cella di memoria.
inst
- un array con le istruzioni.
pc
- contatore di programma; punta all'istruzione corrente.
nesting
- livello di annidamento del loop corrente. nesting
di 0
significa che la posizione corrente non è in un ciclo annidato.
Fondamentalmente, quando si incontra un ciclo di apertura [
, la posizione di memoria corrente viene controllato per vedere se il valore è 0
. In questo caso, viene inserito un ciclo while
per passare allo ]
corrispondente.
Il modo in cui il nesting viene gestito è la seguente:
Se un [
si incontra durante la ricerca per il corrispondente ciclo di chiusura ]
, la variabile nesting
viene incrementato di 1
per indicare che abbiamo inserito un ciclo annidato.
Se si verifica un ]
, e:
a. Se la variabile nesting
è maggiore di 0
, la variabile nesting
viene decrementata di 1
per indicare che abbiamo lasciato un ciclo nidificato.
b. Se la variabile nesting
è 0
, allora sappiamo che è stata rilevata la fine del ciclo, quindi la ricerca della fine del ciclo nel ciclo while
viene terminata eseguendo un'istruzione break
.
Ora, la parte successiva è quella di gestire la chiusura del ciclo da ]
. Analogamente all'apertura del loop, utilizzerà il contatore nesting
per determinare il livello di annidamento corrente del loop e provare a trovare l'apertura del loop corrispondente [
.
Questo metodo potrebbe non essere il modo più elegante di fare le cose, ma sembra essere orientato alle risorse perché richiede solo una variabile extra da utilizzare come contatore per il livello di annidamento corrente.
(Naturalmente, "risorsa-friendly" sta ignorando il fatto che questo interprete è stato scritto in Java - Volevo solo scrivere del codice rapida e Java è capitato di essere quello che ho scritto in.)
@Gary, si prega di consultare la risposta aggiornata per due esempi implementazioni che utilizzano uno stack per il monitoraggio 'punti di salto'. Il primo è ricorsivo mentre il secondo è iterativo ma entrambi fanno la stessa cosa. Per favore fatemi sapere se qualcosa non è chiaro in modo che possa provare a chiarire! –
Grazie. –