Skip to content

🏗️ Architecture Technique - KodLab

Cette documentation détaille l'architecture interne de KodLab, le processus de compilation, et le fonctionnement de l'émulateur.


📋 Table des Matières

  1. Vue d'Ensemble
  2. Compilateur Kod
  3. Émulateur de Console
  4. Interface Utilisateur
  5. Flux de Données
  6. Gestion des Erreurs
  7. Performance et Optimisations

Vue d'Ensemble

Architecture Modulaire

┌─────────────────────────────────────────────────────┐
│                    index.html                       │
│  Interface Utilisateur + Monaco Editor + Canvas    │
└─────────────────┬───────────────────────────────────┘
                  │
┌─────────────────┴───────────────────────────────────┐
│                   main.js                          │
│        Orchestration et Communication               │
└─────────┬─────────────────────────┬─────────────────┘
          │                         │
┌─────────┴─────────┐    ┌─────────┴─────────────────┐
│  kodCompiler.js   │    │     kodEmulator.js        │
│  .kod → JavaScript│    │   Exécution + Canvas      │
└───────────────────┘    └───────────────────────────┘

Technologies Utilisées

  • Frontend : HTML5, CSS3, JavaScript ES6+
  • Éditeur : Monaco Editor (VS Code)
  • Rendu : Canvas 2D Context
  • Parsing : Expressions régulières et AST simple
  • Compilation : Transpilation vers JavaScript ES6 Classes

Compilateur Kod

Processus de Compilation

1. Analyse Lexicale (Tokenisation)

// kodCompiler.js - Méthode compile()
compile(kodCode) {
    // 1. Nettoyage et préparation
    const lines = kodCode
        .split('\n')
        .map(line => line.trim())
        .filter(line => line.length > 0 && !line.startsWith('//'));

    // 2. Séparation variables/fonctions
    let variables = [];
    let functions = [];
    let i = 0;

    // 3. Collecte des variables globales
    while (i < lines.length && !lines[i].startsWith('function')) {
        const varMatch = line.match(/^let\s+(\w+)\s*=\s*(.+)$/);
        if (varMatch) {
            variables.push({ name: varMatch[1], value: varMatch[2] });
        }
        i++;
    }

    // 4. Collecte et compilation des fonctions
    // ...
}

2. Analyse Syntaxique (Parsing)

// Extraction des fonctions avec gestion des blocs
while (i < lines.length) {
    if (lines[i].startsWith('function') || lines[i].match(/^(start|update)\s*\(/)) {
        const funcName = this.extractFunctionName(lines[i]);
        i++; // Passe la ligne de déclaration

        let funcBody = [];
        let braceLevel = 0;

        // Parse du corps de fonction
        while (i < lines.length && (lines[i] !== 'end' || braceLevel > 0)) {
            const line = lines[i];

            if (line === 'end') {
                braceLevel--;
                funcBody.push('        }');
            } else {
                const compiled = this.compileStatement(line);
                if (compiled.trim()) {
                    funcBody.push('        ' + compiled);
                    // Détection des structures de contrôle
                    if (compiled.includes('if (') || 
                        compiled.includes('for (') || 
                        compiled.includes('while (')) {
                        braceLevel++;
                    }
                }
            }
            i++;
        }

        functions.push({ name: funcName, body: funcBody });
    }
    i++;
}

3. Compilation des Instructions

compileStatement(line) {
    // 1. Structures de contrôle
    if (line.startsWith('if ')) {
        return this.compileIf(line);
    } else if (line.startsWith('else if ')) {
        return this.compileElseIf(line);
    } else if (line === 'else') {
        return '} else {';
    } else if (line.startsWith('for ')) {
        return this.compileFor(line);
    } else if (line.startsWith('while ')) {
        return this.compileWhile(line);
    }

    // 2. Assignations
    else if (line.match(/^\w+\s*=\s*.+$/)) {
        return this.compileAssignment(line);
    }

    // 3. Appels de fonction
    else {
        return this.compileFunctionCall(line);
    }
}

// Compilation spécifique des conditions
compileIf(line) {
    const condition = line.substring(3).trim(); // Enlève 'if '
    const jsCondition = this.compileExpression(condition);
    return `if (${jsCondition}) {`;
}

// Compilation des expressions
compileExpression(expr) {
    // Opérateurs logiques Kod → JavaScript
    expr = expr.replace(/\s+and\s+/g, ' && ');
    expr = expr.replace(/\s+or\s+/g, ' || ');
    expr = expr.replace(/\s+not\s+/g, ' !');

    // Appels de fonctions API
    expr = expr.replace(/isKeyPressed\s*\(/g, 'this.isKeyPressed(');

    // Variables → propriétés de classe
    expr = expr.replace(/\b(\w+)\b/g, (match) => {
        if (this.isVariable(match)) {
            return `this.${match}`;
        }
        return match;
    });

    return expr;
}

4. Génération du Code JavaScript

generateGameClass(variables, functions) {
    let code = `
// Code généré automatiquement par le compilateur .kod
class KodGame {
    constructor(emulator) {
        this.emulator = emulator;
        this.ctx = emulator.ctx;
        this.canvas = emulator.canvas;
        this.keys = emulator.keys;

        // Variables globales du jeu
`;

    // Injection des variables comme propriétés
    for (let variable of variables) {
        code += `        this.${variable.name} = ${variable.value};\n`;
    }

    code += `    }

    // === API GRAPHIQUE ===
    clearScreen(color = '#000000') {
        this.ctx.fillStyle = color;
        this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    }

    drawRect(x, y, width, height, color) {
        this.ctx.fillStyle = color;
        this.ctx.fillRect(x, y, width, height);
    }

    // ... autres méthodes API ...

    // === FONCTIONS UTILISATEUR ===
`;

    // Injection des fonctions compilées
    for (let func of functions) {
        code += `    ${func.name}() {\n`;
        code += func.body.join('\n') + '\n';
        code += `    }\n\n`;
    }

    code += `}`;

    // Nettoyage des références doubles
    code = code.replace(/this\.this\./g, 'this.');
    code = code.replace(/\.{2,}/g, '.');

    return code;
}

Gestion des Erreurs de Compilation

compile(kodCode) {
    try {
        // Processus de compilation...
        return { success: true, code: jsCode };
    } catch (error) {
        return { 
            success: false, 
            error: this.formatCompilerError(error)
        };
    }
}

formatCompilerError(error) {
    // Localisation de l'erreur
    const lineMatch = error.message.match(/line (\d+)/);
    if (lineMatch) {
        const lineNum = parseInt(lineMatch[1]);
        return `Erreur ligne ${lineNum}: ${error.message}`;
    }

    // Erreurs de syntaxe courantes
    if (error.message.includes('unexpected token')) {
        return 'Erreur de syntaxe: vérifiez les parenthèses et la structure des blocs';
    }

    return `Erreur de compilation: ${error.message}`;
}

Émulateur de Console

Architecture de l'Émulateur

class KodEmulator {
    constructor(canvasId) {
        // Initialisation du canvas
        this.canvas = document.getElementById(canvasId);
        this.ctx = this.canvas.getContext('2d');

        // État de l'émulateur
        this.gameInstance = null;
        this.isRunning = false;
        this.animationId = null;

        // Métriques de performance
        this.lastFrameTime = 0;
        this.fps = 0;
        this.frameCount = 0;

        // Gestion des entrées
        this.keys = {};

        this.setupEventListeners();
        this.setupCanvas();
    }
}

Boucle de Jeu Principal

// Démarrage du jeu
run() {
    if (!this.gameInstance) {
        return { success: false, error: 'Aucun jeu chargé' };
    }

    this.isRunning = true;
    this.lastFrameTime = performance.now();

    // Appel initial de start()
    if (typeof this.gameInstance.start === 'function') {
        this.gameInstance.start();
    }

    // Lancement de la boucle principale
    this.gameLoop();

    return { success: true, message: 'Jeu démarré' };
}

// Boucle de rendu 60 FPS
gameLoop() {
    if (!this.isRunning) return;

    const currentTime = performance.now();
    const deltaTime = currentTime - this.lastFrameTime;

    // Calcul FPS
    this.frameCount++;
    if (currentTime - this.fpsUpdateTime >= 1000) {
        this.fps = Math.round(this.frameCount * 1000 / (currentTime - this.fpsUpdateTime));
        this.frameCount = 0;
        this.fpsUpdateTime = currentTime;
    }

    // Appel de la fonction update() du jeu
    try {
        if (typeof this.gameInstance.update === 'function') {
            this.gameInstance.update();
        }
    } catch (error) {
        this.handleRuntimeError(error);
        return;
    }

    this.lastFrameTime = currentTime;

    // Programmer la prochaine frame
    this.animationId = requestAnimationFrame(() => this.gameLoop());
}

Gestion des Entrées

setupEventListeners() {
    // Capture des événements clavier
    document.addEventListener('keydown', (e) => {
        this.handleKeyEvent(e, true);
    });

    document.addEventListener('keyup', (e) => {
        this.handleKeyEvent(e, false);
    });

    // Prévention du scroll avec les flèches
    document.addEventListener('keydown', (e) => {
        if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'].includes(e.code)) {
            e.preventDefault();
        }
    });
}

handleKeyEvent(event, isPressed) {
    // Mapping des touches
    const keyMap = {
        'ArrowUp': 'up',
        'ArrowDown': 'down', 
        'ArrowLeft': 'left',
        'ArrowRight': 'right',
        'Space': 'space',
        // Support WASD
        'KeyW': 'up',
        'KeyS': 'down',
        'KeyA': 'left',
        'KeyD': 'right'
    };

    const mappedKey = keyMap[event.code];
    if (mappedKey) {
        this.keys[mappedKey] = isPressed;
    }
}

// API accessible depuis les jeux
isKeyPressed(key) {
    return Boolean(this.keys[key]);
}

Configuration du Canvas

setupCanvas() {
    // Configuration pour le pixel art
    this.ctx.imageSmoothingEnabled = false;
    this.canvas.style.imageRendering = 'pixelated';

    // Écran noir par défaut
    this.clearScreen('#000000');

    // Message d'accueil
    this.ctx.fillStyle = '#666';
    this.ctx.font = '20px Arial';
    this.ctx.textAlign = 'center';
    this.ctx.fillText('KodLab Ready', this.canvas.width / 2, this.canvas.height / 2);
    this.ctx.fillText('Compilez et lancez votre code!', this.canvas.width / 2, this.canvas.height / 2 + 30);
}

// API de rendu optimisée
clearScreen(color = '#000000') {
    this.ctx.fillStyle = color;
    this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}

drawRect(x, y, width, height, color) {
    this.ctx.fillStyle = color;
    this.ctx.fillRect(x, y, width, height);
}

drawCircle(x, y, radius, color) {
    this.ctx.fillStyle = color;
    this.ctx.beginPath();
    this.ctx.arc(x, y, radius, 0, 2 * Math.PI);
    this.ctx.fill();
}

drawText(text, x, y, color) {
    this.ctx.fillStyle = color;
    this.ctx.font = '16px Arial';
    this.ctx.textAlign = 'left';
    this.ctx.fillText(text, x, y);
}

Interface Utilisateur

Structure HTML

<!DOCTYPE html>
<html>
<head>
    <title>KodLab</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <!-- En-tête -->
        <header class="header">
            <h1>🎮 KodLab</h1>
            <div class="controls">
                <select id="exampleSelect">
                    <option value="">Sélectionner un exemple</option>
                </select>
            </div>
        </header>

        <!-- Zone principale -->
        <main class="main">
            <!-- Éditeur de code -->
            <section class="editor-section">
                <div id="editor"></div>
                <div class="editor-controls">
                    <button id="compileBtn">🔧 Compiler</button>
                    <button id="runBtn">▶️ Lancer</button>
                    <button id="stopBtn">⏹️ Stop</button>
                </div>
            </section>

            <!-- Console de jeu -->
            <section class="console-section">
                <div class="console-header">
                    <h3>🎯 Console de Jeu</h3>
                    <span id="fpsDisplay">FPS: --</span>
                </div>
                <canvas id="gameCanvas" width="400" height="300"></canvas>
            </section>
        </main>

        <!-- Console de debug -->
        <footer class="console">
            <div id="consoleOutput"></div>
        </footer>
    </div>

    <!-- Scripts -->
    <script src="https://unpkg.com/monaco-editor@0.44.0/min/vs/loader.js"></script>
    <script src="kodCompiler.js"></script>
    <script src="kodEmulator.js"></script>
    <script src="main.js"></script>
</body>
</html>

Intégration Monaco Editor

// main.js - Configuration de l'éditeur
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.44.0/min/vs' }});

require(['vs/editor/editor.main'], function () {
    // Configuration de la coloration syntaxique pour .kod
    monaco.languages.register({ id: 'kod' });

    monaco.languages.setMonarchTokensProvider('kod', {
        tokenizer: {
            root: [
                // Mots-clés
                [/\b(function|if|else|end|for|while|let|to|step)\b/, 'keyword'],

                // Fonctions API
                [/\b(clearScreen|drawRect|drawCircle|drawText|drawLine|isKeyPressed)\b/, 'function'],

                // Nombres
                [/\d+(\.\d+)?/, 'number'],

                // Chaînes
                [/"[^"]*"/, 'string'],
                [/'[^']*'/, 'string'],

                // Commentaires
                [/\/\/.*$/, 'comment'],

                // Opérateurs
                [/[+\-*\/=<>!&|]/, 'operator'],
            ]
        }
    });

    // Création de l'éditeur
    window.editor = monaco.editor.create(document.getElementById('editor'), {
        value: getDefaultCode(),
        language: 'kod',
        theme: 'vs-dark',
        automaticLayout: true,
        minimap: { enabled: false },
        scrollBeyondLastLine: false,
        fontSize: 14,
        tabSize: 4
    });
});

function getDefaultCode() {
    return `// Bienvenue dans KodLab !
// Créez votre premier jeu en modifiant ce code

let x = 200
let y = 150

function start()
    clearScreen("#000033")
end

function update()
    clearScreen("#000033")

    // Contrôles
    if isKeyPressed("left")
        x = x - 3
    end
    if isKeyPressed("right")
        x = x + 3
    end

    // Affichage
    drawCircle(x, y, 20, "#FF6600")
    drawText("Utilisez les flèches!", 10, 20, "#FFFFFF")
end`;
}

Flux de Données

Cycle de Développement

1. Écriture du Code (.kod)
   ↓
2. Compilation (kodCompiler.js)
   ├─ Succès → Code JavaScript ES6
   └─ Erreur → Message d'erreur
   ↓
3. Chargement dans l'Émulateur
   ├─ eval() du code compilé
   ├─ Création instance KodGame
   └─ Liaison avec l'émulateur
   ↓
4. Exécution
   ├─ Appel start() (une fois)
   ├─ Boucle update() (60 FPS)
   ├─ Gestion des entrées
   └─ Rendu Canvas

Communication entre Modules

// main.js - Orchestration
class KodLabApp {
    constructor() {
        this.compiler = new KodCompiler();
        this.emulator = new KodEmulator('gameCanvas');
        this.editor = null; // Monaco Editor

        this.setupEventListeners();
    }

    compile() {
        const code = this.editor.getValue();
        const result = this.compiler.compile(code);

        if (result.success) {
            this.logMessage('✅ Compilation réussie !', 'success');
            this.compiledCode = result.code;
            return true;
        } else {
            this.logMessage('❌ ' + result.error, 'error');
            return false;
        }
    }

    run() {
        if (!this.compiledCode) {
            if (!this.compile()) return;
        }

        // Chargement dans l'émulateur
        const loadResult = this.emulator.loadGame(this.compiledCode);
        if (loadResult.success) {
            // Démarrage du jeu
            const runResult = this.emulator.run();
            if (runResult.success) {
                this.logMessage('▶️ ' + runResult.message, 'success');
                this.updateUI('running');
            } else {
                this.logMessage('❌ ' + runResult.error, 'error');
            }
        } else {
            this.logMessage('❌ ' + loadResult.error, 'error');
        }
    }

    stop() {
        this.emulator.stop();
        this.logMessage('⏹️ Jeu arrêté', 'info');
        this.updateUI('stopped');
    }
}

Gestion des Erreurs

Types d'Erreurs

1. Erreurs de Compilation

// Erreurs syntaxiques
"Erreur ligne 15: fonction 'drawCircl' inconnue"
"Erreur ligne 8: 'end' attendu après 'if'"
"Erreur ligne 22: variable 'speed' non déclarée"

// Erreurs de structure
"Fonction 'start' ou 'update' manquante"
"Bloc non fermé: vérifiez vos 'end'"

2. Erreurs d'Exécution

// Erreurs JavaScript générées
try {
    this.gameInstance.update();
} catch (error) {
    this.handleRuntimeError(error);
}

handleRuntimeError(error) {
    let message = 'Erreur d\'exécution: ';

    if (error.name === 'ReferenceError') {
        message += 'Variable ou fonction non définie';
    } else if (error.name === 'TypeError') {
        message += 'Type de données incorrect';
    } else {
        message += error.message;
    }

    this.logError(message);
    this.stop();
}

Système de Logging

class Logger {
    static log(message, type = 'info') {
        const console = document.getElementById('consoleOutput');
        const timestamp = new Date().toLocaleTimeString();

        const entry = document.createElement('div');
        entry.className = `console-entry ${type}`;
        entry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`;

        console.appendChild(entry);
        console.scrollTop = console.scrollHeight;

        // Limite le nombre d'entrées
        while (console.children.length > 100) {
            console.removeChild(console.firstChild);
        }
    }

    static clear() {
        document.getElementById('consoleOutput').innerHTML = '';
    }
}

Performance et Optimisations

Optimisations du Compilateur

// Cache des expressions compilées
class KodCompiler {
    constructor() {
        this.expressionCache = new Map();
        this.variableRegistry = new Set();
    }

    compileExpression(expr) {
        // Utilisation du cache
        if (this.expressionCache.has(expr)) {
            return this.expressionCache.get(expr);
        }

        const compiled = this.doCompileExpression(expr);
        this.expressionCache.set(expr, compiled);
        return compiled;
    }
}

Optimisations de l'Émulateur

// Pool d'objets pour éviter le garbage collection
class ObjectPool {
    constructor(createFn, resetFn, initialSize = 10) {
        this.createFn = createFn;
        this.resetFn = resetFn;
        this.available = [];
        this.inUse = [];

        // Pré-allocation
        for (let i = 0; i < initialSize; i++) {
            this.available.push(this.createFn());
        }
    }

    acquire() {
        let obj = this.available.pop();
        if (!obj) {
            obj = this.createFn();
        }
        this.inUse.push(obj);
        return obj;
    }

    release(obj) {
        const index = this.inUse.indexOf(obj);
        if (index >= 0) {
            this.inUse.splice(index, 1);
            this.resetFn(obj);
            this.available.push(obj);
        }
    }
}

Optimisations de Rendu

// Batch rendering pour réduire les appels Canvas
class BatchRenderer {
    constructor(ctx) {
        this.ctx = ctx;
        this.batches = new Map();
    }

    addRect(x, y, w, h, color) {
        if (!this.batches.has(color)) {
            this.batches.set(color, []);
        }
        this.batches.get(color).push({type: 'rect', x, y, w, h});
    }

    flush() {
        for (const [color, shapes] of this.batches) {
            this.ctx.fillStyle = color;

            for (const shape of shapes) {
                if (shape.type === 'rect') {
                    this.ctx.fillRect(shape.x, shape.y, shape.w, shape.h);
                }
            }
        }

        this.batches.clear();
    }
}

Métriques de Performance

class PerformanceMonitor {
    constructor() {
        this.frameTime = 0;
        this.fps = 0;
        this.memoryUsage = 0;
        this.drawCalls = 0;
    }

    startFrame() {
        this.frameStartTime = performance.now();
        this.drawCalls = 0;
    }

    endFrame() {
        this.frameTime = performance.now() - this.frameStartTime;
        this.fps = Math.round(1000 / this.frameTime);

        // Collecte mémoire (si disponible)
        if (performance.memory) {
            this.memoryUsage = Math.round(performance.memory.usedJSHeapSize / 1024 / 1024);
        }
    }

    recordDrawCall() {
        this.drawCalls++;
    }

    getReport() {
        return {
            fps: this.fps,
            frameTime: this.frameTime.toFixed(2),
            drawCalls: this.drawCalls,
            memory: this.memoryUsage
        };
    }
}

🔧 Points d'Extension

Ajout de Nouvelles API

// Dans generateGameClass()
// Ajouter de nouvelles fonctions à la classe générée

playSound(soundId) {
    // Intégration Web Audio API
    if (this.emulator.audioContext) {
        // Lecture du son
    }
}

saveToStorage(key, value) {
    localStorage.setItem('KodLab_' + key, JSON.stringify(value));
}

loadFromStorage(key) {
    const data = localStorage.getItem('KodLab_' + key);
    return data ? JSON.parse(data) : null;
}

Support de Nouveaux Types

// Extension du système de types
compileExpression(expr) {
    // Support des arrays (simulation)
    expr = expr.replace(/(\w+)\[(\d+)\]/g, 'this.$1_$2');

    // Support des structures
    expr = expr.replace(/(\w+)\.(\w+)/g, 'this.$1_$2');

    return expr;
}

Cette architecture modulaire permet une maintenance aisée et des extensions futures tout en gardant une complexité maîtrisée pour l'utilisateur final.

Pour aller plus loin, consultez l'API de Référence ! 🚀