brainfuck/README.md
2020-01-07 12:50:44 +01:00

8.1 KiB

Brainfuck

Dans ce dépot, il y a un interpréteur de brainfuck, ainsi qu'un compilateur depuis un langage minimal vers du brainfuck

L'interpréteur

Vous pouvez utiliser l'interpréteur de plusieurs manière :

  • brainfuck.py FILE va vous permettre d'exécuter le code contenu dans le fichier spécifié.
  • brainfuck.py -i CODE vous permet d'exécuter le code passé directement en argument exemple : brainfuck.py -i ">+++++++++++++++++++++++++++++++++++++." va afficher % et quitter
  • brainfuck.py : si vous appelez le programme sans argument, il va lancer une session interactive

Le brainfuck

Le brainfuck est un langage ésotérique créé en 1993 par Urban Müller, qui ne s'écrit qu'avec 8 caractères. Pour plus d'informations, veuillez consultez wikipédia, ou la page d'esolang dédié.

Il n'y pas de spécificatons officiel du brainfuck, mais cet interpréteur à une implémentation "nice"

Cet interpréteur a plusieurs particularité, par rapport à d'autres implémentations du brainfuck :

  • Le tableau est infini vers la droite, tenter d'aller avant la case 0 provoque un crash
  • Les entiers utilisés sont des big-int : pas de limite supérieure, et donc pas d'overflow. Par exemple, [+], contrairement à certaines implémentations, ne va pas remettre la case à 0, mais boucler indéfinimment.
  • Les entiers ne peuvent pas être négatif : si un code tente de le faire, le programme va crasher.
  • On peut utiliser # pour debug : lorsque l'interpréteur exécutera cette instruction, il affichera l'état de la mémoire (avec l'emplacement du pointeur, et la valeur de la case pointée) on peut écrire #(message) pour afficher le message avant les informations de debug. Attention : mettre des crochets ([ ou ]) dans le message est undefined behavior.

Le REPL

Dans le REPL, vous pouvez écire vos instructions en brainfuck de manière interactive. Vous écrivez le code, vous appuyez sur Entrée, et le code est exécuté. Vous pouvez à tout moment interrompre l'exécution en tapant Ctrl+C. Il y a plusieurs commande possibles dans l'interpréteur, en plus de n'importe quel code brainfuck valide :

  • en tapant a, vous pouvez voir la mémoire du programme.
  • en tapant p, l'emplacement du pointeur
  • en tapant v, la valeur de la case pointée (Si on veut les trois informations en même temps, il suffit de faire #)
  • En tapant r, on réinitialise le tableau et le pointeur (plus rapide que de fermer et réouvrir)
  • en tapant help, on affiche un message d'aide

Si vous entrez un End-Of-File (Ctrl+Z pour Windows, Ctrl+D sinon), le programme se ferme.

Le compilateur

L'utilisation du compilateur est simple : to_bf_compiler.py FILE va compiler le fichier passé en entrée.

Si vous passez prog.mg, le compilateur sortira le fichier prog.bf.

Le langage

Ce langage ne permet d'utiliser que des entiers pour le moment, et supporte les opérateurs arithmétiques usuels.

La syntaxe du langage est décrite de manière formelle dans le fichier grammar.ebnf.

Ce langage ne comporte qu'un seul type de donnée : les entiers. Ces entiers sont de même type que ceux utilisés dans l'implémentation du brainfuck.

On peut écrire un entier de pluesieurs manières :

  • En écrivant sa valeur : 100, 1_000_000, ...
  • En écrivant un caractère : 'C', '0'...

Déclaration de variable

Pour déclarer une variable, il vous suffit d'écrire var a;, ou var a,b;.

Pour affecter une valeur à une variable, c'est très simple : a = var, où var est une expression.

Des structures de controle usuelles

Le If (avec ou sans else),

if (condition) {
	//code_block
} else {
	//Code block
}

et le while :

while (condition) {
	//code_block
}

Il n'y a pour l'instant pas de commande pour faire des sauts (pas de break, etc.)

Les fonctions

vous pouvez déclarer des fonctions ainsi :

def add(a,b) {
	return a + b;
} 

La fonction n'arrète pas son exécution au return automatiquement !!! Le return doit impérativement être la dernière instruction, et ne doit pas être dans un block. Par exemple, la fonction suivante est invalide :

def est_pair(a) {
	if (a%2 == 0) {
		return 1;
	} else {
		return 0;
	}
}

mais la fonction

def est_pair(a) {
	var res;
	if (a%2 == 0) {
		res = 1;
	} else {
		res = 0;
	}
	return res;
}

est valide. (Dans ce cas, on aurait juste pu mettre return a%2 == 0;)

Pour appeler une fonction, il vous suffit d'écrire f(x) . Si vous voulez appeler la fonction est_pair, vous pourrez par exemple écrire :

if (est_pair(a)) {
	//Do something
}

Attention : Appeler une fonction avec un nombre d'argument différend de celui utilisé dans la déclaration est undefined behavior.

Il n'y a pas de concept de variables globales : on ne peut utiliser que des variables locales, dans la fonction. Il est possible (bien qu'inutile) de déclarer une fonction dans une fonction. La seule différence est qu'elle ne peut pas être appelé depuis l'extérieur.

Ex :


def a() {
	def b() {
		c(); //Valide
	}
	c(); //Valide
}

def c() {
	//Do something
}

def d() {
	c(); //Valide
	a(); //Valide
	b(); //Non valide
}

Attention : Aucune récursivité n'est possible !!! Cela fera tourner indéfiniment le compilateur.

Les fonctions en brainfuck :

Vous pouvez également inclure directement du brainfuck :

bf add {{	//2
	[-<+>]<
	}}

Lors de l'appel d'une fonction brainfuck, tout les arguments sont mis dans l'orde d'appel dans le tableau, et le pointeur pointe sur le dernier élément. La fonction doit laisser derrière elle que des cases vides, et la première case (ou il y avait le premier argument) est la valeur de retour. Il est donc essentiel d'appeler la fonction brainfuck avec le bon nombre d'argument.

Opérateurs utilisables :

Actuellement, on peut utiliser les opérateurs suivants (rappel : il n'y a qu'un seul type, l'entier).

  • a + b : somme de a et b
  • a - b : différence de a et de b. Attention : si b > a, le programme crash.
  • a * b : produit de a et de b
  • a / b : quotient de a et de b (division entière bien sur)
  • a % b : reste de la division euclidienne de a par b
  • a == b : vaut 1 si a = b, 0 sinon
  • On peut aussi utiliser a != b, a > b, a >= b, a < b, et a <= b

Pour l'instant, il n'y a pas d'opérateur de logique booléenne (&&, ||), ni d'opérateur comme &, ...

Les import

Vous pouvez écrire une déclaration d'import ainsi : import "my_file.mg", avec my_file.mg le chemin vers le fichier en question. Vous pouvez également mettre un chemin absolu.

Un import fonctionne de manière simple : il simule un copier-coller du fichier importé dans le fichier qui fait l'appel

La stdlib

Le compilateur va avec une librairie standard. Pour l'utiliser, il faut bien penser à écrire import "/path/to/stdlib.mg" dans votre fichier.

la librairie standard comporte pour l'instanc seulement 4 fonctions :

  • La fonction putc(val). Elle affiche le caractère représentée par la valeur de val.
  • La fonction scanc(). Elle récupère un caractère depuis l'entrée standard.
  • La fonction debug(). Elle affiche les informations de debug. (équivalent à mettre # dans le brainfuck). Cette instruction ne fonctionnera que si l'implémentation du brainfuck comprend le #.
  • La fonction print_int(val). Cette fonction va afficher la représentation décimal du nombre passé en argument.

Utilisation

Pour utiliser ce programme, il vous faudra une version suffisemment récente de python. Ce programme n'a été testé que sur python 3.7 et 3.8, mais devrait fonctionner pour quelques version précédentes, et pour les suivantes.

La seule dépendance est le module termcolor, qui sert à afficher certains messages en couleur. Pour l'installer : pip3 install termcolor, ou py -m pip install termcolor sur Windows.