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.
--[[
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-- 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