Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

In the course of trying to answer this question, I now realize that I need to be able to construct a math list and then check if it consists of a single character atom.

What I would like to be able to do is to deconstruct the math list similar to "Dissecting paragraphs with \lastbox" in TeX by Topic.

As described in this answer, one can use \showlists to write a representation of the partial lists to the log file. That doesn't seem to help me unless I want to parse the log file. (I don't.)

share|improve this question

2 Answers 2

up vote 6 down vote accepted

I am pretty sure you can do something that extends the TeX by Topic with \lastbox and box unrolling after the paragraph has completed, but (besides being very tedious) that will not give you access to the actual math list, only to its converted format as a nested tree of \hbox and \vbox-es.

TeX82 does not offer access to the math list other than via \showlists (and for once I think a luatex answer would not really help).

share|improve this answer
I'm not sure how to deal with the \mathon/\mathoff itemsssssss that appear in the boxes. For example, \hbox{$x$} gives \mathon, \teni x, and \mathoff in plain TeX. I guess I haven't played with box unrolling much to know if there's anything that can be done to see that this is just a \teni x, for example. If I could do that, I think that'd be sufficient. –  TH. Oct 8 '10 at 10:35
To follow up on that, \hbox{$x$\setbox0\lastbox\showbox0} tells me that \box0 is void. This makes sense since \mathoff is not a box. I might just be out of luck here. –  TH. Oct 8 '10 at 10:47
@TH Yes, I fear you are right. Parsing the log file it is, then –  Taco Hoekwater Oct 8 '10 at 12:00
@TH: In Chapter 21 of the TeXbook it says that \lastbox is always void in math mode. –  Hendrik Vogt Oct 8 '10 at 13:33
@TH: The following might be worth pointing out: It is not only that \mathoff is not a box, it is not even a TeX command. –  Hendrik Vogt Oct 8 '10 at 14:55

It is only possible to dissect math lists when using LuaTeX. For instance, you can use the mlist_to_hlist callback. The code below emulates \showlists for math lists, to some limited extent: for instance \radical is not taken care of.

Note that I am very much a LuaTeX newbie, so the code is probably very far from optimal, and doesn't follow standard best practices.

    local my = {}
    my.node = {}
    function my.node.tostring(t, newline)
        if t == nil then
            return ''
        if my[node.type(t.id)] ~= nil then
            return my[node.type(t.id)].tostring(t, newline)
            local result = newline .. node.type(t.id)
            for _, v in ipairs(node.fields(t.id)) do
                if v == 'id' or t[v] == nil then else
                    local value = t[v]
                    if type(value) == "userdata" then
                        value = node.type(value.id)
                    result = result .. ', ' .. v .. ': ' .. value
            return result
    my.noad = {}
    my.noad.subtypes = {'op', nil, nil, 'bin', 'rel', 'open',
      'close', 'punct', 'inner', [0] = 'ord'}
    function my.noad.subtype(subtype)
        if my.noad.subtypes[subtype] == nil then
            return '\\\\math?'
            return '\\\\math' .. my.noad.subtypes[subtype]
    function my.noad.tostring(t, newline)
        local result = newline .. my.noad.subtype(t.subtype)
        if t.attr ~= nil then
            result = result .. t.attr
        result = result
                 .. my.node.tostring(t.nucleus, newline .. '.')
                 .. my.node.tostring(t.sup, newline .. '^')
                 .. my.node.tostring(t.sub, newline .. '_')
        return result
    my.math_char = {}
    function my.math_char.tostring(t, newline)
        return newline .. '\\\\fam' .. t.fam .. ' ' .. string.char(t.char)
    my.fence = {}
    function my.fence.tostring(t, newline)
        fencename = ({'\\\\left', '\\\\middle', '\\\\right'})[t.subtype]
        return newline .. fencename .. my.node.tostring(t.delim, ' ')
    my.delim = {}
    function my.delim.tostring(t, newline)
            .. 'small: \\\\fam'
            .. t.small_fam
            .. ' '
            .. (t.small_char == 0 and '^'..'^@' or string.char(t.small_char))
            .. ', large: \\\\fam'
            .. t.large_fam
            .. ' '
            .. (t.large_char == 0 and '^'..'^@' or string.char(t.large_char))
    % subtype and attr ignored for sub_mlist?
    my.sub_mlist = {}
    function my.sub_mlist.tostring(mlist, newline)
        return my.mlist.tostring(mlist.head, newline)
    my.mlist = {}
    function my.mlist.tostring(h, newline)
        local result = ''
        for t in node.traverse(h) do
            result = result .. my.node.tostring(t, newline)
        return result
        function (h, d, p)
            local tab = {}
            my.mlist.tostring(h, '\\n'):gsub('([^\\n]+)', texio.write_nl)
            return node.mlist_to_hlist(h, d, p)

abc $ \mathord{\mathord{a}^b}^b \sum + = (),\left(2\middle|3\right)\sqrt{a}$ f

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.