local V = {}

V.TAGS = {
  ["cadre"]                 = { name = "box" },
  ["tableau"]               = { name = "table" },
  ["grille"]                = { name = "grid" },
  ["image"]                 = { name = "img" },
  ["section"]               = { name = "section" },
  ["sous-section"]          = { name = "subsection" },
  ["sous-sous-section"]     = { name = "subsubsection" },
  ["table des matières"]    = { name = "tableofcontents" },
  ["matrice"]               = { name = "matrix" },
  ["matrice entre crochets"] = { name = "bmatrix" },
  ["système"]               = { name = "system" },
  ["tableau de variations"] = { name = "vartab" },
  ["tableau de signes"]     = { name = "signtab" },
  ["statistique"]           = { name = "stats" },
  ["arbre"]                 = { name = "tree" },
  ["liste"]                 = { name = "list" },
  ["rangée"]                = { name = "row" },
}

V.VERBES_OBJET = { "Affiche", "Dresse", "Construis", "Insère" }

V.LIST_STYLES = {
  ["sans puce"]                          = "none",
  ["à puces vides"]                      = "circle",
  ["à puces carrées"]                    = "square",
  ["à puces"]                            = "disc",
  ["numérotée"]                          = "decimal",
  ["alphabétique majuscule"]             = "ALPHA",
  ["alphabétique"]                       = "alpha",
  ["en chiffres romains majuscules"]     = "ROMAN",
  ["en chiffres romains"]                = "roman",
  ["à cocher"]                           = "check",
}

V.STYLE_WORDS = {
  ["gras"]  = "b",  ["g"] = "b",
  ["italique"] = "i",
  ["souligné"] = "u", ["s"] = "u",
  ["barré"] = "st",
  ["sans empattements"] = "sf", ["sans"] = "sf",
  ["petites capitales"] = "sc", ["capitales"] = "sc",
  ["note"] = "footnote",
  ["sous-section"] = "subsection",
  ["sous-sous-section"] = "subsubsection",
  ["table des matières"] = "tableofcontents",
  ["gauche"] = "l", ["centre"] = "c", ["droite"] = "r", ["justifie"] = "j",
  ["justifié"] = "j",
  ["à gauche"] = "l", ["au centre"] = "c", ["à droite"] = "r",
  ["ligne"] = "line",
  ["tabulation"] = "tab",
  ["page suivante"] = "nextpage",
  ["nouvelle ligne"] = "nextline",
  ["num"] = "num", ["roman"] = "roman", ["ROMAN"] = "ROMAN",
  ["alpha"] = "alpha", ["ALPHA"] = "ALPHA",
  ["romain"] = "roman", ["ROMAIN"] = "ROMAN", ["décimal"] = "num",
}

V.COUNTED_WORDS = {
  ["lignes"]      = "lines",
  ["ligne"]       = "lines",
  ["tabulations"] = "tab",
  ["tabulation"]  = "tab",
}

V.PLACE = {
  ["hg"] = "tl", ["hc"] = "tc", ["hd"] = "tr",
  ["mg"] = "ml", ["mc"] = "mc", ["md"] = "mr",
  ["bg"] = "bl", ["bc"] = "bc", ["bd"] = "br",
  ["haut gauche"]   = "tl", ["haut centre"]   = "tc", ["haut droite"]   = "tr",
  ["milieu gauche"] = "ml", ["milieu centre"] = "mc", ["milieu droite"] = "mr",
  ["bas gauche"]    = "bl", ["bas centre"]    = "bc", ["bas droite"]    = "br",
}

V.PROSE_ATTRS = {
  { pat = "titre en «C» sur fond «C»",    key = { "titletext", "titlefill" } },
  { pat = "entête en «C» sur fond «C»",   key = { "headertext", "headerfill" } },
  { pat = "un entête en «C» sur un fond «C»", key = { "headertext", "headerfill" } },
  { pat = "entête en «C»",                key = "headertext" },
  { pat = "titre en «C»",                 key = "titletext" },
  { pat = "une bordure «C»",              key = "line" },
  { pat = "un bord «C»",                  key = "line" },
  { pat = "un fond «C»",                  key = "fill" },
  { pat = "un texte «C»",                 key = "text" },
  { pat = "un titre «B»",                 key = "title" },
  { pat = "des coins arrondis de «N» mm", key = "radius" },
  { pat = "des coins arrondis de «N»mm",  key = "radius" },
  { pat = "un écart de «N» mm",           key = "gap" },
  { pat = "un écart de «N»mm",            key = "gap" },
  { pat = "une largeur de «N» mm",        key = "width",  unit = "mm" },
  { pat = "une hauteur de «N» mm",       key = "height", unit = "mm" },
  { pat = "un côté de «N» mm",            key = "side",   unit = "mm" },
  { pat = "une largeur de «N» %",         key = "widthpct" },
  { pat = "une largeur de «N»%",          key = "widthpct" },
  { pat = "fond «C»",                     key = "fill" },
  { pat = "texte «C»",                    key = "text" },
  { pat = "coins arrondis de «N» mm",     key = "radius" },
  { pat = "coins arrondis de «N»mm",      key = "radius" },
  { pat = "large de «N» mm",              key = "width",  unit = "mm" },
  { pat = "large de «N»mm",               key = "width",  unit = "mm" },
  { pat = "large de «N»«PCT»",            key = "width",  unit = "%"  },
  { pat = "bordure épaisse de «N» mm",    key = "rule" },
  { pat = "bordure épaisse de «N»mm",     key = "rule" },
  { pat = "marge de «N» mm",              key = "sep" },
  { pat = "marge de «N»mm",               key = "sep" },
  { pat = "titre «B»",                    key = "title" },
  { pat = "avec coupure de page",         key = "break", value = "yes" },
  { pat = "sans coupure de page",         key = "break", value = "no"  },
  { pat = "écart de «N» mm",              key = "gap" },
  { pat = "écart de «N»mm",               key = "gap" },
  { pat = "haut de «N» cm",               key = "height", unit = "cm" },
  { pat = "haut de «N»cm",                key = "height", unit = "cm" },
  { pat = "bordures",                     key = "borders", bare = true },
  { pat = "entête",                       key = "header",  bare = true },
  { pat = "légendée «B»",                 key = "caption" },
  { pat = "légende «B»",                  key = "caption" },
  { pat = "dans le dossier «B»",           key = "dir" },
  { pat = "avec ajustement",              key = "fit",      value = "on" },
  { pat = "ajustement",                   key = "fit",      value = "on" },
  { pat = "avec les produits",            key = "products", value = "on" },
  { pat = "les produits",                 key = "products", value = "on" },
  { pat = "en disposition horizontale",   key = "horizontal", value = "on" },
  { pat = "les zones «BR»",               key = "template" },
  { pat = "les données «W»",              key = "data" },
  { pat = "données «W»",                  key = "data" },
  { pat = "«N» colonnes",                 key = "cols" },
  { pat = "«N» lignes",                   key = "rows" },
  { pat = "avec «N» échantillons",        key = "samples" },
  { pat = "les bornes «B»",               key = "bounds" },
  { pat = "de bornes «B»",                key = "bounds" },
  { pat = "les effectifs «B»",            key = "counts" },
  { pat = "d'effectifs «B»",              key = "counts" },
  { pat = "en «C»",                       key = "color" },
  { pat = "de trait «N»",                 key = "thickness" },
}

V.KV_KEYS = {
  ["dérivée"]    = "deriv",
  ["derivee"]    = "deriv",
  ["variations"] = "var",
  ["seconde"]    = "second",
  ["données"]      = "data",
  ["bornes"]       = "bounds",
  ["effectifs"]    = "counts",
  ["zones"]        = "template",
  ["colonnes"]     = "cols",
  ["lignes"]       = "rows",
  ["echantillons"] = "samples",
  ["échantillons"] = "samples",
  ["pas"]          = "step",
  ["aire"]         = "area",
  ["entre"]        = "between",
  ["intervalle"]   = "set",
  ["points"]       = "points",
  ["x"]            = "x",
}

V.STATS_KINDS = {
  ["barres"]              = "bars",
  ["histogramme"]         = "histogram",
  ["camembert"]           = "pie",
  ["boite a moustaches"]  = "boxplot",
  ["boîte à moustaches"]  = "boxplot",
  ["nuage"]               = "scatter",
}

V.LOIS = {
  ["normale"]   = "norm",
  ["binomiale"] = "binom",
  ["poisson"]   = "poisson",
  ["uniforme"]  = "uniform",
  ["exponentielle"] = "expon",
  ["student"]   = "t",
  ["khi-deux"]  = "chi2",
  ["khi deux"]  = "chi2",
}

V.DOMAINES = {
  ["RR"] = "Reals",   ["les réels"] = "Reals",     ["réels"] = "Reals",
  ["CC"] = "Complexes", ["les complexes"] = "Complexes", ["complexes"] = "Complexes",
  ["ZZ"] = "Integers", ["les entiers"] = "Integers", ["entiers"] = "Integers",
  ["NN"] = "Naturals", ["les naturels"] = "Naturals", ["naturels"] = "Naturals",
  ["QQ"] = "Rationals", ["les rationnels"] = "Rationals", ["rationnels"] = "Rationals",
}

V.COLORS = {
  ["noir"] = "Black", ["blanc"] = "White", ["gris"] = "Gray",
  ["gris clair"] = "LightGray", ["gris foncé"] = "DarkGray",
  ["gris perle"] = "Gainsboro", ["gris anthracite"] = "DarkSlateGray",
  ["gris souris"] = "SlateGray", ["gris souris clair"] = "LightSlateGray",
  ["gris souris foncé"] = "DimGray",
  ["blanc fumé"] = "WhiteSmoke", ["blanc antique"] = "AntiqueWhite",
  ["blanc ivoire"] = "Ivory", ["blanc navajo"] = "NavajoWhite",
  ["blanc fantôme"] = "GhostWhite", ["blanc neige"] = "Snow",
  ["blanc lin"] = "Linen", ["blanc coquillage"] = "Seashell",
  ["blanc vieille dentelle"] = "OldLace",
  ["rouge"] = "Red", ["rouge clair"] = "LightCoral",
  ["rouge foncé"] = "DarkRed", ["rouge sang"] = "Maroon",
  ["rouge brique"] = "Brown",
  ["rouge tomate"] = "Tomato", ["rouge corail"] = "Coral",
  ["rouge indien"] = "IndianRed", ["rouge cramoisi"] = "Crimson",
  ["rouge saumon"] = "Salmon", ["rouge saumon foncé"] = "DarkSalmon",
  ["rouge orange"] = "OrangeRed", ["rouge rosé"] = "PaleVioletRed",
  ["brique de feu"] = "FireBrick",
  ["bleu"] = "Blue", ["bleu ciel"] = "SkyBlue", ["bleu clair"] = "LightBlue",
  ["bleu azur"] = "Azure", ["bleu canard"] = "Teal",
  ["bleu turquoise"] = "CadetBlue",
  ["bleu marine"] = "Navy", ["bleu nuit"] = "MidnightBlue",
  ["bleu foncé"] = "DarkBlue", ["bleu acier"] = "SteelBlue",
  ["bleu acier clair"] = "LightSteelBlue", ["bleu royal"] = "RoyalBlue",
  ["bleu cornflower"] = "CornflowerBlue", ["bleu dodger"] = "DodgerBlue",
  ["bleu poudre"] = "PowderBlue", ["bleu Alice"] = "AliceBlue",
  ["bleu aigue-marine"] = "Aqua",
  ["bleu aigue-marine moyen"] = "MediumAquamarine",
  ["bleu slate"] = "SlateBlue", ["bleu slate clair"] = "LightSlateBlue",
  ["bleu slate foncé"] = "DarkSlateBlue",
  ["bleu slate moyen"] = "MediumSlateBlue",
  ["bleu violet"] = "BlueViolet", ["bleu indigo"] = "Indigo",
  ["bleu lavande"] = "Lavender",
  ["bleu lavande rosé"] = "LavenderBlush",
  ["bleu ciel profond"] = "DeepSkyBlue",
  ["vert"] = "Green", ["vert clair"] = "LightGreen",
  ["vert foncé"] = "DarkGreen", ["vert forêt"] = "ForestGreen",
  ["vert sapin"] = "DarkGreen",
  ["vert pomme"] = "GreenYellow",
  ["vert olive"] = "Olive", ["vert olive foncé"] = "DarkOliveGreen",
  ["vert menthe"] = "MintCream",
  ["vert printanier"] = "SpringGreen",
  ["vert printanier moyen"] = "MediumSpringGreen",
  ["vert de mer"] = "SeaGreen", ["vert de mer clair"] = "LightSeaGreen",
  ["vert de mer foncé"] = "DarkSeaGreen",
  ["vert de mer moyen"] = "MediumSeaGreen",
  ["vert honeydew"] = "Honeydew", ["vert pale"] = "PaleGreen",
  ["vert pâle"] = "PaleGreen",
  ["vert lawn"] = "LawnGreen", ["vert citron"] = "LimeGreen",
  ["vert lime"] = "Lime", ["vert lime clair"] = "LimeGreen",
  ["vert chartreuse"] = "Chartreuse", ["vert jaunâtre"] = "YellowGreen",
  ["jaune"] = "Yellow", ["jaune clair"] = "LightYellow",
  ["jaune paille"] = "LightGoldenrodYellow",
  ["jaune citron"] = "LemonChiffon",
  ["jaune moutarde"] = "DarkGoldenrod",
  ["jaune doré"] = "Gold", ["jaune khaki"] = "Khaki",
  ["jaune khaki foncé"] = "DarkKhaki",
  ["orange"] = "Orange", ["orange clair"] = "LightSalmon",
  ["orange corail"] = "Coral", ["orange foncé"] = "DarkOrange",
  ["orange brique"] = "Chocolate",
  ["orange rouge"] = "OrangeRed", ["orange sable"] = "SandyBrown",
  ["verge d'or"] = "Goldenrod", ["verge d'or pâle"] = "PaleGoldenrod",
  ["verge d'or clair"] = "LightGoldenrod",
  ["mousseline de citron"] = "LemonChiffon",
  ["violet"] = "Violet", ["violet clair"] = "Plum",
  ["violet foncé"] = "DarkViolet", ["violet moyen"] = "MediumPurple",
  ["violet rouge"] = "VioletRed",
  ["violet rouge moyen"] = "MediumVioletRed",
  ["violet rouge pâle"] = "PaleVioletRed",
  ["violet orchidée"] = "Orchid", ["violet orchidée foncé"] = "DarkOrchid",
  ["magenta"] = "Magenta", ["fuchsia"] = "Fuchsia", ["chardon"] = "Thistle",
  ["marron"] = "Maroon", ["brun"] = "Brown",
  ["brun clair"] = "BurlyWood",
  ["brun foncé"] = "SaddleBrown",
  ["brun chocolat"] = "Chocolate", ["brun rosé"] = "RosyBrown",
  ["brun peru"] = "Peru", ["tan"] = "Tan",
  ["marron selle"] = "SaddleBrown", ["marron sable"] = "SandyBrown",
  ["terre de Sienne"] = "Sienna", ["olive"] = "Olive",
  ["olive terne"] = "OliveDrab",
  ["cyan"] = "Cyan", ["cyan clair"] = "LightCyan",
  ["cyan foncé"] = "DarkCyan",
  ["aigue-marine"] = "Aquamarine",
  ["aigue-marine moyen"] = "MediumAquamarine",
  ["turquoise"] = "Turquoise",
  ["turquoise clair"] = "PaleTurquoise",
  ["turquoise foncé"] = "DarkTurquoise",
  ["turquoise moyen"] = "MediumTurquoise",
  ["turquoise pâle"] = "PaleTurquoise",
  ["corail"] = "Coral", ["corail clair"] = "LightCoral",
  ["saumon"] = "Salmon", ["saumon clair"] = "LightSalmon",
  ["saumon foncé"] = "DarkSalmon",
  ["or"] = "Gold", ["argent"] = "Silver",
  ["rose"] = "Pink", ["rose clair"] = "LightPink",
  ["rose profond"] = "DeepPink", ["rose vif"] = "HotPink",
  ["rose brumeux"] = "MistyRose",
  ["pêche duveteuse"] = "PeachPuff", ["prune"] = "Plum",
  ["pourpre"] = "Purple", ["fouet de papaye"] = "PapayaWhip",
  ["coquillage"] = "Seashell", ["soie de maïs"] = "Cornsilk",
  ["biscuit"] = "Bisque", ["cramoisi"] = "Crimson",
  ["lavande"] = "Lavender", ["vieille dentelle"] = "OldLace",
  ["lin"] = "Linen", ["mocassin"] = "Moccasin",
  ["neige"] = "Snow", ["blé"] = "Wheat",
}

V.KEYWORDS = {
  ["soit"]     = "let",
  ["pour"]     = "for",
  ["dans"]     = "in",
  ["si"]       = "if",
  ["sinon"]    = "else",
  ["tant que"] = "while",
}

V.OPERATORS = {
  ["au moins"]     = ">=",
  ["au plus"]      = "<=",
  ["égal à"]       = "==",
  ["différent de"] = "!=",
}

V.MATH_WORDS = {
  ["infini"]       = "inf",
  ["pour tout"]    = "forall",
  ["il n'existe pas"] = "!exists",
  ["il existe"]    = "exists",
  ["déterminant"]  = "det",
  ["norme"]        = "norm",
  ["gradient"]     = "grad",
  ["divergence"]   = "div",
  ["rotationnel"]  = "curl",
  ["laplacien"]    = "lap",
  ["partielle"]    = "partial",
  ["partiel"]      = "partial",
  ["somme"]        = "sum",
  ["produit"]      = "prod",
  ["intégrale"]    = "int",
  ["integrale"]    = "int",
  ["racine"]       = "sqrt",
  ["arrondi"]      = "round",
  ["defaut"]       = "floor",
  ["défaut"]       = "floor",
  ["exces"]        = "ceil",
  ["excès"]        = "ceil",
  ["signe"]        = "sign",
  ["factorielle"]  = "factorial",
  ["naturels"]     = "NN", ["entiers"] = "ZZ", ["décimaux"] = "DD",
  ["rationnels"]   = "QQ", ["réels"]   = "RR", ["complexes"] = "CC",
  ["probabilité"] = "PP", ["corps"]   = "KK", ["quaternions"] = "HH",
  ["dans"]         = "in", ["non"] = "not",
  ["sachant"]      = "mid",
  ["et"]           = "and",
  ["ou"]           = "or",
  ["négation de"]  = "lnot",
  ["n'appartient pas à"] = "!in",
  ["appartient à"] = "in",
  ["inclus dans ou égal à"] = "subseteq",
  ["contient ou est égal à"] = "supseteq",
  ["inclus dans"]  = "subset",
  ["contient"]     = "supset",
  ["union"]        = "cup",
  ["inter"]        = "cap",
  ["intersection"] = "cap",
  ["réunion de"]   = "bigcup",
  ["intersection de"] = "bigcap",
  ["privé de"]     = "setminus",
  ["vide"]         = "emptyset",
  ["parties de"]   = "powerset",
  ["intervalle entier"] = "range",
  ["cardinal"]     = "card",
  ["congru à"]     = "equiv",
  ["modulo"]       = "mod",
  ["conjugué"]     = "conj",
  ["moyenne"]      = "bar",
  ["adhérence"]    = "bar",
  ["espérance"]    = "EE",
  ["variance"]     = "var",
  ["écart type"]  = "std",
  ["covariance"]   = "cov",
  ["normal"]       = "normal", ["binomiale"] = "binomial",
  ["poisson"]      = "poisson",
  ["vecteur"]      = "vec",
  ["répartition de"] = "repart",
  ["densité de"]  = "densite",
  ["engendré par"] = "span",
  ["engendre"]    = "span",
  ["trace"]        = "tr",
  ["rang"]         = "rank",
  ["noyau"]        = "ker",
  ["image"]        = "im",
  ["comatrice"]    = "com",
  ["spectre"]      = "eigen",
  ["transposée"]  = "transpose",
  ["comatrice de"] = "com",
  ["dérivée directionnelle"] = "dirderiv",
  ["parallèle"]       = "parallel",
  ["perpendiculaire"] = "perp",
  ["orthogonal à"]    = "perp",
  ["semblable"]       = "sim",
  ["semblables"]      = "sim",
  ["isométrique"]     = "cong",
  ["isométriques"]    = "cong",
  ["parallélogramme"] = "parallelogram",
  ["angle droit"]     = "rightangle",
  ["milieu"]              = "midpoint",
  ["distance"]           = "distance",
  ["cercle"]             = "circle",
  ["repère orthonormé"]  = "orthoframe",
  ["repère"]             = "frame",
  ["colinéaires"]        = "collinear",
  ["colinéaire"]         = "collinear",
  ["produit scalaire"]   = "inner",
  ["orthogonal"]         = "ortho",
  ["projeté orthogonal"] = "orthogonalprojection",
  ["produit mixte"]      = "triple",
  ["valeur absolue"]     = "abs",
}

-- ============================================================
-- Moteur de correspondance.
--
-- NOTE DE CONCEPTION (issue de mesures, pas seulement de théorie) :
-- un premier essai de fusion "trie par octets + table de frontière
-- O(1)" a été implémenté puis *benchmarké* contre les deux versions
-- d'origine (Lua 5.3, dictionnaires réels du projet). Résultat :
-- le trie perd systématiquement face à l'approche par index de
-- longueur, y compris une fois optimisé. Raison mesurée : en Lua,
-- un lookup de table à clé entière éparse et un lookup à clé string
-- (chaîne internée, hash mis en cache) coûtent quasiment la même
-- chose ; or le trie fait UN lookup PAR OCTET de la clé matchée
-- (ex. "bleu ciel profond" = 18 lookups), alors que l'index par
-- longueur fait 1 à 3 lookups (un par longueur candidate pour le
-- même octet initial), quel que soit la longueur du mot. Sur les
-- dictionnaires du projet (clés de 2 à ~20 caractères, peu de
-- collisions de longueur par octet initial), l'index gagne donc
-- mécaniquement.
--
-- Le hybride retenu est donc : l'architecture de V1 (index par
-- longueur + lookup direct dans le dictionnaire), à laquelle on
-- ajoute la seule optimisation qui a montré un gain mesuré et
-- reproductible (~30-40 % sur les bancs d'essai) : une table de
-- frontière O(1) à la place de la chaîne de comparaisons `or`.
-- ============================================================

local byte, sub, sort = string.byte, string.sub, table.sort

-- Table de frontière : lookup O(1) au lieu d'une chaîne de 8
-- comparaisons `or`. Une métatable renvoie `true` pour toute clé
-- absente (dont nil, càd fin de chaîne quand byte(s,i+1) échoue),
-- ce qui reproduit exactement le comportement de l'ancien `not b`.
local IS_BOUNDARY = {}
for b = 0, 255 do IS_BOUNDARY[b] = false end
IS_BOUNDARY[9]   = true  -- \t
IS_BOUNDARY[10]  = true  -- \n
IS_BOUNDARY[13]  = true  -- \r
IS_BOUNDARY[32]  = true  -- espace
IS_BOUNDARY[44]  = true  -- ,
IS_BOUNDARY[58]  = true  -- :
IS_BOUNDARY[62]  = true  -- >
IS_BOUNDARY[123] = true  -- {
setmetatable(IS_BOUNDARY, { __index = function() return true end })

local function make_index(dict)
  local index = {}
  for k in pairs(dict) do
    local first, len = byte(k, 1), #k
    local lengths = index[first]
    if not lengths then
      lengths = { [len] = true }
      index[first] = lengths
    else
      lengths[len] = true
    end
  end
  for first, set in pairs(index) do
    local lengths, n = {}, 0
    for len in pairs(set) do
      n = n + 1
      lengths[n] = len
    end
    sort(lengths, function(a, b) return a > b end)
    index[first] = lengths
  end
  return index
end

-- Tous les dictionnaires utilisés par le lexer sont connus au
-- chargement du module : on précalcule directement leurs index,
-- sans cache paresseux ni table faible, donc aucune vérification
-- runtime superflue à chaque appel de longest_match pour les
-- dictionnaires standards.
local INDEXES = {}
for _, dict in ipairs {
  V.TAGS, V.LIST_STYLES, V.STYLE_WORDS, V.COUNTED_WORDS, V.PLACE,
  V.KV_KEYS, V.STATS_KINDS, V.LOIS, V.DOMAINES, V.COLORS,
  V.KEYWORDS, V.OPERATORS, V.MATH_WORDS,
} do
  INDEXES[dict] = make_index(dict)
end

function V.longest_match(dict, s)
  local index = INDEXES[dict]
  if not index then
    -- Filet de sécurité pour un dictionnaire ajouté dynamiquement
    -- après le chargement du module (cas hors lexer standard).
    index = make_index(dict)
    INDEXES[dict] = index
  end
  local lengths = index[byte(s, 1)]
  if not lengths then return nil end
  local n = #s
  for i = 1, #lengths do
    local len = lengths[i]
    if len <= n and IS_BOUNDARY[byte(s, len + 1)] then
      local key = sub(s, 1, len)
      local value = dict[key]
      if value ~= nil then
        return key, value, sub(s, len + 1)
      end
    end
  end
  return nil
end

function V.color(name)
  local c = V.COLORS[name]
  if c then return c end
  local n = #name
  if n <= 1 then return nil end
  local base = name
  if byte(base, n) == 115 then
    base = sub(base, 1, n - 1)
    c = V.COLORS[base]
    if c then return c end
    n = n - 1
  end
  if n > 1 and byte(base, n) == 101 then
    base = sub(base, 1, n - 1)
    c = V.COLORS[base]
    if c then return c end
    local b = byte(base, n - 1)
    if b == 104 or b == 116 then
      c = V.COLORS[sub(base, 1, n - 2)]
      if c then return c end
    end
  end
  local first = byte(name, 1)
  if first and first >= 65 and first <= 90 then
    for i = 2, #name do
      local b = byte(name, i)
      if b >= 97 and b <= 122 then return name end
    end
  end
  return nil
end

local COLOR_KEYS_SORTED = {}
for k in pairs(V.COLORS) do COLOR_KEYS_SORTED[#COLOR_KEYS_SORTED + 1] = k end
sort(COLOR_KEYS_SORTED, function(a, b)
  local la, lb = #a, #b
  return la == lb and a < b or la > lb
end)

function V.color_keys_sorted()
  return COLOR_KEYS_SORTED
end

return V