Debugger

catnip debug suspend la VM sur des breakpoints, expose les variables et reprend l'exécution à la demande. La console, l'API Python et le serveur MCP pilotent le même mécanisme.

Le debugger observe une VM suspendue et change son futur avec une commande. Heisenberg avait prévu le problème, pas l'alias c.

CLI

# Script, plusieurs breakpoints ou code inline
catnip debug -b 5 script.cat
catnip debug -b 3 -b 7 script.cat
catnip debug -c "x = 10; y = x * 2; y + 1" -b 1
  • -b, --break LINE ajoute un breakpoint et peut être répété ;
  • -c, --command CODE débogue du code inline à la place d'un fichier.

Au point d'arrêt, le prompt (catnip-dbg:L5) > accepte :

Commande Alias Effet
continue c reprendre jusqu'au prochain arrêt
step s entrer dans l'appel suivant
next n avancer sans entrer dans les appels
out o sortir de la fonction courante
break N b N ajouter un breakpoint
rbreak N rb N retirer un breakpoint
print EXPR p EXPR évaluer dans le scope courant
vars v afficher les variables locales
list l afficher le source voisin
backtrace bt afficher la pile d'appels
repl ouvrir le sous-mode REPL
quit q arrêter l'exécution
help h afficher l'aide

Une ligne vide répète le dernier step.

⇒ catnip debug -b 3 factorial.cat
Paused at line 3, col 5
(catnip-dbg:L3) > v
  n = 5
(catnip-dbg:L3) > p n * 2
  = 10
(catnip-dbg:L3) > c
Execution finished. Result: 120

Quand le programme échoue au lieu de terminer, la session rend l'erreur exactement comme l'exécution ordinaire — même pile d'appels, même type, même message, même extrait — précédée de l'étiquette de l'événement :

(catnip-dbg:L3) > c

Execution error: Traceback (most recent call last):
  File "/chemin/vers/script.cat", line 7, in outer
NameError: Name 'nope' is not defined
  Did you mean 'open'?
  2 |     x + nope
    |         ^

backtrace reste la commande pour lire la pile pendant une pause ; celle-ci est celle du point d'échec.

Les deux frontends — console et session programmatique, celle qu'utilisent l'API Python et les tools MCP — passent par le même rendu que le CLI. Un débogueur qui rapporte autrement que le programme qu'il débogue oblige à traduire entre les deux.

Sous-mode REPL

repl ouvre un interpréteur dans le frame suspendu. Les locales existantes sont visibles et les nouvelles définitions restent disponibles jusqu'à la reprise :

(catnip-dbg:L3) > repl
(repl:L3) => n
  5
(repl:L3) => y = n * 2
(repl:L3) => y + 1
  11

Les commandes de mouvement reprennent directement la VM. /exit ou une ligne vide revient au prompt du debugger. Les alias courts sont prioritaires sur les noms de variables ; p v évalue une variable nommée v.

Breakpoints dans le source

breakpoint() émet un opcode dédié :

x = 10
breakpoint()
y = x * 2

Un breakpoint -b N cible la première instruction associée à la ligne ; breakpoint() fixe directement l'emplacement pendant la compilation. Les deux formes déclenchent le même événement de pause.

API Python

from catnip import Catnip
from catnip.debug import DebugSession

source = """
x = 10
y = x * 2
y + 1
"""

session = DebugSession(Catnip(), source)
session.add_breakpoint(3)
session.start(blocking=False)

event_type, pause = session.wait_for_event(timeout=10)
print(pause.locals)

session.send_command('continue')
event_type, result = session.wait_for_event(timeout=10)

API principale :

  • add_breakpoint(line) et remove_breakpoint(line) ;
  • start(blocking=False) ;
  • wait_for_event(timeout), qui retourne paused, finished ou error ; le timeout est un nombre de secondes fini et non négatif, sans quoi l'appel lève ValueError — et la session reste utilisable, le refus tombant avant l'attente ;
  • send_command(action) avec continue, step_into, step_over ou step_out ;
  • state et last_pause.

MCP

Tool Effet
debug_start démarrer une session et attendre le premier arrêt
debug_continue reprendre jusqu'au prochain événement
debug_step avancer en mode into, over ou out
debug_inspect lire les variables locales
debug_eval évaluer une expression dans le scope
debug_breakpoint ajouter ou retirer un breakpoint

Chaque debug_start crée une session indépendante. Le client conserve le session_id pour les appels suivants.

Contrat d'exécution

La VM s'exécute dans un thread et échange événements et commandes avec le frontend. Les positions bytecode sont reliées aux offsets UTF-8 du source afin que CLI, API et MCP utilisent les mêmes numéros de ligne. Pendant une pause, la VM libère le GIL avant d'attendre la commande suivante, ce qui autorise l'évaluation depuis le frontend Python.

Limites

  • pas de breakpoint conditionnel ; utiliser if condition { breakpoint() } ;
  • pas de watchpoint sur mutation de variable ;
  • après cinq attentes de 60 secondes sans commande, la VM reprend pour ne pas conserver une session suspendue ;
  • debugger disponible uniquement en mode VM, sélectionné automatiquement pour la session.

Après cinq minutes sans commande, la VM reprend. Le silence a un timeout.