2012-04-21
Salutations camarades, amis proches ou éloignés, de tous bords, de tous genres, de toutes origines, de tout penchants religieux ou politiques, de toutes orientations sexuelles, et bien plus que ça.
Je suis actuellement en train de travailler sur un nouveau langage de programmation, non pas pour faire un langage populaire, mais plutôt comme une expérience personnelle. J’ai choisi le chemin facile, à savoir que je laisse le gros de l’optimisation et de la compilation à LLVM.
Il y a plusieurs objectifs à un tel projet :
Je l’ai déjà dit, mais il y a aussi un non-objectif : celui de concurrencer les langages existants.
Mais j’oublie un point important : ce langage s’appelle PIF, ce qui est un acronyme pour Programming Is Fun - ce qui montre bien l’état d’esprit du projet. Il s’agit d’un langage pour apprendre la programmation en s’amusant dans son coin, mais pas particulièrement prévu pour des gros projets. Comprendre aussi : c’est mon langage, c’est mon compilateur, alors je fait ce que je veux (et je vous emmerde - sauf votre respect).
Mais dans la pratique, ça ressemble à quoi ?
J’ai commencé à écrire les spécifications sur mon bloc-notes ici LIEN MORT – (document principal).
Il n’y a que six types de bases : void
,
bool
, byte
, int
,
float
, string
(cf les spécifications LIEN
MORT). On pourra aussi utiliser des types de taille différentes
pour les nombres entiers, mais globalement j’encourage à la simplicité
et donc à n’utiliser que les six types de base, les autres n’ayant leurs
utilité que lorsqu’il s’agit de communiquer avec le monde extérieur
(comprendre : appel de fonctions de bibliothèques en C par exemple).
Il y aura aussi des types pour les tableaux. Ceux-ci fonctionneront
d’une manière similaire aux tableaux de Go. Mais chaque chose en son
temps, les tableaux ne sont pas du tout dans mes priorités pour
l’instant. Les string
non plus, d’ailleurs, parce que c’est
vraiment un gros morceau.
Je m’attend à ce que personne ne comprenne pourquoi j’ai choisi une telle syntaxe. En gros, il y a toujours à peu près deux raisons pour tel ou tel choix :
Voici donc, sans attendre, des exemples de code :
################# MATH LIBRARY ###################
# BECAUSE YOU LOVE MATH AND SO DO I
##################################################
######### BASIC MATH #########
## packages/pif/math/math.pif
Attention : non seulement s’agit-il ici d’un extrait de code de la bibliothèque pour faire des maths - qui en fait fait essentiellement appel aux fonctions de la libm - mais c’est surtout du code non réaliste, qui concrètement sera écrit autrement. C’est vraiment juste pour l’exemple. En passant, remarquez que les commentaires commencent par un #.
const pi : float = 3.1415926536
let tau = 2 * pi
Les deux lignes précédentes étaient assez explicites, je laisse le
soin au lecteur de les comprendre. Remarquez l’utilisation du mot-clef
let
pour déclarer ses variables. Je l’ai choisi car
var
et def
sonnaient très mal, défaut
rédhibitoire pour mon idée - let
est tout aussi
conventionnel et compréhensible à mon avis. Remarquez aussi
l’organisation de la déclaration de pi
: le signe
deux-points (:
) indiquera toujours que ce qui suit est le
type à considérer pour l’objet qui précède - il est donc utilisé dans
toutes les déclarations, définitions de structures, etc.
func abs : (x : float) -> float {
if x >= 0 then return x
else return -x
}
C’est un dilemme pour moi : dois-je écrire la fonction
abs
qui prend un float
pour renvoyer un
float
, ou dois-je l’écrire avec des int
?
Vais-je devoir en écrire deux ? En tout cas, il n’est pas question
d’implémenter de surcharge des fonctions, c’est bien trop lourd et
anti-principes-fondamentaux-décrits-plus-haut.
Mais examinons ces lignes un peu plus en détail, car elles sont très
révélatrices. D’abord, constatez que la fonction abs
est du
type noté (x : float) -> float
, à comprendre qu’elle
prend en argument un float
appelé x
, et
renvoie un float
. C’est plutôt clair, non ? Le nom de
l’argument (ici x
) prend son importance lorsqu’on a une
fonction qui prend vraiment tout plein de paramètres, et que ceux-ci
sont optionnels, et que l’on veut en préciser juste quelques-uns. Sinon,
c’est pas trop grave.
Ensuite, il y a le if
. Peut-être le classique
if...then...else...
vous fait penser trop à du vieux QBasic
foireux - dans ce cas, tout peut s’arranger, parce que dès que l’on veut
mettre plus qu’une seule ligne dans le corps du if
, il est
nécessaire d’utiliser des accolades pour délimiter un bloc de code, ce
qui nous dispense alors d’écrire le then
(mais vous pouvez
aussi le mettre si ça vous fait plaisir, franchement je m’en fous). Dans
ce cas précis, ça peut donner un truc comme ça :
# AUTRE VERSION, JUSTE POUR L'EXEMPLE
func abs : (x : float) -> float {
let ret = x
if ret < 0 {
ret = -ret
}
return ret
}
C’est mieux ? Bien, passons à la suite :
func sqrt : (x : float) -> float
extern sqrt
func cos : (x : float) -> float
extern cos
Ces deux exemples illustrent typiquement la référence à une fonction
externe, en l’occurrence on définit les fonctions sqrt
et
cos
, toutes les deux du même type, et faisant références
aux fonctions du même nom dans la libm.
func sin : (x : float) -> float {
return (extern sin : (x : float) -> float) (x)
}
La fonction ci-dessus montre une autre façon de référencer une
fonction externe. Ici c’est tout à fait inutile, mais c’est pour
l’exemple. Remarquez que l’on a tout simplement importé la fonction
sin
en précisant son type, sans lui donner de nom
particulier dans notre programme (c’est donc une fonction anonyme), puis
on l’a appelée directement.
Maintenant, parlons un peu des pointeurs de fonction. Attention : les pointeurs sur fonction, c’est pas encore au point et je suis pas convaincu que ça va se faire exactement comme ça au niveau syntaxe, même si ça a l’air plutôt pas mal. Ce n’est pas encore rentré dans les spécifications.
func tan : (x : float) -> float {
let externTanPtr : (x : float) -> float = extern tan
return externTanPtr(x)
}
Cette fois c’est un peu pareil, sauf qu’on a utilisé un pointeur de
fonction, auquel on a attribué une référence à la fonction externe
tan
. On a ensuite utilisé ce pointeur pour appeler la
fonction.
Finalement, pour illustrer les fonctions anonymes et les pointeurs de fonctions, du code cette fois vraiment inutile :
let func_ref = func (val : float) -> float {
return sqrt(val)
}
let sqrtOf2 = (func (val : float) -> float { return sqrt(val) }) (2)
Dans ce premier exemple, on définit un pointeur de fonction et on lui
donne comme valeur une fonction qui a été créée sur le champ. Si le
let
avait été un const
, cela aurait été
l’équivalent approximatif d’une définition de fonction classique. Mais
c’est moche et inutile, en fait, donc on préfère faire comme tout le
monde et déclarer ses fonctions normalement.
Dans le deuxième exemple, on crée une fonction qui renvoie la racine
carrée de l’argument, que l’on applique immédiatement avec l’argument
2
, pour donner la valeur récupérée à la variable
sqrtOf2
.
Je ne suis moi-même pas tout à fait convaincu de ce nouveau langage que j’imagine aujourd’hui - et dont j’avais déjà commencer à coder un bout de compilateur, mais la syntaxe a changé depuis, alors je vais devoir modifier plein de trucs. Bref, voilà.
Dans le prochain épisode, s’il y a lieu, on parlera un peu des structures, des classes, des méthodes, etc.