x-calcmath.lua /size: 11 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['x-calcmath'] = {
2    version   = 1.001,
3    comment   = "companion to x-calcmath.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- this really needs to be redone
10
11local next, type = next, type
12local format, lower, upper, gsub, sub = string.format, string.lower, string.upper, string.gsub, string.sub
13local concat = table.concat
14local lpegmatch = lpeg.match
15
16local calcmath      = { }
17local moduledata    = moduledata or { }
18moduledata.calcmath = calcmath
19
20local context       = context
21
22local list_1 = {
23    "median", "min", "max", "round", "ln", "log",
24    "sin", "cos", "tan", "sinh", "cosh", "tanh"
25}
26local list_2 = {
27    "int", "sum", "prod"
28}
29local list_3 = {
30    "f", "g"
31}
32local list_4 = {
33    "pi", "inf"
34}
35
36local list_1_1 = { }
37local list_2_1 = { }
38local list_2_2 = { }
39local list_2_3 = { }
40local list_4_1 = { }
41
42local frozen = false
43
44local function freeze()
45    for k=1,#list_1 do
46        local v = list_1[k]
47        list_1_1[v] = "\\".. upper(v) .." "
48    end
49    for k=1,#list_2 do
50        local v = list_2[k]
51        list_2_1[v .. "%((.-),(.-),(.-)%)"] = "\\" .. upper(v) .. "^{%1}_{%2}{%3}"
52        list_2_2[v .. "%((.-),(.-)%)"]      = "\\" .. upper(v) .. "^{%1}{%2}"
53        list_2_3[v .. "%((.-)%)"]           = "\\" .. upper(v) .. "{%1}"
54    end
55    for k=1,#list_4 do
56        local v = list_4[k]
57        list_4_1[v] = "\\" .. upper(v)
58    end
59    frozen = true
60end
61
62local entities = {
63    ['gt'] = '>',
64    ['lt'] = '<',
65}
66
67local symbols = {
68    ["<="] = "\\LE ",
69    [">="] = "\\GE ",
70    ["=<"] = "\\LE ",
71    ["=>"] = "\\GE ",
72    ["=="] = "\\EQ ",
73    ["<" ] = "\\LT ",
74    [">" ] = "\\GT ",
75    ["="]  = "\\EQ ",
76}
77
78local function nsub(str,tag,pre,post)
79    return (gsub(str,tag .. "(%b())", function(body)
80        return pre .. nsub(sub(body,2,-2),tag,pre,post) .. post
81    end))
82end
83
84local function totex(str,mode)
85    if not frozen then freeze() end
86    local n = 0
87    -- crap
88    str = gsub(str,"%s+",' ')
89    -- xml
90    str = gsub(str,"&(.-);",entities)
91    -- ...E...
92    str = gsub(str,"([%-%+]?[%d%.%+%-]+)E([%-%+]?[%d%.]+)", "{\\SCINOT{%1}{%2}}")
93    -- ^-..
94    str = gsub(str,"%^([%-%+]*%d+)", "^{%1}")
95    -- ^(...)
96    str = nsub(str,"%^", "^{", "}")
97    -- 1/x^2
98    repeat
99        str, n = gsub(str,"([%d%w%.]+)/([%d%w%.]+%^{[%d%w%.]+})", "\\frac{%1}{%2}")
100    until n == 0
101    -- todo: autoparenthesis
102    -- int(a,b,c)
103    for k, v in next, list_2_1 do
104        repeat str, n = gsub(str,k,v) until n == 0
105    end
106    -- int(a,b)
107    for k, v in next, list_2_2 do
108        repeat str, n = gsub(str,k,v) until n == 0
109    end
110    -- int(a)
111    for k, v in next, list_2_3 do
112        repeat str, n = gsub(str,k,v) until n == 0
113    end
114    -- sin(x) => {\\sin(x)}
115    for k, v in next, list_1_1 do
116        repeat str, n = gsub(str,k,v) until n == 0
117    end
118    -- mean
119    str = nsub(str, "mean", "\\OVERLINE{", "}")
120    -- (1+x)/(1+x) => \\FRAC{1+x}{1+x}
121    repeat
122        str, n = gsub(str,"(%b())/(%b())", function(a,b)
123            return "\\FRAC{" .. sub(a,2,-2) .. "}{" .. sub(b,2,-2) .. "}"
124        end )
125    until n == 0
126    -- (1+x)/x => \\FRAC{1+x}{x}
127    repeat
128        str, n = gsub(str,"(%b())/([%+%-]?[%.%d%w]+)", function(a,b)
129            return "\\FRAC{" .. sub(a,2,-2) .. "}{" .. b .. "}"
130        end )
131    until n == 0
132    -- 1/(1+x) => \\FRAC{1}{1+x}
133    repeat
134        str, n = gsub(str,"([%.%d%w]+)/(%b())", function(a,b)
135            return "\\FRAC{" .. a .. "}{" .. sub(b,2,-2) .. "}"
136        end )
137    until n == 0
138    -- 1/x => \\FRAC{1}{x}
139    repeat
140        str, n = gsub(str,"([%.%d%w]+)/([%+%-]?[%.%d%w]+)", "\\FRAC{%1}{%2}")
141    until n == 0
142    -- times
143    str = gsub(str,"%*", " ")
144    -- symbols -- we can use a table substitution here
145    str = gsub(str,"([<>=][<>=]*)", symbols)
146    -- functions
147    str = nsub(str,"sqrt", "\\SQRT{", "}")
148    str = nsub(str,"exp", "e^{", "}")
149    str = nsub(str,"abs", "\\left|", "\\right|")
150    -- d/D
151    str = nsub(str,"D", "{\\FRAC{\\MBOX{d}}{\\MBOX{d}x}{(", ")}}")
152    str = gsub(str,"D([xy])", "\\FRAC{{\\RM d}%1}{{\\RM d}x}")
153    -- f/g
154    for k,v in next, list_3 do -- todo : prepare k,v
155        str = nsub(str,"D"..v,"{\\RM "..v.."}^{\\PRIME}(",")")
156        str = nsub(str,v,"{\\RM "..v.."}(",")")
157    end
158    -- more symbols
159    for k,v in next, list_4_1 do
160        str = gsub(str,k,v)
161    end
162    -- parenthesis (optional)
163    if mode == 2 then
164      str = gsub(str,"%(", "\\left(")
165      str = gsub(str,"%)", "\\right)")
166    end
167    -- csnames
168    str = gsub(str,"(\\[A-Z]+)", lower)
169    -- report
170    return str
171end
172
173calcmath.totex      = totex
174
175function calcmath.tex(str,mode)
176    context(totex(str))
177end
178
179function calcmath.xml(id,mode)
180    context(totex(lxml.id(id).dt[1],mode))
181end
182
183-- work in progress ... lpeg variant
184
185if false then
186
187    -- todo:
188
189    -- maybe rewrite to current lpeg, i.e. string replacement and no Cc's
190
191    -- table approach we have now is less efficient but more flexible
192
193    -- D          \frac  {\rm d}   {{\rm d}x}
194    -- Dx Dy      \frac {{\rm d}y} {{\rm d}x}
195    -- Df Dg      {\rm f}^{\prime}
196    -- f() g()    {\rm f}()
197
198    -- valid utf8
199
200    local S, P, R, C, V, Cc, Ct  = lpeg.S, lpeg.P, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc, lpeg.Ct
201
202    local space      = S(" \n\r\t")^0
203    local integer    = P("-")^-1 * R("09")^1
204    local realpart   = P("-")^-1 * R("09")^1 * S(".")^1 * R("09")^1
205    local number     = Cc("number")     * C(integer) * space
206    local real       = Cc("real")       * C(realpart) * space
207    local float      = Cc("float")      * C(realpart) * lpeg.P("E") * lpeg.C(integer) * space
208    local identifier = Cc("identifier") * C(R("az","AZ")) * space
209    local compareop  = Cc("compare")    * C(P("<") + P("=") + P(">") + P(">=") + P("<=") + P("&gt;") + P("&lt;")) * space
210    local factorop   = Cc("factor")     * C(S("+-^_,")) * space
211    local termop     = Cc("term")       * C(S("*/")) * space
212    local constant   = Cc("constant")   * C(P("pi") + lpeg.P("inf")) * space
213    local functionop = Cc("function")   * C(R("az")^1) * space
214    local open       = P("(") * space
215    local close      = P(")") * space
216
217    local grammar = P {
218        "expression",
219        expression = Ct(V("factor") * ((factorop+compareop) * V("factor"))^0),
220        factor     = Ct(V("term") * (termop * V("term"))^0),
221        term       = Ct(
222            float + real + number +
223            (open * V("expression") * close) +
224            (functionop * open * (V("expression") * (P(",") * V("expression"))^0) * close) +
225            (functionop * V("term")) +
226            constant + identifier
227        ),
228    }
229
230    local parser = space * grammar * -1
231
232    local function has_factor(t)
233        for i=1,#t do
234            if t[i] == "factor" then
235                return true
236            end
237        end
238    end
239
240    -- can be sped up if needed ...
241
242    function totex(t)
243        if t then
244            local one = t[1]
245            if type(one) == "string" then
246                local two, three = t[2], t[3]
247                if one == "number" then
248                    context(two)
249                elseif one == "real" then
250                    context(two)
251                elseif one == "float" then
252                    context("\\scinot{",two,"}{",three,"}")
253                elseif one == "identifier" then
254                    context(two)
255                elseif one == "constant" then
256                    context("\\"..two)
257                elseif one == "function" then
258                    if two == "sqrt" then
259                        context("\\sqrt{")
260                        totex(three)
261                        context("}")
262                    elseif two == "exp" then
263                        context(" e^{")
264                        totex(three)
265                        context("}")
266                    elseif two == "abs" then
267                        context("\\left|")
268                        totex(three)
269                        context("\\right|")
270                    elseif two == "mean" then
271                        context("\\overline{")
272                        totex(three)
273                        context("}")
274                    elseif two == "int" or two == "prod" or two == "sum" then
275                        local four, five = t[4], t[5]
276                        if five then
277                            context("\\"..two.."^{") -- context[two]("{")
278                            totex(three)
279                            context("}_{")
280                            totex(four)
281                            context("}")
282                            totex(five)
283                        elseif four then
284                            context("\\"..two.."^{")
285                            totex(three)
286                            context("}")
287                            totex(four)
288                        elseif three then
289                            context("\\"..two.." ") -- " " not needed
290                            totex(three)
291                        else
292                            context("\\"..two)
293                        end
294                    else
295                        context("\\"..two.."(")
296                        totex(three)
297                        context(")")
298                    end
299                end
300            else
301                local nt = #t
302                local hasfactor = has_factor(t)
303                if hasfactor then
304                    context("\\left(")
305                end
306                totex(one)
307                for i=2,nt,3 do
308                    local what, how, rest = t[i], t[i+1], t[i+2]
309                    if what == "factor" then
310                        if how == '^' or how == "_" then
311                            context(how)
312                            context("{")
313                            totex(rest)
314                            context("}")
315                        else
316                            context(how)
317                            totex(rest)
318                        end
319                    elseif what == "term" then
320                        if how == '/' then
321                            context("\\frac{")
322                            totex(rest)
323                            context("}{")
324                            totex(t[i+3] or "")
325                            context("}")
326                        elseif how == '*' then
327                            context("\\times")
328                            totex(rest)
329                        else
330                            context(how)
331                            totex(three)
332                        end
333                    elseif what == "compare" then
334                        if two == ">=" then
335                            context("\\ge")
336                        elseif two == "<=" then
337                            context("\\le")
338                        elseif two == "&gt;" then
339                            context(">")
340                        elseif two == "&lt;" then
341                            context("<")
342                        end
343                        totex(three)
344                    end
345                end
346                if hasfactor then
347                    context("\\right)")
348                end
349            end
350        end
351    end
352
353    calcmath = { }
354
355    function calcmath.parse(str)
356        return lpegmatch(parser,str)
357    end
358
359    function calcmath.tex(str)
360        str = totex(lpegmatch(parser,str))
361        return (str == "" and "[error]") or str
362    end
363
364end
365