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

Some backgrounds are discussed in x-asciimath.mkiv. 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.

--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 asciimath = { } local moduledata = moduledata or { } moduledata.asciimath = asciimath if not characters then require("char-def") require("char-ini") require("char-ent") end local entities = characters.entities or { } local report_asciimath = logs.reporter("mathematics","asciimath") local type, rawget = type, rawget local lpegmatch, patterns = lpeg.match, lpeg.patterns local S, P, R, C, V, Cc, Ct, Cs = lpeg.S, lpeg.P, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc, lpeg.Ct, lpeg.Cs local concat, remove, sortedhash, sortedkeys, keys = table.concat, table.remove, table.sortedhash, table.sortedkeys, table.keys local rep, gmatch, gsub, find = string.rep, string.gmatch, string.gsub, string.find local formatters = string.formatters local reserved = { -- ["aleph"] = "\\aleph", -- ["vdots"] = "\\vdots", -- ["ddots"] = "\\ddots", -- ["oint"] = "\\oint", -- ["grad"] = "\\nabla", ["prod"] = "\\prod", -- ["prop"] = "\\propto", -- ["sube"] = "\\subseteq", -- ["supe"] = "\\supseteq", ["sinh"] = "\\sinh", ["cosh"] = "\\cosh", ["tanh"] = "\\tanh", ["sum"] = "\\sum", -- ["vvv"] = "\\vee", -- ["nnn"] = "\\cap", -- ["uuu"] = "\\cup", -- ["sub"] = "\\subset", -- ["sup"] = "\\supset", -- ["iff"] = "\\Leftrightarrow", ["int"] = "\\int", -- ["del"] = "\\partial", ["sin"] = "\\sin", ["cos"] = "\\cos", ["tan"] = "\\tan", ["csc"] = "\\csc", ["sec"] = "\\sec", ["cot"] = "\\cot", ["log"] = "\\log", ["det"] = "\\det", ["lim"] = "\\lim", ["mod"] = "\\mod", ["gcd"] = "\\gcd", -- ["lcm"] = "\\lcm", -- undefined in context ["min"] = "\\min", ["max"] = "\\max", -- ["xx"] = "\\times", ["in"] = "\\in", -- ["ox"] = "\\otimes", -- ["vv"] = "\\vee", -- ["nn"] = "\\cap", -- ["uu"] = "\\cup", -- ["oo"] = "\\infty", ["ln"] = "\\ln", -- ["not"] = "\\not", ["and"] = "\\text{and}", ["or"] = "\\text{or}", ["if"] = "\\text{if}", -- ["AA"] = "\\forall", -- ["EE"] = "\\exists", -- ["TT"] = "\\top", ["sqrt"] = "\\rootradical{}", ["root"] = "\\rootradical", ["frac"] = "\\frac", ["stackrel"] = "\\stackrel", -- ["text"] = "\\mathoptext", -- ["bb"] = "\\bb", ["hat"] = "\\widehat", ["overbar"] = "\\overbar", ["underline"] = "\\underline", ["vec"] = "\\overrightarrow", ["dot"] = "\\dot", ["ddot"] = "\\ddot", -- binary operators -- ["+"] = "+", -- ["-"] = "-", ["*"] = "⋅", ["**"] = "⋆", ["//"] = "\\slash", ["\\"] = "\\", ["xx"] = "×", ["times"] = "×", ["-:"] = "÷", ["@"] = "∘", ["o+"] = "⊕", ["ox"] = "⊗", ["o."] = "⊙", ["^^"] = "∧", ["vv"] = "∨", ["nn"] = "∩", ["uu"] = "∪", -- big operators -- ["sum"] = "∑", -- ["prod"] = "∏", ["^^^"] = "⋀", ["vvv"] = "⋁", ["nnn"] = "⋂", ["uuu"] = "⋃", ["int"] = "∫", ["oint"] = "∮", -- brackets -- ["("] = "(, -- [")"] = "), -- ["["] = "[, -- ["]"] = "], -- ["{"] = "{, -- ["}"] = "}, -- ["(:"] = "〈", -- [":)"] = "〉", -- binary relations ["="] = "=", ["!="] = "≠", ["<"] = "<", [">"] = ">", ["<="] = "≤", [">="] = "≥", ["-<"] = "≺", [">-"] = "≻", ["in"] = "∈", ["!in"] = "∉", ["sub"] = "⊂", ["sup"] = "⊃", ["sube"] = "⊆", ["supe"] = "⊇", ["-="] = "≡", ["~="] = "≅", ["~~"] = "≈", ["prop"] = "∝", -- arrows ["rarr"] = "→", ["->"] = "→", ["larr"] = "←", ["harr"] = "↔", ["uarr"] = "↑", ["darr"] = "↓", ["rArr"] = "⇒", ["lArr"] = "⇐", ["hArr"] = "⇔", ["|->"] = "↦", -- logical -- ["and"] = "and", -- ["or"] = "or", -- ["if"] = "if", ["not"] = "¬", ["=>"] = "⇒", ["iff"] = "⇔", ["AA"] = "∀", ["EE"] = "∃", ["_|_"] = "⊥", ["TT"] = "⊤", ["|--"] = "⊢", ["|=="] = "⊨", -- miscellaneous ["del"] = "∂", ["grad"] = "∇", ["+-"] = "±", ["O/"] = "∅", ["oo"] = "∞", ["aleph"] = "ℵ", ["angle"] = "∠", ["/_"] = "∠", [":."] = "∴", ["..."] = "...", -- ldots ["ldots"] = "...", -- ldots ["cdots"] = "⋯", ["vdots"] = "⋮", ["ddots"] = "⋱", ["diamond"] = "⋄", ["square"] = "□", ["|__"] = "⌊", ["__|"] = "⌋", ["|~"] = "⌈", ["~|"] = "⌉", -- more ["_="] = "≡", -- blackboard ["CC"] = "ℂ", ["NN"] = "ℕ", ["QQ"] = "ℚ", ["RR"] = "ℝ", ["ZZ"] = "ℤ", -- greek lowercase alpha = "α", beta = "β", gamma = "γ", delta = "δ", epsilon = "ε", varepsilon = "ɛ", zeta = "ζ", eta = "η", theta = "θ", vartheta = "ϑ", iota = "ι", kappa = "κ", lambda = "λ", mu = "μ", nu = "ν", xi = "ξ", pi = "π", rho = "ρ", sigma = "σ", tau = "τ", upsilon = "υ", phi = "φ", varphi = "ϕ", chi = "χ", psi = "ψ", omega = "ω", -- greek uppercase Gamma = "Γ", Delta = "Δ", Theta = "Θ", Lambda = "Λ", Xi = "Ξ", Pi = "Π", Sigma = "Σ", Phi = "Φ", Psi = "Ψ", Omega = "Ω", -- alternatively we could just inject a style switch + following character -- blackboard ["bbb a"] = "𝕒", ["bbb b"] = "𝕓", ["bbb c"] = "𝕔", ["bbb d"] = "𝕕", ["bbb e"] = "𝕖", ["bbb f"] = "𝕗", ["bbb g"] = "𝕘", ["bbb h"] = "𝕙", ["bbb i"] = "𝕚", ["bbb j"] = "𝕛", ["bbb k"] = "𝕜", ["bbb l"] = "𝕝", ["bbb m"] = "𝕞", ["bbb n"] = "𝕟", ["bbb o"] = "𝕠", ["bbb p"] = "𝕡", ["bbb q"] = "𝕢", ["bbb r"] = "𝕣", ["bbb s"] = "𝕤", ["bbb t"] = "𝕥", ["bbb u"] = "𝕦", ["bbb v"] = "𝕧", ["bbb w"] = "𝕨", ["bbb x"] = "𝕩", ["bbb y"] = "𝕪", ["bbb z"] = "𝕫", ["bbb A"] = "𝔸", ["bbb B"] = "𝔹", ["bbb C"] = "ℂ", ["bbb D"] = "𝔻", ["bbb E"] = "𝔼", ["bbb F"] = "𝔽", ["bbb G"] = "𝔾", ["bbb H"] = "ℍ", ["bbb I"] = "𝕀", ["bbb J"] = "𝕁", ["bbb K"] = "𝕂", ["bbb L"] = "𝕃", ["bbb M"] = "𝕄", ["bbb N"] = "ℕ", ["bbb O"] = "𝕆", ["bbb P"] = "ℙ", ["bbb Q"] = "ℚ", ["bbb R"] = "ℝ", ["bbb S"] = "𝕊", ["bbb T"] = "𝕋", ["bbb U"] = "𝕌", ["bbb V"] = "𝕍", ["bbb W"] = "𝕎", ["bbb X"] = "𝕏", ["bbb Y"] = "𝕐", ["bbb Z"] = "ℤ", -- fraktur ["fr a"] = "𝔞", ["fr b"] = "𝔟", ["fr c"] = "𝔠", ["fr d"] = "𝔡", ["fr e"] = "𝔢", ["fr f"] = "𝔣", ["fr g"] = "𝔤", ["fr h"] = "𝔥", ["fr i"] = "𝔦", ["fr j"] = "𝔧", ["fr k"] = "𝔨", ["fr l"] = "𝔩", ["fr m"] = "𝔪", ["fr n"] = "𝔫", ["fr o"] = "𝔬", ["fr p"] = "𝔭", ["fr q"] = "𝔮", ["fr r"] = "𝔯", ["fr s"] = "𝔰", ["fr t"] = "𝔱", ["fr u"] = "𝔲", ["fr v"] = "𝔳", ["fr w"] = "𝔴", ["fr x"] = "𝔵", ["fr y"] = "𝔶", ["fr z"] = "𝔷", ["fr A"] = "𝔄", ["fr B"] = "𝔅", ["fr C"] = "ℭ", ["fr D"] = "𝔇", ["fr E"] = "𝔈", ["fr F"] = "𝔉", ["fr G"] = "𝔊", ["fr H"] = "ℌ", ["fr I"] = "ℑ", ["fr J"] = "𝔍", ["fr K"] = "𝔎", ["fr L"] = "𝔏", ["fr M"] = "𝔐", ["fr N"] = "𝔑", ["fr O"] = "𝔒", ["fr P"] = "𝔓", ["fr Q"] = "𝔔", ["fr R"] = "ℜ", ["fr S"] = "𝔖", ["fr T"] = "𝔗", ["fr U"] = "𝔘", ["fr V"] = "𝔙", ["fr W"] = "𝔚", ["fr X"] = "𝔛", ["fr Y"] = "𝔜", ["fr Z"] = "ℨ", -- script ["cc a"] = "𝒶", ["cc b"] = "𝒷", ["cc c"] = "𝒸", ["cc d"] = "𝒹", ["cc e"] = "ℯ", ["cc f"] = "𝒻", ["cc g"] = "ℊ", ["cc h"] = "𝒽", ["cc i"] = "𝒾", ["cc j"] = "𝒿", ["cc k"] = "𝓀", ["cc l"] = "𝓁", ["cc m"] = "𝓂", ["cc n"] = "𝓃", ["cc o"] = "ℴ", ["cc p"] = "𝓅", ["cc q"] = "𝓆", ["cc r"] = "𝓇", ["cc s"] = "𝓈", ["cc t"] = "𝓉", ["cc u"] = "𝓊", ["cc v"] = "𝓋", ["cc w"] = "𝓌", ["cc x"] = "𝓍", ["cc y"] = "𝓎", ["cc z"] = "𝓏", ["cc A"] = "𝒜", ["cc B"] = "ℬ", ["cc C"] = "𝒞", ["cc D"] = "𝒟", ["cc E"] = "ℰ", ["cc F"] = "ℱ", ["cc G"] = "𝒢", ["cc H"] = "ℋ", ["cc I"] = "ℐ", ["cc J"] = "𝒥", ["cc K"] = "𝒦", ["cc L"] = "ℒ", ["cc M"] = "ℳ", ["cc N"] = "𝒩", ["cc O"] = "𝒪", ["cc P"] = "𝒫", ["cc Q"] = "𝒬", ["cc R"] = "ℛ", ["cc S"] = "𝒮", ["cc T"] = "𝒯", ["cc U"] = "𝒰", ["cc V"] = "𝒱", ["cc W"] = "𝒲", ["cc X"] = "𝒳", ["cc Y"] = "𝒴", ["cc Z"] = "𝒵", -- bold ["bb a"] = "𝒂", ["bb b"] = "𝒃", ["bb c"] = "𝒄", ["bb d"] = "𝒅", ["bb e"] = "𝒆", ["bb f"] = "𝒇", ["bb g"] = "𝒈", ["bb h"] = "𝒉", ["bb i"] = "𝒊", ["bb j"] = "𝒋", ["bb k"] = "𝒌", ["bb l"] = "𝒍", ["bb m"] = "𝒎", ["bb n"] = "𝒏", ["bb o"] = "𝒐", ["bb p"] = "𝒑", ["bb q"] = "𝒒", ["bb r"] = "𝒓", ["bb s"] = "𝒔", ["bb t"] = "𝒕", ["bb u"] = "𝒖", ["bb v"] = "𝒗", ["bb w"] = "𝒘", ["bb x"] = "𝒙", ["bb y"] = "𝒚", ["bb z"] = "𝒛", ["bb A"] = "𝑨", ["bb B"] = "𝑩", ["bb C"] = "𝑪", ["bb D"] = "𝑫", ["bb E"] = "𝑬", ["bb F"] = "𝑭", ["bb G"] = "𝑮", ["bb H"] = "𝑯", ["bb I"] = "𝑰", ["bb J"] = "𝑱", ["bb K"] = "𝑲", ["bb L"] = "𝑳", ["bb M"] = "𝑴", ["bb N"] = "𝑵", ["bb O"] = "𝑶", ["bb P"] = "𝑷", ["bb Q"] = "𝑸", ["bb R"] = "𝑹", ["bb S"] = "𝑺", ["bb T"] = "𝑻", ["bb U"] = "𝑼", ["bb V"] = "𝑽", ["bb W"] = "𝑾", ["bb X"] = "𝑿", ["bb Y"] = "𝒀", ["bb Z"] = "𝒁", -- sans ["sf a"] = "𝖺", ["sf b"] = "𝖻", ["sf c"] = "𝖼", ["sf d"] = "𝖽", ["sf e"] = "𝖾", ["sf f"] = "𝖿", ["sf g"] = "𝗀", ["sf h"] = "𝗁", ["sf i"] = "𝗂", ["sf j"] = "𝗃", ["sf k"] = "𝗄", ["sf l"] = "𝗅", ["sf m"] = "𝗆", ["sf n"] = "𝗇", ["sf o"] = "𝗈", ["sf p"] = "𝗉", ["sf q"] = "𝗊", ["sf r"] = "𝗋", ["sf s"] = "𝗌", ["sf t"] = "𝗍", ["sf u"] = "𝗎", ["sf v"] = "𝗏", ["sf w"] = "𝗐", ["sf x"] = "𝗑", ["sf y"] = "𝗒", ["sf z"] = "𝗓", ["sf A"] = "𝖠", ["sf B"] = "𝖡", ["sf C"] = "𝖢", ["sf D"] = "𝖣", ["sf E"] = "𝖤", ["sf F"] = "𝖥", ["sf G"] = "𝖦", ["sf H"] = "𝖧", ["sf I"] = "𝖨", ["sf J"] = "𝖩", ["sf K"] = "𝖪", ["sf L"] = "𝖫", ["sf M"] = "𝖬", ["sf N"] = "𝖭", ["sf O"] = "𝖮", ["sf P"] = "𝖯", ["sf Q"] = "𝖰", ["sf R"] = "𝖱", ["sf S"] = "𝖲", ["sf T"] = "𝖳", ["sf U"] = "𝖴", ["sf V"] = "𝖵", ["sf W"] = "𝖶", ["sf X"] = "𝖷", ["sf Y"] = "𝖸", ["sf Z"] = "𝖹", -- monospace ["tt a"] = "𝚊", ["tt b"] = "𝚋", ["tt c"] = "𝚌", ["tt d"] = "𝚍", ["tt e"] = "𝚎", ["tt f"] = "𝚏", ["tt g"] = "𝚐", ["tt h"] = "𝚑", ["tt i"] = "𝚒", ["tt j"] = "𝚓", ["tt k"] = "𝚔", ["tt l"] = "𝚕", ["tt m"] = "𝚖", ["tt n"] = "𝚗", ["tt o"] = "𝚘", ["tt p"] = "𝚙", ["tt q"] = "𝚚", ["tt r"] = "𝚛", ["tt s"] = "𝚜", ["tt t"] = "𝚝", ["tt u"] = "𝚞", ["tt v"] = "𝚟", ["tt w"] = "𝚠", ["tt x"] = "𝚡", ["tt y"] = "𝚢", ["tt z"] = "𝚣", ["tt A"] = "𝙰", ["tt B"] = "𝙱", ["tt C"] = "𝙲", ["tt D"] = "𝙳", ["tt E"] = "𝙴", ["tt F"] = "𝙵", ["tt G"] = "𝙶", ["tt H"] = "𝙷", ["tt I"] = "𝙸", ["tt J"] = "𝙹", ["tt K"] = "𝙺", ["tt L"] = "𝙻", ["tt M"] = "𝙼", ["tt N"] = "𝙽", ["tt O"] = "𝙾", ["tt P"] = "𝙿", ["tt Q"] = "𝚀", ["tt R"] = "𝚁", ["tt S"] = "𝚂", ["tt T"] = "𝚃", ["tt U"] = "𝚄", ["tt V"] = "𝚅", ["tt W"] = "𝚆", ["tt X"] = "𝚇", ["tt Y"] = "𝚈", ["tt Z"] = "𝚉", -- some more undocumented ["dx"] = { "d", "x" }, -- "{dx}" "\\left(dx\\right)" ["dy"] = { "d", "y" }, -- "{dy}" "\\left(dy\\right)" ["dz"] = { "d", "z" }, -- "{dz}" "\\left(dz\\right)" ["atan"] = "\\atan", ["acos"] = "\\acos", ["asin"] = "\\asin", ["arctan"] = "\\arctan", ["arccos"] = "\\arccos", ["arcsin"] = "\\arcsin", ["prime"] = "′", ["'"] = "′", ["''"] = "″", ["'''"] = "‴", } local isbinary = { ["\\frac"] = true, ["\\root"] = true, ["\\rootradical"] = true, ["\\stackrel"] = true, } local isunary = { ["\\sqrt"] = true, ["\\rootradical{}"] = true, -- ["\\bb"] = true, ["\\text"] = true, -- mathoptext ["\\mathoptext"] = true, -- mathoptext ["\\hat"] = true, -- widehat ["\\widehat"] = true, -- widehat ["\\overbar"] = true, -- ["\\underline"] = true, -- ["\\vec"] = true, -- overrightarrow ["\\overrightarrow"] = true, -- overrightarrow ["\\dot"] = true, -- ["\\ddot"] = true, -- -- ["^"] = true, -- ["_"] = true, } local isinfix = { ["^"] = true, ["_"] = true, } local isleft = { ["\\left\\lparent"] = true, ["\\left\\lbrace"] = true, ["\\left\\lbracket"] = true, ["\\left."] = true, } local isright = { ["\\right\\rparent"] = true, ["\\right\\rbrace"] = true, ["\\right\\rbracket"] = true, ["\\right."] = true, } local issimplified = { } local p_number_base = patterns.cpnumber or patterns.cnumber or patterns.number local p_number = C(p_number_base) local p_spaces = patterns.whitespace ----- 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 ----- real = sign * digits * (S(".,") * digits)^-1 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_utf_base = patterns.utf8character local p_utf = C(p_utf_base) local p_entity_base = P("&") * ((1-P(";"))^2) * P(";") local p_entity = P("&") * (((1-P(";"))^2) / entities) * P(";") -- This is (given the large match): -- -- local s = sortedkeys(reserved) -- local p = P(false) -- for i=#s,1,-1 do -- local k = s[i] -- p = p + P(k) -- end -- local p_reserved = p / reserved -- -- twice as slow as: local k_reserved = sortedkeys(reserved) asciimath.keys = { reserved = k_reserved } local k_reserved_different = { } local k_reserved_words = { } for k, v in sortedhash(reserved) do if k ~= v then k_reserved_different[#k_reserved_different+1] = k end if not find(k,"[^a-zA-Z]") then k_reserved_words[#k_reserved_words+1] = k end end local p_reserved = lpeg.utfchartabletopattern(k_reserved_different) / reserved -- local p_text = -- P("text") -- * p_spaces^0 -- * Cc("\\mathoptext") -- * ( -- maybe balanced -- Cs((P("{") ) * (1-P("}"))^0 * P("}") ) -- + Cs((P("(")/"{") * (1-P(")"))^0 * (P(")")/"}")) -- ) -- + Cc("\\mathoptext") * Cs(Cc("{") * patterns.undouble * Cc("}")) local p_text = P("text") * p_spaces^0 * Cc("\\mathoptext") * ( -- maybe balanced Cs( P("{") * (1-P("}"))^0 * P("}") ) + Cs((P("(")/"{") * (1-P(")"))^0 * (P(")")/"}")) ) + Cc("\\mathoptext") * Cs(Cc("{") * patterns.undouble * Cc("}")) -- either map to \left or map to \left\name -- local p_open = S("{[") * P(":") -- local p_close = P(":") * S("]}") -- local p_open_left = (S("{[") * P(":")) / "\\left." -- local p_close_right = (P(":") * S("]}")) / "\\right." -- local p_left = -- P("(:") / "\\left\\langle" -- + P("{:") / "\\left." -- + P("[:") / "\\left." -- + P("(") / "\\left\\lparent" -- + P("[") / "\\left\\lbracket" -- + P("{") / "\\left\\lbrace" -- + P("<<") / "\\left\\langle" -- why not <: -- + P("|_") / "\\left\\lfloor" -- + P("|~") / "\\left\\lceil" -- + P("⟨") / "\\left\\langle" -- + P("〈") / "\\left\\langle" -- + P("〈") / "\\left\\langle" -- local p_right = -- P(")") / "\\right\\rparent" -- + P(":)") / "\\right\\rangle" -- + P(":}") / "\\right." -- + P(":]") / "\\right." -- + P("]") / "\\right\\rbracket" -- + P("}") / "\\right\\rbrace" -- + P(">>") / "\\right\\rangle" -- why not :> -- + P("~|") / "\\right\\rceil" -- + P("_|") / "\\right\\rfloor" -- + P("⟩") / "\\right\\rangle" -- + P("〉") / "\\right\\rangle" -- + P("〉") / "\\right\\rangle" local m_left = { ["(:"] = "\\left\\langle", ["{:"] = "\\left.", ["[:"] = "\\left.", ["("] = "\\left\\lparent", ["["] = "\\left\\lbracket", ["{"] = "\\left\\lbrace", ["<<"] = "\\left\\langle", -- why not <: ["|_"] = "\\left\\lfloor", ["|~"] = "\\left\\lceil", ["⟨"] = "\\left\\langle", ["〈"] = "\\left\\langle", ["〈"] = "\\left\\langle", } local m_right = { [")"] = "\\right\\rparent", [":)"] = "\\right\\rangle", [":}"] = "\\right.", [":]"] = "\\right.", ["]"] = "\\right\\rbracket", ["}"] = "\\right\\rbrace", [">>"] = "\\right\\rangle", -- why not :> ["~|"] = "\\right\\rceil", ["_|"] = "\\right\\rfloor", ["⟩"] = "\\right\\rangle", ["〉"] = "\\right\\rangle", ["〉"] = "\\right\\rangle", } 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("\\|") -- "\\middle\\|" -- maybe always add left / right as in mml ? -- -- faster bug also uglier: local p_special = -- C("/") -- + P("|") * Cc("\\|") -- "\\middle\\|" -- 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) ) -- open | close :: {: | :} local 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_entity -- + p_utf - p_close - p_right + p_utf - p_right )^1, } local function show_state(state,level,t) state = state + 1 report_asciimath(table.serialize(t,formatters["stage %s:%s"](level,state))) return state end local function show_result(str,result) report_asciimath("input > %s",str) report_asciimath("result > %s",result) end local function collapse(t,level) if not t then return "" end local state = 0 if trace_detail then if level then level = level + 1 else level = 1 end state = show_state(state,level,t) end -- 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 == "\\left." and r1 == "\\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 -- if trace_detail then state = show_state(state,level,t) end -- 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] 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 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" i = i + 1 else i = i + 1 end end -- if trace_detail then state = show_state(state,level,t) end -- 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 -- current[c] = nil -- current[1] = "" remove(current,c) remove(current,1) end i = i + 3 else i = i + 1 end end end -- if trace_detail then state = show_state(state,level,t) end -- 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 break end if type(one) == "table" then if isleft[one[1]] and isright[one[#one]] then -- one[1] = "" -- one[#one] = nil 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 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 -- if trace_detail then state = show_state(state,level,t) end -- 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 break end if type(one) == "table" then if isleft[one[1]] and isright[one[#one]] then -- one[1] = "" -- one[#one] = nil remove(one,#one) remove(one,1) end one = collapse(one,level) end if not two then t[m] = current .. "{" .. one .. "}{}" break end if type(two) == "table" then if isleft[two[1]] and isright[two[#two]] then -- two[1] = "" -- two[#two] = nil 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 -- if trace_detail then state = show_state(state,level,t) end -- 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 -- if trace_detail then state = show_state(state,level,t) end -- 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] t[m] = tl .. current .. "{" .. tr .. "}" i = i + 2 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 -- if trace_detail then state = show_state(state,level,t) end -- 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] -- if type(tl) == "table" then -- if isleft[tl[1]] and isright[tl[#tl]] then -- tl[1] = "" -- tl[#tl] = "" -- end -- end -- if type(tr) == "table" then -- if isleft[tr[1]] and isright[tr[#tr]] then -- tr[1] = "" -- tr[#tr] = "" -- end -- end t[m] = "\\frac{" .. tl .. "}{" .. tr .. "}" i = i + 2 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 -- if trace_detail then state = show_state(state,level,t) end -- local n, m, i = #t, 0, 1 while i < n do local current = t[i] if current == "\\slash" and i > 1 then -- t[m] = "{\\left(" .. t[i-1] .. "\\middle/" .. t[i+1] .. "\\right)}" t[m] = "{\\left." .. t[i-1] .. "\\middle/" .. t[i+1] .. "\\right.}" i = i + 2 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 -- if trace_detail then state = show_state(state,level,t) end -- local n = #t if t[1] == "\\left." and t[n] == "\\right." then return concat(t," ",2,n-1) else return concat(t," ") end 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 texcode = collapse(lpegmatch(parser,str)) if trace_mapping then show_result(str,texcode) end if totex then ctx_mathematics(texcode) else return texcode end end local n = 0 local p = ( (S("{[(") + P("\\left" )) / function() n = n + 1 end + (S("}])") + P("\\right")) / function() n = n - 1 end + P(1) )^0 local function invalidtex(str) n = 0 local result = 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_entity_base + 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 function collect(fpattern,element,collected,indexed) local element = element or "am" local mpattern = formatters["<%s>(.-)"](element,element) local filenames = dir.glob(fpattern) 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 for s in gmatch(io.loaddata(filename),mpattern) do 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 texcode = convert(s) local message = invalidtex(texcode) if message then report_asciimath("%s: %s",message,s) end collected[c] = { count = 1, files = { [shortname] = 1 }, texcode = texcode, message = message, cleanedup = s ~= c and 1 or 0, dirty = { [s] = 1 } } end end end local n = 0 for k, v in sortedhash(collected) do n = n + 1 v.n= n indexed[n] = k end return collected, indexed end asciimath.convert = convert asciimath.reserved = reserved asciimath.collect = collect 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 function convert(str) if #str == 1 then ctx_mathematics(str) else local texcode = collapse(lpegmatch(parser,str)) if trace_mapping then show_result(str,texcode) end if #texcode == 0 then report_asciimath("error in asciimath: %s",str) else local message = invalidtex(texcode) if message then report_asciimath("%s: %s",message,str) ctx_type(formatters["<%s>"](message)) else ctx_mathematics(texcode) end end end end commands.asciimath = convert 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]])) -- report_asciimath(cleanedup([[a "α" b]])) -- report_asciimath(cleanedup([[//4]])) -- 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)>2 x^2 ]]) -- convert([[ x^4>x ]]) return end 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.texcode) 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.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 context.starttabulate { "|B||" } context.NC() context("files") context.EQ() context(noffiles) context.NC() context.NR() context.NC() context("formulas") context.EQ() context(nofokay+nofbad) context.NC() context.NR() context.NC() context("uniques") context.EQ() context(#indexed) context.NC() context.NR() context.NC() context("cleanedup") context.EQ() context(nofcleanedup) context.NC() context.NR() context.NC() context("errors") context.EQ() context(nofbad) context.NC() context.NR() context.stoptabulate() end function show.save(name) table.save(name ~= "" and name or "dummy.lua",collected) end -- maybe: -- \backslash \ -- \times × -- \divide ÷ -- \circ ∘ -- \oplus ⊕ -- \otimes ⊗ -- \sum ∑ -- \prod ∏ -- \wedge ∧ -- \bigwedge ⋀ -- \vee ∨ -- \bigvee ⋁ -- \cup ∪ -- \bigcup ⋃ -- \cap ∩ -- \bigcap ⋂ -- \ne ≠ -- \le ≤ -- \leq ≤ -- \ge ≥ -- \geq ≥ -- \prec ≺ -- \succ ≻ -- \in ∈ -- \notin ∉ -- \subset ⊂ -- \supset ⊃ -- \subseteq ⊆ -- \supseteq ⊇ -- \equiv ≡ -- \cong ≅ -- \approx ≈ -- \propto ∝ -- -- \neg ¬ -- \implies ⇒ -- \iff ⇔ -- \forall ∀ -- \exists ∃ -- \bot ⊥ -- \top ⊤ -- \vdash ⊢ -- \models ⊨ -- -- \int ∫ -- \oint ∮ -- \partial ∂ -- \nabla ∇ -- \pm ± -- \emptyset ∅ -- \infty ∞ -- \aleph ℵ -- \ldots ... -- \cdots ⋯ -- \quad -- \diamond ⋄ -- \square □ -- \lfloor ⌊ -- \rfloor ⌋ -- \lceiling ⌈ -- \rceiling ⌉ -- -- \sin sin -- \cos cos -- \tan tan -- \csc csc -- \sec sec -- \cot cot -- \sinh sinh -- \cosh cosh -- \tanh tanh -- \log log -- \ln ln -- \det det -- \dim dim -- \lim lim -- \mod mod -- \gcd gcd -- \lcm lcm -- -- \uparrow ↑ -- \downarrow ↓ -- \rightarrow → -- \to → -- \leftarrow ← -- \leftrightarrow ↔ -- \Rightarrow ⇒ -- \Leftarrow ⇐ -- \Leftrightarrow ⇔ -- -- \mathbf -- \mathbb -- \mathcal -- \mathtt -- \mathfrak