math-tag.lmt /size: 31 Kb    last modification: 2024-01-16 09:02
1if not modules then modules = { } end modules ['math-tag'] = {
2    version   = 1.001,
3    comment   = "companion to math-ini.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-- todo: have a local list with local tags that then get appended
10-- todo: use tex.getmathcodes (no table)
11-- todo: add more spacing details + check text stuff for latest additions
12-- todo: some more font related cleanup + adaption to new scaling
13-- todo: tracing
14-- todo: maybe use lpeg matchers
15
16-- todo: prime
17-- todo: middle in fraction
18
19local find, match = string.find, string.match
20local insert, remove, concat = table.insert, table.remove, table.concat
21
22local attributes        = attributes
23local nodes             = nodes
24
25local nuts              = nodes.nuts
26local tonut             = nuts.tonut
27
28local getchar           = nuts.getchar
29local getprev           = nuts.getprev
30local getcharspec       = nuts.getcharspec
31local getdata           = nuts.getdata
32local getlist           = nuts.getlist
33local getfield          = nuts.getfield
34local getdisc           = nuts.getdisc
35local getattr           = nuts.getattr
36local getattrlist       = nuts.getattrlist
37local setattr           = nuts.setattr
38local getwidth          = nuts.getwidth
39
40local getnumerator      = nuts.getnumerator
41local getdenominator    = nuts.getdenominator
42local getdelimiter      = nuts.getdelimiter
43local getleftdelimiter  = nuts.getleftdelimiter
44local getrightdelimiter = nuts.getrightdelimiter
45local getdegree         = nuts.getdegree
46local gettop            = nuts.gettop
47local getbottom         = nuts.getbottom
48local getchoice         = nuts.getchoice
49
50local getnucleus        = nuts.getnucleus
51
52local setattributes     = nuts.setattributes
53
54local nextnode          = nuts.traversers.node
55
56local nodecodes         = nodes.nodecodes
57
58local noad_code         = nodecodes.noad
59local accent_code       = nodecodes.accent
60local radical_code      = nodecodes.radical
61local fraction_code     = nodecodes.fraction
62local subbox_code       = nodecodes.subbox
63local submlist_code     = nodecodes.submlist
64local mathchar_code     = nodecodes.mathchar
65local mathtextchar_code = nodecodes.mathtextchar
66local delimiter_code    = nodecodes.delimiter
67local style_code        = nodecodes.style
68local choice_code       = nodecodes.choice
69local fence_code        = nodecodes.fence
70
71local accentcodes       = nodes.accentcodes
72local fencecodes        = nodes.fencecodes
73
74local fixedtopaccent_code    = accentcodes.fixedtop
75local fixedbottomaccent_code = accentcodes.fixedbottom
76local fixedbothaccent_code   = accentcodes.fixedboth
77
78local hextensible_code   = nodes.radicalcodes.hextensible
79local lextensible_code   = nodes.listcodes.hextensible
80local gextensible_code   = nodes.glyphcodes.extensible
81
82local leftfence_code     = fencecodes.left
83local middlefence_code   = fencecodes.middle
84local rightfence_code    = fencecodes.right
85local operatorfence_code = fencecodes.operator
86
87local kerncodes         = nodes.kerncodes
88
89local fontkern_code     = kerncodes.fontkern
90local italickern_code   = kerncodes.italickern
91
92local hlist_code        = nodecodes.hlist
93local vlist_code        = nodecodes.vlist
94local glyph_code        = nodecodes.glyph
95local disc_code         = nodecodes.disc
96local glue_code         = nodecodes.glue
97local kern_code         = nodecodes.kern
98local math_code         = nodecodes.math
99
100local processnoads      = noads.process
101
102local a_tagged          = attributes.private('tagged')
103local a_mathcategory    = attributes.private('mathcategory')
104local a_mathmode        = attributes.private('mathmode')
105
106local tags              = structures.tags
107
108local start_tagged      = tags.start
109local restart_tagged    = tags.restart
110local stop_tagged       = tags.stop
111local taglist           = tags.taglist
112
113----- chardata          = characters.data
114
115local getmathcodes         = tex.getmathcodes
116----- mathcodes            = mathematics.codes
117local mathcodes            = mathematics.classes
118local ordinary_mathcode    = mathcodes.ordinary
119local digit_mathcode       = mathcodes.digit
120local punctuation_mathcode = mathcodes.punctuation
121local active_mathcode      = mathcodes.active
122
123local fromunicode16     = fonts.mappings.fromunicode16
124local fontcharacters    = fonts.hashes.characters
125
126local report_tags       = logs.reporter("structure","tags")
127
128local process
129
130local function processnucleus(nucleus,prime)
131    if prime then
132        -- This should work wasn't it that browser handling of primes have an issue with
133        -- "big semi raised text" vs "small supposedly superscripted". So let's play safe
134        -- and use a superscript. Even then we get somewhat different positioning for the
135        -- same primed character in q sqrt and (e.g.) a sequential integral.
136     -- start_tagged("mrow", { prime = true })
137        start_tagged("msup", { prime = true })
138        process(nucleus)
139        process(prime)
140        stop_tagged()
141    else
142        process(nucleus)
143    end
144end
145
146local function processsubsup(start)
147    -- At some point we might need to add an attribute signaling the
148    -- super- and subscripts because TeX and MathML use a different
149    -- order. The mrows are needed to keep mn's separated.
150    local nucleus, prime, sup, sub, presup, presub = getnucleus(start,true)
151    if sub then
152        if sup then
153            setattr(start,a_tagged,start_tagged("msubsup"))
154            processnucleus(nucleus,prime)
155            start_tagged("mrow", { subscript = true })
156            process(sub)
157            stop_tagged()
158            start_tagged("mrow", { superscript = true })
159            process(sup)
160            stop_tagged()
161            stop_tagged()
162        else
163            setattr(start,a_tagged,start_tagged("msub"))
164            processnucleus(nucleus,prime)
165            start_tagged("mrow")
166            process(sub)
167            stop_tagged()
168            stop_tagged()
169        end
170    elseif sup then
171        setattr(start,a_tagged,start_tagged("msup"))
172        processnucleus(nucleus,prime)
173        start_tagged("mrow")
174        process(sup)
175        stop_tagged()
176        stop_tagged()
177    else
178        processnucleus(nucleus,prime)
179    end
180end
181
182-- todo: check function here and keep attribute the same
183
184-- todo: variants -> original
185
186local actionstack = { }
187local fencesstack = { }
188
189-- glyph nodes and such can happen in under and over stuff
190
191local function getunicode(n) -- instead of getchar
192    local char, font = getcharspec(n)
193    local data = fontcharacters[font][char]
194    return data.unicode or char -- can be a table but unlikely for math characters
195end
196
197-------------------
198
199local content = { }
200local found   = false
201
202content[mathchar_code] = function() found = true end
203
204local function hascontent(head)
205    found = false
206    processnoads(head,content,"content")
207    return found
208end
209
210--------------------
211
212-- todo: use properties
213
214-- local function showtag(n,id,old)
215--     local attr = getattr(n,a_tagged)
216--     local curr = tags.current()
217--     report_tags("%s, node %s, attr %s:%s (%s), top %s (%s)",
218--         old and "before" or "after ",
219--         nodecodes[id],
220--         getattrlist(n),
221--         attr or "?",attr and taglist[attr].tagname or "?",
222--         curr or "?",curr and taglist[curr].tagname or "?"
223--     )
224-- end
225
226-- I need to bring this in sync with new or removed mathml 3, not that there has
227-- been many changes. It will happen in sync with other mathml updates in context
228-- where we also keep adapting to a cycling between either or not support in
229-- browsers, the come-and-go of alternatives like ascii math and mathjax. It's the
230-- web and browser support that drives this, not tex and its community. So, maybe
231-- I'll add some more detail here, nto that it matters much in the long run where we
232-- only focus on structure and let the engine deal with the details. Another reason
233-- to update this is that we can add some tracing (lmtx only).
234
235-- This has been working ok for quite but in 2023 it's time to have a look at it
236-- again and see to what extend we need to adapt to new features. Around the time
237-- PG's Panopticom was put on youtube.
238
239process = function(start) -- we cannot use the processor as we have no finalizers (yet)
240    local mtexttag = nil
241    for start, id, subtype in nextnode, start do -- current
242        if id == glyph_code or id == disc_code then
243            if not mtexttag then
244                mtexttag = start_tagged("mtext")
245            end
246            setattr(start,a_tagged,mtexttag)
247        elseif mtexttag and id == kern_code and subtype == fontkern_code or subtype == italickern_code then -- italickern
248            setattr(start,a_tagged,mtexttag)
249        else
250            if mtexttag then
251                stop_tagged()
252                mtexttag = nil
253            end
254            if id == mathchar_code then
255                local char = getchar(start)
256                local code = getmathcodes(char)
257--                 local ch   = chardata[char]
258--                 local mc   = ch and ch.mathclass
259                local tag
260                local properties= { class = code }
261                -- we're a bit early so we can have active here and no rules get applied
262                -- so maybe we have to correct some later on
263                -- todo: we have way more now
264--                 if code == ordinary_mathcode then
265--                     if mc == "number" then
266--                         tag = "mn"
267--                     elseif mc == "variable" or not mc then -- variable is default
268--                         tag = "mi"
269--                     else
270--                         tag = "mo"
271--                     end
272--                 else
273--                     tag = "mo"
274--                 end
275-- print(code,mathematics.classes[code])
276-- print(ordinary_mathcode,digit_mathcode,active_mathcode)
277                if code == ordinary_mathcode then
278                    tag = "mi"
279                elseif code == digit_mathcode then
280                    tag = "mn"
281--                 elseif code == punctuation_mathcode or code == active_mathcode then
282--                     tag = "mo"
283                else
284                    tag = "mo"
285                end
286--                 if mc == "open" or nc == "close" or  mc == "middle" then
287--                     properties = { maxsize =  1 }
288--                 end
289                local a = getattr(start,a_mathcategory)
290                if a then
291                    -- todo / redo
292                    if properties then
293                        properties.mathcategory = a
294                    else
295                        properties = { mathcategory = a }
296                    end
297                end
298                setattr(start,a_tagged,start_tagged(tag,properties))
299                stop_tagged()
300             -- showtag(start,id,false)
301                break -- okay?
302            elseif id == mathtextchar_code then -- or id == glyph_code
303                -- check for code
304                local a = getattr(start,a_mathcategory)
305                if a then
306                    setattr(start,a_tagged,start_tagged("ms",{ mathcategory = a })) -- mtext
307                else
308                    setattr(start,a_tagged,start_tagged("ms")) -- mtext
309                end
310                stop_tagged()
311             -- showtag(start,id,false)
312                break
313            elseif id == delimiter_code then
314                -- check for code
315                setattr(start,a_tagged,start_tagged("mo"))
316                stop_tagged()
317             -- showtag(start,id,false)
318                break
319            elseif id == style_code then
320                -- has a next
321            elseif id == noad_code then
322             -- setattr(start,a_tagged,tags.current())
323                processsubsup(start)
324            elseif id == subbox_code or id == hlist_code or id == vlist_code then
325                -- keep an eye on subbox_code and see what ends up in there
326                -- a hlist can be a nested result (mlist_to_hlist)
327                local attr = getattr(start,a_tagged)
328                if not attr then
329                    -- just skip
330                else
331                    local specification = taglist[attr]
332                    if specification then
333                        local tag = specification.tagname
334                        if tag == "formulacaption" then
335                            -- skip
336                        elseif tag == "mstacker" then
337                            local list = getlist(start)
338                            if list then
339                                process(list)
340                            end
341                        else
342                            if tag ~= "mtable" and tag ~= "mstackertop" and tag ~= "mstackermid" and tag ~= "mstackerbot" then
343                                tag = "mtext"
344                            end
345                            local detail = specification.detail
346                            local text   = start_tagged(tag, detail and { detail = detail } or nil)
347                            setattr(start,a_tagged,text)
348                            local list = getlist(start)
349                            if not list then
350                                -- empty list
351                            elseif not attr then
352                                -- box comes from strange place
353                                setattributes(list,a_tagged,text) -- only the first node ?
354                            else
355                                -- Beware, the first node in list is the actual list so we definitely
356                                -- need to nest. This approach is a hack, maybe I'll make a proper
357                                -- nesting feature to deal with this at another level. Here we just
358                                -- fake structure by enforcing the inner one.
359                                --
360                                -- todo: have a local list with local tags that then get appended
361                                --
362                                local tagdata = specification.taglist
363                                local common = #tagdata + 1
364                                local function runner(list,depth) -- quite inefficient
365                                    local cache = { } -- we can have nested unboxed mess so best local to runner
366                                    local keep = nil
367                                 -- local keep = { } -- in case we might need to move keep outside
368                                    for n, id, subtype in nextnode, list do
369                                        local mth = id == math_code and subtype
370                                        if mth == 0 then -- begin in line / nested math box like stackers
371                                         -- insert(keep,text)
372                                            keep = text
373                                            text = start_tagged("mrow")
374                                            common = common + 1
375                                        end
376                                        local aa = getattr(n,a_tagged)
377                                        if aa then
378                                            local ac = cache[aa]
379                                            if not ac then
380                                                local tagdata = taglist[aa].taglist
381                                                local extra = #tagdata
382                                                if common <= extra then
383                                                    for i=common,extra do
384                                                        ac = restart_tagged(tagdata[i]) -- can be made faster
385                                                    end
386                                                    for i=common,extra do
387                                                        stop_tagged() -- can be made faster
388                                                    end
389                                                else
390                                                    ac = text
391                                                end
392                                                cache[aa] = ac
393                                            end
394                                            setattr(n,a_tagged,ac)
395                                        else
396                                            setattr(n,a_tagged,text)
397                                        end
398                                        if id == hlist_code or id == vlist_code then
399-- if subtype == lextensible_code then
400-- print("L")
401-- end
402                                            runner(getlist(n),depth+1)
403                                        elseif id == glyph_code then
404-- if subtype == gextensible_code then
405--     print("G")
406-- end
407                                        elseif id == disc_code then
408                                            -- this should not be needed
409                                            local pre, post, replace = getdisc(n)
410                                            if pre then
411                                                runner(pre,depth+1)
412                                            end
413                                            if post then
414                                                runner(post,depth+1)
415                                            end
416                                            if replace then
417                                                runner(replace,depth+1)
418                                            end
419                                        end
420                                        if mth == 1 then -- end in line
421                                            stop_tagged()
422                                         -- text = remove(keep)
423                                            text = keep
424                                            common = common - 1
425                                        end
426                                    end
427                                end
428                                runner(list,0)
429                            end
430                            stop_tagged()
431                        end
432                    end
433                end
434            elseif id == submlist_code then -- normally a hbox
435                local list = getlist(start)
436                if list then
437                    local attr = getattr(start,a_tagged)
438                    local last = attr and taglist[attr]
439                    if last then
440                        local tag    = last.tagname
441                        local detail = last.detail
442                        if tag == "maction" then
443                            if detail == "" then
444                                setattr(start,a_tagged,start_tagged("mrow"))
445                                process(list)
446                                stop_tagged()
447                            elseif actionstack[#actionstack] == action then
448                                setattr(start,a_tagged,start_tagged("mrow"))
449                                process(list)
450                                stop_tagged()
451                            else
452                                insert(actionstack,action)
453                                setattr(start,a_tagged,start_tagged("mrow",{ detail = action }))
454                                process(list)
455                                stop_tagged()
456                                remove(actionstack)
457                            end
458                        elseif tag == "mstacker" then -- or tag == "mstackertop" or tag == "mstackermid" or tag == "mstackerbot" then
459                            -- looks like it gets processed twice
460                            -- do we still end up here ?
461                            setattr(start,a_tagged,restart_tagged(attr)) -- so we just reuse the attribute
462                            process(list)
463                            stop_tagged()
464                        else
465                            setattr(start,a_tagged,start_tagged("mrow"))
466                            process(list)
467                            stop_tagged()
468                        end
469                    else -- never happens, we're always document
470                        setattr(start,a_tagged,start_tagged("mrow"))
471                        process(list)
472                        stop_tagged()
473                    end
474                end
475            elseif id == fraction_code then
476                --
477                -- if middle then we have a stacker!
478                --
479                local num   = getnumerator(start)
480                local denom = getdenominator(start)
481                local left  = getleftdelimiter(start)
482                local right = getrightdelimiter(start)
483                if left then
484                   setattr(left,a_tagged,start_tagged("mo"))
485                   process(left)
486                   stop_tagged()
487                end
488                setattr(start,a_tagged,start_tagged("mfrac"))
489                process(num)
490                process(denom)
491                stop_tagged()
492                if right then
493                    setattr(right,a_tagged,start_tagged("mo"))
494                    process(right)
495                    stop_tagged()
496                end
497            elseif id == choice_code then
498                local display      = getchoice(start,1)
499                local text         = getchoice(start,2)
500                local script       = getchoice(start,3)
501                local scriptscript = getchoice(start,4)
502                if display then
503                    process(display)
504                end
505                if text then
506                    process(text)
507                end
508                if script then
509                    process(script)
510                end
511                if scriptscript then
512                    process(scriptscript)
513                end
514            elseif id == fence_code then
515                local delimiter = getdelimiter(start)
516-- print(subtype,fencecodes[subtype],delimiter)
517                if subtype == leftfence_code then
518                    local properties = { }
519                    insert(fencesstack,properties)
520                    setattr(start,a_tagged,start_tagged("mfenced",properties)) -- needs checking
521                    if delimiter then
522                        start_tagged("ignore")
523                        local chr = getchar(delimiter)
524                        if chr ~= 0 then
525                            properties.left = chr
526                        end
527                        process(delimiter)
528                        stop_tagged()
529                    end
530                    start_tagged("mrow") -- begin of subsequence
531                elseif subtype == middlefence_code then
532                    if delimiter then
533                        start_tagged("ignore")
534                        local top = fencesstack[#fencesstack]
535                        local chr = getchar(delimiter)
536                        if chr ~= 0 then
537                            local mid = top.middle
538                            if mid then
539                                mid[#mid+1] = chr
540                            else
541                                top.middle = { chr }
542                            end
543                        end
544                        process(delimiter)
545                        stop_tagged()
546                    end
547                    stop_tagged()        -- end of subsequence
548                    start_tagged("mrow") -- begin of subsequence
549                elseif subtype == rightfence_code then
550                    local properties = remove(fencesstack)
551                    if not properties then
552                        report_tags("missing right fence")
553                        properties = { }
554                    end
555                    if delimiter then
556                        start_tagged("ignore")
557                        local chr = getchar(delimiter)
558                        if chr ~= 0 then
559                            properties.right = chr
560                        end
561                        process(delimiter)
562                        stop_tagged()
563                    end
564                    stop_tagged() -- end of subsequence
565                    stop_tagged()
566                elseif subtype == operatorfence_code then
567                    -- the same as left but different key
568                    local properties = { }
569                    insert(fencesstack,properties)
570                    setattr(start,a_tagged,start_tagged("mfenced",properties)) -- needs checking
571                    if delimiter then
572                        local chr = getchar(delimiter)
573                        if chr ~= 0 then
574                            properties.operator = chr
575                        end
576                        process(delimiter)
577                    end
578                    processsubsup(start)
579                    start_tagged("mrow") -- begin of subsequence
580                end
581            elseif id == radical_code then
582                if subtype == hextensible_code then
583                    -- eventually we have no radical but just some box
584                else
585                    local left   = getleftdelimiter(start)
586                    local right  = getrightdelimiter(start)
587                    local degree = getdegree(start)
588                    if left then
589                        start_tagged("ignore")
590                        process(left) -- root symbol, ignored
591                        stop_tagged()
592                    end
593                    if right then
594                        start_tagged("ignore")
595                        process(lright) -- actuarian symbol, ignored
596                        stop_tagged()
597                    end
598                    if degree and hascontent(degree) then
599                        setattr(start,a_tagged,start_tagged("mroot"))
600                        processsubsup(start)
601                        process(degree)
602                        stop_tagged()
603                    else
604                        setattr(start,a_tagged,start_tagged("msqrt"))
605                        processsubsup(start)
606                        stop_tagged()
607                    end
608                end
609            elseif id == accent_code then
610                local topaccent = gettop(start)
611                local bottomaccent = getbottom(start)
612                if bottomaccent then
613                    if topaccent then
614                        setattr(start,a_tagged,start_tagged("munderover", {
615                            accent      = true,
616                            top         = getunicode(topaccent),
617                            bottom      = getunicode(bottomaccent),
618                            topfixed    = subtype == fixedtopaccent_code or subtype == fixedbothaccent_code,
619                            bottomfixed = subtype == fixedbottomaccent_code or subtype == fixedbothaccent_code,
620                        }))
621                        processsubsup(start)
622                        process(bottomaccent)
623                        process(topaccent)
624                        stop_tagged()
625                    else
626                        setattr(start,a_tagged,start_tagged("munder", {
627                            accent      = true,
628                            bottom      = getunicode(bottomaccent),
629                            bottomfixed = subtype == fixedbottomaccent_code or subtype == fixedbothaccent_code,
630                        }))
631                        processsubsup(start)
632                        process(bottomaccent)
633                        stop_tagged()
634                    end
635                elseif topaccent then
636                    setattr(start,a_tagged,start_tagged("mover", {
637                        accent   = true,
638                        top      = getunicode(topaccent),
639                        topfixed = subtype == fixedtopaccent_code or subtype == fixedbothaccent_code,
640                    }))
641                    processsubsup(start)
642                    process(topaccent)
643                    stop_tagged()
644                else
645                    processsubsup(start)
646                end
647            elseif id == glue_code then
648                -- before processing, so other intermathglue is not tagged
649                local em = fonts.hashes.emwidths[nuts.getfont(start)]
650                local wd = getwidth(start)
651                if em and wd then
652                    setattr(start,a_tagged,start_tagged("mspace",{ emfactor = wd/em }))
653                    stop_tagged()
654                end
655            else
656                --rule boundary
657             -- setattr(start,a_tagged,start_tagged("merror", { detail = nodecodes[id] }))
658             -- stop_tagged()
659            end
660        end
661-- showtag(start,id,false)
662    end
663    if mtexttag then
664        stop_tagged()
665    end
666end
667
668function noads.handlers.tags(head,style,penalties)
669    start_tagged("math", { mode = (getattr(head,a_mathmode) == 1) and "display" or "inline" })
670    setattr(head,a_tagged,start_tagged("mrow"))
671-- showtag(head,getid(head),true)
672    process(head)
673-- showtag(head,getid(head),false)
674    stop_tagged()
675    stop_tagged()
676end
677
678do
679
680    -- This one is meant for tracing (in m4all/m4mbo where it complements some other
681    -- tracing) but it actually can also replace the embedding feature although that
682    -- one might be better when we have more complex code with dependencies outside
683    -- the blob. I'll deal with that when it's needed (trivial). The current
684    -- interface is rather minimalistic.
685
686    local enabled = false
687    local export  = false
688    local allmath = false
689    local warned  = false
690
691    function mathematics.startcollecting()
692        if structures.tags.enabled() then
693            if not enabled then
694                nodes.tasks.enableaction("math", "noads.handlers.export")
695            end
696            enabled = true
697            export  = structures.tags.localexport
698            allmath = { }
699        elseif not warned then
700            report_tags("math collecting only works when tagging is enabled")
701            warned = true
702        end
703    end
704
705    function mathematics.stopcollecting()
706        export = false
707    end
708
709    local function collected(asstring)
710        local a = allmath or { }
711        return asstring and concat(a) or a
712    end
713
714    mathematics.collected = collected
715
716    interfaces.implement {
717        name      = "startcollectingmath",
718     -- public    = true,
719        protected = true,
720        actions   = mathematics.startcollecting
721    }
722
723    interfaces.implement {
724        name      = "stopcollectingmath",
725     -- public    = true,
726        protected = true,
727        actions   = mathematics.stopcollecting
728    }
729
730    interfaces.implement {
731        name      = "processcollectedmath",
732     -- public    = true,
733        protected = true,
734        arguments = "2 strings",
735        actions   = function(filename,buffername)
736            if filename and filename ~= "" then
737                io.savedata(filename,collected(true))
738            elseif buffername then
739                buffers.assign(buffername == interfaces.variables.yes and "" or buffername,collected(true))
740            else
741                return collected
742            end
743        end
744    }
745
746    interfaces.implement {
747        name      = "collectedmath",
748        usage     = "value",
749        protected = true,
750        public    = true,
751        actions = function(what)
752            if what == "value" then
753                return tokens.values.integer, allmath and #allmath or 0
754            else
755                context(allmath and allmath[tokens.scanners.integer()] or nil)
756            end
757        end
758    }
759
760    function noads.handlers.export(head)
761        if export then
762            allmath[#allmath+1] = export(head)
763        end
764        return head
765    end
766
767    nodes.tasks.appendaction("math", "finalizers", "noads.handlers.export", nil, "nonut", "disabled")
768
769end
770