local V = require("texecole-vocab") local P = require("texecole-prose") local L = {} local function trim(s) return (s:gsub("^%s+", ""):gsub("%s+$", "")) end local function err(msg) error("texecole : " .. msg, 0) end local function num_fr(s) return (s:gsub("(%d),(%d)", "%1.%2")) end local MATH_KEYS_SORTED local function math_keys() if not MATH_KEYS_SORTED then MATH_KEYS_SORTED = {} for k in pairs(V.MATH_WORDS) do MATH_KEYS_SORTED[#MATH_KEYS_SORTED+1] = k end table.sort(MATH_KEYS_SORTED, function(a, b) return #a > #b end) end return MATH_KEYS_SORTED end local CALC_SKIP = { ["et"] = true, ["ou"] = true, ["non"] = true, ["union"] = true, ["inter"] = true, ["intersection"] = true, ["dans"] = true, ["contient"] = true, } -- Deux jeux compilés une seule fois : les clés multi-jetons (espace, trait -- d'union, apostrophe) restent dans une boucle appliquée du plus long au plus -- court ; les clés mono-jeton sont traitées en UN seul balayage via une table -- de correspondance, au lieu d'un gsub par clé. local W_SET = "%w_\128-\255" local MATH_MULTI, MATH_SINGLE local function math_build() if MATH_SINGLE then return end MATH_MULTI, MATH_SINGLE = {}, {} for _, k in ipairs(math_keys()) do -- déjà trié du plus long au plus court if k:find("[^" .. W_SET .. "]") then -- contient espace/tiret/apostrophe local esc = k:gsub("[%-%s']", "%%%0") MATH_MULTI[#MATH_MULTI + 1] = { k = k, pat = "%f[" .. W_SET .. "]" .. esc .. "%f[^" .. W_SET .. "]", repl = (V.MATH_WORDS[k]:gsub("%%", "%%%%")), } else MATH_SINGLE[k] = V.MATH_WORDS[k] end end end local function translate_math(expr, calc) math_build() expr = num_fr(expr) -- Vecteur nommé avec composantes, AVANT toute autre traduction : -- « vecteur u(3;5) » -> vec(u) suivi des composantes en ligne -- « vecteur colonne u(3;5) » -> vec(u) suivi des composantes en colonne -- La forme sans nom « vecteur(u) » (parenthèse collée) reste la flèche seule. expr = expr:gsub("vecteur%s+colonne%s+([%a_][%w_]*)%s*(%b())", function(nom, co) return "vec(" .. nom .. ") colvec" .. co end) expr = expr:gsub("vecteur%s+([%a_][%w_]*)%s*(%b())", function(nom, co) return "vec(" .. nom .. ") vector" .. co end) expr = expr:gsub("%s*;%s*", ", ") -- 1) clés multi-jetons d'abord (préséance du plus long) for _, e in ipairs(MATH_MULTI) do if not (calc and CALC_SKIP[e.k]) then expr = expr:gsub(e.pat, e.repl) end end -- 2) clés mono-jeton : un seul passage tokenisant expr = expr:gsub("%f[" .. W_SET .. "][" .. W_SET .. "]+%f[^" .. W_SET .. "]", function(tok) if calc and CALC_SKIP[tok] then return nil end return MATH_SINGLE[tok] end) return expr end local function translate_math_zones(line) line = line:gsub("%$(.-)%$", function(inner) return "$" .. translate_math(inner) .. "$" end) line = line:gsub("#(%b{})", function(braced) return "#{" .. translate_math(braced:sub(2, -2), true) .. "}" end) return line end -- Inscrit une fonction posée par (via t.fn, singulier, ou t.fns, -- pluriel factorisé) : même émission « » et même registre -- L._fns dans les deux cas, pour que le reste du transpileur n'ait -- jamais à distinguer les deux formes. local function inscrire_fn(out, brut) local corps = translate_math(brut) out[#out + 1] = "" L._fns = L._fns or {} local fname = corps:match("^([%a_][%w_]*)%s*%(") if fname then L._fns[fname] = corps end end local function inscrire_fns(out, t) if t.fn then inscrire_fn(out, t.fn) end if t.fns then for _, brut in ipairs(t.fns) do inscrire_fn(out, brut) end end end local function translate_operators(cond) for fr, op in pairs(V.OPERATORS) do cond = cond:gsub("%f[%a]" .. fr:gsub(" ", "%%s+") .. "%f[%A]", op) end -- Connecteurs logiques du français : « et », « ou » (règle n°10 — -- chaque fois qu'un mot français existe, la prose gagne). cond = cond:gsub("%f[%a]et%f[%A]", "and") cond = cond:gsub("%f[%a]ou%f[%A]", "or") return num_fr(cond) end local function translate_control(line) local indp, vp, ap, bp, pp = line:match( "^(%s*)pour%s+([%a_][%w_]*)%s+de%s+(%S+)%s+à%s+(%S+)%s+avec%s+un%s+pas%s+de%s+(%S+)%s*{%s*$") if vp then return indp .. "for " .. vp .. " in " .. num_fr(ap) .. ".." .. num_fr(bp) .. " step " .. num_fr(pp) .. " {" end if line:match("^%s*pour%s+[%a_][%w_]*%s+de%s+%S+%s+à%s+%S+%s+par%s+pas%s+de%s+%S+%s*{%s*$") then err("le pas d'une boucle se dit « avec un pas de » : « pour i de " .. "0,5 à 2,5 avec un pas de 0,5 { ... } » — comme tout réglage, " .. "il s'introduit par « avec » (l'ancienne forme « par pas de » " .. "n'existe plus).") end local ind, v, a, b = line:match("^(%s*)pour%s+([%a_][%w_]*)%s+de%s+(%S+)%s+à%s+(%S+)%s*{%s*$") if v then return ind .. "for " .. v .. " in " .. num_fr(a) .. ".." .. num_fr(b) .. " {" end local ind2, v2, lst = line:match("^(%s*)pour%s+([%a_][%w_]*)%s+dans%s+(%b[])%s*{%s*$") if v2 then return ind2 .. "for " .. v2 .. " in " .. lst .. " {" end local ind3, cond = line:match("^(%s*)si%s+(.-)%s*{%s*$") if cond then return ind3 .. "if " .. translate_operators(cond) .. " {" end local ind3b, cond2 = line:match("^(%s*)}%s*sinon%s+si%s+(.-)%s*{%s*$") if cond2 then return ind3b .. "} elseif " .. translate_operators(cond2) .. " {" end if line:match("^%s*}%s*sinon%s*{%s*$") then return (line:gsub("sinon", "else")) end -- « tant que CONDITION faire { » — le « faire » est facultatif ; la -- condition est vérifiée avant chaque passage. local ind4, wc = line:match("^(%s*)tant%s+que%s+(.-)%s+faire%s*{%s*$") if not wc then ind4, wc = line:match("^(%s*)tant%s+que%s+(.-)%s*{%s*$") end if wc then return ind4 .. "while " .. translate_operators(wc) .. " {" end -- « faire { ... } tant que CONDITION » — le corps s'exécute au moins -- une fois, la condition est vérifiée après chaque passage. local ind6 = line:match("^(%s*)faire%s*{%s*$") if ind6 then return ind6 .. "repeat {" end local ind7, dc = line:match("^(%s*)}%s*tant%s+que%s+(.-)%s*$") if dc and not dc:match("{%s*$") then return ind7 .. "} until " .. translate_operators(dc) end local ind5, rest = line:match("^(%s*)soit%s+(.+)$") if rest then return ind5 .. "let " .. num_fr(rest) end return nil end local function translate_object_words(words, require_avec) words = trim(words or "") if words == "" then return "" end if require_avec then local placement, apres = words:match("^(%b[])%s*(.*)$") local prefixe = "" if placement then prefixe = placement .. " " else apres = words end apres = trim(apres) if apres ~= "" then local corps = apres:match("^avec%s+(.+)$") if not corps then err("liste d'attributs mal introduite : « " .. apres .. " ». " .. "Les attributs d'un objet s'ouvrent par « avec » : écrivez " .. "par exemple « " .. trim(prefixe) .. " avec bordures, entête " .. "et fond bleu Alice » — virgules entre les attributs, « et » " .. "avant le dernier.") end apres = corps end return translate_object_words(prefixe .. apres) end do local buf, depth = {}, 0 local i, n = 1, #words while i <= n do local c = words:sub(i, i) if c == "{" or c == "[" or c == "(" then depth = depth + 1 end if c == "}" or c == "]" or c == ")" then depth = depth - 1 end if depth == 0 and words:sub(i, i + 3) == " et " then buf[#buf + 1] = ", "; i = i + 4 else buf[#buf + 1] = c; i = i + 1 end end words = table.concat(buf) end words = words:gsub("(entête en [%a%s'éèêàâîôûç%-]-),%s*(sur fond)", "%1 %2") words = words:gsub("(titre en [%a%s'éèêàâîôûç%-]-),%s*(sur fond)", "%1 %2") local out = {} words = words:gsub("%b[]", function(br) local inner = br:sub(2, -2) local nom, pospart = inner:match("^(.-)%s*:%s*(.+)$") local src = pospart or inner local codes, vert, horiz = {}, nil, nil if nom then codes[#codes + 1] = trim(nom) end local VNAT = { haut = "t", milieu = "m", bas = "b" } local HNAT = { gauche = "l", centre = "c", droite = "r" } for piece in src:gmatch("[^,]+") do local p = trim(piece) local nu = p:gsub("^en%s+", ""):gsub("^au%s+", ""):gsub("^à%s+", "") if VNAT[nu] then vert = VNAT[nu] elseif HNAT[nu] then horiz = HNAT[nu] elseif V.PLACE[nu] then codes[#codes + 1] = V.PLACE[nu] elseif V.PLACE[p] then codes[#codes + 1] = V.PLACE[p] else codes[#codes + 1] = p end end if vert or horiz then codes[#codes + 1] = (vert or "m") .. (horiz or "c") end return "[" .. table.concat(codes, ", ") .. "]" end) if words:match("%f[%w_\128-\255]bordée?%s+de%f[%W]") then err("« bordé de » n'existe plus : la couleur de bordure se dit avec " .. "le nom et son article — « une bordure bleue », « une bordure " .. "bleu marine » (règle n°11, l'attribut est un nom, pas un " .. "adjectif).") end for _, rule in ipairs(V.PROSE_ATTRS) do local lua_pat = rule._luapat if not lua_pat then lua_pat = rule.pat :gsub("[%%%-%(%)%.%+%*%?%[%]%^%$]", "%%%0") :gsub("«C»", "([%%a%%s'éèêàâîôûç%%-]+)") :gsub("«W»", "([%%a_][%%w_]*)") :gsub("«N»", "([%%d,%%.]+)") :gsub("«B»", "(%%b{})") :gsub("«BR»", "(%%b[])") :gsub("«PCT»", "%%%%") rule._luapat = lua_pat end words = words:gsub(lua_pat, function(c1, c2) local keys = type(rule.key) == "table" and rule.key or { rule.key } local caps = { c1, c2 } local frag = {} for i, key in ipairs(keys) do if rule.bare then frag[#frag + 1] = key else local v = rule.value or caps[i] if not rule.value then local raw = trim(v or "") local svg = V.color(raw) local reste = nil if not svg and rule.pat:find("«C»") then for _, ck in ipairs(V.color_keys_sorted()) do local suite = raw:match("^" .. ck:gsub("[%-']", "%%%0") .. "%f[^%w_\128-\255]%s*(.*)$") if suite then svg, reste = V.COLORS[ck], suite break end end end if svg then v = svg elseif raw:match("^[%d,%.]+$") then v = num_fr(raw) elseif raw:match("^%b{}$") then v = num_fr(raw):gsub("%s*;%s*", ", ") elseif raw:match("^%b[]$") then v = raw elseif rule.pat:find("«W»") and raw:match("^[%a_][%w_]*$") then v = raw else return nil end end frag[#frag + 1] = key .. ":" .. (v or "on") if reste and reste ~= "" then frag[#frag + 1] = reste end end end return table.concat(frag, " ") end) end words = words:gsub("données%s*(%b{})", function(b) local inner = b:sub(2, -2) if inner:find(":") then return "data:{" .. num_fr(inner):gsub("%s*;%s*", " | ") .. "}" end return "data:{" .. num_fr(inner):gsub("%s*;%s*", ", ") .. "}" end) words = words:gsub("pas%s+de%s+(%$.-%$)", function(m) return "step:{" .. m:sub(2, -2) .. "}" end) words = words:gsub("pas%s+de%s+([%d,%.]+)", function(n) return "step:" .. n:gsub(",", ".") end) words = words:gsub("([%d,%.]+)%s*mm%s+sur%s+([%d,%.]+)%s*mm", function(a, b) return a:gsub(",", ".") .. "x" .. b:gsub(",", ".") end) words = words:gsub("(%d+)%s+(tabulations?)%f[^%w]", "%1%2") words = words:gsub("(%d+)%s+(lignes?)%f[^%w]", "%1%2") words = words:gsub("%f[%w_]exposant%s+(%d+)mm", "up%1") words = words:gsub("%f[%w_]indice%s+(%d+)mm", "down%1") words = words:gsub("%f[%w_]exposant%f[^%w_]", "up2") words = words:gsub("%f[%w_]indice%f[^%w_]", "down1") local mw = {} for k in pairs(V.STYLE_WORDS) do if k:find(" ") then mw[#mw+1] = k end end table.sort(mw, function(a, b) return #a > #b end) for _, k in ipairs(mw) do local w = "%w_\128-\255" local pat = "%f[" .. w .. "]" .. k:gsub("[%-%s']", "%%%0") .. "%f[^" .. w .. "]" words = words:gsub(pat, V.STYLE_WORDS[k]) end do local cleaned, d = {}, 0 for i = 1, #words do local ch = words:sub(i, i) if ch == "{" or ch == "[" then d = d + 1 end if ch == "}" or ch == "]" then d = d - 1 end if ch == "," and d == 0 then ch = " " end cleaned[#cleaned + 1] = ch end words = table.concat(cleaned):gsub("%f[%w_]et%f[^%w_]", " ") for _, mot in ipairs({ "avec", "les", "des", "un", "une", "le", "la" }) do words = words:gsub("%f[%w_]" .. mot .. "%f[^%w_]", " ") end words = words:gsub("%f[%w_]l'", "") end local tokens, buf, depth = {}, {}, 0 for i = 1, #words do local ch = words:sub(i, i) if ch == "{" then depth = depth + 1 end if ch == "}" then depth = depth - 1 end if depth == 0 and ch:match("%s") then if #buf > 0 then tokens[#tokens+1] = table.concat(buf); buf = {} end else buf[#buf+1] = ch end end if #buf > 0 then tokens[#tokens+1] = table.concat(buf) end for _, word in ipairs(tokens) do local done = false local k, v = word:match("^([%wéèê_]+):(.+)$") if k then v = v:gsub("^%$(.-)%$$", "{%1}") out[#out + 1] = (V.KV_KEYS[k] or k) .. ":" .. num_fr(v) done = true end if not done then local n, radical = word:match("^(%d+)([%aé]+)$") if n and V.COUNTED_WORDS[radical] then out[#out + 1] = n .. V.COUNTED_WORDS[radical] done = true end end if not done and V.STYLE_WORDS[word] then out[#out + 1] = V.STYLE_WORDS[word]; done = true end if not done then out[#out + 1] = word end end local joined = table.concat(out, " ") local shelters = {} joined = joined:gsub("%b{}", function(g) shelters[#shelters + 1] = g return "\1" .. #shelters .. "\1" end) for _, k in ipairs(V.color_keys_sorted()) do local w = "%w_\128-\255" local pat = "%f[" .. w .. "]" .. k:gsub("[%-']", "%%%0") .. "%f[^" .. w .. "]" joined = joined:gsub(pat, V.COLORS[k]) end joined = joined:gsub("\1(%d+)\1", function(n) return shelters[tonumber(n)] end) return joined end local ENGLISH_TAGS = { table = "tableau", box = "cadre", list = "liste", img = "image", grid = "grille", area = "zone", matrix = "matrice", bmatrix = "matrice entre crochets", system = "système", vartab = "tableau de variations", signtab = "tableau de signes", numberline = "droite graduée", tree = "arbre", stats = "statistique", plot = "Trace la fonction ...", draw = "Trace ...", fn = "Soit une fonction ...", section = "section", subsection = "sous-section", toc = "sommaire", } local TAGS_KEYS_SORTED local function tags_keys() if not TAGS_KEYS_SORTED then TAGS_KEYS_SORTED = {} for k in pairs(V.TAGS) do TAGS_KEYS_SORTED[#TAGS_KEYS_SORTED+1] = k end table.sort(TAGS_KEYS_SORTED, function(a, b) return #a > #b end) end return TAGS_KEYS_SORTED end local function translate_tag_head(head) for _, v in ipairs(V.VERBES_OBJET) do local rest = head:match("^" .. v .. "%s+(.*)$") if rest then head = rest; break end end head = head:gsub("^[Uu]ne?%s+", ""):gsub("^[Ll]es?%s+", "") :gsub("^[Dd]es%s+", ""):gsub("^[Ll]a%s+", "") :gsub("^[Ll]'", "") local first = head:match("^([%a_][%w_]*)") if first and ENGLISH_TAGS[first] then local francais = false for _, k in ipairs(tags_keys()) do if head:find("^" .. k:gsub("[%-%s']", "%%%0") .. "%f[^%w_\128-\255]") or head == k then francais = true; break end end if not francais then error("texecole : « <" .. first .. " » est du scholatex. En texecole, " .. "on écrit « <" .. ENGLISH_TAGS[first] .. " ... » — une seule " .. "façon de dire chaque chose.", 0) end end local lst = head:match("^liste%s*(.*)$") if lst ~= nil then local style, rest = nil, lst local k, v, r = V.longest_match(V.LIST_STYLES, trim(lst)) if k then style, rest = v, r end local words = translate_object_words(rest, true) return "list" .. (style and (":" .. style) or "") .. (words ~= "" and (" " .. words) or "") end local st = head:match("^statistique%s+en%s+(.*)$") or head:match("^statistique%s+(.*)$") if st then local k, v, r = V.longest_match(V.STATS_KINDS, trim(st)) if k then return "stats kind:" .. v .. " " .. translate_object_words(r, true) end return "stats " .. translate_object_words(st, true) end local vt, vkv = head:match("^tableau de variations%s+de%s+([%a_][%w_]*)%s*,?%s*(.*)$") if vt then vkv = trim(vkv or "") if vkv ~= "" then local def = L._fns and L._fns[vt] if not def then error("texecole : le tableau de variations de « " .. vt .. " » avec rangées demande que la fonction ait été posée " .. "par une fonction " .. vt .. "(x) = ... auparavant.", 0) end return "fn " .. def .. "\n " .. translate_object_words(translate_math(vkv)) .. ">\n%s*$") if not inner then return line end for _, v in ipairs(CALC_VERBS) do local reste = inner:match("^" .. v .. "%s+(.+)$") if reste then for fr, interne in pairs(CALC_NOUNS) do local arg = reste:match("^" .. fr:gsub("[%-']", "%%%0") .. "%s+(.+)$") if arg then return ind .. "<" .. interne .. " " .. arg .. ">" end end local ordinal = reste:match("^les%s+racines%s+([%a\128-\255]+)%s+de%s+") if ordinal then err("racines « " .. ordinal .. " » : ordinal non reconnu. Les " .. "racines n-ièmes se disent en toutes lettres, de « carrées » " .. "à « douzièmes » : , .") end end end return line end -- Factorisation plurielle des actions de calcul (règle n°10) : le pluriel -- se marque sur le nom — « » vaut -- trois calculs, un par fonction. L'expansion demande un nom au pluriel -- ET une pure liste de noms (au moins deux), virgules entre eux, « et » -- avant le dernier. Un nom au singulier suivi d'une liste est refusé ; -- « les zéros de » et « les valeurs propres de », pluriels par nature, -- se distribuent tels quels. local CALC_NOUNS_PLURIELS = { ["les dérivées secondes de"] = "la dérivée seconde de", ["les dérivées de"] = "la dérivée de", ["les primitives de"] = "la primitive de", ["les formes canoniques de"] = "la forme canonique de", ["les déterminants de"] = "le déterminant de", ["les inverses de"] = "l'inverse de", ["les espérances de"] = "l'espérance de", ["les écarts types de"] = "l'écart type de", ["les zéros de"] = "les zéros de", ["les valeurs propres de"] = "les valeurs propres de", } local CALC_PLURIELS_ORDONNES = {} for k in pairs(CALC_NOUNS_PLURIELS) do CALC_PLURIELS_ORDONNES[#CALC_PLURIELS_ORDONNES + 1] = k end table.sort(CALC_PLURIELS_ORDONNES, function(a, b) return #a > #b end) local function liste_de_noms(arg) local noms = {} for nom in arg:gsub("%s+et%s+", ", "):gmatch("[^,]+") do nom = trim(nom) if not nom:match("^[%a][%w_]*$") then return nil end noms[#noms + 1] = nom end if #noms < 2 then return nil end return noms end -- Règle n°12 : une balise commence par un verbe d'action à l'impératif -- présent (, , ), -- jamais par l'objet nu — l'objet porte toujours son article, et -- l'article suit un verbe. Seules exceptions : les styles (), -- les commandes structurelles (
, ) et les -- définitions d'alias (soit nom = ), qui décrivent un objet -- sans le poser dans le document. local OBJETS_VERBE = { ["cadre"] = true, ["tableau"] = true, ["grille"] = true, ["image"] = true, ["matrice"] = true, ["système"] = true, ["statistique"] = true, ["arbre"] = true, ["liste"] = true, ["rangée"] = true, -- Noms calculants : jamais nus non plus — , -- pas . Les formes internes sont produites après cette -- vérification et ne la traversent pas. ["Dérivée"] = true, ["Limite"] = true, ["Développement"] = true, ["Zéros"] = true, ["Primitive"] = true, ["Racines"] = true, ["Forme"] = true, ["Intégrale"] = true, ["Valeurs"] = true, ["Déterminant"] = true, ["Inverse"] = true, ["Somme"] = true, ["Produit"] = true, ["Espérance"] = true, ["Écart"] = true, ["Quantile"] = true, ["Probabilité"] = true, } local function verifier_verbe_objet(line) if line:match("^%s*soit%s+[%w\128-\255_%-]+%s*%b{}%s*=%s*<") or line:match("^%s*soit%s+[%w\128-\255_%-]+%s*=%s*<") then return end local l = line:gsub("<<", " ") for tete in l:gmatch("<([%a\128-\255][%w\128-\255_%-]*)") do if OBJETS_VERBE[tete] then err("une balise commence par un verbe d'action à l'impératif " .. "présent — , , " .. ", — jamais " .. "par l'objet nu : « <" .. tete .. " ... ». L'objet porte " .. "toujours son article, et l'article suit un verbe " .. "(règle n°12).") end end end local function etendre_calcul_pluriel(body) local out = {} for line in (body .. "\n"):gmatch("(.-)\n") do local fait = false local ind, inner = line:match("^(%s*)<(.+)>%s*$") -- Objets pluriels après un verbe d'action : « » vaut trois tableaux, un par -- fonction — l'article pluriel (ou un nombre, vérifié) factorise. if inner then for _, v in ipairs(V.VERBES_OBJET) do local reste = inner:match("^" .. v .. "%s+(.+)$") if reste then local nb, tail = reste:match("^(%d+)%s+(.+)$") local corps = tail or reste:match("^[ld]es%s+(.+)$") if corps then for _, obj in ipairs { "variations", "signes" } do local arg = corps:match("^tableaux%s+de%s+" .. obj .. "%s+de%s+(.+)$") if arg then local noms = liste_de_noms(arg) if noms then if nb and tonumber(nb) ~= #noms then err("le compte n'y est pas : la phrase annonce " .. nb .. " tableaux de " .. obj .. " mais nomme " .. #noms .. " fonctions. Le nombre, comme l'article pluriel, " .. "factorise : il doit correspondre au nombre de " .. "fonctions nommées.") end for _, nom in ipairs(noms) do out[#out + 1] = ind .. "<" .. v .. " le tableau de " .. obj .. " de " .. nom .. ">" end fait = true end end if fait then break end end end end if fait then break end end end if inner and not fait then for _, v in ipairs(CALC_VERBS) do local reste = inner:match("^" .. v .. "%s+(.+)$") if reste then for _, fr in ipairs(CALC_PLURIELS_ORDONNES) do local arg = reste:match("^" .. fr:gsub("[%-']", "%%%0") .. "%s+(.+)$") if arg then local noms = liste_de_noms(arg) if noms then local sing = CALC_NOUNS_PLURIELS[fr] for _, nom in ipairs(noms) do out[#out + 1] = ind .. "<" .. v .. " " .. sing .. " " .. nom .. ">" end fait = true end end if fait then break end end if not fait then -- Nom au singulier suivi d'une liste : le pluriel manque. for fr in pairs(CALC_NOUNS) do if not CALC_NOUNS_PLURIELS[fr] then local arg = reste:match("^" .. fr:gsub("[%-']", "%%%0") .. "%s+(.+)$") if arg and liste_de_noms(arg) then err("le pluriel factorise, et se marque sur le nom : " .. "« <" .. v .. " " .. fr:gsub("^l[ae]s?%s+", "les ") .. " f, g et h> » se dit avec le nom au pluriel — " .. "par exemple « les dérivées de f, g et h » — " .. "tandis que le singulier ne prend qu'une seule " .. "fonction : « " .. fr .. " f ».") end end end end end if fait then break end end end if not fait then out[#out + 1] = line end end local res = table.concat(out, "\n") return (res:gsub("\n$", "")) end local function normaliser_verbe(line) for _, v in ipairs(V.VERBES_OBJET) do line = line:gsub("<" .. v .. "%s+([^<>]*)", function(rest) rest = rest:gsub("^[Uu]ne?%s+", ""):gsub("^[Ll]es?%s+", "") :gsub("^[Ll]a%s+", ""):gsub("^[Ll]'%s*", "") return "<" .. rest end) end return line end local function remplacer_tetes(line) local out, i, n = {}, 1, #line while i <= n do local c = line:sub(i, i) if c == "<" and line:sub(i + 1, i + 1) == "<" then out[#out + 1] = "<<" i = i + 2 elseif c == "<" then local j, depth, fin = i + 1, 0, nil while j <= n do local ch = line:sub(j, j) if ch == "{" then depth = depth + 1 elseif ch == "}" then depth = depth - 1 elseif ch == "<" and depth == 0 then break elseif ch == ">" and depth == 0 then fin = j; break end j = j + 1 end if fin then out[#out + 1] = "<" .. translate_tag_head(line:sub(i + 1, fin - 1)) .. ">" i = fin + 1 else out[#out + 1] = c i = i + 1 end elseif c == ">" and line:sub(i + 1, i + 1) == ">" then out[#out + 1] = ">>" i = i + 2 else out[#out + 1] = c i = i + 1 end end return table.concat(out) end local function translate_line(line) local ctrl = translate_control(line) if ctrl then line = ctrl end return translate_math_zones(remplacer_tetes(line)) end local function translate_table_row(line) local indent, row = line:match("^([ ]*\t?)(.*)$") if row == "" then return line end local function fusion(span) return function(n, attrs, contenu) local gras = attrs:find("%f[%a]entête%f[%A]") ~= nil attrs = attrs:gsub("%f[%a]entête%f[%A]", "") attrs = attrs:gsub("%f[%w]en%s+([tmb][lcr])%f[^%w]", "%1") if gras then contenu = "{" .. contenu .. "}" end return "<" .. span .. ":" .. n .. " " .. translate_object_words(attrs) .. ">" .. contenu end end row = row:gsub("]-)>(%b{})", fusion("colspan")) row = row:gsub("]-)>(%b{})", fusion("rowspan")) row = row:gsub("<(%d+)%s*colonnes?%s*([^<>]-)>(%b{})", fusion("colspan")) row = row:gsub("<(%d+)%s*lignes?%s*([^<>]-)>(%b{})", fusion("rowspan")) local cells = {} for cell in (row .. "\t"):gmatch("(.-)\t") do cell = trim(cell) if cell == "" then cell = "." end cells[#cells + 1] = cell end while #cells > 1 and cells[#cells] == "." and row:match("%\t%s*$") == nil do table.remove(cells) end local joined = {} for i, c in ipairs(cells) do joined[i] = translate_line(c) end return indent .. table.concat(joined, " | ") end -- Un « < » hors maths ouvre-t-il une balise ? Une balise commence par une -- lettre (>", "") local ouverte, depth, in_math = false, 0, false for i = 1, #l do local c = l:sub(i, i) if c == "$" then in_math = not in_math elseif in_math then -- ignore < > { } rencontrés dans une zone de maths : ce sont des -- opérateurs de comparaison (<=, =>, <=>...), pas des balises. elseif c == "{" then depth = depth + 1 elseif c == "}" then depth = depth - 1 elseif c == "<" and depth <= 0 then if chevron_est_balise(l, i) then ouverte = true end elseif c == ">" and depth <= 0 then ouverte = false end end return ouverte end local function joindre_notes(body) local lines, buf, buf_kind = {}, nil, nil for line in (body .. "\n"):gmatch("(.-)\n") do if buf then buf = buf .. " " .. line:gsub("^%s+", "") local fini if buf_kind == "note" then fini = select(2, buf:gsub("{", "")) <= select(2, buf:gsub("}", "")) else fini = not tete_ouverte(buf) end if fini then lines[#lines + 1] = buf; buf, buf_kind = nil, nil end else local notepos = line:find("%s*{") or line:find("%s*{") if notepos then local no = select(2, line:gsub("{", "")) local nc = select(2, line:gsub("}", "")) if no > nc then buf, buf_kind = line, "note" else lines[#lines + 1] = line end elseif tete_ouverte(line) then buf, buf_kind = line, "tete" else lines[#lines + 1] = line end end end if buf then lines[#lines + 1] = buf end return table.concat(lines, "\n") end local function fn_requise(nom, quoi) local def = L._fns and L._fns[nom] if not def then error("texecole : <" .. quoi .. " de " .. nom .. "> demande que la " .. "fonction ait été posée par une fonction " .. nom .. "(x) = ... auparavant.", 0) end return def, def:match("%((%a[%w_]*)%)") or "x", def:match("=%s*(.+)$") end local function mat_requise(nom, quoi) local mat = L._mats and L._mats[nom] if not mat then error("texecole : <" .. quoi .. " de " .. nom .. "> demande que la " .. "matrice ait été posée : { ... }", 0) end return mat end local LOI_OPS = { ["<="] = "le", [">="] = "ge", ["="] = "eq", ["<"] = "lt", [">"] = "gt", } local function loi_ou_erreur(nomloi) local code = V.LOIS[nomloi] if not code then error("texecole : loi inconnue : « " .. nomloi .. " » (attendu : " .. "normale, binomiale, poisson, uniforme, exponentielle, student, " .. "khi-deux).", 0) end return code end local function params_loi(p) return trim(num_fr(p):gsub("%s*;%s*", " ")) end local function transformer_edo(eq, f, var) var = var or "x" eq = num_fr(eq) eq = eq:gsub(f .. "''", "\1"):gsub(f .. "'", "\2") eq = eq:gsub("%f[%a]" .. f .. "%f[%A]", "\3") eq = eq:gsub("\1", "Derivative(" .. f .. "(" .. var .. ")," .. var .. ",2)") eq = eq:gsub("\2", "Derivative(" .. f .. "(" .. var .. ")," .. var .. ")") eq = eq:gsub("\3", f .. "(" .. var .. ")") return eq end local function transformer_edp(eq, f) eq = num_fr(eq) eq = eq:gsub(f .. "_xx", "\1"):gsub(f .. "_yy", "\2") eq = eq:gsub(f .. "_xy", "\4"):gsub(f .. "_x", "\5"):gsub(f .. "_y", "\6") eq = eq:gsub("%f[%a]" .. f .. "%f[%A]", "\3") eq = eq:gsub("\1", "Derivative(" .. f .. "(x,y),x,2)") eq = eq:gsub("\2", "Derivative(" .. f .. "(x,y),y,2)") eq = eq:gsub("\4", "Derivative(" .. f .. "(x,y),x,y)") eq = eq:gsub("\5", "Derivative(" .. f .. "(x,y),x)") eq = eq:gsub("\6", "Derivative(" .. f .. "(x,y),y)") eq = eq:gsub("\3", f .. "(x,y)") return eq end local function traduire_zone(inner) local nom, reste = inner:match("^%s*([%a_][%w_]*)%s*:%s*(.*)$") if not nom then nom, reste = inner:match("^%s*([%a_][%w_]*)%s*(.*)$") end if not nom then error("texecole : zone de grille illisible : « [" .. inner .. "] » — " .. "la forme est [nom en mc avec attributs] : le placement " .. "s'introduit par « en », les attributs par « avec ».", 0) end local VNAT = { haut = "t", milieu = "m", bas = "b" } local HNAT = { gauche = "l", centre = "c", droite = "r" } local vert, horiz, code local attrtxt for piece in (reste .. ","):gmatch("(.-),") do local p = trim(piece) if attrtxt then if p ~= "" then attrtxt = attrtxt .. ", " .. p end elseif p ~= "" then -- placement « en CODE » éventuel en tête de morceau local c2, suite = p:match("^en%s+([tmb][lcrj])%f[%W]%s*(.*)$") if not c2 then c2, suite = p:match("^en%s+(j)%f[%W]%s*(.*)$") end if c2 then code = c2; p = trim(suite) end if p:match("^avec%f[%W]") then attrtxt = p elseif p ~= "" then local nu = p:gsub("^en%s+", ""):gsub("^au%s+", ""):gsub("^à%s+", "") if VNAT[nu] then vert = VNAT[nu] elseif HNAT[nu] then horiz = HNAT[nu] elseif p:match("^[tmb][lcr]%f[%W]") or p:match("^j%f[%W]") then err("le placement d'une zone s'introduit par « en » : " .. "[" .. nom .. " en " .. p:match("^%S+") .. " ...] — " .. "comme dans « en mc » (milieu, centré) ou « en j » " .. "(justifié).") else err("propriété de zone illisible : « " .. p .. " ». Une zone " .. "s'écrit [nom en mc avec une bordure bleue, un fond " .. "blanc et des coins arrondis de 2 mm] — le placement " .. "d'abord (« en mc », ou « en haut, à gauche »), puis " .. "les attributs introduits par « avec », séparés par " .. "des virgules, « et » avant le dernier.") end end end end if vert or horiz then code = (vert or "m") .. (horiz or "c") end local morceaux = { nom } if code then morceaux[#morceaux + 1] = code end if attrtxt then local mots = translate_object_words(attrtxt, true) if mots ~= "" then morceaux[#morceaux + 1] = mots end end return table.concat(morceaux, " ") end local function delta_braces(line) local o = select(2, line:gsub("{", "")) local c = select(2, line:gsub("}", "")) return o - c end -- Deux écritures naturelles de la matrice, parenthèses ou crochets -- servant eux-mêmes de délimiteur de bloc — au lieu des accolades -- neutres habituelles — puis ramenées à la forme canonique interne -- <... la matrice>{...} / <... la matrice entre crochets>{...} qui, -- seule, est ensuite traduite. Deux tournures acceptées : -- la matrice( -- 1 ; 2 ; 3 1 ; 2 ; 3 -- )> ) local function convertir_matrices_delimiteurs(body) body = body:gsub("<([^<>]-)matrice%s*%((.-)\n%s*%)>", function(prefixe, rangs) return "<" .. prefixe .. "matrice>{" .. rangs .. "\n}" end) body = body:gsub("<([^<>]-)matrice%s*%[(.-)\n%s*%]>", function(prefixe, rangs) return "<" .. prefixe .. "matrice entre crochets>{" .. rangs .. "\n}" end) body = body:gsub("<([%a][%w]*)>%s*la%s+matrice%s*%((.-)\n%s*%)", function(verbe, rangs) return "<" .. verbe .. " la matrice>{" .. rangs .. "\n}" end) body = body:gsub("<([%a][%w]*)>%s*la%s+matrice%s*%[(.-)\n%s*%]", function(verbe, rangs) return "<" .. verbe .. " la matrice entre crochets>{" .. rangs .. "\n}" end) -- Forme scindée nommée (référence ultérieure) : la matrice NOM { -- ou le système NOM { — le nom sert à un calcul plus loin -- (déterminant, inverse, valeurs propres...), donc les accolades -- neutres restent le délimiteur, comme pour la forme combinée. body = body:gsub( "<([%a][%w]*)>%s*la%s+matrice%s+([%a_][%w_]*)%s+entre%s+crochets%s*{", function(verbe, nom) return "<" .. verbe .. " la matrice " .. nom .. " entre crochets>{" end) body = body:gsub("<([%a][%w]*)>%s*la%s+matrice%s+([%a_][%w_]*)%s*{", function(verbe, nom) return "<" .. verbe .. " la matrice " .. nom .. ">{" end) body = body:gsub("<([%a][%w]*)>%s*le%s+système%s+([%a_][%w_]*)%s*{", function(verbe, nom) return "<" .. verbe .. " le système " .. nom .. ">{" end) return body end function L.traduire(body) -- est le verbe de l'analyse (fonction, -- repère, courbe, droite graduée) : son vocabulaire de figures est -- celui, déjà fermé, de . Pour tout le reste (une statistique, -- par exemple), qui ne relève pas de ce vocabulaire de figures mais -- du registre générique verbe+objet, il se comporte comme . body = body:gsub("<[Rr]eprésente%s+graphiquement>%s*{", "{") body = body:gsub("<[Rr]eprésente%s+graphiquement%s+dans%s+un%s+repère", "%s*{%s*$") then mode, acc = "soit", {} elseif line:match("^%s*") then local decl = trim(line:match("^%s*%s*(.*)$")) local t = P.translate_soit_decl(decl) out[#out + 1] = "Soit " .. t.display inscrire_fns(out, t) if t.points then L._points = L._points or {} for _, pt in ipairs(t.points) do L._points[pt.nom] = pt.co end end elseif line:match("^%s*") then local verbe, reste = line:match("^%s*%s*(.*)$") if verbe ~= "pose" and verbe ~= "considère" and verbe ~= "note" then error("texecole : « » n'est pas une action : " .. "les tournures reconnues sont , et " .. ", synonymes de .", 0) end local t = P.translate_soit_decl(trim(reste)) out[#out + 1] = "On " .. verbe .. " " .. t.display inscrire_fns(out, t) if t.points then L._points = L._points or {} for _, pt in ipairs(t.points) do L._points[pt.nom] = pt.co end end elseif line:match("^%s*].->%s*{%s*$") or line:match("^%s*%s*{%s*$") then local head = line:match("^%s*%s*{%s*$") or "" mode, acc = "trace", {} frame = nil if trim(head) ~= "" then local t = P.translate_trace_phrase(head) if t.kind == "frame" then frame = t else acc[#acc + 1] = t.line end end elseif line:match("^%s*]") then local phrase = line:match("^%s*%s*(.*)$") local after = line:match("^%s*%s*(.*)$") or "" phrase = trim((phrase or "") .. " " .. after) local t = P.translate_trace_phrase(phrase) if t.kind == "plot" then out[#out + 1] = "" elseif t.kind == "numberline" then out[#out + 1] = "" elseif t.kind == "frame" then local ax = "axes:{" .. (t.x or "{-5, 5}"):gsub("[{}]", "") .. ", " .. (t.y or "{-5, 5}"):gsub("[{}]", "") .. "}" local extra = {} for _, w in ipairs(t.opts or {}) do extra[#extra+1] = w end out[#out + 1] = " 0 and (" " .. table.concat(extra, " ")) or "") .. ">{" out[#out + 1] = "}" else out[#out + 1] = "" .. t.line end elseif line:match("^%s*%s*{%s*$") then acc = { nom = line:match("%s*$") if not nom then error("texecole : la forme est .", 0) end local _, fvar, fexpr = fn_requise(nom, "Intégrale numérique") out[#out + 1] = "{" out[#out + 1] = fexpr out[#out + 1] = "}" elseif line:match("^%s*%s*$") if not nom then error("texecole : la forme est , avec a fini, +infini ou -infini, suivie au besoin de « à droite » ou « à gauche ».", 0) end local pt, dir = reste:match("^(%S+)%s+à%s+(droite)$") if not pt then pt, dir = reste:match("^(%S+)%s+à%s+(gauche)$") end if not pt then pt, dir = reste, "" end local _, fvar, fexpr = fn_requise(nom, "Limite") out[#out + 1] = "{" out[#out + 1] = fexpr out[#out + 1] = "}" elseif line:match("^%s*%s*$") if not nom then error("texecole : la forme est .", 0) end local _, fvar, fexpr = fn_requise(nom, "Développement limité") out[#out + 1] = "{" out[#out + 1] = fexpr out[#out + 1] = "}" elseif line:match("^%s*%s*$") local n = ordinal and ORD[ordinal] if not n then error("texecole : la forme est avec l'ordinal en toutes lettres — racines carrées, " .. "cubiques, quatrièmes... douzièmes. Par exemple : " .. " (les racines " .. "cinquièmes de l'unité).", 0) end expr = expr:gsub("^l'unité$", "1") out[#out + 1] = "{" out[#out + 1] = num_fr(expr) out[#out + 1] = "}" elseif line:match("^%s*%s*$") or line:match("^%s*%s*$") then local verbe, expr = line:match("^%s*<(%a+)%s+(.-)>%s*$") out[#out + 1] = "<" .. (verbe == "Factorise" and "sympyfactor" or "sympysimplify") .. ">{" out[#out + 1] = num_fr(expr) out[#out + 1] = "}" elseif line:match("^%s*%s*$") then local expr = line:match("^%s*%s*$") expr = expr:gsub("^en%s+éléments%s+simples%s+", "") out[#out + 1] = "{" out[#out + 1] = num_fr(expr) out[#out + 1] = "}" elseif line:match("^%s*%s*$") then local eq, a, b = line:match( "^%s*%s*$") if not eq then error("texecole : la forme est — l'intervalle de recherche est " .. "obligatoire.", 0) end out[#out + 1] = "{" out[#out + 1] = num_fr(eq) out[#out + 1] = "}" elseif line:match("^%s*%s*$") then local eq = line:match( "^%s*%s*$") local f = eq:match("([%a])_[xy]") or "u" out[#out + 1] = "{" out[#out + 1] = transformer_edp(eq, f) out[#out + 1] = "}" elseif line:match("^%s*%s*$") then local eq = line:match("^%s*%s*$") local fdecl, vdecl = eq:match(",%s*d'inconnue%s+([%a])%(([%a])%)%s*$") if fdecl then eq = eq:gsub(",%s*d'inconnue%s+[%a]%([%a]%)%s*$", "") end local f = fdecl or eq:match("([%a])''") or eq:match("([%a])'") or "y" local var = vdecl or "x" out[#out + 1] = "{" out[#out + 1] = transformer_edo(eq, f, var) out[#out + 1] = "}" elseif line:match("^%s*%s*$") then local dom, eq = line:match( "^%s*%s*$") local domaine = V.DOMAINES[dom] if not domaine then error("texecole : domaine de résolution inconnu : « " .. dom .. " » (attendu : RR, CC, ZZ, NN, QQ ou leur nom en toutes " .. "lettres : les réels, les complexes...).", 0) end out[#out + 1] = "{" out[#out + 1] = num_fr(eq) out[#out + 1] = "}" elseif line:match("^%s*%s*$") then local eq = line:match("^%s*%s*$") out[#out + 1] = "{" out[#out + 1] = num_fr(eq) out[#out + 1] = "}" elseif line:match("^%s*%s*{%s*$") or line:match("^%s*%s*{%s*$") then local crochets = line:match("entre%s+crochets%s*>%s*{%s*$") ~= nil acc = { nom = line:match("{" or "{" mode = "matrice" elseif line:match("^%s*%s*$") or line:match("^%s*%s*$") then local verbe, nom = line:match("^%s*<([%a\128-\255]+)%s+de%s+([%a_][%w_]*)%s*>%s*$") local mat = mat_requise(nom, verbe) out[#out + 1] = "<" .. (verbe == "Déterminant" and "sympydet" or "sympyinv") .. " " .. nom .. ">{" for _, r in ipairs(mat) do out[#out + 1] = r end out[#out + 1] = "}" elseif line:match("^%s*%s*$") then local nom = line:match("de%s+([%a_][%w_]*)%s*>") local mat = mat_requise(nom, "Valeurs propres") out[#out + 1] = "{" for _, r in ipairs(mat) do out[#out + 1] = r end out[#out + 1] = "}" elseif line:match("^%s*%s*$") then local nom = line:match("^%s*%s*$") local mat = mat_requise(nom, "Diagonalise") out[#out + 1] = "{" for _, r in ipairs(mat) do out[#out + 1] = r end out[#out + 1] = "}" elseif line:match("^%s*%s*$") then local nom = line:match("de%s+([%a_][%w_]*)%s*>") local _, fvar, fexpr = fn_requise(nom, "Primitive") out[#out + 1] = "{" out[#out + 1] = fexpr out[#out + 1] = "}" elseif line:match("^%s*%s*$") then local expr = line:match("^%s*%s*$") expr = expr:gsub("^et%s+réduis%s+", "") out[#out + 1] = "{" out[#out + 1] = num_fr(expr) out[#out + 1] = "}" elseif line:match("^%s*%s*$") or line:match("^%s*%s*$") then local expr = line:match("^%s*%s*$") or line:match("^%s*%s*$") out[#out + 1] = "{" out[#out + 1] = num_fr(expr) out[#out + 1] = "}" elseif line:match("^%s*%s*$") if not quoi then error("texecole : la forme est " .. "(ou ).", 0) end out[#out + 1] = "<" .. (quoi == "Somme" and "sympysum" or "sympyprod") .. " " .. var .. " " .. num_fr(a) .. " " .. num_fr(b) .. ">{" out[#out + 1] = num_fr(expr) out[#out + 1] = "}" elseif line:match("^%s*]=?)%s*([%-%d,%.]+)%s+pour%s+la%s+loi%s+([%a%- ]+)%s*%((.-)%)%s*>%s*$") if not op then op = "=" val, nomloi, params = line:match( "^%s*%s*$") end if not val then error("texecole : la forme est — opérateurs <=, >=, =, <, > ; lois : " .. "normale(m;s), binomiale(n;p), poisson(l), uniforme(a;b), " .. "exponentielle(l), student(k), khi-deux(k).", 0) end local code = loi_ou_erreur(trim(nomloi)) out[#out + 1] = "{" out[#out + 1] = "}" elseif line:match("^%s*%s*$") if not p then error("texecole : la forme est .", 0) end local code = loi_ou_erreur(trim(nomloi)) out[#out + 1] = "{" out[#out + 1] = "}" elseif line:match("^%s*%s*$") if not nomloi then error("texecole : la forme est ou <Écart type de la loi ...>.", 0) end local code = loi_ou_erreur(trim(nomloi)) out[#out + 1] = "{" out[#out + 1] = "}" elseif line:match("^%s*%s*$") if not fname then error("texecole : la forme est — les paramètres libres sont " .. "a, b, c, d.", 0) end out[#out + 1] = "{" out[#out + 1] = num_fr(model) out[#out + 1] = num_fr(data):gsub("%s*;%s*", ",") out[#out + 1] = "}" elseif line:match("^%s*<Étudie%s+les%s+variations%s+de%s+[%a_][%w_]*%s*>%s*$") then local nom = line:match("de%s+([%a_][%w_]*)%s*>") out[#out + 1] = "" elseif line:match("^%s*%s*$") or line:match("^%s*%s*$") then local ordre = 1 if line:find("seconde") then ordre = 2 elseif line:find("troisième") then ordre = 3 end local nom = line:match("de%s+([%a_][%w_]*)%s*>") local quoi = line:find("Zéros") and "Zéros" or "Dérivée" local _, fvar, fexpr = fn_requise(nom, quoi) if line:find("Zéros") then out[#out + 1] = "{" else out[#out + 1] = "{" end out[#out + 1] = fexpr out[#out + 1] = "}" elseif line:match("^%s*%s*$") then local nom = line:match(" demande que le " .. "système ait été posé auparavant : { ... }", 0) end local pas = line:find("pas%s+à%s+pas") and " steps:on" or "" out[#out + 1] = "{" for _, eq in ipairs(sysdef) do out[#out + 1] = eq end out[#out + 1] = "}" elseif line:match("^%s*].->%s*{%s*$") then local head = line:match("^%s*<(.-)>%s*{%s*$") out[#out + 1] = "<" .. translate_tag_head(head) .. ">{" mode = "arbre" acc = { depth = 1 } elseif line:match("^%s*soit%s+[%a_][%w_]*%s*=%s*{%s*$") then out[#out + 1] = translate_control(line) mode = "soittable" acc = { depth = 1 } elseif line:match("^%s*].->%s*{%s*$") then local head = line:match("^%s*<(.-)>%s*{%s*$") out[#out + 1] = "<" .. translate_tag_head(head) .. ">{" mode = "grille" acc = { depth = 1 } elseif line:match("^%s*].->%s*{%s*$") then local head = line:match("^%s*<(.-)>%s*{%s*$") out[#out + 1] = "<" .. translate_tag_head(head) .. ">{" mode = "tableau" acc = { depth = 1 } else out[#out + 1] = translate_line(line) end elseif mode == "matrice" then if line:match("^%s*}%s*$") then out[#out + 1] = line L._mats = L._mats or {} L._mats[acc.nom] = acc.rows acc = nil mode = "normal" else local r = num_fr(line) acc.rows[#acc.rows + 1] = trim(r) out[#out + 1] = r end elseif mode == "systeme" then if line:match("^%s*}%s*$") then out[#out + 1] = line L._sys = L._sys or {} L._sys[acc.nom] = acc.eqs acc = nil mode = "normal" else local eq = num_fr(line) acc.eqs[#acc.eqs + 1] = eq out[#out + 1] = eq end elseif mode == "soittable" then local d = delta_braces(line) if acc.depth + d <= 0 and line:match("^%s*}%s*$") then out[#out + 1] = line mode, acc = "normal", nil else acc.depth = acc.depth + d local ind, cle, val = line:match( "^(%s*)([%wÀ-ÿ_]+)%s*[:=]%s*(-?[%d,%.]+)%s*,?%s*$") if cle then out[#out + 1] = ind .. "[\"" .. cle .. "\"] = " .. num_fr(val) .. "," else out[#out + 1] = num_fr(line) end end elseif mode == "grille" then local d = delta_braces(line) if acc.depth + d <= 0 and line:match("^%s*}%s*$") then out[#out + 1] = line mode, acc = "normal", nil else acc.depth = acc.depth + d local zind, zin, zrest = line:match("^(%s*)%[(.-)%](%s*{.*)$") if zin then out[#out + 1] = zind .. "" .. translate_line(zrest) else out[#out + 1] = translate_line(line) end end elseif mode == "arbre" then local d = delta_braces(line) if acc.depth + d <= 0 and line:match("^%s*}%s*$") then out[#out + 1] = line mode, acc = "normal", nil else acc.depth = acc.depth + d out[#out + 1] = num_fr(line) end elseif mode == "tableau" then local d = delta_braces(line) if acc.depth + d <= 0 and line:match("^%s*}%s*$") then out[#out + 1] = line mode, acc = "normal", nil else acc.depth = acc.depth + d local ctrl = translate_control(line) if ctrl then out[#out + 1] = ctrl if line:match("{%s*$") then acc.ctrl = (acc.ctrl or 0) + 1 end elseif line:match("^%s*}%s*$") then out[#out + 1] = line if (acc.ctrl or 0) > 0 then acc.ctrl = acc.ctrl - 1 end else local depouille = line for _ = 1, (acc.ctrl or 0) do depouille = depouille:gsub("^\t", "", 1) end out[#out + 1] = translate_table_row(depouille) end end elseif mode == "soit" then if line:match("^%s*}%s*$") then mode = "normal" else local decl = trim(line) if decl ~= "" then local t = P.translate_soit_decl(decl) out[#out + 1] = "" .. t.display inscrire_fns(out, t) if t.points then L._points = L._points or {} for _, pt in ipairs(t.points) do L._points[pt.nom] = pt.co end end end end elseif mode == "trace" then local repere_consomme = false if not frame and #acc == 0 and line:match("^%s*un%s+repère%f[%s>]") then local t = P.translate_trace_phrase(trim(line)) if t.kind == "frame" then frame = t; repere_consomme = true end end if repere_consomme then elseif line:match("^%s*}%s*$") then local head if frame then local extra = {} for _, w in ipairs(frame.opts or {}) do if w:match("^unit[xy]:") then extra[#extra + 1] = w end end head = " 0 and (" " .. table.concat(extra, " ")) or "") .. ">" else head = "" end out[#out + 1] = head .. "{" if frame and L._points then local corps = table.concat(acc, "\n") local noms = {} for n in pairs(L._points) do noms[#noms + 1] = n end table.sort(noms) for _, n in ipairs(noms) do if corps:find("%f[%u]" .. n .. "%f[^%w]") and not corps:find("point%s+" .. n .. "%f[^%w]") then out[#out + 1] = "\tpoint " .. n .. " " .. L._points[n] end end end for _, l in ipairs(acc) do out[#out + 1] = "\t" .. l end out[#out + 1] = "}" mode, acc, frame = "normal", nil, nil else local phrase = trim(line) if phrase ~= "" then local t = P.translate_trace_phrase(phrase, { frame = frame ~= nil, block = true }) if t.kind == "draw_multi" then for _, l in ipairs(t.lines) do acc[#acc + 1] = l end else acc[#acc + 1] = t.line end end end end end return table.concat(out, "\n") end return L