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 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:
[">"] = { true, ">" },
["<"] = { 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 > 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)>2 x^2 ]])
-- convert([[ x^4>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