if not modules then modules = { } end modules ['x-asciimath'] = {
    version   = 1.001,
    comment   = "companion to x-asciimath.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

--[[ldx--
<p>Some backgrounds are discussed in <t>x-asciimath.mkiv</t>. This is a third version. I first
tried a to make a proper expression parser but it's not that easy. First we have to avoid left
recursion, which is not that trivial (maybe a future version of lpeg will provide that), and
second there is not really a syntax but a mix of expressions and sequences with some fuzzy logic
applied. Most problematic are fractions and we also need to handle incomplete expressions. So,
instead we (sort of) tokenize the string and then do some passes over the result. Yes, it's real
ugly and unsatisfying code mess down here. Don't take this as an example.</p>
--ldx]]--

-- todo: spaces around all elements in cleanup?
-- todo: filter from files listed in tuc file

local trace_mapping    = false  if trackers then trackers.register("modules.asciimath.mapping", function(v) trace_mapping = v end) end
local trace_detail     = false  if trackers then trackers.register("modules.asciimath.detail",  function(v) trace_detail  = v end) end
local trace_digits     = false  if trackers then trackers.register("modules.asciimath.digits",  function(v) trace_digits  = v end) end

local report_asciimath = logs.reporter("mathematics","asciimath")

local asciimath        = { }
local moduledata       = moduledata or { }
moduledata.asciimath   = asciimath

if not characters then
    require("char-def")
    require("char-ini")
    require("char-ent")
end

local type, rawget = type, rawget
local concat, insert, remove = table.concat, table.insert, table.remove
local rep, gmatch, gsub, find = string.rep, string.gmatch, string.gsub, string.find
local utfchar, utfbyte = utf.char, utf.byte

local lpegmatch, patterns = lpeg.match, lpeg.patterns
local S, P, R, C, V, Cc, Ct, Cs, Carg = lpeg.S, lpeg.P, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc, lpeg.Ct, lpeg.Cs, lpeg.Carg

local sortedhash   = table.sortedhash
local sortedkeys   = table.sortedkeys
local formatters   = string.formatters

local entities     = characters.entities or { }

local xmltext      = xml.text
local xmlinclusion = xml.inclusion
local xmlcollected = xml.collected

-- todo: use private unicodes as temporary slots ... easier to compare

local s_lparent  = "\\left\\lparent"
local s_lbrace   = "\\left\\lbrace"
local s_lbracket = "\\left\\lbracket"
local s_langle   = "\\left\\langle"
local s_lfloor   = "\\left\\lfloor"
local s_lceil    = "\\left\\lceil"
local s_left     = "\\left."

local s_rparent  = "\\right\\rparent"
local s_rbrace   = "\\right\\rbrace"
local s_rbracket = "\\right\\rbracket"
local s_rangle   = "\\right\\rangle"
local s_rfloor   = "\\right\\rfloor"
local s_rceil    = "\\right\\rceil"
local s_right    = "\\right."

local s_mslash   = "\\middle/"

local s_lbar     = "\\left\\|"
local s_mbar     = "\\middle\\|"
local s_rbar     = "\\right\\|"

local s_lnothing = "\\left ."  -- space fools checker
local s_rnothing = "\\right ." -- space fools checker

local reserved = {

    ["prod"]      = { false, "\\prod" },
    ["sinh"]      = { false, "\\sinh" },
    ["cosh"]      = { false, "\\cosh" },
    ["tanh"]      = { false, "\\tanh" },
    ["sum"]       = { false, "\\sum" },
    ["int"]       = { false, "\\int" },
    ["sin"]       = { false, "\\sin" },
    ["cos"]       = { false, "\\cos" },
    ["tan"]       = { false, "\\tan" },
    ["csc"]       = { false, "\\csc" },
    ["sec"]       = { false, "\\sec" },
    ["cot"]       = { false, "\\cot" },
    ["log"]       = { false, "\\log" },
    ["det"]       = { false, "\\det" },
    ["lim"]       = { false, "\\lim" },
    ["mod"]       = { false, "\\mod" },
    ["gcd"]       = { false, "\\gcd" },
    ["min"]       = { false, "\\min" },
    ["max"]       = { false, "\\max" },
    ["ln"]        = { false, "\\ln" },

    ["atan"]      = { false, "\\atan" }, -- extra
    ["acos"]      = { false, "\\acos" }, -- extra
    ["asin"]      = { false, "\\asin" }, -- extra

    ["arctan"]    = { false, "\\arctan" }, -- extra
    ["arccos"]    = { false, "\\arccos" }, -- extra
    ["arcsin"]    = { false, "\\arcsin" }, -- extra

    ["and"]       = { false, "\\text{and}" },
    ["or"]        = { false, "\\text{or}" },
    ["if"]        = { false, "\\text{if}" },

    ["sqrt"]      = { false, "\\asciimathsqrt",     "unary" },
    ["root"]      = { false, "\\asciimathroot",     "binary" },
 -- ["\\frac"]    = { false, "\\frac",              "binary" },
    ["frac"]      = { false, "\\frac",              "binary" },
    ["stackrel"]  = { false, "\\asciimathstackrel", "binary" },
    ["hat"]       = { false, "\\widehat",           "unary" },
    ["bar"]       = { false, "\\overbar",           "unary" },
    ["overbar"]   = { false, "\\overbar",           "unary" },
    ["underline"] = { false, "\\underline",         "unary" },
    ["ul"]        = { false, "\\underline",         "unary" },
    ["vec"]       = { false, "\\overrightarrow",    "unary" },
    ["dot"]       = { false, "\\dot",               "unary" }, -- 0x2D9
    ["ddot"]      = { false, "\\ddot",              "unary" }, -- 0xA8

    -- binary operators

    ["+"]         = { true,  "+" },
    ["-"]         = { true,  "-" },
    ["*"]         = { true,  "⋅" },
    ["**"]        = { true,  "⋆" },
    ["//"]        = { true,  "⁄" }, -- \slash
    ["\\"]        = { true,  "\\" },
    ["xx"]        = { true,  "×" },
    ["times"]     = { true,  "×" },
    ["-:"]        = { true,  "÷" },
    ["@"]         = { true,  "∘" },
    ["circ"]      = { true,  "∘" },
    ["o+"]        = { true,  "⊕" },
    ["ox"]        = { true,  "⊗" },
    ["o."]        = { true,  "⊙" },
    ["^^"]        = { true,  "∧" },
    ["vv"]        = { true,  "∨" },
    ["nn"]        = { true,  "∩" },
    ["uu"]        = { true,  "∪" },

    -- big operators

    ["^^^"]       = { true,  "⋀" },
    ["vvv"]       = { true,  "⋁" },
    ["nnn"]       = { true,  "⋂" },
    ["uuu"]       = { true,  "⋃" },
    ["int"]       = { true,  "∫" },
    ["oint"]      = { true,  "∮" },

    -- brackets

    ["("]         = { true, "(" },
    [")"]         = { true, ")" },
    ["["]         = { true, "[" },
    ["]"]         = { true, "]" },
    ["{"]         = { true, "{" },
    ["}"]         = { true, "}" },

    -- binary relations

    ["="]         = { true,  "=" },
    ["eq"]        = { true,  "=" },
    ["!="]        = { true,  "≠" },
    ["ne"]        = { true,  "≠" },
    ["neq"]       = { true,  "≠" },
    ["<"]         = { true,  "<" },
    ["lt"]        = { true,  "<" },
    [">"]         = { true,  ">" },
    ["gt"]        = { true,  ">" },
    ["<="]        = { true,  "≤" },
    ["le"]        = { true,  "≤" },
    ["leq"]       = { true,  "≤" },
    [">="]        = { true,  "≥" },
    ["ge"]        = { true,  "≥" },
    ["geq"]       = { true,  "≥" },
    ["-<"]        = { true,  "≺" },
    [">-"]        = { true,  "≻" },
    ["in"]        = { true,  "∈" },
    ["!in"]       = { true,  "∉" },
    ["sub"]       = { true,  "⊂" },
    ["sup"]       = { true,  "⊃" },
    ["sube"]      = { true,  "⊆" },
    ["supe"]      = { true,  "⊇" },
    ["-="]        = { true,  "≡" },
    ["~="]        = { true,  "≅" },
    ["~~"]        = { true,  "≈" },
    ["prop"]      = { true,  "∝" },

    -- arrows

    ["rarr"]      = { true,  "→" },
    ["->"]        = { true,  "→" },
    ["larr"]      = { true,  "←" },
    ["harr"]      = { true,  "↔" },
    ["uarr"]      = { true,  "↑" },
    ["darr"]      = { true,  "↓" },
    ["rArr"]      = { true,  "⇒" },
    ["lArr"]      = { true,  "⇐" },
    ["hArr"]      = { true,  "⇔" },
    ["|->"]       = { true,  "↦" },

    -- logical

    ["not"]       = { true,  "¬" },
    ["=>"]        = { true,  "⇒" },
    ["iff"]       = { true,  "⇔" },
    ["AA"]        = { true,  "∀" },
    ["EE"]        = { true,  "∃" },
    ["_|_"]       = { true,  "⊥" },
    ["TT"]        = { true,  "⊤" },
    ["|--"]       = { true,  "⊢" },
    ["|=="]       = { true,  "⊨" },

    -- miscellaneous

    ["del"]       = { true,  "∂" },
    ["grad"]      = { true,  "∇" },
    ["+-"]        = { true,  "±" },
    ["O/"]        = { true,  "∅" },
    ["oo"]        = { true,  "∞" },
    ["aleph"]     = { true,  "ℵ" },
    ["angle"]     = { true,  "∠" },
    ["/_"]        = { true,  "∠" },
    [":."]        = { true,  "∴" },
    ["..."]       = { true,  "..." }, -- ldots
    ["ldots"]     = { true,  "..." }, -- ldots
    ["cdots"]     = { true,  "⋯" },
    ["vdots"]     = { true,  "⋮" },
    ["ddots"]     = { true,  "⋱" },
    ["diamond"]   = { true,  "⋄" },
    ["square"]    = { true,  "□" },
    ["|__"]       = { true,  "⌊" },
    ["__|"]       = { true,  "⌋" },
    ["|~"]        = { true,  "⌈" },
    ["~|"]        = { true,  "⌉" },

    -- more

    ["_="]        = { true, "≡" },

    -- bonus

    ["prime"]     = { true,  "′" }, -- bonus
    ["'"]         = { true,  "′" }, -- bonus
    ["''"]        = { true,  "″" }, -- bonus
    ["'''"]       = { true,  "‴" }, -- bonus

    -- special

    ["%"]         = { false, "\\mathpercent" },
    ["&"]         = { false, "\\mathampersand" },
    ["#"]         = { false, "\\mathhash" },
    ["$"]         = { false, "\\mathdollar" },

    -- blackboard

    ["CC"]        = { true, "ℂ" },
    ["NN"]        = { true, "ℕ" },
    ["QQ"]        = { true, "ℚ" },
    ["RR"]        = { true, "ℝ" },
    ["ZZ"]        = { true, "ℤ" },

    -- greek lowercase

    ["alpha"]      = { true, "α" },
    ["beta"]       = { true, "β" },
    ["gamma"]      = { true, "γ" },
    ["delta"]      = { true, "δ" },
    ["epsilon"]    = { true, "ε" },
    ["varepsilon"] = { true, "ɛ" },
    ["zeta"]       = { true, "ζ" },
    ["eta"]        = { true, "η" },
    ["theta"]      = { true, "θ" },
    ["vartheta"]   = { true, "ϑ" },
    ["iota"]       = { true, "ι" },
    ["kappa"]      = { true, "κ" },
    ["lambda"]     = { true, "λ" },
    ["mu"]         = { true, "μ" },
    ["nu"]         = { true, "ν" },
    ["xi"]         = { true, "ξ" },
    ["pi"]         = { true, "π" },
    ["rho"]        = { true, "ρ" },
    ["sigma"]      = { true, "σ" },
    ["tau"]        = { true, "τ" },
    ["upsilon"]    = { true, "υ" },
    ["phi"]        = { true, "ϕ" },
    ["varphi"]     = { true, "φ" },
    ["chi"]        = { true, "χ" },
    ["psi"]        = { true, "ψ" },
    ["omega"]      = { true, "ω" },

    -- greek uppercase

    ["Gamma"]  = { true, "Γ" },
    ["Delta"]  = { true, "Δ" },
    ["Theta"]  = { true, "Θ" },
    ["Lambda"] = { true, "Λ" },
    ["Xi"]     = { true, "Ξ" },
    ["Pi"]     = { true, "Π" },
    ["Sigma"]  = { true, "Σ" },
    ["Phi"]    = { true, "Φ" },
    ["Psi"]    = { true, "Ψ" },
    ["Omega"]  = { true, "Ω" },

    -- blackboard

    ["bbb a"] = { true, "𝕒" },
    ["bbb b"] = { true, "𝕓" },
    ["bbb c"] = { true, "𝕔" },
    ["bbb d"] = { true, "𝕕" },
    ["bbb e"] = { true, "𝕖" },
    ["bbb f"] = { true, "𝕗" },
    ["bbb g"] = { true, "𝕘" },
    ["bbb h"] = { true, "𝕙" },
    ["bbb i"] = { true, "𝕚" },
    ["bbb j"] = { true, "𝕛" },
    ["bbb k"] = { true, "𝕜" },
    ["bbb l"] = { true, "𝕝" },
    ["bbb m"] = { true, "𝕞" },
    ["bbb n"] = { true, "𝕟" },
    ["bbb o"] = { true, "𝕠" },
    ["bbb p"] = { true, "𝕡" },
    ["bbb q"] = { true, "𝕢" },
    ["bbb r"] = { true, "𝕣" },
    ["bbb s"] = { true, "𝕤" },
    ["bbb t"] = { true, "𝕥" },
    ["bbb u"] = { true, "𝕦" },
    ["bbb v"] = { true, "𝕧" },
    ["bbb w"] = { true, "𝕨" },
    ["bbb x"] = { true, "𝕩" },
    ["bbb y"] = { true, "𝕪" },
    ["bbb z"] = { true, "𝕫" },

    ["bbb A"] = { true, "𝔸" },
    ["bbb B"] = { true, "𝔹" },
    ["bbb C"] = { true, "ℂ" },
    ["bbb D"] = { true, "𝔻" },
    ["bbb E"] = { true, "𝔼" },
    ["bbb F"] = { true, "𝔽" },
    ["bbb G"] = { true, "𝔾" },
    ["bbb H"] = { true, "ℍ" },
    ["bbb I"] = { true, "𝕀" },
    ["bbb J"] = { true, "𝕁" },
    ["bbb K"] = { true, "𝕂" },
    ["bbb L"] = { true, "𝕃" },
    ["bbb M"] = { true, "𝕄" },
    ["bbb N"] = { true, "ℕ" },
    ["bbb O"] = { true, "𝕆" },
    ["bbb P"] = { true, "ℙ" },
    ["bbb Q"] = { true, "ℚ" },
    ["bbb R"] = { true, "ℝ" },
    ["bbb S"] = { true, "𝕊" },
    ["bbb T"] = { true, "𝕋" },
    ["bbb U"] = { true, "𝕌" },
    ["bbb V"] = { true, "𝕍" },
    ["bbb W"] = { true, "𝕎" },
    ["bbb X"] = { true, "𝕏" },
    ["bbb Y"] = { true, "𝕐" },
    ["bbb Z"] = { true, "ℤ" },

    -- fraktur

    ["fr a"] = { true, "𝔞" },
    ["fr b"] = { true, "𝔟" },
    ["fr c"] = { true, "𝔠" },
    ["fr d"] = { true, "𝔡" },
    ["fr e"] = { true, "𝔢" },
    ["fr f"] = { true, "𝔣" },
    ["fr g"] = { true, "𝔤" },
    ["fr h"] = { true, "𝔥" },
    ["fr i"] = { true, "𝔦" },
    ["fr j"] = { true, "𝔧" },
    ["fr k"] = { true, "𝔨" },
    ["fr l"] = { true, "𝔩" },
    ["fr m"] = { true, "𝔪" },
    ["fr n"] = { true, "𝔫" },
    ["fr o"] = { true, "𝔬" },
    ["fr p"] = { true, "𝔭" },
    ["fr q"] = { true, "𝔮" },
    ["fr r"] = { true, "𝔯" },
    ["fr s"] = { true, "𝔰" },
    ["fr t"] = { true, "𝔱" },
    ["fr u"] = { true, "𝔲" },
    ["fr v"] = { true, "𝔳" },
    ["fr w"] = { true, "𝔴" },
    ["fr x"] = { true, "𝔵" },
    ["fr y"] = { true, "𝔶" },
    ["fr z"] = { true, "𝔷" },

    ["fr A"] = { true, "𝔄" },
    ["fr B"] = { true, "𝔅" },
    ["fr C"] = { true, "ℭ" },
    ["fr D"] = { true, "𝔇" },
    ["fr E"] = { true, "𝔈" },
    ["fr F"] = { true, "𝔉" },
    ["fr G"] = { true, "𝔊" },
    ["fr H"] = { true, "ℌ" },
    ["fr I"] = { true, "ℑ" },
    ["fr J"] = { true, "𝔍" },
    ["fr K"] = { true, "𝔎" },
    ["fr L"] = { true, "𝔏" },
    ["fr M"] = { true, "𝔐" },
    ["fr N"] = { true, "𝔑" },
    ["fr O"] = { true, "𝔒" },
    ["fr P"] = { true, "𝔓" },
    ["fr Q"] = { true, "𝔔" },
    ["fr R"] = { true, "ℜ" },
    ["fr S"] = { true, "𝔖" },
    ["fr T"] = { true, "𝔗" },
    ["fr U"] = { true, "𝔘" },
    ["fr V"] = { true, "𝔙" },
    ["fr W"] = { true, "𝔚" },
    ["fr X"] = { true, "𝔛" },
    ["fr Y"] = { true, "𝔜" },
    ["fr Z"] = { true, "ℨ" },

    -- script

    ["cc a"] = { true, "𝒶" },
    ["cc b"] = { true, "𝒷" },
    ["cc c"] = { true, "𝒸" },
    ["cc d"] = { true, "𝒹" },
    ["cc e"] = { true, "ℯ" },
    ["cc f"] = { true, "𝒻" },
    ["cc g"] = { true, "ℊ" },
    ["cc h"] = { true, "𝒽" },
    ["cc i"] = { true, "𝒾" },
    ["cc j"] = { true, "𝒿" },
    ["cc k"] = { true, "𝓀" },
    ["cc l"] = { true, "𝓁" },
    ["cc m"] = { true, "𝓂" },
    ["cc n"] = { true, "𝓃" },
    ["cc o"] = { true, "ℴ" },
    ["cc p"] = { true, "𝓅" },
    ["cc q"] = { true, "𝓆" },
    ["cc r"] = { true, "𝓇" },
    ["cc s"] = { true, "𝓈" },
    ["cc t"] = { true, "𝓉" },
    ["cc u"] = { true, "𝓊" },
    ["cc v"] = { true, "𝓋" },
    ["cc w"] = { true, "𝓌" },
    ["cc x"] = { true, "𝓍" },
    ["cc y"] = { true, "𝓎" },
    ["cc z"] = { true, "𝓏" },

    ["cc A"] = { true, "𝒜" },
    ["cc B"] = { true, "ℬ" },
    ["cc C"] = { true, "𝒞" },
    ["cc D"] = { true, "𝒟" },
    ["cc E"] = { true, "ℰ" },
    ["cc F"] = { true, "ℱ" },
    ["cc G"] = { true, "𝒢" },
    ["cc H"] = { true, "ℋ" },
    ["cc I"] = { true, "ℐ" },
    ["cc J"] = { true, "𝒥" },
    ["cc K"] = { true, "𝒦" },
    ["cc L"] = { true, "ℒ" },
    ["cc M"] = { true, "ℳ" },
    ["cc N"] = { true, "𝒩" },
    ["cc O"] = { true, "𝒪" },
    ["cc P"] = { true, "𝒫" },
    ["cc Q"] = { true, "𝒬" },
    ["cc R"] = { true, "ℛ" },
    ["cc S"] = { true, "𝒮" },
    ["cc T"] = { true, "𝒯" },
    ["cc U"] = { true, "𝒰" },
    ["cc V"] = { true, "𝒱" },
    ["cc W"] = { true, "𝒲" },
    ["cc X"] = { true, "𝒳" },
    ["cc Y"] = { true, "𝒴" },
    ["cc Z"] = { true, "𝒵" },

    -- bold

    ["bb a"] = { true, "𝒂" },
    ["bb b"] = { true, "𝒃" },
    ["bb c"] = { true, "𝒄" },
    ["bb d"] = { true, "𝒅" },
    ["bb e"] = { true, "𝒆" },
    ["bb f"] = { true, "𝒇" },
    ["bb g"] = { true, "𝒈" },
    ["bb h"] = { true, "𝒉" },
    ["bb i"] = { true, "𝒊" },
    ["bb j"] = { true, "𝒋" },
    ["bb k"] = { true, "𝒌" },
    ["bb l"] = { true, "𝒍" },
    ["bb m"] = { true, "𝒎" },
    ["bb n"] = { true, "𝒏" },
    ["bb o"] = { true, "𝒐" },
    ["bb p"] = { true, "𝒑" },
    ["bb q"] = { true, "𝒒" },
    ["bb r"] = { true, "𝒓" },
    ["bb s"] = { true, "𝒔" },
    ["bb t"] = { true, "𝒕" },
    ["bb u"] = { true, "𝒖" },
    ["bb v"] = { true, "𝒗" },
    ["bb w"] = { true, "𝒘" },
    ["bb x"] = { true, "𝒙" },
    ["bb y"] = { true, "𝒚" },
    ["bb z"] = { true, "𝒛" },

    ["bb A"] = { true, "𝑨" },
    ["bb B"] = { true, "𝑩" },
    ["bb C"] = { true, "𝑪" },
    ["bb D"] = { true, "𝑫" },
    ["bb E"] = { true, "𝑬" },
    ["bb F"] = { true, "𝑭" },
    ["bb G"] = { true, "𝑮" },
    ["bb H"] = { true, "𝑯" },
    ["bb I"] = { true, "𝑰" },
    ["bb J"] = { true, "𝑱" },
    ["bb K"] = { true, "𝑲" },
    ["bb L"] = { true, "𝑳" },
    ["bb M"] = { true, "𝑴" },
    ["bb N"] = { true, "𝑵" },
    ["bb O"] = { true, "𝑶" },
    ["bb P"] = { true, "𝑷" },
    ["bb Q"] = { true, "𝑸" },
    ["bb R"] = { true, "𝑹" },
    ["bb S"] = { true, "𝑺" },
    ["bb T"] = { true, "𝑻" },
    ["bb U"] = { true, "𝑼" },
    ["bb V"] = { true, "𝑽" },
    ["bb W"] = { true, "𝑾" },
    ["bb X"] = { true, "𝑿" },
    ["bb Y"] = { true, "𝒀" },
    ["bb Z"] = { true, "𝒁" },

    -- sans

    ["sf a"] = { true, "𝖺" },
    ["sf b"] = { true, "𝖻" },
    ["sf c"] = { true, "𝖼" },
    ["sf d"] = { true, "𝖽" },
    ["sf e"] = { true, "𝖾" },
    ["sf f"] = { true, "𝖿" },
    ["sf g"] = { true, "𝗀" },
    ["sf h"] = { true, "𝗁" },
    ["sf i"] = { true, "𝗂" },
    ["sf j"] = { true, "𝗃" },
    ["sf k"] = { true, "𝗄" },
    ["sf l"] = { true, "𝗅" },
    ["sf m"] = { true, "𝗆" },
    ["sf n"] = { true, "𝗇" },
    ["sf o"] = { true, "𝗈" },
    ["sf p"] = { true, "𝗉" },
    ["sf q"] = { true, "𝗊" },
    ["sf r"] = { true, "𝗋" },
    ["sf s"] = { true, "𝗌" },
    ["sf t"] = { true, "𝗍" },
    ["sf u"] = { true, "𝗎" },
    ["sf v"] = { true, "𝗏" },
    ["sf w"] = { true, "𝗐" },
    ["sf x"] = { true, "𝗑" },
    ["sf y"] = { true, "𝗒" },
    ["sf z"] = { true, "𝗓" },

    ["sf A"] = { true, "𝖠" },
    ["sf B"] = { true, "𝖡" },
    ["sf C"] = { true, "𝖢" },
    ["sf D"] = { true, "𝖣" },
    ["sf E"] = { true, "𝖤" },
    ["sf F"] = { true, "𝖥" },
    ["sf G"] = { true, "𝖦" },
    ["sf H"] = { true, "𝖧" },
    ["sf I"] = { true, "𝖨" },
    ["sf J"] = { true, "𝖩" },
    ["sf K"] = { true, "𝖪" },
    ["sf L"] = { true, "𝖫" },
    ["sf M"] = { true, "𝖬" },
    ["sf N"] = { true, "𝖭" },
    ["sf O"] = { true, "𝖮" },
    ["sf P"] = { true, "𝖯" },
    ["sf Q"] = { true, "𝖰" },
    ["sf R"] = { true, "𝖱" },
    ["sf S"] = { true, "𝖲" },
    ["sf T"] = { true, "𝖳" },
    ["sf U"] = { true, "𝖴" },
    ["sf V"] = { true, "𝖵" },
    ["sf W"] = { true, "𝖶" },
    ["sf X"] = { true, "𝖷" },
    ["sf Y"] = { true, "𝖸" },
    ["sf Z"] = { true, "𝖹" },

    -- monospace

    ["tt a"] = { true, "𝚊" },
    ["tt b"] = { true, "𝚋" },
    ["tt c"] = { true, "𝚌" },
    ["tt d"] = { true, "𝚍" },
    ["tt e"] = { true, "𝚎" },
    ["tt f"] = { true, "𝚏" },
    ["tt g"] = { true, "𝚐" },
    ["tt h"] = { true, "𝚑" },
    ["tt i"] = { true, "𝚒" },
    ["tt j"] = { true, "𝚓" },
    ["tt k"] = { true, "𝚔" },
    ["tt l"] = { true, "𝚕" },
    ["tt m"] = { true, "𝚖" },
    ["tt n"] = { true, "𝚗" },
    ["tt o"] = { true, "𝚘" },
    ["tt p"] = { true, "𝚙" },
    ["tt q"] = { true, "𝚚" },
    ["tt r"] = { true, "𝚛" },
    ["tt s"] = { true, "𝚜" },
    ["tt t"] = { true, "𝚝" },
    ["tt u"] = { true, "𝚞" },
    ["tt v"] = { true, "𝚟" },
    ["tt w"] = { true, "𝚠" },
    ["tt x"] = { true, "𝚡" },
    ["tt y"] = { true, "𝚢" },
    ["tt z"] = { true, "𝚣" },

    ["tt A"] = { true, "𝙰" },
    ["tt B"] = { true, "𝙱" },
    ["tt C"] = { true, "𝙲" },
    ["tt D"] = { true, "𝙳" },
    ["tt E"] = { true, "𝙴" },
    ["tt F"] = { true, "𝙵" },
    ["tt G"] = { true, "𝙶" },
    ["tt H"] = { true, "𝙷" },
    ["tt I"] = { true, "𝙸" },
    ["tt J"] = { true, "𝙹" },
    ["tt K"] = { true, "𝙺" },
    ["tt L"] = { true, "𝙻" },
    ["tt M"] = { true, "𝙼" },
    ["tt N"] = { true, "𝙽" },
    ["tt O"] = { true, "𝙾" },
    ["tt P"] = { true, "𝙿" },
    ["tt Q"] = { true, "𝚀" },
    ["tt R"] = { true, "𝚁" },
    ["tt S"] = { true, "𝚂" },
    ["tt T"] = { true, "𝚃" },
    ["tt U"] = { true, "𝚄" },
    ["tt V"] = { true, "𝚅" },
    ["tt W"] = { true, "𝚆" },
    ["tt X"] = { true, "𝚇" },
    ["tt Y"] = { true, "𝚈" },
    ["tt Z"] = { true, "𝚉" },

    -- some more undocumented

    ["dx"] = { false, { "d", "x" } }, -- "{dx}" "\\left(dx\\right)"
    ["dy"] = { false, { "d", "y" } }, -- "{dy}" "\\left(dy\\right)"
    ["dz"] = { false, { "d", "z" } }, -- "{dz}" "\\left(dz\\right)"

    -- fences

    ["(:"] = { true, "(:" },
    ["{:"] = { true, "{:" },
    ["[:"] = { true, "[:" },
    ["("]  = { true, "(" },
    ["["]  = { true, "[" },
    ["{"]  = { true, "{" },
    ["<<"] = { true, "⟨" },     -- why not <:
    ["|_"] = { true, "⌊" },
    ["|~"] = { true, "⌈" },
    ["⟨"]  = { true, "⟨" },
    ["〈"]  = { true, "⟨" },
    ["〈"]  = { true, "⟨" },

    [":)"] = { true, ":)" },
    [":}"] = { true, ":}" },
    [":]"] = { true, ":]" },
    [")"]  = { true, ")" },
    ["]"]  = { true, "]" },
    ["}"]  = { true, "}" },
    [">>"] = { true, "⟩" },   -- why not :>
    ["~|"] = { true, "⌉" },
    ["_|"] = { true, "⌋" },
    ["⟩"]  = { true, "⟩" },
    ["〉"]  = { true, "⟩" },
    ["〉"]  = { true, "⟩" },

    ["lparent"]  = { true, "(" },
    ["lbracket"] = { true, "[" },
    ["lbrace"]   = { true, "{" },
    ["langle"]   = { true, "⟨" },
    ["lfloor"]   = { true, "⌊" },
    ["lceil"]    = { true, "⌈" },

    ["rparent"]  = { true, ")" },
    ["rbracket"] = { true, "]" },
    ["rbrace"]   = { true, "}" },
    ["rangle"]   = { true, "⟩" },
    ["rfloor"]   = { true, "⌋" },
    ["rceil"]    = { true, "⌉" },

    -- a bit special:

--     ["\\frac"]   = { true, "frac" },

    -- now it gets real crazy, only these two:

    ["&gt;"]     = { true, ">" },
    ["&lt;"]     = { true, "<" },

    -- extra:

    -- also, invisible times

    ["dd"]       = { false, "{\\tf d}" },
    ["ee"]       = { false, "{\\tf e}" },
    ["xxx"]      = { true, utfchar(0x2063) }, -- invisible times

}

-- a..z A..Z : allemaal op italic alphabet
-- en dan default naar upright "upr a"

for k, v in next, characters.data do
    local name = v.mathname
    if name and not reserved[name] then
        local char = { true, utfchar(k) }
        reserved[        name] = char
     -- reserved["\\" .. name] = char
    end
 -- local spec = v.mathspec
 -- if spec then
 --     for i=1,#spec do
 --         local name = spec[i].name
 --         if name and not reserved[name] then
 --             reserved[name] = { true, utfchar(k) }
 --         end
 --     end
 -- end
end

reserved.P  = nil
reserved.S  = nil

local isbinary = {
    ["\\frac"]              = true,
    ["\\root"]              = true,
    ["\\asciimathroot"]     = true,
    ["\\asciimathstackrel"] = true,
}

local isunary = { -- can be taken from reserved
    ["\\sqrt"]            = true,
    ["\\asciimathsqrt"]   = true,
    ["\\text"]            = true, --  mathoptext
    ["\\mathoptext"]      = true, --  mathoptext
    ["\\asciimathoptext"] = true, --  mathoptext
    ["\\hat"]             = true, --  widehat
    ["\\widehat"]         = true, --  widehat
    ["\\bar"]             = true, --
    ["\\overbar"]         = true, --
    ["\\underline"]       = true, --
    ["\\vec"]             = true, --  overrightarrow
    ["\\overrightarrow"]  = true, --  overrightarrow
    ["\\dot"]             = true, --
    ["\\ddot"]            = true, --

}

local isfunny = {
    ["\\sin"]           = true,
}

local isinfix = {
    ["^"] = true,
    ["_"] = true,
}

local isstupid = {
    ["\\prod"]      = true,
    ["\\sinh"]      = true,
    ["\\cosh"]      = true,
    ["\\tanh"]      = true,
    ["\\sum"]       = true,
    ["\\int"]       = true,
    ["\\sin"]       = true,
    ["\\cos"]       = true,
    ["\\tan"]       = true,
    ["\\csc"]       = true,
    ["\\sec"]       = true,
    ["\\cot"]       = true,
    ["\\log"]       = true,
    ["\\det"]       = true,
    ["\\lim"]       = true,
    ["\\mod"]       = true,
    ["\\gcd"]       = true,
    ["\\min"]       = true,
    ["\\max"]       = true,
    ["\\ln"]        = true,

    ["\\atan"]      = true,
    ["\\acos"]      = true,
    ["\\asin"]      = true,
                      true,
    ["\\arctan"]    = true,
    ["\\arccos"]    = true,
    ["\\arcsin"]    = true,

    ["f"]           = true,
    ["g"]           = true,
}

local isleft = {
    [s_lparent]  = true,
    [s_lbrace]   = true,
    [s_lbracket] = true,
    [s_langle]   = true,
    [s_lfloor]   = true,
    [s_lceil]    = true,
    [s_left]     = true,
}

local isright = {
    [s_rparent]  = true,
    [s_rbrace]   = true,
    [s_rbracket] = true,
    [s_rangle]   = true,
    [s_rfloor]   = true,
    [s_rceil]    = true,
    [s_right]    = true,
}

local issimplified = {
}

--

-- special mess

local d_one         = R("09")
local d_two         = d_one * d_one
local d_three       = d_two * d_one
local d_four        = d_three * d_one
local d_split       = P(-1) + Carg(2) * (S(".") /"")

local d_spaced      = (Carg(1) * d_three)^1

local digitized_1   = Cs ( (
                        d_three * d_spaced * d_split +
                        d_two   * d_spaced * d_split +
                        d_one   * d_spaced * d_split +
                        P(1)
                      )^1 )

local p_fourbefore  = d_four * d_split
local p_fourafter   = d_four * P(-1)

local p_beforecomma = d_three * d_spaced^0 * d_split
                    + d_two   * d_spaced^0 * d_split
                    + d_one   * d_spaced^0 * d_split
                    + d_one   * d_split

local p_aftercomma  = p_fourafter
                    + d_three * d_spaced
                    + d_two   * d_spaced
                    + d_one   * d_spaced

local digitized_2   = Cs (
                         p_fourbefore  *   (p_aftercomma^0) +
                         p_beforecomma * ((p_aftercomma + d_one^1)^0)
                      )

local p_fourbefore  = d_four * d_split
local p_fourafter   = d_four
local d_spaced      = (Carg(1) * (d_three + d_two + d_one))^1
local p_aftercomma  = p_fourafter * P(-1)
                    + d_three * d_spaced * P(1)^0
                    + d_one^1

-- local digitized_3   = Cs (
--                          p_fourbefore  * p_aftercomma^0 +
--                          p_beforecomma * p_aftercomma^0
--                       )

local digitized_3   = Cs((p_fourbefore + p_beforecomma) * p_aftercomma^0)

local splitmethods = {
    digitized_1,
    digitized_2,
    digitized_3,
}

local splitmethod    = nil
local symbolmethod   = nil
local digitseparator = utfchar(0x2008)
local digitsymbol    = "."

function asciimath.setup(settings)
    splitmethod = splitmethods[tonumber(settings.splitmethod) or 0]
    if splitmethod then
        digitsymbol = settings.symbol
        if not digitsymbol or digitsymbol == "" then
             digitsymbol = "."
        end
        local separator = settings.separator
        if separator == true or not interfaces or interfaces.variables.yes then
            digitseparator = utfchar(0x2008)
        elseif type(separator) == "string" and separator ~= "" then
            digitseparator = separator
        else
            splitmethod = nil
        end
        if digitsymbol ~= "." then
            symbolmethod = lpeg.replacer(".",digitsymbol)
        else
            symbolmethod = nil
        end
    end
end

local collected_digits   = { }
local collected_filename = "asciimath-digits.lua"

function numbermess(s)
    if splitmethod then
        local d = lpegmatch(splitmethod,s,1,digitseparator,digitsymbol)
        if not d and symbolmethod then
            d = lpegmatch(symbolmethod,s)
        end
        if d then
            if trace_digits and s ~= d then
                collected_digits[s] = d
            end
            return d
        end
    end
    return s
end

-- asciimath.setup { splitmethod = 3, symbol = "," }
-- local t = {
--     "0.00002",
--     "1", "12", "123", "1234", "12345", "123456", "1234567", "12345678", "123456789",
--     "1.1",
--     "12.12",
--     "123.123",
--     "1234.123",
--     "1234.1234",
--     "12345.1234",
--     "1234.12345",
--     "12345.12345",
--     "123456.123456",
--     "1234567.1234567",
--     "12345678.12345678",
--     "123456789.123456789",
--     "0.1234",
--     "1234.0",
--     "1234.00",
--     "0.123456789",
--     "100.00005",
--     "0.80018",
--     "10.80018",
--     "100.80018",
--     "1000.80018",
--     "10000.80018",
-- }
-- for i=1,#t do print(formatters["%-20s : [%s]"](t[i],numbermess(t[i]))) end

statistics.register("asciimath",function()
    if trace_digits then
        local n = table.count(collected_digits)
        if n > 0 then
            table.save(collected_filename,collected_digits)
            return string.format("%s digit conversions saved in %s",n,collected_filename)
        else
            os.remove(collected_filename)
        end
    end
end)

local p_number_base = patterns.cpnumber or patterns.cnumber or patterns.number
local p_number      = C(p_number_base)
----- p_number      = p_number_base
local p_spaces      = patterns.whitespace

local p_utf_base    = patterns.utf8character
local p_utf         = C(p_utf_base)
-- local p_entity      = (P("&") * C((1-P(";"))^2) * P(";"))/ entities

-- entities["gt"]    = ">"
-- entities["lt"]    = "<"
-- entities["amp"]   = "&"
-- entities["dquot"] = '"'
-- entities["quot"]  = "'"

local p_onechar     = p_utf_base * P(-1)

----- p_number = Cs((patterns.cpnumber or patterns.cnumber or patterns.number)/function(s) return (gsub(s,",","{,}")) end)

local sign    = P("-")^-1
local digits  = R("09")^1
local integer = sign * digits
local real    = digits * (S(".") * digits)^-1
local float   = real * (P("E") * integer)^-1

-- local number  = C(float + integer)
-- local p_number  = C(float)
local p_number  = float / numbermess

local k_reserved = sortedkeys(reserved)
local k_commands = { }
local k_unicode  = { }

asciimath.keys = {
    reserved = k_reserved
}

local k_reserved_different = { }
local k_reserved_words     = { }

for k, v in sortedhash(reserved) do
    local replacement = v[2]
    if v[1] then
        k_unicode[k] = replacement
    else
        k_unicode[k] = k -- keep them ... later we remap these
        if k ~= replacement then
            k_reserved_different[#k_reserved_different+1] = k
        end
    end
    if find(k,"^[a-zA-Z]+$") then
        k_unicode["\\"..k] = replacement
    else
        k_unicode["\\"..k] = k  -- dirty trick, no real unicode (still needed ?)
    end
    if not find(k,"[^a-zA-Z]") then
        k_reserved_words[#k_reserved_words+1] = k
    end
    k_commands[k] = replacement
end

local p_reserved =
    lpeg.utfchartabletopattern(k_reserved_different) / k_commands

local p_unicode =
--     lpeg.utfchartabletopattern(table.keys(k_unicode)) / k_unicode
    lpeg.utfchartabletopattern(k_unicode) / k_unicode

local p_texescape = patterns.texescape

local function texescaped(s)
    return lpegmatch(p_texescape,s) or s
end

local p_text =
    P("text")
  * p_spaces^0
  * Cc("\\asciimathoptext")
  * ( -- maybe balanced
        Cs( P("{")      * ((1-P("}"))^0/texescaped) *  P("}")     )
      + Cs((P("(")/"{") * ((1-P(")"))^0/texescaped) * (P(")")/"}"))
    )
  + Cc("\\asciimathoptext") * Cs(Cc("{") * (C(patterns.undouble)/texescaped) * Cc("}"))

local m_left = {
    ["(:"] = s_langle,
    ["{:"] = s_left,
    ["[:"] = s_left,
    ["("]  = s_lparent,
    ["["]  = s_lbracket,
    ["{"]  = s_lbrace,
    ["⟨"]  = s_langle,
    ["⌈"] = s_lceil,
    ["⌊"] = s_lfloor,

 -- ["<<"] = s_langle,     -- why not <:
 -- ["|_"] = s_lfloor,
 -- ["|~"] = s_lceil,
 -- ["〈"]  = s_langle,
 -- ["〈"]  = s_langle,

 -- ["lparent"]  = s_lparent,
 -- ["lbracket"] = s_lbracket,
 -- ["lbrace"]   = s_lbrace,
 -- ["langle"]   = s_langle,
 -- ["lfloor"]   = s_lfloor,
 -- ["lceil"]    = s_lceil,
}

local m_right = {
    [":)"] = s_rangle,
    [":}"] = s_right,
    [":]"] = s_right,
    [")"]  = s_rparent,
    ["]"]  = s_rbracket,
    ["}"]  = s_rbrace,
    ["⟩"]  = s_rangle,
    ["⌉"]  = s_rceil,
    ["⌋"]  = s_rfloor,

 -- [">>"] = s_rangle,   -- why not :>
 -- ["~|"] = s_rceil,
 -- ["_|"] = s_rfloor,
 -- ["〉"]  = s_rangle,
 -- ["〉"]  = s_rangle,

 -- ["rparent"]  = s_rparent,
 -- ["rbracket"] = s_rbracket,
 -- ["rbrace"]   = s_rbrace,
 -- ["rangle"]   = s_rangle,
 -- ["rfloor"]   = s_rfloor,
 -- ["rceil"]    = s_rceil,
}

local islimits = {
    ["\\sum"]  = true,
 -- ["∑"]      = true,
    ["\\prod"] = true,
 -- ["∏"]      = true,
    ["\\lim"]  = true,
}

local p_left =
    lpeg.utfchartabletopattern(m_left) / m_left
local p_right =
    lpeg.utfchartabletopattern(m_right) / m_right

-- special cases

-- local p_special =
--     C("/")
--   + P("\\ ")  * Cc("{}") * p_spaces^0 * C(S("^_"))
--   + P("\\ ")  * Cc("\\space")
--   + P("\\\\") * Cc("\\backslash")
--   + P("\\")   * (R("az","AZ")^1/entities)
--   + P("|")    * Cc("\\|")
--
-- faster bug also uglier:

local p_special =
    P("|")  * Cc("\\|") -- s_mbar -- maybe always add left / right as in mml ?
  + P("\\") * (
        (
            P(" ") * (
                  Cc("{}") * p_spaces^0 * C(S("^_"))
                + Cc("\\space")
            )
        )
      + P("\\") * Cc("\\backslash")
   -- + (R("az","AZ")^1/entities)
      + C(R("az","AZ")^1)
    )

-- open | close :: {: | :}

local u_parser = Cs ( (
    patterns.doublequoted +
    P("text") * p_spaces^0 * P("(") * (1-P(")"))^0 * P(")") + -- -- todo: balanced
    P("\\frac") / "frac" + -- bah
    p_unicode +
    p_utf_base
)^0 )

local a_parser = Ct { "tokenizer",
    tokenizer = (
        p_spaces
      + p_number
      + p_text
   -- + Ct(p_open * V("tokenizer") * p_close)        -- {: (a+b,=,1),(a+b,=,7) :}
   -- + Ct(p_open * V("tokenizer") * p_close_right)  -- {  (a+b,=,1),(a+b,=,7) :}
   -- + Ct(p_open_left * V("tokenizer") * p_right)   -- {: (a+b,=,1),(a+b,=,7)  }
      + Ct(p_left * V("tokenizer") * p_right)        -- {  (a+b,=,1),(a+b,=,7)  }
      + p_special
      + p_reserved
  --  + p_utf - p_close - p_right
      + (p_utf - p_right)
    )^1,
}

local collapse  = nil
local serialize = table.serialize
local f_state   = formatters["level %s : %s : intermediate"]

local function show_state(t,level,state)
    report_asciimath(serialize(t,f_state(level,state)))
end

local function show_result(original,unicoded,texcoded)
    report_asciimath("original > %s",original)
    report_asciimath("unicoded > %s",unicoded)
    report_asciimath("texcoded > %s",texcoded)
end

local function collapse_matrices(t)
    local n = #t
    if n > 4 and t[3] == "," then
        local l1 = t[1]
        local r1 = t[n]
        if isleft[l1] and isright[r1] then
            local l2 = t[2]
            local r2 = t[n-1]
            if type(l2) == "table" and type(r2) == "table" then
                -- we have a matrix
                local valid = true
                for i=3,n-2,2 do
                    if t[i] ~= "," then
                        valid = false
                        break
                    end
                end
                if valid then
                    for i=2,n-1,2 do
                        local ti = t[i]
                        local tl = ti[1]
                        local tr = ti[#ti]
                        if isleft[tl] and isright[tr] then
                            -- ok
                        else
                            valid = false
                            break
                        end
                    end
                    if valid then
                        local omit = l1 == s_left and r1 == s_right
                        if omit then
                            t[1] = "\\startmatrix"
                        else
                            t[1] = l1 .. "\\startmatrix"
                        end
                        for i=2,n-1 do
                            if t[i] == "," then
                                t[i] = "\\NR"
                            else
                                local ti = t[i]
                                ti[1] = "\\NC"
                                for i=2,#ti-1 do
                                    if ti[i] == "," then
                                        ti[i] = "\\NC"
                                    end
                                end
                                ti[#ti] = nil
                            end
                        end
                        if omit then
                            t[n] = "\\NR\\stopmatrix"
                        else
                            t[n] = "\\NR\\stopmatrix" .. r1
                        end
                    end
                end
            end
        end
    end
    return t
end

local function collapse_bars(t)
    local n, i, l, m = #t, 1, false, 0
    while i <= n do
        local current = t[i]
        if current == "\\|" then
            if l then
                m = m + 1
                t[l] = s_lbar
                t[i] = s_rbar
                t[m] = { unpack(t,l,i) }
                l = false
            else
                l = i
            end
        elseif not l then
            m = m + 1
            t[m] = current
        end
        i = i + 1
    end
    if l then
        -- problem: we can have a proper nesting
local d = false
for i=1,m do
    if find(t[i],"\\left") then
        d = true
        break
    end
end
if not d then
        local tt = { s_lnothing } -- space fools final checker
        local tm  = 1
        for i=1,m do
            tm = tm + 1
            tt[tm] = t[i]
        end
        tm = tm + 1
        tt[tm] = s_mbar
        for i=l+1,n do
            tm = tm + 1
            tt[tm] = t[i]
        end
        tm = tm + 1
        tt[tm] = s_rnothing -- space fools final checker
        m = tm
        t = tt
end
    elseif m < n then
        for i=n,m+1,-1 do
            t[i] = nil
        end
    end
    return t
end

local function collapse_pairs(t)
    local n, i = #t, 1
    while i < n do
        local current = t[i]
        if current == "/" and i > 1 then
            local tl = t[i-1]
            local tr = t[i+1]
            local tn = t[i+2]
            if type(tl) == "table" then
                if isleft[tl[1]] and isright[tl[#tl]] then
                    tl[1]   = "" -- todo: remove
                    tl[#tl] = nil
                end
            end
            if type(tr) == "table" then
                if tn == "^" then
                    -- brr 1/(1+x)^2
                elseif isleft[tr[1]] and isright[tr[#tr]] then
                    tr[1]   = "" -- todo: remove
                    tr[#tr] = nil
                end
            end
            i = i + 2
        elseif current == "," or current == ";" then
         -- t[i] = current .. "\\thinspace" -- look sbad in (a,b)
            i = i + 1
        else
            i = i + 1
        end
    end
    return t
end

local function collapse_parentheses(t)
    local n, i = #t, 1
    if n > 2 then
        while i < n do
            local current = t[i]
            if type(current) == "table" and isleft[t[i-1]] and isright[t[i+1]] then
                local c = #current
                if c > 2 and isleft[current[1]] and isright[current[c]] then
                    remove(current,c)
                    remove(current,1)
                end
                i = i + 3
            else
                i = i + 1
            end
        end
    end
    return t
end

local function collapse_stupids(t)
    local n, m, i = #t, 0, 1
    while i <= n do
        m = m + 1
        local current = t[i]
        if isstupid[current] then
            local one = t[i+1]
            if type(one) == "table" then
                one = collapse(one,level)
                t[m] = current .. "{" .. one .. "}"
                i = i + 2
            else
                t[m] = current
                i = i + 1
            end
        else
            t[m] = current
            i = i + 1
        end
    end
    if i == n then -- yes?
        m = m + 1
        t[m] = t[n]
    end
    if m < n then
        for i=n,m+1,-1 do
            t[i] = nil
        end
    end
    return t
end

local function collapse_signs(t)
    local n, m, i = #t, 0, 1
    while i <= n do
        m = m + 1
        local current = t[i]
        if isunary[current] then
            local one = t[i+1]
            if not one then
--                 m = m + 1
                t[m] = current .. "{}" -- error
                return t
--                 break
            end
            if type(one) == "table" then
                if isleft[one[1]] and isright[one[#one]] then
                    remove(one,#one)
                    remove(one,1)
                end
                one = collapse(one,level)
            elseif one == "-" and i + 2 <= n then -- or another sign ? or unary ?
                local t2 = t[i+2]
                if type(t2) == "string" then
                    one = one .. t2
                    i = i + 1
                end
            end
            t[m] = current .. "{" .. one .. "}"
            i = i + 2
        elseif i + 2 <= n and isfunny[current] then
            local one = t[i+1]
            if isinfix[one] then
                local two = t[i+2]
                if two == "-" then -- or another sign ? or unary ?
                    local three = t[i+3]
                    if three then
                        if type(three) == "table" then
                            three = collapse(three,level)
                        end
                        t[m] = current .. one .. "{" .. two .. three .. "}"
                        i = i + 4
                    else
                        t[m] = current
                        i = i + 1
                    end
                else
                    t[m] = current
                    i = i + 1
                end
            else
                t[m] = current
                i = i + 1
            end
        else
            t[m] = current
            i = i + 1
        end
    end
    if i == n then -- yes?
        m = m + 1
        t[m] = t[n]
    end
    if m < n then
        for i=n,m+1,-1 do
            t[i] = nil
        end
    end
    return t
end

local function collapse_binaries(t)
    local n, m, i = #t, 0, 1
    while i <= n do
        m = m + 1
        local current = t[i]
        if isbinary[current] then
            local one = t[i+1]
            local two = t[i+2]
            if not one then
                t[m] = current .. "{}{}" -- error
return t
--                 break
            end
            if type(one) == "table" then
                if isleft[one[1]] and isright[one[#one]] then
                    remove(one,#one)
                    remove(one,1)
                end
                one = collapse(one,level)
            end
            if not two then
                t[m] = current .. "{" .. one .. "}{}"
return t
--                 break
            end
            if type(two) == "table" then
                if isleft[two[1]] and isright[two[#two]] then
                    remove(two,#two)
                    remove(two,1)
                end
                two = collapse(two,level)
            end
            t[m] = current .. "{" .. one .. "}{" .. two .. "}"
            i = i + 3
        else
            t[m] = current
            i = i + 1
        end
    end
    if i == n then -- yes?
        m = m + 1
        t[m] = t[n]
    end
    if m < n then
        for i=n,m+1,-1 do
            t[i] = nil
        end
    end
    return t
end

local function collapse_infixes_1(t)
    local n, i = #t, 1
    while i <= n do
        local current = t[i]
        if isinfix[current] then
            local what = t[i+1]
            if what then
                if type(what) == "table" then
                    local f, l = what[1], what[#what]
                    if isleft[f] and isright[l] then
                        remove(what,#what)
                        remove(what,1)
                    end
                    t[i+1] = collapse(what,level) -- collapse ?
                end
                i = i + 2
            else
                break
            end
        else
            i = i + 1
        end
    end
    return t
end

function collapse_limits(t)
    local n, m, i = #t, 0, 1
    while i <= n do
        m = m + 1
        local current = t[i]
        if islimits[current] then
            local one, two, first, second = nil, nil, t[i+1], t[i+3]
            if first and isinfix[first] then
                one = t[i+2]
                if one then
                 -- if type(one) == "table" then
                 --     if isleft[one[1]] and isright[one[#one]] then
                 --         remove(one,#one)
                 --         remove(one,1)
                 --     end
                 --     one = collapse(one,level)
                 -- end
                    if second and isinfix[second] then
                        two = t[i+4]
                     -- if type(two) == "table" then
                     --     if isleft[two[1]] and isright[two[#two]] then
                     --         remove(two,#two)
                     --         remove(two,1)
                     --     end
                     --     two = collapse(two,level)
                     -- end
                    end
                    if two then
                        t[m] = current .. "\\limits" .. first .. "{" .. one .. "}" .. second .. "{" .. two .. "}"
                        i = i + 5
                    else
                        t[m] = current .. "\\limits" .. first .. "{" .. one .. "}"
                        i = i + 3
                    end
                else
                    t[m] = current
                    i = i + 1
                end
            else
                t[m] = current
                i = i + 1
            end
        else
            t[m] = current
            i = i + 1
        end
    end
    if i == n then -- yes?
        m = m + 1
        t[m] = t[n]
    end
    if m < n then
        for i=n,m+1,-1 do
            t[i] = nil
        end
    end
    return t
end

local function collapse_tables(t)
    local n, m, i = #t, 0, 1
    while i <= n do
        m = m + 1
        local current = t[i]
        if type(current) == "table" then
            if current[1] == "\\NC" then
                t[m] = collapse(current,level)
            else
                t[m] = "{" .. collapse(current,level) .. "}"
            end
            i = i + 1
        else
            t[m] = current
            i = i + 1
        end
    end
    if i == n then -- yes?
        m = m + 1
        t[m] = t[n]
    end
    if m < n then
        for i=n,m+1,-1 do
            t[i] = nil
        end
    end
    return t
end

local function collapse_infixes_2(t)
    local n, m, i = #t, 0, 1
    while i < n do
        local current = t[i]
        if isinfix[current] and i > 1 then
            local tl = t[i-1]
            local tr = t[i+1]
            local ti = t[i+2]
            local tn = t[i+3]
            if ti and tn and isinfix[ti] then
                t[m] = tl .. current .. "{" .. tr .. "}" .. ti .. "{" .. tn .. "}"
                i = i + 4
            else
                t[m] = tl .. current .. "{" .. tr .. "}"
                i = i + 2
            end
        else
            m = m + 1
            t[m] = current
            i = i + 1
        end
    end
    if i == n then
        m = m + 1
        t[m] = t[n]
    end
    if m < n then
        for i=n,m+1,-1 do
            t[i] = nil
        end
    end
    return t
end

local function collapse_fractions_1(t)
    local n, m, i = #t, 0, 1
    while i < n do
        local current = t[i]
        if current == "/" and i > 1 then
            local tl = t[i-1]
            local tr = t[i+1]
            t[m] = "\\frac{" .. tl .. "}{" .. tr .. "}"
            i = i + 2
            if i < n then
                m = m + 1
                t[m] = t[i]
                i = i + 1
            end
        else
            m = m + 1
            t[m] = current
            i = i + 1
        end
    end
    if i == n then
        m = m + 1
        t[m] = t[n]
    end
    if m < n then
        for i=n,m+1,-1 do
            t[i] = nil
        end
    end
    return t
end

local function collapse_fractions_2(t)
    local n, m, i = #t, 0, 1
    while i < n do
        local current = t[i]
        if current == "⁄" and i > 1 then -- \slash
            t[m] = "{" .. s_left .. t[i-1] .. s_mslash .. t[i+1] .. s_right .. "}"
            i = i + 2
            if i < n then
                m = m + 1
                t[m] = t[i]
                i = i + 1
            end
        else
            m = m + 1
            t[m] = current
            i = i + 1
        end
    end
    if i == n then
        m = m + 1
        t[m] = t[n]
    end
    if m < n then
        for i=n,m+1,-1 do
            t[i] = nil
        end
    end
    return t
end

local function collapse_result(t)
    local n = #t
    if t[1] == s_left and t[n] == s_right then -- see bar .. space needed there
        return concat(t," ",2,n-1)
    else
        return concat(t," ")
    end
end

collapse = function(t,level)
    -- check
    if not t then
        return ""
    end
    -- tracing
    if trace_detail then
        if level then
            level = level + 1
        else
            level = 1
        end
        show_state(t,level,"parsed")
    end
    -- steps
    t = collapse_matrices   (t) if trace_detail then show_state(t,level,"matrices")      end
    t = collapse_bars       (t) if trace_detail then show_state(t,level,"bars")          end
    t = collapse_stupids    (t) if trace_detail then show_state(t,level,"stupids")         end
    t = collapse_pairs      (t) if trace_detail then show_state(t,level,"pairs")         end
    t = collapse_parentheses(t) if trace_detail then show_state(t,level,"parentheses")   end
    t = collapse_signs      (t) if trace_detail then show_state(t,level,"signs")         end
    t = collapse_binaries   (t) if trace_detail then show_state(t,level,"binaries")      end
    t = collapse_infixes_1  (t) if trace_detail then show_state(t,level,"infixes (1)")   end
    t = collapse_limits     (t) if trace_detail then show_state(t,level,"limits")        end
    t = collapse_tables     (t) if trace_detail then show_state(t,level,"tables")        end
    t = collapse_infixes_2  (t) if trace_detail then show_state(t,level,"infixes (2)")   end
    t = collapse_fractions_1(t) if trace_detail then show_state(t,level,"fractions (1)") end
    t = collapse_fractions_2(t) if trace_detail then show_state(t,level,"fractions (2)") end
    -- done
    return collapse_result(t)
end

-- todo: cache simple ones, say #str < 10, maybe weak

local context         = context
local ctx_mathematics = context and context.mathematics or report_asciimath
local ctx_type        = context and context.type        or function() end
local ctx_inleft      = context and context.inleft      or function() end

local function convert(str,totex)
    local unicoded = lpegmatch(u_parser,str) or str
    local texcoded = collapse(lpegmatch(a_parser,unicoded))
    if trace_mapping then
        show_result(str,unicoded,texcoded)
    end
    if totex then
        ctx_mathematics(texcoded)
    else
        return texcoded
    end
end

local n = 0
local p = (
    (S("{[(") + P("\\left" )) / function() n = n + 1 end
  + (S("}])") + P("\\right")) / function() n = n - 1 end
  + p_utf_base
)^0

local function invalidtex(str)
    n = 0
    lpegmatch(p,str)
    if n == 0 then
        return false
    elseif n < 0 then
        return formatters["too many left fences: %s"](-n)
    elseif n > 0 then
        return formatters["not enough right fences: %s"](n)
    end
end

local collected = { }
local indexed   = { }

-- bonus

local p_reserved_spaced =
    C(lpeg.utfchartabletopattern(k_reserved_words)) / " %1 "

local p_text =
    C(P("text")) / " %1 "
  * p_spaces^0
  * ( -- maybe balanced
        (P("{") * (1-P("}"))^0 * P("}"))
      + (P("(") * (1-P(")"))^0 * P(")"))
    )
  + patterns.doublequoted

local p_expand   = Cs((p_text + p_reserved_spaced + p_utf_base)^0)
local p_compress = patterns.collapser

local function cleanedup(str)
    return lpegmatch(p_compress,lpegmatch(p_expand,str)) or str
end

-- so far

local function register(s,cleanedup,collected,shortname)
    local c = cleanedup(s)
    local f = collected[c]
    if f then
        f.count = f.count + 1
        f.files[shortname] = (f.files[shortname] or 0) + 1
        if s ~= c then
            f.cleanedup = f.cleanedup + 1
        end
        f.dirty[s] = (f.dirty[s] or 0) + 1
    else
        local texcoded = convert(s)
        local message  = invalidtex(texcoded)
        if message then
            report_asciimath("%s: %s : %s",message,s,texcoded)
        end
        collected[c] = {
            count     = 1,
            files     = { [shortname] = 1 },
            texcoded  = texcoded,
            message   = message,
            cleanedup = s ~= c and 1 or 0,
            dirty     = { [s] = 1 }
        }
    end
end

local function wrapup(collected,indexed)
    local n = 0
    for k, v in sortedhash(collected) do
        n = n + 1
        v.n= n
        indexed[n] = k
    end
end

function collect(fpattern,element,collected,indexed)
    local element   = element or "am"
    local mpattern  = formatters["<%s>(.-)</%s>"](element,element)
    local filenames = resolvers.findtexfile(fpattern)
    if filenames and filenames ~= "" then
        filenames = { filenames }
    else
        filenames = dir.glob(fpattern)
    end
    local cfpattern = gsub(fpattern,"^%./",lfs.currentdir())
    local cfpattern = gsub(cfpattern,"\\","/")
    local wildcard  = string.split(cfpattern,"*")[1]
    if not collected then
        collected = { }
        indexed   = { }
    end
    for i=1,#filenames do
        filename = gsub(filenames[i],"\\","/")
        local splitname = (wildcard and wildcard ~= "" and string.split(filename,wildcard)[2]) or filename
        local shortname = gsub(splitname or file.basename(filename),"^%./","")
        if shortname == "" then
            shortname = filename
        end
        local fullname = resolvers.findtexfile(filename) or filename
        if fullname ~= "" then
            for s in gmatch(io.loaddata(fullname),mpattern) do
                register(s,cleanedup,collected,shortname)
            end
        end
    end
    wrapup(collected,indexed)
    return collected, indexed
end

function filter(root,pattern,collected,indexed)
    if not pattern or pattern == "" then
        pattern = "am"
    end
    if not collected then
        collected = { }
        indexed   = { }
    end
    for c in xmlcollected(root,pattern) do
        register(xmltext(c),cleanedup,collected,xmlinclusion(c) or "" )
    end
    wrapup(collected,indexed)
    return collected, indexed
end

asciimath.convert    = convert
asciimath.reserved   = reserved
asciimath.collect    = collect
asciimath.filter     = filter
asciimath.invalidtex = invalidtex
asciimath.cleanedup  = cleanedup

-- sin(x) = 1 : 3.3 uncached 1.2 cached , so no real gain (better optimize the converter then)

local uncrapped = {
    ["%"] = "\\mathpercent",
    ["&"] = "\\mathampersand",
    ["#"] = "\\mathhash",
    ["$"] = "\\mathdollar",
    ["^"] = "\\Hat{\\enspace}", -- terrible hack ... tex really does it sbest to turn any ^ into a superscript
    ["_"] = "\\underline{\\enspace}",
}

local function convert(str,nowrap)
    if #str > 0 then
        local unicoded = lpegmatch(u_parser,str) or str
        if lpegmatch(p_onechar,unicoded) then
            ctx_mathematics(uncrapped[unicoded] or unicoded)
        else
            local texcoded = collapse(lpegmatch(a_parser,unicoded))
            if trace_mapping then
                show_result(str,unicoded,texcoded)
            end
            if #texcoded == 0 then
                report_asciimath("error in asciimath: %s",str)
            else
                local message = invalidtex(texcoded)
                if message then
                    report_asciimath("%s: %s : %s",message,str,texcoded)
                    ctx_type(formatters["<%s>"](message))
                elseif nowrap then
                     context(texcoded)
                else
                    ctx_mathematics(texcoded)
                end
            end
        end
    end
end

local context = context

if not context then

--     trace_mapping = true
--     trace_detail  = true

--     report_asciimath(cleanedup([[ac+sinx+xsqrtx+sinsqrtx+sinsqrt(x)]]))
--     report_asciimath(cleanedup([[a "αsinsqrtx" b]]))
--     convert([[a "αsinsqrtx" b]])
--     report_asciimath(cleanedup([[a "α" b]]))
--     report_asciimath(cleanedup([[//4]]))

-- convert("leq\\leq")
-- convert([[\^{1/5}log]])
-- convert("sqrt")
-- convert("^")

-- convert[[\frac{a}{b}]]
-- convert[[frac{a}{b}]]

-- convert("frac{a}{b}")
-- convert("\\sin{a}{b}")
-- convert("sin{a}{b}")
-- convert("1: rightarrow")
-- convert("2: \\rightarrow")

-- convert("((1,2,3),(4,5,6),(7,8,9))")

-- convert("1/(t+x)^2")

--     convert("AA a > 0 ^^ b > 0 | {:log_g:} a + {:log_g:} b")
--     convert("AA a &gt; 0 ^^ b > 0 | {:log_g:} a + {:log_g:} b")

--     convert("10000,00001")
--     convert("4/18*100text(%)~~22,2")
--     convert("4/18*100text(%)≈22,2")
--     convert("62541/(197,6)≈316,05")

--     convert([[sum x]])
--     convert([[sum^(1)_(2) x]])
--     convert([[lim_(1)^(2) x]])
--     convert([[lim_(1) x]])
--     convert([[lim^(2) x]])

--     convert([[{: rangle]])
--     convert([[\langle\larr]])
--     convert([[langlelarr]])
--     convert([[D_f=[0 ,→〉]])
--     convert([[ac+sinx+xsqrtx]])
--     convert([[ac+\alpha x+xsqrtx-cc b*pi**psi-3alephx / bb X]])
--     convert([[ac+\ ^ x+xsqrtx]])
--     convert([[d/dx(x^2+1)]])
--     convert([[a "αsinsqrtx" b]])
--     convert([[a "α" b]])
--     convert([[//4]])
--     convert([[ {(a+b,=,1),(a+b,=,7)) ]])

--     convert([[ 2/a // 5/b = (2 b) / ( a b) // ( 5 a ) / ( a b ) = (2 b ) / ( 5 a ) ]])
--     convert([[ (2+x)/a // 5/b  ]])

--     convert([[ ( 2/a ) // ( 5/b ) = ( (2 b) / ( a b) ) // ( ( 5 a ) / ( a b ) ) = (2 b ) / ( 5 a ) ]])

--     convert([[ (x/y)^3 = x^3/y^3 ]])

--     convert([[ {: (1,2) :} ]])
--     convert([[ {: (a+b,=,1),(a+b,=,7) :} ]])
--     convert([[ {  (a+b,=,1),(a+b,=,7) :} ]])
--     convert([[ {: (a+b,=,1),(a+b,=,7)  } ]])
--     convert([[ {  (a+b,=,1),(a+b,=,7)  } ]])

--     convert([[(1,5 ±sqrt(1,25 ),0 )]])
--     convert([[1//2]])
--     convert([[(p)/sqrt(p)]])
--     convert([[u_tot]])
--     convert([[u_tot=4,4 L+0,054 T]])

--     convert([[ [←;0,2] ]])
--     convert([[ [←;0,2⟩ ]])
--     convert([[ ⟨←;0,2 ) ]])
--     convert([[ ⟨←;0,2 ] ]])
--     convert([[ ⟨←;0,2⟩ ]])

--     convert([[ x^2(x-1/16)=0 ]])
--     convert([[ y = ax + 3 - 3a ]])
--     convert([[ y= ((1/4)) ^x ]])
--     convert([[ x=\ ^ (1/4) log(0 ,002 )= log(0,002) / (log(1/4) ]])
--     convert([[ x=\ ^glog(y) ]])
--     convert([[ x^ (-1 1/2) =1/x^ (1 1/2)=1/ (x^1*x^ (1/2)) =1/ (xsqrt(x)) ]])
--     convert([[ x^2(10 -x)&gt;2 x^2 ]])
--     convert([[ x^4&gt;x ]])

    return

end

interfaces.implement {
    name      = "asciimath",
    actions   = convert,
    arguments = "string"
}

interfaces.implement {
    name      = "justasciimath",
    actions   = convert,
    arguments = { "string", true },
}

local ctx_typebuffer  = context.typebuffer
local ctx_mathematics = context.mathematics
local ctx_color       = context.color

local sequenced       = table.sequenced
local assign_buffer   = buffers.assign

local show     = { }
asciimath.show = show

local collected, indexed, ignored = { }, { }, { }

local color = { "darkred" }

function show.ignore(n)
    if type(n) == "string" then
        local c = collected[n]
        n = c and c.n
    end
    if n then
        ignored[n] = true
    end
end

function show.count(n,showcleanedup)
    local v = collected[indexed[n]]
    local count = v.count
    local cleanedup = v.cleanedup
    if not showcleanedup or cleanedup == 0 then
        context(count)
    elseif count == cleanedup then
        ctx_color(color,count)
    else
        context("%s+",count-cleanedup)
        ctx_color(color,cleanedup)
    end
end

local h  = { }
local am = { "am" }

function show.nofdirty(n)
    local k = indexed[n]
    local v = collected[k]
    local n = v.cleanedup
    h = { }
    if n > 0 then
        for d, n in sortedhash(v.dirty) do
            if d ~= k then
                h[#h+1] = { d, n }
            end
        end
    end
    context(#h)
end

function show.dirty(m,wrapped)
    local d = h[m]
    if d then
        ctx_inleft(d[2])
        if wrapped then
            assign_buffer("am",'"' .. d[1] .. '"')
        else
            assign_buffer("am",d[1])
        end
        ctx_typebuffer(am)
    end
end

function show.files(n)
    context(sequenced(collected[indexed[n]].files," "))
end

function show.input(n,wrapped)
    if wrapped then
        assign_buffer("am",'"' .. indexed[n] .. '"')
    else
        assign_buffer("am",indexed[n])
    end
    ctx_typebuffer(am)
end

function show.result(n)
    local v = collected[indexed[n]]
    if ignored[n] then
        context("ignored")
    elseif v.message then
        ctx_color(color, v.message)
    else
        ctx_mathematics(v.texcoded)
    end
end

function show.load(str,element)
    collected, indexed, ignored = { }, { }, { }
    local t = utilities.parsers.settings_to_array(str)
    for i=1,#t do
        asciimath.collect(t[i],element or "am",collected,indexed)
    end
end

function show.filter(id,element)
    collected, indexed, ignored = { }, { }, { }
    asciimath.filter(lxml.getid(id),element or "am",collected,indexed)
end

function show.max()
    context(#indexed)
end

function show.statistics()
    local usedfiles    = { }
    local noffiles     = 0
    local nofokay      = 0
    local nofbad       = 0
    local nofcleanedup = 0
    for k, v in next, collected do
        if ignored[v.n] then
            nofbad = nofbad + v.count
        elseif v.message then
            nofbad = nofbad + v.count
        else
            nofokay = nofokay + v.count
        end
        nofcleanedup = nofcleanedup + v.cleanedup
        for k, v in next, v.files do
            local u = usedfiles[k]
            if u then
                usedfiles[k] = u + 1
            else
                noffiles = noffiles + 1
                usedfiles[k] = 1
            end
        end
    end
    local NC = context.NC
    local NR = context.NR
    local EQ = context.EQ
    context.starttabulate { "|B||" }
        NC() context("files")     EQ() context(noffiles)       NC() NR()
        NC() context("formulas")  EQ() context(nofokay+nofbad) NC() NR()
        NC() context("uniques")   EQ() context(#indexed)       NC() NR()
        NC() context("cleanedup") EQ() context(nofcleanedup)   NC() NR()
        NC() context("errors")    EQ() context(nofbad)         NC() NR()
    context.stoptabulate()
end

function show.save(name)
    table.save(name ~= "" and name or "dummy.lua",collected)
end
