Bonjour, Habr! Allez droit au but. En ce moment, je lis "The Dragon Book" et je développe un compilateur pour mon langage de programmation appelé Lolo (en l'honneur du pingouin du dessin animé soviéto-japonais). Je prévois de terminer dans un an si rien ne me fait mal. En parallèle, je posterai des extraits intéressants de l’expérience de la traduction, de la construction de code intermédiaire, de l’optimisation, etc. Asseyez-vous et partez.
Le langage est compilé, impératif, pas orienté objet, la sémantique a été impudemment supprimée de C et complétée par de nombreuses fonctionnalités utiles. Commençons par eux.
Modifications sémantiques
Pointeurs sûrs
Vous avez peut-être pensé aux pointeurs intelligents de Rust en ce moment, mais ils ne le sont pas. Dans ma langue, la sécurité d'accès à la mémoire est assurée par deux idiomes. Premièrement: l'absence d'une opération de déréférencement de pointeurs. Au lieu de cela, lors de l'accès au pointeur déclaré, l'objet lui-même est référencé. Autrement dit, vous pouvez et devez écrire comme ceci:
int # pointer ~~ new int(5) int variable ~ pointer + 7
La variable variable contient maintenant le nombre 12. Maintenant, vous voyez une syntaxe inconnue et, peut-être, vous êtes un peu perplexe, mais je vais tout expliquer au cours de l'article. Deuxième idiome: manque d'opérations sur les pointeurs. Encore une fois: toutes les opérations lors de l'accès aux pointeurs, y compris l'affectation, l'incrémentation et la décrémentation sont effectuées sur les objets. La seule opération qui se rapporte directement au pointeur est l'affectation par adresse ou, comme je l'appelle, l'identification. Dans l'exemple de code ci-dessus, dans la première ligne, c'est précisément l'identification. Tout pointeur peut être défini à l'adresse de la zone mémoire déjà allouée, qui est la nouvelle opération renvoyée. Vous pouvez également placer un pointeur sur l'adresse d'une autre variable allouée même sur le tas, même sur la pile. Voici un exemple:
int variable ~ 5 int # pointer ~~ variable
Ici "~" est l'opération d'affectation habituelle. Vous pouvez également identifier des pointeurs avec un pointeur nul spécial. Il agit comme un pointeur qui fait référence à une adresse nulle. Après avoir identifié les opérations de comparaison et de comparaison sur l'identité (adresses identiques) avec null, elles donneront vrai:
int # pointer ~~ null if (pointer = null) nop ;; true if (pointer == nul) nop ;; true
Ici "=" est une comparaison de valeurs, "==" est une comparaison par adresses, "nop" est une opération vide et après ";;" - commentaire. Et oui, null est la seule opération de pointeur avec laquelle il est possible de vérifier la compatibilité des types.
Ainsi, les pointeurs ne peuvent être affectés qu'à la mémoire allouée ou aux zones nulles et ne peuvent être déplacés nulle part. Cependant, ces mesures ne protègent pas complètement contre les erreurs de segmentation. Pour l'obtenir, suivez simplement ces étapes:
int # pointer1 ~~ new int(5) int # pointer2 ~~ pointer1 delete pointer1 int variable ~ pointer2 ;; segmentation fault!
Je pense que tout est clair ici. Mais commettre une telle erreur ne peut être fait exprès, et ensuite, avoir travaillé dur. Après tout, l'opération de suppression fait la même chose que le garbage collector, mais moins en toute sécurité. En parlant de lui ...
Collecteur d'ordures
Collecteur d'ordures - il est également collecteur à Lolo. Probablement pas besoin d'expliquer ce que c'est. Je peux seulement dire qu'il peut être désactivé par une option spéciale lors de la compilation. Nous avons testé le programme avec le collecteur, tout fonctionne comme il se doit - vous pouvez entrer l'option et essayer d'optimiser le programme en utilisant la gestion manuelle de la mémoire.
Tableaux intégrés
Bien que je dise que la sémantique du langage est radiée de C, les différences sont assez importantes. Ici, les tableaux sont des pointeurs. Les tableaux ont leur propre syntaxe et un adressage sécurisé. Non, pas avec un contrôle de portée. Avec eux, en principe, il est difficile d'obtenir une erreur d'exécution. C'est parce que chaque tableau stocke la longueur dans la taille variable, comme en Java, et à chaque indexation de l'index ... il y a le reste de la division par cette taille! Une décision stupide, à première vue, jusqu'à ce que nous examinions les indices négatifs. Si vous trouvez le reste de la division de -1 par la longueur du tableau, vous obtenez un nombre égal à taille-1, c'est-à-dire l'élément le plus fini. Par une telle manœuvre, nous pouvons accéder aux index non seulement depuis le début, mais aussi depuis la fin du tableau. Une autre astuce consiste à convertir n'importe quel type primitif dans le tableau d'octets []. Mais comment obtenez-vous une erreur d'exécution, demandez-vous? Je vais vous laisser cette question comme une énigme facile.
Les références
Je ne sais pas avec certitude si la norme C actuelle comprend des liens, mais ils seront certainement dans Lolo. Peut-être le manque de références dans les versions antérieures de C est l'une des principales raisons des pointeurs vers des pointeurs. Ils sont nécessaires pour passer des arguments à l'adresse, pour renvoyer les valeurs des fonctions sans copier. Les pointeurs et les tableaux peuvent également être passés par référence (car lors du passage par valeur, les tableaux seront entièrement copiés et les pointeurs définis sur un nouvel emplacement par l'opération ~~ ne l'enregistreront pas).
Multithreading
Tout est plus beau et plus beau. Je suis déjà amoureux de ma langue. Son prochain passe-temps est le multithreading. Honnêtement, je n'ai pas entièrement décidé des outils qui lui seront fournis. Très probablement, le mot clé synchronisé avec toutes les propriétés d'ala-Java et, éventuellement, le mot clé simultané devant des fonctions non en ligne, ce qui signifie «exécuter ces fonctions dans des threads parallèles».
Chaînes en ligne
Ce sont des chaînes, pas des littéraux de chaîne, comme en C ++. Chaque ligne aura sa propre longueur, l'indexation se fera avec la recherche du reste. En général, les chaînes de Lolo sont très similaires aux tableaux de caractères, sauf que les tableaux n'ont pas de concaténation via "+", d'animation via "*" et de comparaisons via "<" et ">". Et puisque nous parlons de lignes, il faut mentionner les personnages. Les symboles dans Lolo ne sont pas des nombres, comme dans C ++. Et ils ne contiennent pas un octet, mais 4 pour les caractères DKOTI et 6 pour les caractères UTF. Je parlerai de DKOTI la prochaine fois, mais pour l'instant, sachez que Lolo prend en charge les caractères et les chaînes dans deux encodages. Et oui, la propriété length peut même être prise à partir de constantes:
int len ~ "Hello, world!".length ;; len = 13
Type booléen avec trois valeurs
La grande majorité des langages de programmation qui ont un type de données logique utilisent une logique binaire. Mais à Lolo ce sera ternaire, ou plutôt ternaire flou. Trois valeurs: vrai - vrai, faux - faux et aucun - rien. Jusqu'à présent, je n'ai pas trouvé dans le langage des opérations qui n'en retournent aucun, mais je me souviens de nombreux exemples de pratique où des drapeaux avec trois valeurs seraient très utiles. J'ai dû utiliser des énumérations ou un type entier. Plus besoin de le faire. C'est juste le nom de ce type que je ne peux pas choisir. Le lieu le plus courant est «logique», mais trop long. D'autres options sont «luk» en l'honneur de Jan Lukasevich, «brus» en l'honneur de N. P. Brusnetsov et «trit», mais à proprement parler, ce type n'est pas un trit. En général, l'enquête se trouve à la fin de l'article.
Listes d'initialisation de structures et de listes
Si, après avoir déclaré une variable structurelle, mettez le signe ~ et ouvrez les crochets, vous pouvez définir les valeurs de ses champs tour à tour ou sous la forme d'un dictionnaire. Si vous effectuez une telle procédure avec un tableau, vous pouvez définir les valeurs de ses cellules, uniquement sans dictionnaire. Il n'y a rien de spécial à dire, il suffit de regarder le code:
struct { int i; real r; str s; } variable ~ [ i: 5, r: 3.14, s: "Hello!" ] int[5] arr ~ [ 1, 2, 3, 4, 5 ]
Renvoyer plusieurs valeurs des fonctions
Comme dans Go! Vous pouvez écrire plusieurs noms de variables séparés par des virgules et leur affecter toutes les valeurs renvoyées par la fonction à la fois:
int, real function() { return 5, 3.14 } byte § { int i; real r i, r ~ function }
Modules au lieu d'en-têtes
Tout est clair ici. Au lieu d'en-têtes C-timides - modules de Java.
pour (élément automatique: tableau)
Encore Java natif. Puisque nous avons des tableaux de longueur, c'est un péché de ne pas utiliser l'expression pour chacun.
L'opérateur de sélection n'est pas seulement pour int
Je ne sais pas pour vous, mais en C et C ++, je suis terriblement enragé par le manque de capacité à utiliser l’opération de changement de casse pour les variables non entières. Et la syntaxe exaspère également. Ici, en Pascal, c'est une autre affaire. Et maintenant à Lolo:
case variable { "hello", "HELLO": nop "world": { nop; nop } "WORLD": nop }
Opérateurs de puissance et de division
Et cela vient de Python.
real r ~ 3.14 ** 2 int i ~ r // 3
Tuples de paramètres de fonction
Rappelez-vous que toutes les opérations avec des pointeurs sont interdites à Lolo, sauf pour l'identification? Maintenant, rappelons-nous comment accéder aux paramètres de fonction à partir de listes de paramètres de longueur variable. Vous devez déclarer un pointeur sur le premier élément, puis incrémenter jusqu'à ce que la vérification de vérité renvoie true. Vous ne pouvez pas incrémenter dans Lolo. Mais ça va. Après tout, la liste des paramètres ici est présentée sous la forme d'un tuple d'une longueur fixe (dépendante de l'appel), avec index-safe, comme dans les tableaux. Son nom est "?" La vérification de type est effectuée uniquement pour les paramètres définis dans la définition de fonction. Les paramètres de repos («multipoints») sont réduits à n'importe quel type, et avec un mouvement maladroit, leur comportement n'est pas défini. Mais encore, un tel tuple est beaucoup plus sûr et plus pratique que les macros en C.
void function(...) { if (?.size > 1) { int i ~ ?[0] real r ~ ?[1] } }
Intervalles numériques
Et un autre caractère - une famille de types d'intervalles (plage, urange, lrange, etc.). Ils sont donnés par deux entiers à travers deux points (..) et peuvent couper un tableau d'un tableau, une chaîne d'une chaîne, en général, une chose utile, je pense.
int[5] arr ~ [ 1, 2, 3, 4, 5 ] int[3] subarr = arr[1..3] ;; [ 2, 3, 4 ]
En opérateur
De Pascal. Fonctionne avec des chaînes, des tableaux, des tuples? et plages.
int[5] arr ~ [ 1, 2, 3, 4, 5 ] if (4 in arr) nop
Dictionnaire des paramètres de fonction
Honnêtement, je suis déjà confus comment cette chose est correctement appelée, avec elle, vous pouvez directement spécifier les arguments des fonctions non pures:
int pos = str_find(string, npos: -1)
Options par défaut
De C ++. Ici, même un exemple n'est pas nécessaire de donner, et donc tout est clair.
Exceptions
Eh bien, et où sans eux?
try { raise SEGMENTATION_FAULT_EXCEPTION } except (Exception e) { print(e.rus) }
Pas de saut inconditionnel
Parce qu'en 2019, l'utilisation de l'opérateur de décès GOTO est similaire.
Syntaxe
Eh bien, un petit discours sur la syntaxe. Comme vous l'avez remarqué, le point-virgule est peu profond. Les langages de programmation modernes fonctionnent très bien sans cette source d'erreur. Les exemples sont Python, Kotlin. L'opérateur flèche (->) est combiné avec l'opérateur point. Lors de l'appel de fonctions sans arguments, les crochets sont facultatifs. Les chaînes sont données en nombre et vice versa. Les opérateurs logiques et au niveau du bit sont combinés. Il existe des modificateurs de fonction pour la tabulation. Fonctions imbriquées type_of. Et surtout - le multilinguisme. Oui, je vais dupliquer les mots clés, les propriétés des chaînes et des tableaux et tous les identifiants de la bibliothèque standard dans toutes les langues de communication internationale, à savoir: anglais, russe, japonais, chinois, espagnol, portugais, arabe, français, allemand et latin.
En fait, tout ce qui précède ne comprend pas la moitié des capacités de Lolo. Je ne peux tout simplement pas rappeler immédiatement toutes ses fonctionnalités. J'ajouterai que le compilateur est prêt.