[Langage] Jade
| Mod |
Posté le 12 Juil 2008 à 22:09
|
|
![]() Messages : 4954 GCPoints : 2100823 |
![]() Qu'est-ce que Jade ? Jade est un langage de programmation orienté objet dont le compilateur est en cours de développement. Le compilateur génère des exécutables x86. L'objectif clairement défini du projet est de faciliter la création de jeux vidéo, tout en restant un langage générique qui soit accessible, concis, et performant. Par ces objectifs, il faut comprendre qu'il s'agit de synthétiser ce qui peut l'être, de clarifier les syntaxes, et de disposer de base de bibliothèques multimédia. Pourquoi un énième langage ? La création d'un langage de programmation performant est sans doute l'une des plus intéressantes épreuve que l'on puisse imaginer pour un développeur. C'est extrêmement enrichissant, cela mettant en oeuvre une multitude de techniques et de connaissances. Bien sûr, même si l'intérêt personnel est nécessairement (et heureusement) là, ce n'est pas la seule raison à la création de Jade. Lorsque j'ai commencé à développer des jeux il y a une dizaine d'années de cela, j'ai entrepris l'apprentissage du Dark Basic, un langage dont la simplicité n'a d'équivalent que son manque d'optimisations. Au fil des années, j'ai fini par être amené à développer des dizaines de plugins pour le langage dans le but de l'améliorer. Mais au final, le résultat restait décevant, et c'est bien logique : le coeur du langage reste assez lent, chose que l'on ne peut malheureusement pas contrer en tant qu'utilisateur. Depuis, même si l'envie ne manque pas, le fait de développer avec ce langage est assez rebutant. Parallèlement à cela, je n'ai rencontré à ce jour aucune solution gratuite permettant de développer des jeux performants tout en gardant la simplicité de mise en oeuvre d'un Dark Basic. La plupart des langages consacrés à la création de jeu sont payants. Le C++ est soumis au syndrome du "en fait non" (au retour du travail/cours, ouverture de l'IDE, regard désespéré devant l'expansivité de la syntaxe, fermeture de l'IDE...), et peu d'autres langages sont réellement adaptés à la création de jeux, le plus souvent par manque de bibliothèques multimédia ou de performances. J'ai donc commencé à créer mon propre langage sur ce modèle que j'avais apprécié du Dark Basic, à savoir simplicité du langage et mise en oeuvre immédiate. Performances ? Les exécutables générés à l'heure actuelle par Jade ont des performances qui me paraissent intéressantes : dans les tests que j'ai pu effectuer (boucles sur des tests, calculs, etc), il en ressort que ceux-ci présentent une rapidité équivalente à celle du C dans la plupart des cas (compilation GCC/Visual Studio, optimisations off). Cette rapidité est liée au fait que le compilateur génère directement du code Assembleur (la source .asm est générée automatiquement lors de la compilation, en même temps que l'exécutable). Pourquoi le nom de Jade ? Le nom de Jade a de multiple origines, autant personnelles que logiques : - Tout d'abord, il faut savoir que le jade est une gemme, à l'instar du rubis... Une première référence pointe ici : Ruby. Bien qu'il y ait de grandes différences entre les deux langages, et que je ne partage pas la notion de "50 façons de faire la même chose", le Ruby reste l'une des sources d'inspiration de Jade, un peu comme a pu l'être Perl pour Ruby. - C'est aussi un clin d'oeil à la scène du RPG Making, plus particulièrement à ceux utilisant RPG Maker, et son RGSS (Ruby Game Scripting System). J'y ait été connu pour développer un logiciel similaire à RPG Maker (RPG Engine), qui actuellement est en stand-by en attendant que Jade avance. Là où RPG Maker utilise le Ruby, RPG Engine* utilisera donc Jade. Tout un symbole ^^. - De manière plus personnelle, j'ai suivi des études de géologie, et j'ai donc tout logiquement étudié ces roches que sont le jade et le rubis. - Enfin, par rapport au terme "Jade", jade en français, c'est jade en anglais, donc compréhensible par la majorité de gens, c'est une barrière linguistique en moins. Qui plus est, c'est un terme plutôt simple à retenir. Quelles sont les caractéristiques du langage ? Le langage suit trois principes régissant sa syntaxe : - ne pas avoir à écrire ce qui n'est pas utile à la lecture, si le compilateur est capable de le déduire - disposer d'une écriture concise mais claire, de mots-clés rapides à écrire et faciles à mémoriser - le langage ne doit pas limiter l'utilisateur dans l'accès bas niveau Typage : Le typage des variables est statique : une fois une variable typée, celle-ci ne peut plus changer de type. Le typage peut être explicitement realisé, mais également inféré par le compilateur : celui-ci déduit le type d'une variable à partir de l'expression qui lui est assignée. L'intérêt de cela est de permettre une plus grande souplesse d'écriture sans avoir à préciser constamment les types. Cela correspond typiquement à l'un des principes de Jade : le compilateur peut déduire le type, et le préciser n'est pas indispensable à la compréhension. Code : unEntier as integer // Déclaration explicite unAutreEntier = 9 // Déclaration inférée et assignation : type int uneInstance = Classe.new() // Déclaration inférée et instanciation : type Classe Boucles : Les boucles implémentées sont plutôt classique : Code : do // Boucle infinie. ... loop while conditionVraie ... end repeat ... until conditionVraie Conditions : Là encore du classique : Code : if conditionVraie then ... if conditionVraie ... end if conditionVraie ... else ... end if conditionVraie ... elseif ... else ... end Modules : Les programmes créés en Jade sont découpés sous forme de modules. C'est la structure hiérarchiquement de plus haut niveau. Un module peut contenir des classes, des fonctions et du code. Le code contenu au sein d'un module permet d'exécuter un code d'initialisation au chargement de celui-ci. Par défaut, le code écrit l'est dans le module "main" qui est exécuté au lancement d'une application. Code : module NomDeModule
// classes, énumération, etc.
def NomDeProcedure()
...
end
def NomDeFonction() as TypeRetourne
...
return valeurRetournee
end
// du code ici
endOrienté objet : Jade est orienté objet, dans la mesure de l'utilité et de l'efficacité des notions de l'orienté objet. Toutes ces notions ne sont/seront pas nécessairement incluses au langage. Une partie des fonctionnalités à ajouter est déjà établie, mais tout est susceptible d'évoluer grandement au fil de l'avancée du développement et des retours. Code : class NomDeClasse as ClasseHeritee
champ1 as TypeChamp
// constructeur
def new()
end
// destructeur
def delete()
end
def NomDeMethode()
...
end
def NomDeMethode2() as TypeRetourne
...
return valeurRetournee
end
self def NomDeMethodeDeClasse()
...
end
end
a = NomDeClasse.new() // Instanciation.
a.NomDeMethode() // Appel de méthode d'instance.
NomDeClasse.NomDeMethodeDeClasse() // Appel de méthode de classe
a.delete() // Destruction.Enumérations : Les énumérations permettent de gérer des listes de valeurs à utiliser comme paramètres. Les valeurs peuvent être définies explicitement si besoin est. Code : enum NomEnumeration Champ1, // vaut 1 Champ2, // vaut 2 Champ3 = 8, // vaut 8 Champ4 // vaut 9 end test = NomEnumeration.Champ3 // variable de type NomEnumeration valant Champ3 Tableaux : Les tableaux sous Jade sont des conteneurs de données multidimensionnels : Code : unTableau as integer() // Déclaration explicite d'un tableau d'entier. unAutreTableau = integer(10) // Déclaration inférée et assignation d'un tableau de 10 integer. unAutreTableau(0) = 13 // Assignation unTableau = Classe(10,10) // Déclaration inférée et assignation d'un tableau de 10x10 Classe... unTableau(0,0) = Classe.new() // ... qui doivent être instanciés. Itérateurs : Les itérateurs permettent de manipuler plus facilement les listes de données grâce à la boucle for...in : Code : def Iterateur() as integer a = 0 while a <= 15 yield a // Retourne une valeur déclenchant la boucle for. a++ end end for a in Iterateur() Console.Print(a) // Affiche les nombres de 0 à 15. end Ou encore : Code : def Premiers() as integer yield 0 yield 1 yield 3 yield 5 yield 7 yield 11 yield 13 end for a in Premiers() Console.Print(a) end Headers : Ah, en fait non, pas besoin de headers au sens C/C++ du terme, le compilateur analyse préalablement le code avant de le compiler. Déclarez, c'est prêt à l'emploi. Ce que je tends à appeler "header", ce sont les fichiers permettant d'importer des .lib. Moyennant la création d'un code d'import, toute bibliothèque suivant les conventions d'appel standards (cdecl, stdcall, fastcall) et exportant ses noms de fonction peut être utilisée. Le code est détaillé plus bas. Evolution du langage Spoiler - Cliquez pour afficher Décembre 2006 : - début du projet sous forme de CLI (Command Line Interpreter) - déclaration implicite de variables - calculs arithmétiques Courant 2007 : - support des tests logiques if...endif et if...else...endif - boucle do...loop Décembre 2007 : - support de l'appel de fonctions externes issues de DLL Juillet 2008 : - implémentation de la gestion des DLL comme plugins - gestion des chaînes de caractères - implémentation des fonctions utilisateur (0.007) - typage des variables (0.007) - support des structures à un niveau (0.008) Août 2008 : - support des tableaux - itération for...to...next - code semi-compilé (bytecode interprété) Septembre 2008 : - code compilé, utilisation de FASM (0.010) - les structures gèrent plusieurs niveaux d'imbrication (structures de structures) - implémentation d'opérateurs de manipulation des pointeurs (& et $) Octobre 2008 : - remplacement des fichiers .dlls par les fichiers .lib - ajout d'un format de header pour les .lib - boucle while...endwhile Novembre 2008 : - implémentations des tableaux dynamiques - suppression du CLI et de l'interpréteur de bytecode, le langage est dès lors uniquement compilé - début de création de l'IDE "Dragon" Janvier 2009 : - options de compilations : SUBSYSTEM, ENTRY - support de l'assembleur dans le langage (asm...endasm) Février 2009 : - support des calculs sur des nombres à virgule flottante - support des conversions de type - les fonctions gère maintenant pleinement les types de retour - reprise à zéro du compilateur - début d'implémentation des classes - amélioration du if pour gérer la structure if...then et if...elseif...else...endif - mots-clés break et continue - implémentation d'un garbage collector de type "mark and compact" Courant 2009 : - retrait du garbage collector - debug et modifications mineures Courant 2010 : - arrêt du support des structures - debug et modifications mineures Mai-Juin 2011 : - support des classes (méthodes et champs) - debug et modifications mineures Août 2011 : - implémentation des énumérations - implémentation des self def (méthodes statiques) - support des surcharges de méthodes et fonctions - modification du modèle de header (utilisation de la syntaxe du langage) - modifications de syntaxe : mot-clé de fin de structure unique "end", définition de fonction/méthode par le mot-clé "def" - déclarations à typage inféré - ajout des itérateurs pour la boucle for...in - révision des tableaux. Avancement du langage Ajouté - En cours d'ajout - En attente Spoiler - Cliquez pour afficher Variables : - Affectation - Typage - Portée Typage : - boolean - byte - integer - float - double - string Calculs : - Addition (+) - Soustraction (-) - Multiplication (*) - Division (/) - Décalage gauche (<<) - Décalage droit (>>) Gestion des conditions : - if...then - if...end - if....else...end - if....else...elseif...end - Opérateur ternaire (... ? ... : ...) Opérations booléennes - Egalité - Inégalité - Supériorité stricte - Supériorité ou égalité - Infériorité stricte - Infériorité ou égalité Oérateurs logiques - and - or - not - xor Gestion des boucles et itérations : - do...loop - while...end - repeat...until - for...to...end - for...to...step...end - for...in...end Fonctions - def...end - return - Paramètres d'entrée - Paramètre de retour Structures de données : - struct...end - enum...end Orientation objet : - class...end - Champs - Méthodes - Constructeur - Destructeur - Surcharge - Get - Set - Méthodes statiques (self def) Création de bibliothèques supplémentaires Spoiler - Cliquez pour afficher Cette section s'adresse aux personnes qui souhaiteraient développer (dans le futur) des bibliothèques supplémentaires pour Jade. Le langage a été conçu pour permettre à tout le monde de créer et d'ajouter sa propre bibliothèque au langage. La création de telles bibliothèques s'effectue de manière simple, soit via des LIB, soit des DDL (uniquement Windows à l'heure actuelle, donc). La seule chose à faire pour rendre votre bibliothèque compatible avec Jade est de créer un fichier reprenant la convention (avec entre crochet l'optionnel) : Code : [self] def NomDeFonction([arg1 as type, arg2 as type...argn as type]) [as type] alias NomDeFonctionCompilé Le nom de la fonction est celui qui sera utilisé sous Jade. Le nom de fonction compilé est le nom interne de la fonction dans votre bibliothèque après compilation et name mangling. Vous devez nécessairement préciser la commande include "Bibliothèque.extension" pour que votre code soit fonctionnel. Exemple : Code : include "Jade_Console.lib" class Console self def Print(value as string) alias "_Print" self def Print(value as boolean) alias "_PrintBool" self def Print(value as integer) alias "_PrintInt" self def Print(value as float) alias "_PrintFloat" self def Pause() alias "_Pause" end Le fichier de code Jade créé devra être placé dans le dossier "Headers", tandis que la bibliothèque compilée le sera sous le dossier "Library". Historique 12/07/2008 : Première release publique (v0.001). 13/07/2008 : Release v0.002. 13/07/2008 : Release v0.003. 13/07/2008 : Release v0.004. 14/07/2008 : Release v0.005. 26/07/2008 : Release v0.006. 27/07/2008 : Release v0.007. 27/07/2008 : Release v0.008. 13/09/2008 : Release v0.011. Aucun téléchargement disponible à l'heure actuelle Tout avis sur le langage et sa syntaxe m'intéresse : est-ce simple, complexe ? Agréable à lire ? Quelles fonctionnalités vous sembleraient intéressantes pour un langage de ce genre ?
Dernière édition le 31 Août 2011 à 13:22
|
|
| bebou007 |
Posté le 12 Juil 2008 à 23:44
|
|
![]() Messages : 238 GCPoints : 43228 |
l'application na pas pu demarer la configuration de l'application et incorect reinstaller pourais resoudre le probleme sa commence bien lol | |
| Maike |
Posté le 12 Juil 2008 à 23:54
|
|
![]() Messages : 57 GCPoints : 19142 |
Salut, pk utiliser direct x ? Quittes a creer un langage, rend le portable, c'est bcp plus utile, d'autant plus que ca manque. Bonne soiree |
|
| gouessej |
Posté le 13 Juil 2008 à 02:28
|
|
![]() Messages : 337 GCPoints : 64624 |
Je rejoins Maike sur un point, tu devrais rendre ton langage plus portable. Dans ce cas, utilise plutôt OpenGL qui marche très bien à la fois sous Mac, sous Windows, sous Unix et sous Linux. Par contre, comme langage portable, il y a déjà Java. |
|
| Mod |
Posté le 13 Juil 2008 à 10:04
|
|
![]() Messages : 4954 GCPoints : 2100823 |
@ bebou : tu tournes sur quel OS ? Je vais modifier la configuration de compilation. @ Maike & gouessej : comme je le disais, utilisation de DirectX, mais dans un premier temps ^^. Je maîtrise bien mieux DirectX qu'OpenGL, ça me semblait donc logique de partir de ça pour l'instant. Les librairies externes sont compilés sous forme de DLL (même schéma que DBPro). Mais je pense bien sûr à un portage Linuxien (très lointain). Il faudra alors que je me documente sur les équivalents des DLL. |
|
| Tersaken |
Posté le 13 Juil 2008 à 13:23
|
|
![]() Messages : 302 GCPoints : 23103 |
Même erreur que celle rencontrée par Bebou, Je tourne sous Windows XP SP3. |
|
| bebou007 |
Posté le 13 Juil 2008 à 13:50
|
|
![]() Messages : 238 GCPoints : 43228 |
salut je tourne sous xp sp2 et sa me fait pensser a metre le sp3 | |
| Mod |
Posté le 13 Juil 2008 à 14:03
|
|
![]() Messages : 4954 GCPoints : 2100823 |
Ok, le problème doit donc bien venir de la compilation... Curieux sachant que c'est uniquement de la console pour l'instant. J'ai recompilé l'interpréteur, téléchargeable au même lien que ci-dessus. |
|
| Tersaken |
Posté le 13 Juil 2008 à 14:18
|
|
![]() Messages : 302 GCPoints : 23103 |
dorénavant, ça fonctionne C'est assez impressionnant, quel autre langage as tu utilisé pour mettre au point Jade ?
Dernière édition le 13 Juil 2008 à 15:42
|
|
| Mod |
Posté le 13 Juil 2008 à 14:38
|
|
![]() Messages : 4954 GCPoints : 2100823 |
Je code ça en C pour la majorité du code, du C++ pour simplifier la gestion des données, et de l'Assembleur pour la communication bas niveau. Là je travaille justement dessus, pour l'implémentation des chaînes de caractères. Et surtout, celles des fonctions de librairies. Toute la couche de communication entre DLL et interpréteur est réalisée, il ne reste qu'à gérer convenablement la lecture des types de données en entrée des fonctions. Donc a priori, il devrait être rapidement possible de développer des plugins pour le langage. Ca devrait permettre de donner des résultats graphiques rapidement :). |
|
| Mod |
Posté le 13 Juil 2008 à 17:12
|
|
![]() Messages : 4954 GCPoints : 2100823 |
Nouvelle version en ligne. Un très gros pas en avant ^^. Les librairies externes sont gérées. Les chaînes de caractères sont prises en compte. Je fournis dans l'archive une première ébauche de Jade_Window.dll, qui servira à gérer les fenêtres win32, comme son nom l'indique... CreateMainWindow Syntaxe : CreateMainWindow(titre fenêtre, abscisse fenêtre, ordonnée fenêtre, largeur fenêtre, hauteur fenêtre) Créé une fenêtre principale de type "Jade Application". ProcessMainWindow Syntaxe : ProcessMainWindow() Gestionnaire de fenêtre à placer en boucle. ShowMessage Syntaxe : ShowMessage(titre, texte, type de boîte de message) Affiche un message à l'écran dans une boîte de dialogue. Types des boîtes de message à déterminer (constantes Win32). WindowBeep Syntaxe : WindowBeep(numéro du beep) Joue un son de fenêtre. Numéro des beeps à déterminer (constantes Win32). GetMouseX Syntaxe : GetMouseX() Retourne l'abscisse de la souris par rapport à l'écran. GetMouseY Syntaxe : GetMouseY() Retourne l'ordonnée de la souris par rapport à l'écran.
Dernière édition le 13 Juil 2008 à 17:12
|
|
| bebou007 |
Posté le 13 Juil 2008 à 19:23
|
|
![]() Messages : 238 GCPoints : 43228 |
re alors tous marche bien c'est vraiment bien par contre tu parle de pluging comment sa se passe on pouras créee des dll pour ajouter des commande a jade | |
| noob4ever |
Posté le 13 Juil 2008 à 20:06
|
|
![]() Messages : 295 GCPoints : 48742 |
Pour l'erreur lors de l'exécution, cela vient du fait que Mod compile avec le runtime microsoft ( tu utilises Visual Studio n'est-ce pas ? ), pour régler ça, il faut que Mod, tu compiles en debug multithread, ou en release multithread. Ensuite j'ai une question, pourquoi j'arrêterai de programmer en ruby pour utiliser ton langage Mod Sinon je te souhaite bonne continuation !
What did C:/DARTHVADER said to C:/DARTHVADER/LUKESKYWALKER ?
I'm your folder |
|
| Mod |
Posté le 13 Juil 2008 à 20:12
|
|
![]() Messages : 4954 GCPoints : 2100823 |
@ bebou : oui, je parle de plugins, car Jade fonctionne comme DBPro, avec des DLL contenant diverses fonctions comme les fenêtres, la 2D, le texte, etc... L'avantage de ce système de plugin, c'est que tout le monde peut créer des extensions au langage Le mode de création des DLL est identique à celui de Dark Basic à ceci près qu'il n'y a pour l'instant pas besoin de créer de string table (pour le moment). En théorie, une DLL compatible avec Dark Basic n'ayant pas besoin des DLL de celui-ci devrait aussi être compatible avec Jade. @ noob4ever : oui, le problème est déjà réglé ;). A l'heure actuelle, ça me paraît difficile de parler performances, donc Ruby vs Jade, je mettrais sur le second la présence (même si c'est encore hypothétique) dès le départ de librairies graphiques, sonores et d'entrées-sorties modernes (pas de SDL...). Le langage reste orienté pour la création de jeux-vidéo.
Dernière édition le 13 Juil 2008 à 20:23
|
|
| bebou007 |
Posté le 13 Juil 2008 à 20:25
|
|
![]() Messages : 238 GCPoints : 43228 |
ok donc on pourais ajouter un moteur 3d et tous se que l'on veut. mais si il y a pas de string table comment fait tu pour reconnaitre les fonction? |
|
| Mod |
Posté le 13 Juil 2008 à 20:45
|
|
![]() Messages : 4954 GCPoints : 2100823 |
Oui, moteur 3D ou quoi que ce soit d'autre ^^. Les fonctions ne sont justement pas reconnues pour l'instant, elles sont juste appelées en supposant qu'elles sont bien dans les DLL sélectionnées... Le langage ignore si elles sont là, mais le suppose. S'il est impossible d'appeler la fonction, la commande est ignorée. Et pour les paramètres, même chose. La couche de communication Assembleur envoie les paramètres à la fonction en supposant qu'elle saura les utiliser convenablement. J'en aurai donc besoin, et c'est ce que j'essaie de faire en ce moment : lire les string table. |
|
| Tersaken |
Posté le 13 Juil 2008 à 20:50
|
|
![]() Messages : 302 GCPoints : 23103 |
Pour ce qui est des " expressions arithmétiques ", tu as codé ça " maison " ou alors tu fais appel à des fonctions déjà existantes ? Les variables sont gérées de quelle façon ? a la rpg maker ( 2003 )? [ c'est a dire un immense tableau de type [Nom de La Variable, Valeur] ]
Dernière édition le 13 Juil 2008 à 20:51
|
|
| Mod |
Posté le 13 Juil 2008 à 21:12
|
|
![]() Messages : 4954 GCPoints : 2100823 |
L'analyse des expressions arithmétiques est faite à la main. C'est la base de l'interpréteur. Le système que j'utilise là est celui de l'analyse récurrente descendante. Les variables sont gérées comme tu le dis : nom de variable -> valeur. Toutefois ce système sera un peu modifié pour aboutir à nom de la variable -> pointeur sur la valeur. Car une valeur peut être de type double ou booléen, ça peut poser problème. En utilisant un pointeur à la place, c'est une adresse mémoire qui est stockée, le problème de place ne se pose donc plus. D'ailleurs pour l'évaluation des chaînes de caractères, le nombre qui est retourné, c'est le pointeur vers celles-ci. |
|
| Mod |
Posté le 13 Juil 2008 à 22:15
|
|
![]() Messages : 4954 GCPoints : 2100823 |
Système de plugins achevé... Les String Table sont maintenant prises en compte. Voici par exemple la string table pour Jade_Window : Code : STRINGTABLE DISCARDABLE BEGIN 0 "6" 1 "ShowMessage ShowMessage SSI" 2 "WindowBeep WindowBeep I" 3 "GetMouseX GetMouseX 0" 4 "GetMouseY GetMouseY 0" 5 "CreateMainWindow CreateMainWindow SIIII" 6 "ProcessMainWindow ProcessMainWindow 0" END La chaîne d'ID 0 indique le nombre de fonctions contenues dans la string table. Les lignes en dessous sont bien sûr les fonctions, qui sont identifiés ainsi : - Par un identifiant unique suivant la numérotation de 1 à XXX. - Par le nom de la fonction dans la DLL. - Par un alias de la fonction, c'est à dire le nom de la fonction tel que souhaité dans Jade. - Par une description schématique des arguments de la fonction. Pour l'instant, 0 = aucun paramètre, S = chaîne de caractères, I = entier. Sous Jade, l'importation d'une librairie se fait via la commande : import "nom du plugin.dll" De préférence en haut du code du programme ^^. Au passage, la rapidité d'appel des fonctions a été grandement augmenté. Archive mise à jour, au même lien qu'en haut ! Si quelqu'un souhaite tester la compatibilité plugin, tout est donc fonctionnel ! |
|
| Aeon Syx |
Posté le 14 Juil 2008 à 10:27
|
|
![]() Messages : 660 GCPoints : 26061 |
L O L Y en a qui savent même pas programmer et il y en a qui savent même pas programmer et y en a qui font des nouveaux languages. Gamecorp c'est le forum de la science fonction Je sais pas si j'essaierai de programmer dans ton Jade tout de suite, Mod, mais si il y a de bons tutos pourquoi pas Bonne continuation ! ![]() |
|








