.gitignore | ||
brainfuck.py | ||
grammar.ebnf | ||
op.bf | ||
README.md | ||
stdlib.mg | ||
to_bf_compiler.py |
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 quitterbrainfuck.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 ba - b
: différence de a et de b. Attention : si b > a, le programme crash.a * b
: produit de a et de ba / b
: quotient de a et de b (division entière bien sur)a % b
: reste de la division euclidienne de a par ba == b
: vaut 1 si a = b, 0 sinon- On peut aussi utiliser
a != b
,a > b
,a >= b
,a < b
, eta <= 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.