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(keys(m_left)) / m_left
local p_right =
lpeg.utfchartabletopattern(keys(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>(.-)%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