Jaguar — Documentation du langage

Documentation de référence du langage Jaguar telle qu'implémentée par jcc.py.

Important : cette documentation décrit l'implémentation actuelle du compilateur, pas une spécification future. Certaines fonctionnalités peuvent être volontairement limitées.


1. Présentation

Jaguar est un langage compilé qui est traduit en C par JaguarCC (jcc.py), puis compilé avec GCC.

Le pipeline est :

Code Jaguar (.ja)
       ↓
     Lexer
       ↓
     Parser
       ↓
    Resolver
       ↓
    CodeGen
       ↓
      C (.c)
       ↓
      GCC
       ↓
  Exécutable

Jaguar reprend volontairement plusieurs idées du C/C++ tout en simplifiant certains aspects :


2. Compiler un programme

2.1 Compilation directe

python jcc.py main.ja -o game

Le compilateur génère :

game.c
game

Puis appelle GCC automatiquement.

Le nom de sortie ne doit pas se terminer par .c :

python jcc.py main.ja -o game

et non :

python jcc.py main.ja -o game.c

2.2 Générer uniquement le C

Sans -o, le C est écrit sur la sortie standard :

python jcc.py main.ja > main.c

2.3 Mode C89

python jcc.py main.ja --c89 -o game

En mode normal, les déclarations peuvent apparaître après des instructions, comme en C99+.

Avec --c89, Jaguar déplace les déclarations au début des blocs et laisse l'initialisation à son emplacement logique.

La sémantique Jaguar reste identique.


2.4 Fichiers Jaguar

Jaguar utilise principalement deux extensions :

Techniquement, un .jah utilise la même syntaxe que Jaguar. L'extension indique simplement qu'il s'agit d'une interface destinée à être réutilisée avec using.

Exemple :

include/
    math.jah
src/
    main.ja

Puis :

using math;

void main(string param) {
    math:add(10, 20);
}

Les .jah sont particulièrement utilisés pour les bindings d'API C générés par BindGen.


2.5 Interopérabilité C

Jaguar peut appeler des fonctions définies dans une bibliothèque C avec @extern.

@extern
int add(int a, int b);

Pour les API C plus complexes, BindGen génère automatiquement un .jah à partir du header C.

Les types d'interopérabilité bas niveau comprennent notamment :

cptr
cstr
cfuncptr

ainsi que l'opérateur & pour transmettre l'adresse d'une variable à une API C :

u32 buffer = 0;
gl:glGenBuffers(1, &buffer);

cstr représente une chaîne C (const char *) et cptr représente un pointeur C opaque. cfuncptr représente un pointeur de fonction C, notamment utile pour les callbacks et les APIs comme GLAD.

3. Syntaxe générale

Jaguar n'est pas sensible aux retours à la ligne.

Les blocs utilisent {} et les instructions se terminent généralement par ;.

Exemple :

int main(string param) {
    int x = 10;
    int y = 20;

    if (x < y) {
        sys:print("x est plus petit");
    }

    return 0;
}

4. Commentaires

Commentaire sur une ligne

// Ceci est un commentaire
int x = 10;

Commentaire multiligne

/*
   Commentaire
   sur plusieurs lignes
*/

Les commentaires de documentation /** ... */ sont également supprimés par le lexer.


5. Préprocesseur

Jaguar conserve certaines directives du préprocesseur C et les transmet au C généré. #include n'est pas supporté : les headers C doivent être intégrés avec BindGen/Jaguar headers.

Les directives supportées sont :

#define
#undef

#if
#ifdef
#ifndef
#elif
#else
#endif

#pragma
#error
#warning
#line

Exemple :

#define PI 3.14159265
#define GAME_VERSION 1

#ifdef JAGUAR_DEBUG
    // code de debug
#elif defined(JAGUAR_RELEASE)
    // code de release
#else
    // autre configuration
#endif

Jaguar accepte également #elseif comme écriture alternative de #elif; elle est normalisée vers #elif dans le C généré.

Les conditions sont évaluées par le préprocesseur C lors de la compilation GCC. Les blocs peuvent être imbriqués.

#include est volontairement interdit :

#include <stdio.h> // invalide

Pour utiliser une API C, utilisez un fichier .jah généré par BindGen et using.

Les macros ne sont pas des variables Jaguar et leur type n'est pas inféré par le compilateur.

#define START_HEALTH 100

int default_health = START_HEALTH;

Les macros ne sont pas des variables Jaguar et leur type n'est pas inféré par le compilateur Jaguar.


6. Types

6.1 Types principaux

Jaguar Alias Type C approximatif
void — void
int i32 int
uint u32 unsigned int
short i16 short
ushort u16 unsigned short
long i64 long long
ulong u64 unsigned long long
char i8 signed char
uchar u8 unsigned char
sbyte i8 signed char
byte u8 unsigned char
float f32 float
double f64 double
bool — _jBool
string — objet string Jaguar

Les alias sont normalisés par le compilateur. Par exemple :

int a = 10;
i32 b = 20;

utilisent tous deux le type logique i32.


7. Variables

Déclaration

int x;
f64 speed;
bool alive;
string name;

Initialisation

int x = 42;
f64 speed = 12.5;
bool alive = true;
string name = "Oscar";

Une variable locale peut être déclarée avec n'importe quel type connu.


8. const

const rend une variable ou un paramètre non modifiable.

const int max_health = 100;

Un paramètre peut également être const :

void print_value(const int value) {
    sys:print(value);
}

Les méthodes peuvent également être déclarées const :

int get_health() const {
    return health;
}

Une variable const locale doit être initialisée immédiatement.

const int x = 10;

9. auto

auto permet de déduire le type d'une variable locale depuis son initialisation.

auto x = 42;
auto value = 3.14;
auto name = "hello";

Une variable auto doit obligatoirement être initialisée.

auto x; // invalide

auto est destiné aux variables locales et aux constructions de boucle prévues par Jaguar.


10. Littéraux

Entiers

0
42
-10

Décimaux

3.14
0.5
10.0

Booléens

true
false

Strings

"Hello world"

11. string

string est le type chaîne natif de Jaguar.

string name = "Jaguar";

Il ne s'agit pas d'un simple char* exposé au programme Jaguar.

Méthodes disponibles

length()

int n = name.length();

empty()

bool empty = name.empty();

equals()

if (name.equals("Jaguar")) {
    sys:print("OK");
}

contains()

bool found = name.contains("gua");

starts_with()

bool result = name.starts_with("Jag");

ends_with()

bool result = name.ends_with("ar");

concat()

string full = name.concat("CC");

substring()

string part = name.substring(0, 3);

char_at()

i32 c = name.char_at(0);

Le résultat est le code du caractère, ou -1 si l'index est invalide.

to_upper() / to_lower()

string upper = name.to_upper();
string lower = name.to_lower();

12. Opérateurs

Arithmétiques

+   -   *   /   %

Exemple :

int result = (10 + 5) * 2;

Comparaisons

==  !=  <  >  <=  >=

Les comparaisons produisent un bool.

bool result = health <= 0;

Logique

&&  ||  !

Exemple :

if (alive && health > 0) {
    sys:print("vivant");
}

Priorité

La priorité est proche du C :

||
&&
== !=
< > <= >=
+ -
* / %
- !

Les parenthèses peuvent être utilisées pour rendre une expression explicite.


13. Casts

Jaguar utilise volontairement la syntaxe de cast C :

f64 value = (f64)10;
i32 integer = (i32)3.14;

La forme suivante n'est pas un cast Jaguar :

i32(3.14); // ce n'est pas la syntaxe de cast

14. Conversion implicite pour les surcharges

Le resolver peut utiliser des conversions numériques simples pour choisir une surcharge :

Exemple :

void test(i32 value) {
    sys:print(value);
}

void test(f64 value) {
    sys:print(value);
}

test(10);
test(3.14);

Si plusieurs surcharges restent équivalentes, l'appel est considéré comme ambigu.


15. Fonctions

Déclaration

int add(int a, int b) {
    return a + b;
}

Appel

int result = add(10, 20);

void

void say_hello() {
    sys:print("Hello");
}

16. Paramètres par défaut

Un paramètre peut avoir une valeur par défaut :

int add(int a, int b = 10) {
    return a + b;
}

Les deux appels suivants sont valides :

add(5);
add(5, 20);

17. Arguments nommés

Les arguments peuvent être transmis par nom :

int move(int x, int y, int speed = 1) {
    return x + y + speed;
}

move(y = 20, x = 10);
move(x = 10, y = 20, speed = 5);

Une fois qu'un argument nommé est utilisé, un argument positionnel ne peut plus suivre.

move(x = 10, 20); // invalide

Les fonctions intégrées jcc:* et sys:* n'acceptent pas les arguments nommés.


18. Prototypes

Une fonction peut être déclarée sans corps :

int add(int a, int b);

Puis définie plus loin :

int add(int a, int b) {
    return a + b;
}

Le prototype et la définition doivent avoir exactement la même signature.

Deux prototypes identiques ou deux définitions identiques provoquent une erreur.


19. Surcharge

Jaguar permet plusieurs fonctions de même nom avec des paramètres différents :

void print_value(i32 value) {
    sys:print(value);
}

void print_value(f64 value) {
    sys:print(value);
}

Le compilateur choisit la surcharge appropriée.

Le name mangling utilise les types des paramètres lorsque le nom est réellement surchargé.


20. @extern

@extern empêche le name mangling.

@extern
int my_native_function(int value);

Le nom C reste exactement my_native_function.

Cela permet notamment d'appeler des fonctions définies ailleurs dans le projet ou dans une bibliothèque native.


21. Fonction main

La forme spéciale recommandée est :

void main(string param) {
    sys:print("Hello");
}

ou une forme retournant un entier selon les fonctions supportées par le programme.

Jaguar traduit cette fonction vers un main C standard.

Le compilateur évite d'émettre un main(string) C non standard.


22. return

int square(int x) {
    return x * x;
}

Pour une fonction void :

void stop() {
    return;
}

23. Conditions

if

if (health > 0) {
    sys:print("alive");
}

else

if (health > 0) {
    sys:print("alive");
} else {
    sys:print("dead");
}

else if

if (health > 75) {
    sys:print("high");
} else if (health > 25) {
    sys:print("medium");
} else {
    sys:print("low");
}

Les accolades sont obligatoires.


24. while

Le while Jaguar est volontairement différent du C : il ne prend pas de condition.

while {
    if (health <= 0) {
        break;
    }

    health = health - 1;
}

La sortie de boucle se fait avec break ou return.

La forme suivante est interdite :

while (health > 0) {
}

25. break et continue

while {
    if (value == 10) {
        break;
    }

    if (value == 5) {
        continue;
    }
}

Ils ne sont valides qu'à l'intérieur d'une boucle.


26. for_loop

Jaguar fournit une boucle numérique simplifiée :

int i = for_loop(0, 10) {
    sys:print(i);
}

La borne finale est incluse.

Donc :

0 1 2 3 4 5 6 7 8 9 10

Si le début est supérieur à la fin, aucune itération n'est effectuée.

Le type de l'index doit être entier :

int / i8 / u8 / i16 / u16 / i32 / u32 / i64 / u64

L'index est en lecture seule dans la boucle.


27. Portée des variables

Chaque bloc possède sa propre portée.

void test() {
    int x = 10;

    if (true) {
        int y = 20;
        sys:print(y);
    }

    // y n'existe plus ici
}

Deux blocs frères peuvent utiliser le même nom :

if (true) {
    int x = 10;
}

if (true) {
    int x = 20;
}

En revanche, masquer une variable visible depuis un scope englobant est interdit.



28. Variables globales

Une variable peut être déclarée au niveau global :

int max_health = 100;
f64 gravity = 9.81;

Un initialiseur global doit être une expression constante :

Ceci est valide :

#define MAX_HEALTH 100
int max_health = MAX_HEALTH;

Ceci n'est pas valide comme initialisation globale :

int x = get_value();

Les variables globales doivent également être déclarées avant leur utilisation.

Les list, map et container ne sont actuellement pas utilisables comme variables globales.


29. struct

Les structures Jaguar sont des structures de données simples.

struct Vector2 {
    f32 x;
    f32 y;
}

Utilisation :

Vector2 position;
position.x = 10.0;
position.y = 20.0;

Elles sont générées comme des typedef struct C.


30. Classes

Jaguar possède un système de classes avec :

Exemple :

class Player {
    $int health = 100;

    void take_damage(int amount) {
        health = health - amount;
    }

    int get_health() const {
        return health;
    }
}

31. Contrôle d'accès des classes

Les membres sont privés par défaut.

Public : $

class Player {
    $int health;
}

Protected : %

class Player {
    %int health;
}

Private

Sans marqueur :

class Player {
    int secret_value;
}

Le contrôle d'accès est vérifié par le compilateur.


32. Méthodes

class Player {
    $int health;

    void damage(int amount) {
        health = health - amount;
    }
}

Appel :

Player player = Player();
player.damage(10);

Une méthode possède implicitement un self dans le C généré.


33. this

Dans une classe, this désigne l'instance courante.

class Player {
    $int health;

    void reset() {
        this.health = 100;
    }
}

Dans le C généré, this correspond à self.

this n'est disponible que dans une classe.


34. Constructeur constr

class Player {
    $int health;

    constr(int initial_health) {
        health = initial_health;
    }
}

Création :

Player player = Player(100);

Le compilateur génère également l'allocation de l'objet.


35. Destructeur destr

class Player {
    destr() {
        sys:print("destroyed");
    }
}

Chaque classe possède un point d'entrée destructeur généré par le compilateur, même si aucun destr() n'est écrit.

Avec l'héritage, le destructeur de la classe de base est appelé automatiquement.


36. Héritage

La syntaxe actuelle est :

class Enemy {
    $int health;
}

class Zombie, Enemy {
    void attack() {
        health = health - 10;
    }
}

La classe dérivée contient la partie correspondant à sa classe de base.


37. Méthodes virtuelles

Une méthode peut être virtuelle :

class Enemy {
    virtual void attack() {
        sys:print("enemy attack");
    }
}

Une classe dérivée peut l'override :

class Zombie, Enemy {
    void attack() override {
        sys:print("zombie attack");
    }
}

override doit correspondre à une méthode virtual de la classe de base avec une signature exacte.


38. Méthodes const

class Player {
    $int health;

    int get_health() const {
        return health;
    }
}

Les paramètres peuvent également être const.


39. new

Jaguar possède l'expression new :

new Player(100)

Elle peut être utilisée dans les mécanismes d'ownership de container.


40. container<T>

container<T> représente une valeur dont le container possède la donnée.

container<int> value = new int(42);

Une syntaxe courte est disponible :

container<int> value => 42;

Lecture :

int x = value.get();

Pour une classe :

container<Player> player = new Player(100);
player.get().damage(10);

Le container détruit automatiquement l'objet qu'il possède.

Les container ne peuvent pas être indexés avec [].


41. list<T>

list<T> est une collection homogène.

list<int> numbers = {1, 2, 3, 4};

Ajouter un élément :

numbers.push(5);

Lire un élément :

int value = numbers[2];

L'indexation est en lecture seule.

Cette syntaxe n'est pas autorisée :

numbers[2] = 10;

Pour modifier/ajouter des valeurs, utilisez les opérations prévues par la collection.


42. map<K,V>

map<K,V> est une collection clé/valeur.

map<string, int> scores = {
    "alice", 10,
    "bob", 20
};

Ajouter ou remplacer une entrée :

scores.emplace("charlie", 30);

Lire une valeur :

int score = scores["alice"];

L'indexation est en lecture seule :

scores["alice"] = 100; // invalide

Pour écrire dans la map, utilisez emplace().

La clé string dispose d'une comparaison par contenu.


43. pair<K,V>

Une paire contient deux valeurs : first et second.

pair<string, int> result = {"score", 42};

sys:print(result.first);
sys:print(result.second);

pair est notamment utilisé par loop_map.


44. dynamic_list

dynamic_list permet de stocker plusieurs types dans une même collection.

dynamic_list values = {
    42,
    "hello",
    true,
    3.14
};

Ajouter :

values.push(123);
values.push("world");

Taille :

int count = values.size();

Type d'une valeur :

string type = values.type(1);

Accès brut :

auto value = values.get(0);

L'indexation est également disponible :

sys:print(values[0]);

Pour extraire une valeur primitive avec un type connu, un cast peut être utilisé :

i32 number = (i32)values[0];
f64 decimal = (f64)values[3];

Pour les objets/classes, le cast conserve le pointeur vers l'objet stocké.


45. Boucle loop_list

loop_list parcourt une list<T>.

list<int> numbers = {1, 2, 3, 4};

auto item = loop_list(numbers) {
    sys:print(item);
}

Pendant la boucle, la collection est considérée comme en lecture seule.

Ceci est donc interdit :

auto item = loop_list(numbers) {
    numbers.push(10); // interdit
}

loop_list ne fonctionne pas sur dynamic_list.


46. Boucle loop_map

loop_map parcourt une map<K,V> et fournit un pair<K,V>.

map<string, int> scores = {
    "alice", 10,
    "bob", 20
};

auto item = loop_map(scores) {
    sys:print(item.first);
    sys:print(item.second);
}

La map est en lecture seule pendant la boucle.


47. Signal de changement de variable

Jaguar possède une syntaxe signal: pour associer un bloc à une variable :

signal:health {
    sys:print("Health changed");
}

Cette fonctionnalité est destinée à associer une réaction aux modifications d'une variable.

Note : le comportement exact dépend de la génération actuellement implémentée dans jcc.py. Cette syntaxe fait partie de l'AST du compilateur et doit être considérée comme une fonctionnalité spécifique de Jaguar, pas comme une construction C standard.


48. Namespaces

Les namespaces utilisent :.

namespace math {
    int add(int a, int b) {
        return a + b;
    }
}

Appel :

int value = math:add(10, 20);

Les namespaces peuvent être imbriqués :

namespace engine {
    namespace math {
        int add(int a, int b) {
            return a + b;
        }
    }
}

Appel :

engine:math:add(10, 20);

Le nom C généré est manglé pour éviter les collisions.

Les namespaces supportent actuellement les fonctions, structs, classes et namespaces imbriqués selon les règles du compilateur. Les variables globales directement dans un namespace ne sont pas supportées.


49. Appels de fonctions au niveau global

Une expression d'appel peut être placée au niveau global :

my_function();

Le compilateur peut alors générer l'appel correspondant dans le C produit lorsque le contexte le permet.


50. Bibliothèque sys

sys fournit les fonctions système directement intégrées à JaguarCC.


sys:print

sys:print("Hello");
sys:print(42);
sys:print(3.14);
sys:print(true);

Types imprimables automatiquement :

Chaque appel imprime une valeur suivie d'un retour à la ligne.


51. Console

sys:console:set_color

sys:console:set_color("\\033[31m");
sys:print("Red");
sys:console:reset_color();

La fonction attend une séquence ANSI.

sys:console:reset_color

sys:console:reset_color();

52. Exécution de programme

int result = sys:execute("program", ".");

Le deuxième argument représente le répertoire de travail.

Le résultat correspond au code retourné par l'exécution système.


53. Système de fichiers

Lire un fichier

string data = sys:fs:read("save.txt");

Écrire un fichier

sys:fs:write("save.txt", "hello");

Ces fonctions utilisent l'API fichier C en interne sans exposer FILE* au langage Jaguar.


54. Bibliothèque standard jcc

jcc est la bibliothèque standard simplifiée de JaguarCC.

Elle expose des fonctions inspirées de la libc, mais adaptées aux types et conventions Jaguar.

Les pointeurs et les API libc trop bas niveau ne sont volontairement pas exposés directement.


54.1 Processus

jcc:exit

jcc:exit(0);

Termine le programme avec le code fourni.

jcc:abort

jcc:abort();

Arrête immédiatement le programme.


54.2 Entiers

jcc:abs_i32

i32 x = jcc:abs_i32(-42);

jcc:min_i32

i32 x = jcc:min_i32(10, 20);

jcc:max_i32

i32 x = jcc:max_i32(10, 20);

jcc:clamp_i32

i32 x = jcc:clamp_i32(value, 0, 100);

jcc:random_i32

i32 x = jcc:random_i32(0, 100);

jcc:time_ms

i64 now = jcc:time_ms();

Retourne un temps en millisecondes.

jcc:assert

jcc:assert(health >= 0, "health must not be negative");

55. Fonctions mathématiques jcc

Toutes les fonctions suivantes utilisent principalement f64.

Fonction Signature
sqrt f64 -> f64
pow f64, f64 -> f64
sin f64 -> f64
cos f64 -> f64
tan f64 -> f64
asin f64 -> f64
acos f64 -> f64
atan f64 -> f64
atan2 f64, f64 -> f64
floor f64 -> f64
ceil f64 -> f64
round f64 -> f64
log f64 -> f64
log10 f64 -> f64
exp f64 -> f64
fmod f64, f64 -> f64

Exemple :

f64 distance = jcc:sqrt(x * x + y * y);
f64 angle = jcc:atan2(y, x);
f64 power = jcc:pow(2.0, 8.0);

56. Fonctions string de jcc

jcc expose également des wrappers simples autour des opérations de chaînes.

string a = "Hello";
string b = " World";

int length = jcc:string_length(a);
bool same = jcc:string_equals(a, b);
string result = jcc:string_concat(a, b);

Fonctions :

string_length
string_equals
string_compare
string_contains
string_starts_with
string_ends_with
string_concat
string_substring
string_char_at
string_find
string_to_upper
string_to_lower

Exemple :

if (jcc:string_contains("Hello world", "world")) {
    sys:print("found");
}


57. Conversions de chaînes

String → nombre :

i32 a = jcc:string_to_i32("42");
i64 b = jcc:string_to_i64("100000");
f64 c = jcc:string_to_f64("3.14");

Nombre → string :

string a = jcc:i32_to_string(42);
string b = jcc:i64_to_string(100000);
string c = jcc:f64_to_string(3.14);

58. Fonctions caractères

Les fonctions inspirées de ctype.h utilisent des codes de caractères entiers.

bool digit = jcc:is_digit('0');

Fonctions disponibles :

is_digit
is_alpha
is_alnum
is_space
is_upper
is_lower
to_upper_char
to_lower_char

Elles peuvent être utilisées sur le résultat de string.char_at().

Exemple :

i32 c = name.char_at(0);
if (jcc:is_alpha(c)) {
    sys:print("letter");
}

59. Fichiers et environnement avec jcc

file_exists

bool exists = jcc:file_exists("save.dat");

remove_file

bool success = jcc:remove_file("save.dat");

rename_file

bool success = jcc:rename_file("old.txt", "new.txt");

env_get

string home = jcc:env_get("HOME");

Ces fonctions évitent de manipuler directement FILE*, char*, etc.


60. Réflexion

Jaguar possède un système de réflexion volontairement limité.

Une classe peut être enregistrée avec @register :

@register
class Player {
    $int health = 100;
}

Un champ public peut être exposé avec @exposed :

@register
class Player {
    @exposed
    $int health = 100;
}

@exposed ne peut être utilisé que sur un champ public.


61. factory:construct

Une classe enregistrée peut être construite dynamiquement avec son nom :

string class_name = "Player";
auto object = factory:construct(class_name);

La classe doit être enregistrée avec @register.

Le nom est résolu à l'exécution.

Si la classe n'est pas enregistrée, le runtime produit une erreur.


62. GetMember

Un membre exposé peut être récupéré dynamiquement :

string member = "health";
int health = (int)object.GetMember(member);

Le nom du membre est une string.

Si le membre n'existe pas ou n'est pas exposé, le runtime affiche une erreur.

Le cast est important pour obtenir une valeur typée.


63. MemberExists

Avant un accès dynamique :

string name = "health";

if (object.MemberExists(name)) {
    int health = (int)object.GetMember(name);
}

Cela permet de vérifier si un membre est exposé.


64. SetMember

Un membre exposé peut être modifié dynamiquement :

object.SetMember("health", 50);

Les valeurs primitives et string sont prises en charge par le runtime de réflexion.


65. Exemple complet de réflexion

@register
class Enemy {
    @exposed
    $int health = 100;

    $string name = "Enemy";
}

void main(string param) {
    string class_name = "Enemy";
    auto enemy = factory:construct(class_name);

    if (enemy.MemberExists("health")) {
        sys:print((int)enemy.GetMember("health"));
        enemy.SetMember("health", 25);
        sys:print((int)enemy.GetMember("health"));
    }
}

Seuls les membres marqués @exposed sont accessibles via la réflexion.


66. Attributs disponibles

Les attributs actuellement reconnus par le compilateur sont :

@extern
@register
@exposed

Leur utilisation dépend du contexte :

Attribut Utilisation
@extern fonction
@register classe
@exposed champ public de classe

Un attribut inconnu produit une erreur de compilation.


67. Création de collections avec {}

Les accolades peuvent créer des littéraux de collection.

Liste :

list<int> values = {1, 2, 3};

Map :

map<string, int> values = {
    "one", 1,
    "two", 2
};

Pair :

pair<string, int> value = {"score", 42};

dynamic_list :

dynamic_list values = {1, "hello", true};

68. Indexation

[] est disponible sur certaines collections.

int x = numbers[0];
int score = scores["player"];
auto value = values[0];

Elle est volontairement en lecture seule.

Les container<T> ne sont pas indexables.


69. Gestion mémoire

Jaguar ne donne volontairement pas accès à toute la libc mémoire directement.

Le runtime gère notamment :

Les structures internes utilisent C malloc, calloc, realloc, free, etc., mais ces pointeurs ne sont pas exposés comme API Jaguar générale.

Cela permet de conserver une syntaxe Jaguar plus sûre et plus simple.


70. Fonctions libc volontairement non exposées

Le compilateur connaît plusieurs noms libc afin d'éviter les collisions avec les fonctions Jaguar.

Par exemple, les noms suivants sont réservés lorsqu'ils sont utilisés comme fonctions Jaguar ordinaires :

malloc
free
memcpy
memset
printf
scanf
fopen
fclose
strlen
strcmp
strcpy
qsort
rand
srand
system
...

L'objectif est d'éviter qu'une fonction Jaguar génère accidentellement un symbole C entrant en collision avec la libc.

Lorsqu'une fonctionnalité libc est utile, elle doit idéalement être exposée par une API Jaguar jcc:* adaptée plutôt que par les pointeurs C bruts.


71. Name mangling

Jaguar génère des noms C compatibles avec les namespaces et la surcharge.

Une fonction simple :

int add(int a, int b) {
    return a + b;
}

reste généralement add côté C lorsqu'elle n'est pas surchargée.

Une fonction dans un namespace :

namespace math {
    int add(int a, int b) {
        return a + b;
    }
}

est manglée pour éviter les collisions, par exemple sous une forme du type :

math_add

Une surcharge utilise également les types des paramètres dans son nom C.


72. Résolution des appels

Lorsqu'un appel est rencontré, Jaguar recherche :

  1. le namespace et le nom ;
  2. les fonctions candidates ;
  3. les paramètres par défaut ;
  4. les arguments nommés ;
  5. le type des arguments ;
  6. les conversions numériques autorisées ;
  7. la surcharge la plus adaptée.

Un appel ambigu provoque une erreur.


73. Fonctions natives inconnues

Pour appeler une fonction C externe, la forme prévue est @extern.

@extern
int native_function(int value);

Cela évite d'exposer arbitrairement toutes les fonctions de la libc au langage.


74. Compatibilité C

Jaguar génère du C destiné principalement à GCC et à des compilateurs C modernes.

Le mode :

--c89

demande au générateur de produire une organisation compatible avec C89.

Le runtime évite notamment d'utiliser _Bool pour bool et génère son propre type _jBool.


75. Ce que Jaguar n'expose volontairement pas

Le langage Jaguar général n'expose pas directement plusieurs mécanismes bas niveau du C/C++ :

Cependant, l'interopérabilité C possède un ensemble contrôlé de types et opérations bas niveau :

cptr
cstr
cfuncptr
&

Ils existent pour permettre d'utiliser des bibliothèques C réelles sans transformer Jaguar en langage de pointeurs général.

Les détails de ces types sont documentés dans la section dédiée à l'interopérabilité C.

Les fonctionnalités nécessitant des mécanismes C plus complexes peuvent être implémentées dans le runtime C ou exposées par BindGen.


76. Interopérabilité avec les API C

Jaguar peut utiliser des bibliothèques C externes. Le principe est :

Header C (.h)
     ↓
  jbg.py
     ↓
Jaguar Header (.jah)
     ↓
    using
     ↓
Code Jaguar
     ↓
    JCC
     ↓
Code C
     ↓
   GCC
     ↓
Bibliothèque C

76.1 @extern

Une fonction C externe peut être déclarée manuellement :

@extern
int add(int a, int b);

@extern conserve le nom C de la fonction et empêche le name mangling Jaguar.

76.2 cptr

cptr représente un pointeur C opaque.

Il est destiné aux handles, contextes, structures C et autres pointeurs dont Jaguar n'a pas besoin de connaître la représentation.

76.3 cstr

cstr représente une chaîne C, typiquement const char *.

Exemple :

cstr version = gl:get_string(GL_VERSION);

Il s'agit d'une chaîne C et non du type string propriétaire de Jaguar.

76.4 cfuncptr

cfuncptr représente un pointeur de fonction C.

Il est utile pour les callbacks et les APIs qui exposent des pointeurs de fonctions, notamment les chargeurs d'API graphiques.

76.5 Opérateur &

L'opérateur & permet de transmettre l'adresse d'une variable à une fonction C :

u32 buffer = 0;
gl:glGenBuffers(1, &buffer);

Il est particulièrement utile pour les paramètres C de type T *.


77. BindGen (jbg.py)

jbg.py transforme un header C en Jaguar Header (.jah).

Utilisation directe :

python jbg.py glad/glad.h -o glad.jah

Le résultat peut ensuite être utilisé avec :

using glad;

BindGen est conçu pour les headers d'API C et peut notamment représenter :

Les fonctions externes gardent leur nom C.


78. Exemple d'utilisation avec une API de type GLAD

Un header C peut contenir des fonctions comme :

void glClear(unsigned int mask);
void glGenBuffers(int count, unsigned int *buffers);
const char *glGetString(unsigned int name);

Après BindGen, le code Jaguar peut utiliser l'API via son .jah :

using glad;

void main(string param) {
    u32 buffer = 0;

    gl:glGenBuffers(1, &buffer);

    cstr version = gl:glGetString(GL_VERSION);

    gl:glClear(GL_COLOR_BUFFER_BIT);
}

Les constantes provenant du header peuvent également être utilisées lorsqu'elles sont générées par BindGen.


79. Jaguar Headers (.jah)

Un .jah est un fichier Jaguar destiné à être utilisé comme header.

Exemple :

glad.jah
math.jah
window.jah

Puis :

using glad;
using math;
using window;

Un .jah peut contenir des prototypes, namespaces, déclarations @extern, typedefs/bindings et autres déclarations Jaguar nécessaires à l'interface.

Le .jah ne constitue pas une nouvelle syntaxe : c'est une convention d'organisation du code.


80. Build System

JaguarCC possède un build system séparé : JBS.

Un fichier .jbs décrit les sources, bibliothèques, includes et targets.

Exemple minimal :

version 1.0

out build

compile Main {
    main.ja
}

Cela produit l'exécutable Main dans build.


80.1 out

out définit le répertoire de sortie :

out build

Les targets sont écrites dans ce répertoire.

L'option CLI peut surcharger ce choix :

python jbs.py game.jbs -o other_build

80.2 compile

Compile une target Jaguar en exécutable :

compile Main {
    main.ja
    player.ja
}

JBS effectue :

.ja
 ↓
JCC
 ↓
.c
 ↓
GCC
 ↓
exécutable

Plusieurs targets peuvent être définies dans le même fichier. Sans --target, JBS les construit dans l'ordre du fichier.

Pour ne construire qu'une target :

python jbs.py game.jbs --target Main

80.3 compile_static

Construit une bibliothèque statique :

compile_static MyLib {
    math.ja
    player.ja
}

Le résultat est typiquement :

libMyLib.a

Cette target ne nécessite pas de fonction main.


80.4 compile_shared

Construit une bibliothèque dynamique :

compile_shared MyLib {
    math.ja
    player.ja
}

Sur les plateformes supportées, JBS produit le format de bibliothèque dynamique correspondant, par exemple .so sous Linux ou .dll avec import library sous MinGW/Windows.


80.5 link

Les bibliothèques externes peuvent être ajoutées à une target :

link Main {
    MyLib.a
    SDL2
}

JBS utilise GCC pour effectuer le lien final.

Les bibliothèques C peuvent donc être utilisées directement.

Des chemins de bibliothèques peuvent également être utilisés avec les directives de chemins du build system.


80.6 include

Ajoute un répertoire de recherche pour les headers/bindings :

include include

Les répertoires d'include sont utilisés notamment pour résoudre les using et les headers nécessaires aux bindings.


80.7 libpath

Ajoute un répertoire de recherche de bibliothèques :

libpath lib

Cela permet à JBS/GCC de rechercher les bibliothèques externes dans lib.


80.8 define

Définit une macro de compilation :

define PI 3.14
define MY_GAME_VERSION 2

Les définitions sont disponibles dans le C généré :

#define PI 3.14
#define MY_GAME_VERSION 2

Elles peuvent ensuite être utilisées dans Jaguar :

f64 x = PI;

80.9 bindgen

JBS peut lancer automatiquement BindGen :

bindgen glad {
    glad/glad.h
}

Cela génère automatiquement :

glad.jah

et permet ensuite :

using glad;

Les paramètres peuvent être indiqués sur la directive :

bindgen glad namespace=gl {
    glad/glad.h
}

Le principe est donc :

glad.h
  ↓
bindgen
  ↓
glad.jah
  ↓
using glad

80.10 Modes de build

JBS possède plusieurs modes prédéfinis :

mode debug
mode release
mode relwithdebinfo
mode minsizerel

Ils configurent principalement les flags GCC et définissent une macro correspondante :

Mode Macro
debug JAGUAR_DEBUG
release JAGUAR_RELEASE
relwithdebinfo JAGUAR_RELWITHDEBINFO
minsizerel JAGUAR_MINSIZEREL

Cela permet notamment :

#ifdef JAGUAR_DEBUG
    sys:print("Debug build");
#elif defined(JAGUAR_RELEASE)
    sys:print("Release build");
#endif

Le mode peut également être sélectionné depuis la ligne de commande :

python jbs.py game.jbs --mode release

80.11 c89

JBS peut demander à JCC de générer du C compatible C89 :

c89

80.12 keep_c

Par défaut, JBS peut supprimer les fichiers C intermédiaires.

Pour les conserver :

keep_c

Cela est utile pour inspecter le C généré par JaguarCC ou diagnostiquer une erreur GCC.


80.13 Exemple de projet complet

Un projet utilisant une API C peut être organisé ainsi :

MyGame/
├── example.jbs
├── src/
│   └── main.ja
├── include/
│   └── ...
└── lib/
    └── ...

Fichier example.jbs :

version 1.0

out build

mode debug

include include
libpath lib

c89
keep_c

define GAME_VERSION 1

bindgen glad {
    glad/glad.h
}

compile Main {
    src/main.ja
}

link Main {
    glad
}

Le pipeline complet devient :

glad.h
   ↓
jbg.py
   ↓
glad.jah
   ↓
using glad
   ↓
main.ja
   ↓
JCC
   ↓
Main.c
   ↓
GCC + bibliothèques C
   ↓
Main

81. Exemple complet

Voici un petit programme utilisant plusieurs fonctionnalités du langage :

#define MAX_HEALTH 100

@register
class Player {
    @exposed
    $int health = MAX_HEALTH;

    constr(int initial_health = MAX_HEALTH) {
        health = initial_health;
    }

    void damage(int amount) {
        health = health - amount;
    }

    bool alive() const {
        return health > 0;
    }
}

int add(int a, int b = 0) {
    return a + b;
}

void main(string param) {
    Player player = Player(100);

    list<int> values = {10, 20, 30};

    auto item = loop_list(values) {
        sys:print(item);
    }

    player.damage(25);

    sys:print(player.health);

    if (player.alive()) {
        sys:print("Player alive");
    }

    f64 distance = jcc:sqrt(3.0 * 3.0 + 4.0 * 4.0);
    sys:print(distance);

    string text = jcc:i32_to_string(add(10, b = 20));
    sys:print(text);
}

82. Exemple : petit programme de jeu

class Player {
    $f32 x = 0.0;
    $f32 y = 0.0;
    $int health = 100;

    void move(f32 dx, f32 dy) {
        x = x + dx;
        y = y + dy;
    }

    void damage(int amount) {
        health = health - amount;
    }

    bool alive() const {
        return health > 0;
    }
}

void main(string param) {
    Player player = Player();

    int i = for_loop(0, 4) {
        player.move(1.0, 0.5);
        sys:print(i);
    }

    player.damage(20);

    if (player.alive()) {
        sys:print("Player is alive");
    }

    sys:print(player.x);
    sys:print(player.y);
}

83. Résumé syntaxique

// commentaire
/* commentaire */

#define NAME VALUE

const int x = 10;
auto y = 20;

int add(int a, int b) {
    return a + b;
}

int add(int a, int b);

if (condition) {
} else if (other) {
} else {
}

while {
    break;
    continue;
}

int i = for_loop(0, 10) {
}

struct Vector2 {
    f32 x;
    f32 y;
}

class Player {
    $int health;
    %int protected_value;
    int private_value;

    constr() {
    }

    virtual void update() {
    }

    void update_child() override {
    }

    destr() {
    }
}

namespace game {
    int start() {
        return 0;
    }
}

list<int> values = {1, 2, 3};
map<string, int> scores = {"a", 10};
pair<string, int> result = {"score", 42};
dynamic_list data = {42, "hello", true};
container<int> value => 42;

sys:print("Hello");
jcc:sqrt(25.0);

84. Référence rapide

Types

void
int / i32
uint / u32
short / i16
ushort / u16
long / i64
ulong / u64
char / i8
uchar / u8
sbyte / i8
byte / u8
float / f32
double / f64
bool
string
list<T>
map<K,V>
pair<K,V>
container<T>
dynamic_list

Contrôle

if / else
while
for_loop
break
continue
return

Classes

class
constr
destr
virtual
override
this
$
%
const

Namespaces

namespace
namespace:function()

Attributs

@extern
@register
@exposed

C interop

cptr
cstr
cfuncptr
&

Préprocesseur

#define
#undef
#if
#ifdef
#ifndef
#elif
#else
#endif
#pragma
#error
#warning
#line

#elseif est accepté comme alias Jaguar de #elif.

#include n'est pas supporté.

Collections

list.push()
list[index]

map.emplace()
map[key]

pair.first
pair.second

container.get()

 dynamic_list.push()
 dynamic_list.get()
 dynamic_list.type()
 dynamic_list.size()
 dynamic_list[index]

loop_list()
loop_map()

Système

sys:print()
sys:console:set_color()
sys:console:reset_color()
sys:execute()
sys:fs:read()
sys:fs:write()

Standard jcc

jcc:exit()
jcc:abort()
jcc:abs_i32()
jcc:min_i32()
jcc:max_i32()
jcc:clamp_i32()
jcc:random_i32()
jcc:time_ms()
jcc:assert()

jcc:sqrt()
jcc:pow()
jcc:sin()
jcc:cos()
jcc:tan()
jcc:asin()
jcc:acos()
jcc:atan()
jcc:atan2()
jcc:floor()
jcc:ceil()
jcc:round()
jcc:log()
jcc:log10()
jcc:exp()
jcc:fmod()

jcc:string_length()
jcc:string_equals()
jcc:string_compare()
jcc:string_contains()
jcc:string_starts_with()
jcc:string_ends_with()
jcc:string_concat()
jcc:string_substring()
jcc:string_char_at()
jcc:string_find()
jcc:string_to_upper()
jcc:string_to_lower()

jcc:string_to_i32()
jcc:string_to_i64()
jcc:string_to_f64()
jcc:i32_to_string()
jcc:i64_to_string()
jcc:f64_to_string()

jcc:is_digit()
jcc:is_alpha()
jcc:is_alnum()
jcc:is_space()
jcc:is_upper()
jcc:is_lower()
jcc:to_upper_char()
jcc:to_lower_char()

jcc:file_exists()
jcc:remove_file()
jcc:rename_file()
jcc:env_get()

85. Philosophie du langage

Jaguar cherche à garder un compromis entre :

Le compilateur s'occupe autant que possible des détails bas niveau tout en laissant les fonctionnalités essentielles du langage explicites.


86. Statut des fonctionnalités

Cette documentation correspond au comportement actuellement présent dans jcc.py.

Les points à considérer comme particulièrement spécifiques à l'implémentation actuelle sont :

Pour toute fonctionnalité non décrite ici, il faut se référer au comportement réel de jcc.py plutôt que supposer qu'elle est héritée du C ou du C++.


87. Référence rapide JBS

version
out
include
libpath

define
mode
c89
keep_c

bindgen

compile
compile_static
compile_shared
link

CLI :

python jbs.py project.jbs
python jbs.py project.jbs --target Main
python jbs.py project.jbs --mode release

Le build system utilise JCC pour Jaguar → C et GCC pour les étapes de compilation et de linkage.


88. Organisation recommandée d'un projet Jaguar

Une organisation typique peut être :

Project/
├── build/
├── include/
│   ├── engine.jah
│   └── glad.jah
├── lib/
│   ├── libMyEngine.a
│   └── ...
├── src/
│   ├── main.ja
│   └── game.ja
└── project.jbs

Les interfaces réutilisables sont placées dans des .jah, les implémentations dans des .ja, et les API C externes sont généralement introduites par BindGen.


89. Résumé du pipeline Jaguar moderne

                         ┌──────────────┐
                         │   API C .h   │
                         └──────┬───────┘
                                │
                              jbg.py
                                │
                                ▼
                         ┌──────────────┐
                         │   API .jah   │
                         └──────┬───────┘
                                │
                              using
                                │
┌──────────────┐                ▼
│   source .ja │ ──────────►  JCC
└──────────────┘                │
                                ▼
                              .c
                                │
                         ┌──────┴──────┐
                         │     GCC     │
                         └──────┬──────┘
                                │
                         ┌──────┴──────┐
                         │ C libraries │
                         └──────┬──────┘
                                ▼
                           executable

JBS orchestre l'ensemble du processus.