Meta-language used to generate code stubs in a variety of real-world programming languages.
Clone or download
Latest commit 399a9f1 Oct 7, 2018
Permalink
Failed to load latest commit information.
Stdlib fixing count for cpp Mar 18, 2018
js new javascript demo (removing useless files + new syntax for dom_html) Mar 17, 2018
out updating tests Oct 7, 2018
src fixing readcharline in LUA Oct 7, 2018
tests adding a test for count Mar 18, 2018
tools updating emacs mode Apr 13, 2017
.gitignore gitignore fix Mar 20, 2015
.merlin adding a .merlin Mar 20, 2015
LICENCE licence changes Apr 3, 2012
LICENCE_HEADER various licence changes Apr 13, 2017
Makefile Makefile: fix colorization of testsuite Sep 23, 2018
README.rst mise à jour des dépendances Oct 7, 2018
VERSIONS.rst updating VERSIONS.rst Mar 17, 2018
_tags removing a dependency to module Str Mar 17, 2018
addtest.sh fixing add test script Oct 8, 2017
cmpAll.sh new option for tests scripts : we can choose our language Apr 3, 2012
docintro.txt adding some untracked files Apr 21, 2013
gitstats.plot fixing stats script Aug 10, 2014
gitstats.sh stats script update : relative chars counting Aug 23, 2014
langs.png fixing stats script Aug 10, 2014
metalang.odocl refactoring Prog module Aug 8, 2012
metalang2prologin.sh tools for prologin Feb 23, 2015
metalecheAll.sh tools for prologin Feb 23, 2015
myocamlbuild.ml renaming module Stdlib in Ext Oct 8, 2017
proloMake prologin : adding a script to test demies Jan 21, 2015
reindent.sh reindent all May 24, 2014
size.png fixing stats script Aug 10, 2014
stats.sh fixing stats script Oct 8, 2017
statstime.sh statistics Aug 21, 2014
testDemieProlo.sh prologin : adding a script to test demies Jan 21, 2015
todo updating tests Apr 13, 2017

README.rst

Metalang

C'est quoi ?

Metalang est un langage de programmation. Il permet d'écrire une fois un code et d'obtenir le même programme dans plusieurs autres langages (voir plus bas)

Le blog du projet se trouve ici : A [eelte.megami.fr](http://eelte.megami.fr).

Ce langage a été conçu pour ce A [concours](http://prologin.org).

Hello World

Voici le programme helloworld.metalang

main
        print "Hello World"
end

Dépendances :

  • ocaml >= 4.04.0
  • menhir
  • ocamlbuild

Quick start

ouvrir une console, aller dans le dossier metalang, taper

make metalang
./metalang tests/prog/aaa_01hello.metalang

Le hello world apparait alors dans les différents langages.

Pour lancer metalang en mode évaluateur

./metalang -eval fichier.metalang

Pour générer uniquement une langue

./metalang -lang lang1,lang2,...

Les langues possibles sont : adb pour ADA, c, cc pour le C++, cl pour le common lisp, cpp pour un c++ version prologin (les accès mémoire sont buggés sur certains tests mais pour les tests prologin ça passe et c'est plus lisible), cs pour C#, fs pour forth, fsscript pour F#, fun.ml est une version écrite dans un style fonctionnel en ocaml, go, groovy, hs pour haskell, java, js pour javascript, lua, m pour objective-c, ml pour ocaml en style impératif, pas pour pascal, php, pl pour perl, py pour python 3, rb pour ruby, rkt pour racket, scala, st pour smalltalk, vb pour visual basic .Net]

L'option -o vous permet de choisir le dossier de destination. Comme nous générons plusieurs fichiers, on ne peut pas choisir le nom des fichiers générés.

pour afficher l'aide

./metalang -help

pour afficher la version

./metalang -version

Modes

Pour vous permettre une édition plus confortable, nous fournissons quelques modes pour vos éditeurs préférés :

Le dossier tools contient des outils comme le mode emacs et un mode vim, pour installer le mode emacs, il faut ajouter ces lignes dans son .emacs:

(setq auto-mode-alist (cons '("\\.metalang$" . metalang-mode) auto-mode-alist))
(autoload 'metalang-mode "CHEMIN_ABSOLU/tools/metalang-mode.el" "Mode majeur pour éditer du code Metalang" t)

Exemples

Vous trouverez de nombreux exemples de programmes dans le dossier tests/progs le plus notable est probablement le tictactoe avec une IA. En tapant make testCompare, on peut lancer ces tests, et comparer les résultats dans les différents langages Si vous souhaitez vérifier que votre programme metalang génère bien le même programme dans tous les langages, vous pouvez placer votre programme dans le dossier tests/prog accompagné d'un fichier {programme}.in pour l'entrée standard et taper

make out/{programme}.test

l'effet est de recompiler metalang si besoin, de générer les différents langages cibles et de comparer les sorties

HOWTO Metalang

Dans le cadre du concours prologin, on a besoin d'écrire des codes à compléter. Ces codes lisent des entrées, et appellent une fonction "vide" que le candidat devra remplir.

Dans un premier temps, la structure d'un code metlang pour prologin est la suivante

def ...1 la_fonction_a_completer( ...2 )
end
main
  ...3
end
  • dans ...1 on écrit le type de retour de la fonction. Souvent void ou int.
  • dans ...2 on écrit les paramètres comme en C : type1 nom_de_l_argument1, type2 nom_de_l_argument2, ...
  • dans ...3 on écrit le code de parsing, et de quoi appeller la fonction, et afficher son résultat.

En métalang, on a trois primitives de parsing : une qui lit un entier, une qui lit un char, et une qui saute les espaces Respectivement :

  • read int variable
  • read char variable
  • skip

Ces trois méthodes peuvent générer du code dégueu dans certains langages (ceux qui n'ont pas scanf, donc python, C#, php, etc...)

Pour éviter ce problème, on a plusieurs fonctions alternatives dans la lib standard : read_int et read_int_line. Il ne FAUT PAS melanger ces deux façons de parser, sinon ça risque de faire tout planter (le compilateur vérifie ça pour certains langages).

En metalang, on ne peut pas savoir quelle est la taille d'une ligne, donc on ne peut pas parser une ligne et récupérer un tableau d'entier de taille variable. On ne peut pas non plus le faire pour une chaine de caractères. La fonction read_int_line prend en paramètre le nombre d'entiers à parser.

Les exemples suivant présentent du code du même type que certains des codes à compléter pour les demies finales :

  • tests/prog/prologin_template_2charline2.metalang
  • tests/prog/prologin_template_charmatrix.metalang
  • tests/prog/prologin_template_2charline.metalang
  • tests/prog/prologin_template_intlist.metalang
  • tests/prog/prologin_template_charline.metalang
  • tests/prog/prologin_template_intmatrix.metalang

Normalement, avec cet outil, vous avez les moyens de faire des codes à compléter. N'oubliez pas de tester vos codes générés (en printant simplement une addition des valeurs parsées par exemple).

Types simples manipulables

Les types simples que metalang gère sont les entiers (notés int), les booleans (notés bool) les chars et les chaines de caractères (notés string). Il n'existe pratiquement aucune fonction pour manipuler les chaines de caractères. Elles n'existent que pour l'instruction print. Le type int représente des entiers, leur taille n'est pas définie. En C ils font 32 bits, en ocaml 31 bits, en python ce sont des bigints. Le type char représente un caractère, parfois en unicode, parfois signé, parfois non.

Il n'existe pas de type float en metalang. Les conversions automatiques entre deux types ne sont pas possibles en metalang. Pour convertir un char en int, il faut utiliser la fonction int_of_char.

Null n'existe pas en metalang.

Commentaires

Il existe deux types de commentaires :

  • Les commentaires sont compris entre /* et */ sont retranscrits dans les codes générés. Ils doivent etre placés comme des instructions.
  • après le caractère #, la fin de la ligne est ignorée. Ces commentaires là ne sont pas retranscrits dans les codes générés.

Declaration de variables

Une variable doit toujours avoir une valeur. Pour définir un entier x de valeur 42, on fait

def int x = 42

On est pas obligé de définir le type : une passe de typage s'arrangera pour l'inférer.

def x = 42

Lorsque l'on veut déclarer une variable et lire sa valeur depuis l'entrée standard en même temps, on peut taper

def read int x

ou encore

def read x

Declaration de tableaux

Pour définir un tableau, rien de plus simple

def array<type> tab[taille] with variable do /* instructions */ return valeur end

Cette syntaxe correspond plus ou moins aux Array.init d'ocaml. Dans les autres langage, ce code est compilé vers une boucle for pour l'initialisation ::

  type *tab = calloc(taille, sizeof(type));
  for (variable = 0; variable < taille; variable++)
  {
    /* instructions */
    tab[variable] = valeur;
  }

Declaration de struct

Pour définir une struct

record @nom_de_la_struct
  field1 : type1
  field2 : type2
end

Ensuite, la structure a pour nom @nom_de_la_struct. Pour définir une variable de type @nom_de_la_struct

def variable = record
  field1 = valeur1
  field2 = valeur2
end

Pour récupérer ou affecter la valeur du champ 1, on utilise variable.field1

Deux structures doivent avoir des noms de champs distincts.

Declaration d'enums

Pour définir un enum

enum @foo_t
  Foo Bar Blah
end

Ensuite, le type a pour nom @foo_t et on utilise Foo, Bar Blah comme des valeurs La majuscule est obligatoire.

Fonctions

Les fonctions ne peuvent pas être mutuellement récursives, mais les fonctions récursives ne posent aucun problème. Les arguments sont passés par valeur pour les entiers, enum et chars, et par référence pour les tableaux et structures.

Pour définir une fonction

def type_de_retour nom_fonction(type1 param1, type2 param2)
  ...
end

Pour renvoyer une valeur

return valeur

Une fonction qui renvoie quelque chose doit forcément avoir un return dans chaque chemin d'execution (comme en java).

Il est impossible de mettre un return dans une fonction qui renvoie void.

Boucles

Les boucles for ont pour syntaxe

for variable = debut to fin do
  ...
end

L'incrémentation ne peut pas être définie. Elle est toujours égale à 1. Attention : la boucle for déclare une nouvelle variable read-only.

Les boucles while ont pour syntaxe

while condition do
  ...
end

Les instructions break et continue n'existent pas en metalang. Cependant, vous pouvez utiliser return à l'interieur de ces boucles pour les fonctions qui ne renvoient pas void.

If Then Else

La syntaxe est

if ... then
  ...
elsif ... then
  ...
else
  ...
end

Print

L'instruction print vous permet d'écrire sur la sortie standard:

print "foo"
def x = 42
print x print "\n"

Print permet d'afficher des chaines, des entiers et des chars.

Librairie Standard

La librairie standard contient un enum

enum @target_language
  LANG_C
  LANG_Cc
  LANG_Cl
  LANG_Cs
  ...
end

Cet enum permet d'écrire des macros, il y a une valeur par extension que metalang génère.

Elle comprend aussi les fonctions suivantes :

  • int isqrt(int)
  • char char_of_int(int)
  • int int_of_char(char)
  • bool is_number(char)
  • int abs(int)
  • int max2(int, int)
  • int min2(int, int)
  • int min3(int, int, int)
  • int min4(int, int, int, int)
  • int pgcd(int, int)
  • int read_int()
  • array<int> read_int_line(int len)
  • array<char> read_char_line(int len)
  • array<array<char>> read_char_matrix(int x, int y)
  • array<array<int>> read_int_matrix(int x, int y)
  • (int, int) read_int_couple()
  • (int, int, int) read_3ints()
  • @target_language current_language ()

Elles sont définies dans le fichier Stdlib/stdlib.metalang.

Les fonctions définies dans la librairie standard ne seront compilées que si elles sont utilisées.

Couples & tuples

Les couples existent en metalang, ils ne sont cependant pas recommandés : leur utilisation produit du code assez illisible pour la plupart des backends. Pour la plupart des langages, ils sont compilés vers des structures.

Le type s'écrit (a, b) (exemple : (int, int) ) Les valeurs de types tuples s'écrivent aussi (a, b) (exemple : (1, 2) )

Un exemple se trouve ici : tests/prog/tuple.metalang

inline

Le mot clé inline se place lors d'une définition de variables ou de fonction. Il indique que l'on peut inliner la fonction et la variable.

Pour inliner une fonction, il faut qu'elle n'ai qu'un seul return et qu'il soit terminal. Si une fonction est marquée inline et qu'elle n'est pas prévue pour, alors une erreur se produira.

Si une variable est marquée inline et que le compilateur ne trouve pas de méthode pour la supprimer, alors elle restera et aucune erreur ne sera levée.

Exemple

def inline toto = b
a = toto

Le code ci-dessus sera compilé vers

a = b

Macros

Les macros metalang sont utilisées pour écrire des primitives.

La librairie standard regorge d'exemples

macro type fonction_name(parametres)
  langage1 do "chaine1"
  langage2 do "chaine2"
  ** do "chaine3"
end

les noms de langages sont définis par la méthode lang définie dans les printers.

les chaines sont inserées dans les fichiers générés, après un remplacement de : $parametre1 par le code qui correspond.

Lorsque l'on écrit une macro, il faut faire attention au parenthésage et aux conversions automatiques de types (entre int et float par exemple.)

Lexems

Il existe un autre type en metalang : lexems. Ce type représente une liste de "mots" metalang. Pour en créer une, il suffit de faire taper du code entre des accolades

def lexems i = { x = x + 1 }

Il existe deux façons d'utiliser la variable i : l'une d'entre elle consiste à l'inserrer dans un autre lexems

def lexems j = { ${i} ${i} }

Cet exemple là permet de duppliquer le code compris dans i.

L'autre façon d'utiliser une valeur de type lexems est de faire "sortir" ce code. Il sera ensuite parsé et inserré dans le le flux d'instructions

${i}

L'exemple le plus simple se trouve dans le fichier tests/prog/loop_unroll.metalang On trouve un exemple plus complexe dans tests/prog/sudoku.metalang. On y génère une expression qui détermine si un sudoku est valide ou non.

Générer un code différent par langage

Pour faire ceci, il faut utiliser des macros et des lexems. On trouve un exemple de ceci dans la librairie standard pour min3 par exemple : en C on utilise la fonction metalang min2, alors qu'en python min permet de prendre trois valeurs pour un seul appel de fonction.

Pour tester si on compile bien dans un langage précis, on peut utiliser la fonction current_language

if current_language() == LANG_Java then
  ...
end

Cette méthode est utilisée aussi pour minimiser les lectures sur l'entrée standard.

Quels langages ne seront probablement jamais gérés dans Metalang ?

  • TCL : le passage des tableaux par référence n'est pas une feature propre du langage, et le retour d'un tableau par référence ne fonctionne pas.
  • Bash pour les memes raisons.
  • Erlang pour des raisons de modèle mémoire
  • Tout les langages qui ne se testent pas facilement sous linux.

Et pour développer ?

Le Makefile propose plusieurs règles : * clean permet de nettoyer un peu le dossier * metalang permet de compiler le compilateur * testCompare permet de lancer les gros tests : plusieurs programmes seront compilés dans tous les langages gérés par metalang et on comparera les sorties de ces programmes pour vérifier que tout a bien fonctionné * testNotCompile lance des tests qui ne doivent pas compiler * doc permet de générer la documentation du code * unit permet de lancer quelque tests unitaires sur la librairie standard

Si vous développez une fonctionalité précise liée à un langage, vous pouvez utiliser une des règle suivante :
  • test_fsscript.exe
  • test_groovy
  • test_fs
  • test_exeVB
  • test_st
  • test_adb.bin
  • etc...

pour ne lancer les tests qu'entre ocaml et un autre langage.

Si un test plante à cause d'une passe précise et que vous souhaitez lancer un test dans tous les langages, vous pouvez faire par exemple

make out/aaa_00hello.test

Du point de vue des dossiers : * src contient tout le code source * tools contient essentiellement les modes vim et emacs * tests/demo contient du code metalang de présentation * tests/not_compile contient des tests qui ne doivent pas compiler * tests/prog contient des tests compilables avec chaque backend : par test on a un programme et une entrée standard. Les sorties des programmes générés sont comparés par la suite pour vérifier que le compilateur fonctionne correctement.