Implémentation d’un JIT Simplifié pour un Sous-ensemble d’Expressions Lua

Implémentation d’un compilateur JIT simplifié en Lua pour expressions arithmétiques, avec analyse, génération de code, cache et gestion d’erreurs.

Brève Introduction

Implémentation d’un JIT Simplifié pour un Sous-ensemble d’Expressions Lua (lua)
DESCRIPTION :
Cet exemple montre comment implémenter un compilateur Just-In-Time (JIT) très simplifié pour un sous-ensemble d'expressions arithmétiques en Lua. Le code analyse des expressions mathématiques simples, génère du code Lua dynamique et l'exécute, améliorant les performances par rapport à l'interprétation directe répétée. Le système est conçu pour être extensible et robuste, avec gestion des erreurs et mise en cache des fonctions compilées.

NOTES SUPPLÉMENTAIRES :
Cet exemple est un JIT très simplifié et pédagogique, non adapté pour remplacer des moteurs JIT professionnels. Le parseur supporte uniquement les expressions arithmétiques avec des nombres et des opérateurs de base. La génération de code protège contre la division par zéro à l'exécution. Le cache évite les recompilations inutiles. L'implémentation est autonome et ne nécessite pas de bibliothèques externes. Elle peut être étendue pour supporter plus d'opérateurs, variables, fonctions ou optimisations plus sophistiquées.

EXPLICATIONS :
- Lexer : divise la chaîne d'entrée en tokens en reconnaissant nombres, opérateurs et parenthèses, en ignorant les espaces.
- Parser : construit un arbre syntaxique (AST) respectant la priorité des opérateurs (+ et - plus faibles que * et /) et gère les parenthèses.
- generate_code : transforme l'AST en une chaîne de code Lua exécutable, avec protection à l'exécution contre la division par zéro.
- JIT.compile : compile l'expression en une fonction Lua, la stockant en cache pour réutilisation.
- JIT.eval : exécute la fonction compilée en toute sécurité, capturant les erreurs d'exécution.
- test : montre des exemples d'évaluation d'expressions, y compris des cas d'erreur comme la division par zéro.

Code d'Exemple : lua

--[[
  JIT simplifié pour expressions arithmétiques en Lua
  Supporte les nombres, +, -, *, / et parenthèses.
  Compile les expressions en fonctions Lua pour une exécution plus rapide.
  Inclut un cache pour éviter les recompilations.
]]

-- Module JIT
local JIT = {}

-- Cache pour fonctions compilées : clé = expression, valeur = fonction
JIT.cache = {}

-- Fonction de parsing : transforme une chaîne en AST (arbre syntaxique)
-- L'AST est une table avec des nœuds de type {tag = 'number'|'binop', value = number, op = string, left = node, right = node}

-- Lexer : tokenize l'expression
local function lexer(input)
  local pos = 1
  local tokens = {}
  local patterns = {
    {"number", "^%d+%.?%d*"},
    {"op", "^[%+%-%*%/%(%)"]},
    {"space", "^%s+"}
  }

  while pos <= #input do
    local matched = false
    for _, p in ipairs(patterns) do
      local ttype, pattern = p[1], p[2]
      local s, e = string.find(input, pattern, pos)
      if s == pos then
        local token = string.sub(input, s, e)
        if ttype ~= "space" then
          table.insert(tokens, {type = ttype, value = token})
        end
        pos = e + 1
        matched = true
        break
      end
    end
    if not matched then
      error("Jeton non reconnu près de : '" .. string.sub(input, pos, pos+10) .. "'")
    end
  end
  return tokens
end

-- Parser avec priorité des opérateurs
local Parser = {}
Parser.__index = Parser

function Parser:new(tokens)
  local o = {tokens = tokens, pos = 1}
  setmetatable(o, self)
  return o
end

function Parser:peek()
  return self.tokens[self.pos]
end

function Parser:consume(ttype, val)
  local t = self:peek()
  if t and t.type == ttype and (not val or t.value == val) then
    self.pos = self.pos + 1
    return t
  else
    return nil
  end
end

-- Parsing avec priorité : expression -> term { (+|-) term }
-- term -> factor { (*|/) factor }
-- factor -> number | '(' expression ')'

function Parser:parse_expression()
  local node = self:parse_term()
  while true do
    local op = self:consume('op', '+') or self:consume('op', '-')
    if not op then break end
    local right = self:parse_term()
    node = {tag = 'binop', op = op.value, left = node, right = right}
  end
  return node
end

function Parser:parse_term()
  local node = self:parse_factor()
  while true do
    local op = self:consume('op', '*') or self:consume('op', '/')
    if not op then break end
    local right = self:parse_factor()
    node = {tag = 'binop', op = op.value, left = node, right = right}
  end
  return node
end

function Parser:parse_factor()
  local t = self:peek()
  if not t then error("Fin inattendue de l'expression") end

  if t.type == 'number' then
    self.pos = self.pos + 1
    return {tag = 'number', value = tonumber(t.value)}
  elseif t.type == 'op' and t.value == '(' then
    self.pos = self.pos + 1
    local node = self:parse_expression()
    if not self:consume('op', ')') then
      error("Parenthèse fermante manquante")
    end
    return node
  else
    error("Jeton inattendu : '" .. t.value .. "'")
  end
end

-- Fonction pour générer du code Lua à partir de l'AST
local function generate_code(ast)
  if ast.tag == 'number' then
    return tostring(ast.value)
  elseif ast.tag == 'binop' then
    local left = generate_code(ast.left)
    local right = generate_code(ast.right)
    -- Protection contre la division par zéro à l'exécution
    if ast.op == '/' then
      return string.format("(function() local r = %s if r == 0 then error('Division par zéro') end return %s / r end)()", right, left)
    else
      return string.format("(%s %s %s)", left, ast.op, right)
    end
  else
    error("Nœud AST inconnu : " .. tostring(ast.tag))
  end
end

-- Fonction principale JIT : compile ou récupère du cache
function JIT.compile(expr)
  if JIT.cache[expr] then
    return JIT.cache[expr]
  end

  local tokens = lexer(expr)
  local parser = Parser:new(tokens)
  local ast = parser:parse_expression()
  if parser:peek() then
    error("Jetons supplémentaires après la fin de l'expression")
  end

  local code = "return " .. generate_code(ast)

  local f, err = load(code, "jit_expr", "t", {})
  if not f then
    error("Erreur de compilation du code : " .. err)
  end

  JIT.cache[expr] = f
  return f
end

-- Fonction d'exécution sécurisée
function JIT.eval(expr)
  local f = JIT.compile(expr)
  local ok, result = pcall(f)
  if not ok then
    error("Erreur d'exécution : " .. result)
  end
  return result
end

-- Exemple d'utilisation
local function test()
  local expressions = {
    "1 + 2 * 3",
    "(1 + 2) * 3",
    "10 / (5 - 5)", -- division par zéro
    "4 + 5 / 2",
    "7 - 3 + 2",
    "(2 + 3) * (4 - 1)"
  }

  for _, expr in ipairs(expressions) do
    io.write("Évaluation : ", expr, " = ")
    local ok, res = pcall(JIT.eval, expr)
    if ok then
      print(res)
    else
      print("Erreur : " .. res)
    end
  end
end

-- Décommenter pour tester
-- test()

return JIT
×
×
×
Veux-tu régénérer la réponse ?
×
Exporter le chat
Choisissez le format d'export
⏳ Generazione PDF in corso…
Allegati
×
⚠️ Vous êtes sur le point de fermer le chat et de passer au générateur d’images. Si vous n’êtes pas connecté, vous perdrez notre conversation. Confirmez-vous ?
👁 Tu consultes une discussion partagée en mode temporaire. Elle ne sera pas enregistrée.
💬
×
Prompts enregistrés
×
Note privée
×
Étiquette
×
Rechercher dans toutes les discussions
×
Vos insights
Analyse en cours…
×
Préférences IA
Nom de l'assistant 30
Longueur des réponses
Modèle IA
Réponses structurées
Inclure des exemples
Utiliser des emojis
Guide-moi, ne résous pas Des indices et des questions plutôt que la réponse toute faite
Plus de réglages →
×
Partager cette discussion
Toute personne ouvrant ce lien pourra consulter la discussion ou l'ajouter à son profil comme discussion personnelle.
⚠️ Attention : les pièces jointes de la discussion seront également partagées. Qui l'ajoutera recevra une copie des fichiers dans son dossier.
Discussion partagée
Quelqu'un a partagé une discussion avec toi. Veux-tu seulement la consulter ou l'ajouter à tes discussions ?
⚠️ Attention : les pièces jointes de la discussion seront également partagées. Qui l'ajoutera recevra une copie des fichiers dans son dossier.
×

📌 Messages enregistrés

Chargement...

×

Historique des discussions

programmes-exemples · HISTORIQUE DU CHAT

Chargement...

Préférences IA

×
  • 🟢 BasiqueRéponses rapides et essentielles pour étudier
  • 🔵 MoyenQualité supérieure pour étude et programmation
  • 🟣 AvancéRaisonnement complexe et analyses détaillées
Expliquer les étapes

Exemple Complet

-- Exemple complet pour tester le module JIT simplifié

--[=[
  JIT simplifié pour expressions arithmétiques en Lua
  Supporte les nombres, +, -, *, / et parenthèses.
  Compile les expressions en fonctions Lua pour une exécution plus rapide.
  Inclut un cache pour éviter les recompilations.
]=]

-- Module JIT
local JIT = {}

-- Cache pour fonctions compilées : clé = expression, valeur = fonction
JIT.cache = {}

-- Fonction de parsing : transforme une chaîne en AST (arbre syntaxique)
-- L'AST est une table avec des nœuds de type {tag = 'number'|'binop', value = number, op = string, left = node, right = node}

-- Lexer : tokenize l'expression
local function lexer(input)
  local pos = 1
  local tokens = {}
  local patterns = {
    {"number", "^%d+%.?%d*"},
    {"op", "^[%+%-%*%/%(%)"]},
    {"space", "^%s+"}
  }

  while pos <= #input do
    local matched = false
    for _, p in ipairs(patterns) do
      local ttype, pattern = p[1], p[2]
      local s, e = string.find(input, pattern, pos)
      if s == pos then
        local token = string.sub(input, s, e)
        if ttype ~= "space" then
          table.insert(tokens, {type = ttype, value = token})
        end
        pos = e + 1
        matched = true
        break
      end
    end
    if not matched then
      error("Jeton non reconnu près de : '" .. string.sub(input, pos, pos+10) .. "'")
    end
  end
  return tokens
end

-- Parser avec priorité des opérateurs
local Parser = {}
Parser.__index = Parser

function Parser:new(tokens)
  local o = {tokens = tokens, pos = 1}
  setmetatable(o, self)
  return o
end

function Parser:peek()
  return self.tokens[self.pos]
end

function Parser:consume(ttype, val)
  local t = self:peek()
  if t and t.type == ttype and (not val or t.value == val) then
    self.pos = self.pos + 1
    return t
  else
    return nil
  end
end

-- Parsing avec priorité : expression -> term { (+|-) term }
-- term -> factor { (*|/) factor }
-- factor -> number | '(' expression ')'

function Parser:parse_expression()
  local node = self:parse_term()
  while true do
    local op = self:consume('op', '+') or self:consume('op', '-')
    if not op then break end
    local right = self:parse_term()
    node = {tag = 'binop', op = op.value, left = node, right = right}
  end
  return node
end

function Parser:parse_term()
  local node = self:parse_factor()
  while true do
    local op = self:consume('op', '*') or self:consume('op', '/')
    if not op then break end
    local right = self:parse_factor()
    node = {tag = 'binop', op = op.value, left = node, right = right}
  end
  return node
end

function Parser:parse_factor()
  local t = self:peek()
  if not t then error("Fin inattendue de l'expression") end

  if t.type == 'number' then
    self.pos = self.pos + 1
    return {tag = 'number', value = tonumber(t.value)}
  elseif t.type == 'op' and t.value == '(' then
    self.pos = self.pos + 1
    local node = self:parse_expression()
    if not self:consume('op', ')') then
      error("Parenthèse fermante manquante")
    end
    return node
  else
    error("Jeton inattendu : '" .. t.value .. "'")
  end
end

-- Fonction pour générer du code Lua à partir de l'AST
local function generate_code(ast)
  if ast.tag == 'number' then
    return tostring(ast.value)
  elseif ast.tag == 'binop' then
    local left = generate_code(ast.left)
    local right = generate_code(ast.right)
    -- Protection contre la division par zéro à l'exécution
    if ast.op == '/' then
      return string.format("(function() local r = %s if r == 0 then error('Division par zéro') end return %s / r end)()", right, left)
    else
      return string.format("(%s %s %s)", left, ast.op, right)
    end
  else
    error("Nœud AST inconnu : " .. tostring(ast.tag))
  end
end

-- Fonction principale JIT : compile ou récupère du cache
function JIT.compile(expr)
  if JIT.cache[expr] then
    return JIT.cache[expr]
  end

  local tokens = lexer(expr)
  local parser = Parser:new(tokens)
  local ast = parser:parse_expression()
  if parser:peek() then
    error("Jetons supplémentaires après la fin de l'expression")
  end

  local code = "return " .. generate_code(ast)

  local f, err = load(code, "jit_expr", "t", {})
  if not f then
    error("Erreur de compilation du code : " .. err)
  end

  JIT.cache[expr] = f
  return f
end

-- Fonction d'exécution sécurisée
function JIT.eval(expr)
  local f = JIT.compile(expr)
  local ok, result = pcall(f)
  if not ok then
    error("Erreur d'exécution : " .. result)
  end
  return result
end

-- Exemple d'utilisation
local function test()
  local expressions = {
    "1 + 2 * 3",
    "(1 + 2) * 3",
    "10 / (5 - 5)", -- division par zéro
    "4 + 5 / 2",
    "7 - 3 + 2",
    "(2 + 3) * (4 - 1)"
  }

  for _, expr in ipairs(expressions) do
    io.write("Évaluation : ", expr, " = ")
    local ok, res = pcall(JIT.eval, expr)
    if ok then
      print(res)
    else
      print("Erreur : " .. res)
    end
  end
end

-- Exécuter le test
print("--- Test du JIT simplifié pour expressions Lua ---")
test()

return JIT

Qu'est-ce qu'il faut ?

Logiciel :
  • Lua 5.3+ (nécessaire pour load et fonctions de compilation dynamique)
  • Aucune bibliothèque externe requise
  • Configuration : aucune configuration externe, entièrement autonome
Composants principaux :
  • Parser : analyse des chaînes d'expressions arithmétiques (nombres, +, -, *, /, parenthèses)
  • Générateur de code : transforme l'arbre syntaxique en code Lua exécutable
  • Cache JIT : stocke les fonctions compilées pour les expressions déjà vues
  • Exécution sécurisée : gestion des erreurs de parsing et d'exécution
Liens/Configurations :
  • Aucun lien matériel (exemple purement logiciel)
  • Configuration interne pour la taille maximale du cache et les politiques d'invalidation (extensible)

Sujets Similaires

Script Lua pour renommer en série des fichiers dans un dossier
Script Lua pour renommer automatiquement des fichiers en série dans un dossier avec préfixe et numérotation progressive, idéal pour organiser vos fichiers.
Script Lua pour fusionner plusieurs fichiers texte en un seul fichier
Script Lua pour fusionner plusieurs fichiers texte en un seul fichier, idéal pour combiner rapidement des données ou notes sans bibliothèque externe.
Script Lua pour sauvegarde sélective de fichiers selon l’extension
Script Lua pour sauvegarde sélective de fichiers selon l’extension, copiant uniquement les fichiers désirés d’un répertoire source vers une destination.
Implémentation d’une file de tâches avec priorité utilisant des tables Lua
Exemple d’implémentation d’une file de tâches avec priorité en Lua utilisant des tables, idéale pour gérer des tâches ordonnées par priorité.
Petit ORM artisanal pour fichiers JSON comme des tables de base de données
Petit ORM Lua pour gérer fichiers JSON comme tables de base de données, supportant CRUD, idéal pour projets légers et prototypes sans base externe.
Moteur de règles basé sur des tables Lua pour valider des données structurées
Moteur de règles Lua modulaire pour valider des données structurées avec règles personnalisées, messages d’erreur et support des types courants.
Implémentation d’une machine à états finis configurable via fichier Lua
Exemple d’implémentation d’une machine à états finis configurable via fichier Lua pour Arduino, avec gestion des boutons et LED, facile à adapter.
Ordonnanceur coopératif de coroutines pour exécuter des tâches périodiques
Ordonnanceur coopératif en Lua utilisant des coroutines pour exécuter des tâches périodiques avec intervalles variés sans bloquer le thread principal.