Syntaxe
Sommaire
- Introduction
- Caractéristiques principales
- Premiers pas
- Premier programme
- Séparateurs de Statements
- Newlines (retours à la ligne)
- Semicolons (;)
- Conventions et bonnes pratiques
- Nommage
- Commentaires
- Organisation du code
- Enums
- Unions taggées
- Gestion d'erreurs
- try/except/finally
- raise
- Types d'exception
- with (context managers)
- Astuces et pièges à éviter
- Évaluation court-circuit
- Match exhaustif
- Annexes
- Priorité des opérateurs
Introduction
Catnip est un langage interprété pensé pour être simple, expressif et performant. Il combine une syntaxe claire avec des fonctionnalités modernes comme le pattern matching et les lambdas.
Conventions de lecture des exemples : voir META.
Caractéristiques principales
- Syntaxe claire et concise : inspirée par des langages modernes
- Typage dynamique : les types sont déterminés à l'exécution
- Pattern matching : pour un code plus expressif et sûr
- Fonctions de première classe : les fonctions sont des valeurs comme les autres
- Performance : VM et JIT pour les workloads intensifs
- REPL interactive : pour expérimenter et apprendre rapidement
Premiers pas
Premier programme
Catnip n'a jamais eu vocation à parler au monde.
Seulement à l'exécuter.
print("BORN TO SEGFAULT")
Séparateurs de Statements
Catnip supporte deux types de séparateurs pour délimiter les statements :
Newlines (retours à la ligne)
Les newlines sont significatifs et séparent automatiquement les statements :
# Chaque ligne est un statement séparé
x = 1
y = 2
z = x + y
Cas spéciaux : Les newlines ne sont PAS significatifs dans :
# Arguments de fonction
result = max(10,
20,
30) # OK - newlines ignorés
# Listes et collections
values = list(1,
2,
3) # OK - newlines ignorés
# Blocs
x = {
a = 1
b = 2
a + b
} # OK - newlines significatifs DANS le bloc, ignorés autour des {}
# if/else multilignes
result = if condition {
42
}
else {
0
} # OK - newline avant 'else' non significatif
# Chaînage (dot-continuation)
result = data
.[(x) => { x * 2 }]
.method()
.attribute # OK - newline avant '.' non significatif
Semicolons (;)
Les semicolons permettent de séparer explicitement les statements sur une même ligne :
# Plusieurs statements sur une ligne
x = 1; y = 2; z = x + y
Combinaison : On peut mélanger semicolons et newlines :
# mix semicolons et newlines
x = 1; y = 2
z = x + y
result = z * 2; print(result)
Séparateurs multiples : Les séparateurs consécutifs sont autorisés :
# OK - semicolon suivi de newline
x = { 42 };
y = 1
# OK - newlines multiples
x = 1
y = 2
Les semicolons sont des points de suture syntaxique. On peut en mettre plusieurs d'affilée si on aime vraiment la redondance, un peu comme mettre deux pansements sur la même coupure. Ça ne fait pas de mal, c'est juste une preuve de prudence excessive.
Conventions et bonnes pratiques
Nommage
# Variables et fonctions : snake_case
ma_variable = 42
ma_fonction = (x) => { x * 2 }
# Constantes : MAJUSCULES (convention, pas imposé)
PI = 3.14159
MAX_VALEUR = 100
Commentaires
# Commentaire sur une ligne
# Commentaires
# sur plusieurs
# lignes
Organisation du code
# 1. Constantes en haut
MAX_ITERATIONS = 1000
SEUIL = 0.001
# 2. Définitions de fonctions
fonction_helper = () => {
# …
}
fonction_principale = () => {
# …
}
# 3. Code principal
resultat = fonction_principale()
print(resultat)
Enums
Le mot-clé enum déclare un type à variantes finies :
enum Direction { up; down; left; right }
Direction.up
# → Direction.up
Les variantes sont qualifiées (Direction.up, pas up). Dans un pattern match, un identifiant nu est une capture de
variable -- utiliser la forme qualifiée pour tester une variante :
match d {
Direction.up => { "haut" }
Direction.down => { "bas" }
_ => { "autre" }
}
Voir ENUMS pour la référence complète.
Unions taggées
Le mot-clé union déclare un type somme : variantes nommées avec ou sans charge utile.
union Option { Some(value); None; }
x = Option.Some(42)
y = Option.None
Les variantes avec payload portent des champs typés optionnels (génériques aussi optionnels) :
union Result[T, E] {
Ok(value: T);
Err(error: E);
}
Les annotations de type sur les paramètres sont appliquées à l'entrée de la fonction (voir FUNCTIONS, «
Arité ») : primitives (int, float, bool, None) vérifiées et coercées selon la tour numérique, types nominaux
(structs/enums/unions/traits) vérifiés avec sous-typage sans coercion, et unions de types (int | str, Point | None)
acceptées si la valeur satisfait l'un des membres. Les composites list[T]/set[T]/dict[K, V]/tuple[...] sont
vérifiés conteneur et paramètres (élément, clé/valeur ou position, récursivement), les génériques nominaux
(Option[int]) avec substitution des paramètres dans les payloads, et les types de fonctions ((int, str) -> bool,
flèche absorbante à droite) sur la callabilité et l'arité — le retour d'un callback est vérifié à chaque appel, côté
appelant. Le pattern matching utilise Union.Variant{field} pour les variantes avec payload, Union.Variant pour les
nullaires :
match opt {
Option.Some{value} => { value }
Option.None => { 0 }
}
Une union peut déclarer des méthodes après ses variantes -- au niveau de l'union, self reçoit la variante appelée et
le corps discrimine par match :
union Opt {
Some(value);
Nothing;
unwrap_or(self, default) => {
match self {
Opt.Some{value} => { value }
Opt.Nothing => { default }
}
}
}
Opt.Some(7).unwrap_or(0)
# → 7
Voir UNIONS pour la référence complète (méthodes, égalité, exhaustivité).
Gestion d'erreurs
try/except/finally
Le bloc except utilise une syntaxe match-like avec => :
try {
resultat = operation_risquee()
} except {
e: TypeError => { gerer_type(e) }
e: ValueError | KeyError => { gerer_valeur(e) }
_ => { gerer_tout() }
} finally {
cleanup()
}
Règles :
try+ bloc obligatoireexceptoptionnel, contient 1..N clausespattern => { body }finallyoptionnel, exécuté dans tous les cas (succès, exception, return, break)- au moins un
exceptoufinallyrequis _(catch-all) doit être la dernière clause- binding optionnel :
e: TypeErrorbind le message d'erreur danse - union de types :
ValueError | KeyErrormatch l'un ou l'autre
raise
raise ValueError("message") # lever une exception typée
raise "message" # lever un RuntimeError
raise # re-lever l'exception courante (dans except)
Types d'exception
Hiérarchie compatible Python :
Exception
├── TypeError
├── ValueError
├── NameError
├── AttributeError
├── RuntimeError
│ └── RecursionError
├── MemoryError
├── ArithmeticError
│ └── ZeroDivisionError
└── LookupError
├── IndexError
└── KeyError
Le matching except suit la hiérarchie : except { e: ArithmeticError => { ... } } catch aussi ZeroDivisionError.
Cet arbre n'est pas une liste fermée. Une exception levée par du code Python appelé depuis Catnip garde son propre type et sa hiérarchie réelle, telle que Python la déclare :
# lecture(...) appelle du Python qui lève FileNotFoundError
try {
lecture("absent.txt")
} except {
e: FileNotFoundError => { "type exact" }
e: OSError => { "type parent -- FileNotFoundError en hérite" }
}
Une seule famille échappe à except : les exceptions qui dérivent de BaseException sans dériver d'Exception —
KeyboardInterrupt, GeneratorExit, et toute sous-classe que tu définirais. Ni un handler typé, ni le catch-all _,
ni un finally imbriqué dans un try extérieur ne les interceptent ; les blocs finally s'exécutent quand même. C'est
ce qui garde un Ctrl-C efficace sur un programme dont la boucle principale est dans un try.
Un bloc qui rattrape tout rattraperait aussi la demande d'arrêt. Le catch-all s'arrête donc juste avant l'ordre de tout arrêter.
with (context managers)
with f = open("data.csv") {
f.read()
}
# Multi-binding (cleanup en ordre inverse)
with a = open("in"), b = open("out") {
b.write(a.read())
}
Règles :
with+ au moins un bindingname = expr+ bloc obligatoire- bindings séparés par
,, chacun visible pour les suivants __enter__()appelé de gauche à droite,__exit__()de droite à gauche- si
__exit__retourne truthy, l'exception est supprimée - traceback toujours
None(écart avec Python)
Astuces et pièges à éviter
Évaluation court-circuit
# AND s'arrête au premier False
resultat = False and fonction_couteuse() # fonction_couteuse() n'est PAS appelée
# OR s'arrête au premier True
resultat = True or fonction_couteuse() # fonction_couteuse() n'est PAS appelée
# ?? s'arrête si le LHS n'est pas None
resultat = 42 ?? fonction_couteuse() # fonction_couteuse() n'est PAS appelée
Match exhaustif
# Toujours prévoir un cas par défaut
match valeur {
1 => { "un" }
2 => { "deux" }
_ => { "autre" } # IMPORTANT : évite les cas non gérés
}
Annexes
Priorité des opérateurs
du plus fort au plus faible
| Opérateur | Description |
|---|---|
() |
Parenthèses |
** |
Exponentiation |
+x, -x, ~x |
Unaires |
*, /, //, % |
Multiplication, division |
+, - |
Addition, soustraction |
& |
AND binaire |
^ |
XOR binaire |
| |
OR binaire |
<, <=, >, >=, ==, !=, in, not in, is, is not |
Comparaisons |
not |
NOT logique |
and |
AND logique |
or |
OR logique |
?? |
Nil-coalescing |