5333Fermer5335
GodzilLe 11/08/2026 à 17:36
Zeph (./5333) :
C'est pas pour être défaitiste, et je conçois tout à fait que l'exercice soit intéressant à titre personnel, mais je rejoins vince sur un point : sortir un nouveau langage aujourd'hui, développé par une seule personne qui plus est, me semble voué à tomber dans l'oubli. Il faudrait vraiment qu'il ait un avantage radical par rapport à ce qui existe déjà pour connaître un minimum de popularité.

Franchement?
C'est avec genre d'argument qu'on avance pas et des langages comme Zig n'aurais jamais vu le jour.
Tu ne veux pas l'utiliser? C'est ton choix.
Tu veux l'utiliser? Bienvenue.


Le langage a ses arguments pour lui mem, il comble des points qu'aucun autre langage ne propose a ma connaissance, et se base sur l'experience de ce qui est été jusqu'a present:
- Syntaxe tres proche du C,
- Support de plateforme larges
- Fait pour le systeme, mais pas que.




Liste de points clefs pour Caudex:

- Chaques ajouts par rapport au C a ete choisi pour n'avoir aucun impact sur la qualité du code produit et a minima. Beaucoup de solutions ne sont que du "sucre syntaxique"

- Tagged unions:

Union en C ne donne aucune information sur le champ utilise. Les unions taggé permettent de savoir quell champ est utilise:

union FileResult
{
   Success { uint8 file_handler; };
   Failure { ErrorCode code; };
};



FileResult my_result = file_open("...");

switch (my_result) 
{
    case Success as s: use_file(s.file_handle); break;
    case Failure as f: handle_error(f.code); break;
}

- Pas de fallthrough automatique dans un switch:

Utilisation de `break` ou `fallthrough` est obligatoire. Bonus les `case` peuvent representer une etendue tel que `'a' .. 'z'`

switch (character) 
{
    case 'a' .. 'z', 'A' .. 'Z':
        print("Letter");
        break; // Mandatory
    case ' ':
        fallthrough; // Explicitly declared
    default:
        break;
}

- Les tailles de listes sont connus en interne

Ce qui permet les "slice", aka un object qui pointe vers une liste ainsi que la taille. Ce qui permet de faire ce genre de choses:

uint8[128] array = { ... };

uint8[64] array2 = array[0:64];

En interne, c'est une structure avec un pointer vers les donnés et un champ representant la taille.


- Un type `string` natif, et `char` n'est que pour representer un character. `string` et `char[]` sont deux choses differente.

Une chaine est representé par une slice comme pour les listes, mais une chaine n'est pas une liste de characteres.

- Transtypage explicite

Passage d'un type plus petit a plus grand (genre 8bit vers 16bit) ne necessite pas de declaration.
Passage d'un type compatible mais ou il peut y avoir de la perte de donnée, on utilise le mot clef `as`
Passage d'un type a un autre type qui ne sont pas compatible, on utilise le mot clef `transmute`

uint8 small = 10;
uint16 big = small; // Implicit (Always Safe)
uint8 narrowed = 300 as uint8; // Explicit (Safe, but lossy)
ptr Player p = transmute(ptr Player, 0xC000); // Unsafe Reinterpretation

- Suppression des ficher d'entete.

A l'image de Python, un module (aka un fichier source) se contient lui meme et fourni ce qu'il export directement et ce dont il a besoin:

module graphics;
import math;
readonly uint16 MAX_SPRITES = 100;

/* non exported function */
uint8 get_random(void)
{
 ...
}

/* Exported function */
export uint8 get_number(void)
{
 ...
}

- Suppression du preprocesseur de manière globale

Plus de #ifdef #if etc.. Les macro et relatif sont remplacé par "comptime" qui permet d'executer du code caudex a la compilation (avec limites) genre:

comptime 
{
    // Evaluates during compilation; produces a static ROM table
    readonly uint8[256] sine_table = generate_sine_table(); 
}

- Types clairs

Pas de `short`, `long`, `char` `unsigned long long` etc.. et uniquement des types clairement definie des le depart:

uint8, int16, bool, ...

Les listes font partit du type:
uint8[128] a,b; // Define two 128 elements arrays named `a` and `b`

`void *` est remplacé par le type `rawptr` et `NULL` par `nullptr` qui sont des mot clefs et non des constantes.

le `*` dans la declaration d'un pointeur est remplacé par `ptr`:

ptr uint8 pointer;

`typedef` pour les structure et unions est optionel, mais il est present pour nomber des types.

Le passage par reference est aussi disponible en utilisant le mot clef `ref`:

void increment(ref uint8 value) { value = value + 1; }
// Calling:
increment(&my_counter); 


- Pseudo objet

Exemple de sucre syntaxique, tel Lua, Caudex permet d'attacher des function a une structure:

struct Player 
{
    uint8 health;
}

void Player:take_damage(uint8 amt) 
{
    self->health -= amt; // 'self' is implicitly passed
}

[...]
    Player one = [...];
    one:take_damage(10);
[...]

Qui reviendrai a ecrire en pur C:

struct Player 
{
    uint8 health;
}

void Player_take_damage(struct Player *self, uint8 amt) 
{
    self->health -= amt; // 'self' is implicitly passed
}

[...]
    Player one = [...];
    Player_take_damage(&one, 10);
[...]

- Control d'ABI natif

Il est possible de dire explicitement ou chaque parameter d'un function doit etre passé genre:

// Passes the 'param1' argument specifically in the A register
void func(uint8(register:A) param1); 

// Short-hand alias for the above
void func_short(uint8(A) param1);

Il y a aussi tout un tas d'attribut qui peuvent etre attaché au function ou variables.

exemple:
// Forces the struct to be packed (no padding) and aligned to a 4-byte boundary
struct NetworkHeader @(packed, align(4))
{
    uint8  version;
    uint32 payload_size;
    uint8  flags;
}

Ca inclue le stockage dans une structure, l'endianess d'un champ ou la structure, indiquer comment les bits sont packé dans une structure, ...


- Mot clefs a usage unique et au sense clair:

fini `static`, `register`, `const` dont la majorité des devs oublie la moitié du temps le sense suivant le contexte.

Caudex utilise des mot clefs a usage unique:

- `static` pour une fonction ou variable globale dans un fichier est remplacé par `internal`, qui est le default pour un module. Le mot clef existe mais est optionel.
- `static` pour une variable dans une fonction est remplace par `preserve`
- `extern` est remplace par `external`, il est special ceci dit car il ne sert principalement que pour acceder a des variables ou funciton hors code caudex.
- `const` est remplace par `readonly`
- `volatile` est remplace par `hardware`

Le choix d'utiliser des mots differents dans ce cas est pour eviter la confusion avec le C.


- Plus besoin de `goto`

L'utilisation du mot clef `defer` permet d'indiquer au compilateur ce qu'il faut faire lors de la sortie d'une function:

void foo()
{
   FileHandle f = open_file("log.txt");
   defer 
   {  // Will always run when function exits
      close_file(f); 
   } 
}

L'ordre est garantit etre a l'inverse de leur apparition dans le code (telle une pile LIFO)

- Le format objet natif est majoritairement CPU agnostic. Ce qui veux dire que les libraries n'ont pas besoin d'etre compile pour chaque plateforme supporté en dehors possiblement de code optimisé pour une plateform.

- Le compilateur utilise un parseur de type PEG base sur une forme de machine virtuelle. Ca permet de corriger certains problemes de parseur de type PEG, mais permet aussi de ne pas avoir de code a generer. La grammaire est compile une fois dans le format de la machine virtuelle et est execute directent tout en lisant le fichier a compiler. Ce qui permet d'avoir un parseur identique quelque soit le language, plutot que generer des centaines de lignes de code generallement illisible, il suffit de porter la machine virtuelle dans le langage de choix. La VM permet aussi d'executer facilement le code Caudex dans les sections `comptime`.
Les backend CPU fournissent aussi la grammaire dans un format compatible pour le code ASM qui peux etre inclue dans un fichier caudex.

- Caudex essaye autant que possible d'eviter les UBs en definissant la comment le compilateur doit reagir dans les cas marginaux. Example, une variable 8bit, 255 + 1 garanti le resultat 0.

- Caudex est fait pour supporter n'importe quelle architecture, et peux supporter n'importe quelle architecture a partir d'un CPU 8bit a un CPU 64bits.

Certaines foncitonalité du language n'ont par forcement de sense sur une machine limité, mais le language est configurable dynamiquement pour chaque plateforme.
Il supporte aussi nativement les variables a virgule fixe, leur implementation est par contre dependante de la plateforme cible.

Les archi tel que le 6502 seront suporté principalement via une methode de compilation statique. Ce qui limite la recursion, mais permet de se passer de 99% de la pile.

Caudex est né a partir de la reflexion de "Commment est-ce que je peux faire un language modern pour le 6502" et "Comment est-ce que je peux ameliorer le C". Le 6502 n'est pas la cible principale pour le moment, le compilateur est la cible principale et je ne vais pas m'amuser a faire fonctionner le compilateur sur des machines a base de 6502.



La liste ici n'est pas complete, lister tout reviendrais a re-ecrire la documentation du language.

La documentation n'est pas encore prete a etre publié, mais le parseur PEG est quasiment pret, et sera tres probablement la premiere pierre de l'edifice mis a disposition publiquement.