node-ser.lmt /size: 8309 b    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['node-ser'] = {
2    version   = 1.001,
3    comment   = "companion to node-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-- needs to be updated (mayeb fetch field types)
10
11local type, tostring = type, tostring
12local concat, tohash, sortedkeys, sortedhash, printtable, serialize = table.concat, table.tohash, table.sortedkeys, table.sortedhash, table.print, table.serialize
13local formatters, format, rep = string.formatters, string.format, string.rep
14
15local allocate = utilities.storage.allocate
16
17local context     = context
18local nodes       = nodes
19
20local getfields   = nodes.fields
21local isnode      = nodes.isnode
22local nodecodes   = nodes.nodecodes
23local subtypes    = nodes.subtypes
24local tonode      = nodes.tonode
25local tonut       = nodes.tonut
26
27local hlist_code  = nodecodes.hlist
28local vlist_code  = nodecodes.vlist
29
30local f_char      = formatters["%U"]
31local f_attr      = formatters["<attribute : %i>"]
32local f_recurse   = formatters["<recursive : %i>"]
33
34-- flat    : don't use next, but indexes
35-- verbose : also add type
36
37local canbezero = {
38    integer   = true,
39    dimension = true,
40    number    = true,
41}
42
43local canbelist = {
44    attribute  = "<attribute>",
45    node       = "<node>",
46    token      = "<token>",
47    node_list  = "<nodes>",
48    token_list = "<tokens>",
49}
50
51local canbeignored = {
52    prev = "<node>"
53}
54
55local canbechar = {
56    char      = true,
57    smallchar = true,
58    largechar = true,
59}
60
61local fieldtypes = table.setmetatableindex(function(t,k)
62    local v = getfields(k,true) or false
63    t[k] = v
64    return v
65end)
66
67nodes.fieldtypes = fieldtypes
68
69-- todo: subtype zero too
70
71local function astable(n)
72    n = tonode(n)
73    if n then
74        local id     = n.id
75        local fields = fieldtypes[id]
76        if fields then
77            local subtype = n.subtype
78            local result  = { }
79            for field, fieldtype in sortedhash(fields) do -- no need to sort
80                local value = n[field]
81                if value then
82                    if field == "subtype" then
83                        -- we always show them now
84                    elseif canbeignored[field] then
85                        value = nil
86                    elseif canbezero[fieldtype] and value == 0 then
87                        value = nil
88                    elseif canbelist[fieldtype] then
89                        value = canbelist[fieldtype]
90                    end
91                    if value then
92                        result[field] = value
93                    end
94                end
95            end
96            id = nodecodes[id]
97            result.id = id
98            if subtype then
99                local subtypes = subtypes[id]
100                if subtypes then
101                    result.subtype = subtypes[subtype]
102                end
103            end
104            return result
105        end
106    end
107end
108
109nodes.astable = astable
110
111setinspector("node",function(v) if isnode(v) then printtable(astable(v),tostring(v)) return true end end)
112
113local function to_table(n,flat,verbose,noattributes,done)
114    local d = tonut(n)
115    if done[d] then
116        return f_recurse(d)
117    else
118        done[d] = true
119        local fields = fieldtypes[n.id]
120        if fields then
121            local result = { }
122            for field, fieldtype in sortedhash(fields) do
123                local value = n[field]
124                if value then
125                    if fieldtype == "attribute" then
126                        if noattributes then
127                            result[value] = canbeignored[value]
128                        else
129                            result[value] = to_table(value,flat,verbose,noattributes,done)
130                        end
131                    elseif canbeignored[field] then
132                        result[value] = canbeignored[value]
133                    elseif not verbose and canbezero[fieldtype] and value == 0 then
134                        value = nil
135                    elseif canbelist[fieldtype] then
136                        if flat then
137                            result[value] = canbelist[value]
138                        else
139                            result[value] = to_table(value,flat,verbose,noattributes,done)
140                        end
141                    end
142                    if value then
143                        result[field] = value
144                    end
145                end
146            end
147            if verbose then
148                local id = result.id
149                if id then
150                    id = nodecodes[id]
151                    result.id = id
152                    local subtype = result.subtype
153                    if subtype then
154                        local subtypes = subtypes[id]
155                        if subtypes then
156                            result.subtype = subtypes[subtype]
157                        end
158                    end
159                end
160                for k, v in next, canbechar do
161                    local v = result[k]
162                    if v then
163                        result[k] = f_char(v)
164                    end
165                end
166            end
167            return result
168        end
169    end
170end
171
172local function totable(n,flat,verbose,noattributes) -- nicest: n,true,true,true
173    if n then
174        local d = { }
175        if flat then
176            local t, tn = { }, 0
177            while n do
178                tn = tn + 1
179                local nt = to_table(n,flat,verbose,noattributes,d)
180                t[tn] = nt
181                nt.next = nil
182                nt.prev = nil
183                n = n.next
184            end
185            done = nil
186            return t
187        else
188            local t = to_table(n,flat,verbose,noattributes,d)
189            local n = n.next
190            if n then
191                t.next = totable(n,flat,verbose,noattributes,d)
192            end
193            return t
194        end
195    else
196        return { }
197    end
198end
199
200nodes.totable = function(n,...) return totable(tonode(n),...) end
201nodes.totree  = function(n)     return totable(tonode(n),true,true,true) end -- no attributes, todo: attributes in k,v list
202
203local function key(k)
204    return ((type(k) == "number") and "["..k.."]") or k
205end
206
207function nodes.serialize(root,flat,verbose,noattributes,name)
208    return serialize(totable(tonode(root),flat,verbose,noattributes),name)
209end
210
211function nodes.serializebox(n,flat,verbose,noattributes,name)
212    return serialize(totable(tex.box[n],flat,verbose,noattributes),name)
213end
214
215function nodes.visualizebox(n,flat,verbose,noattributes,name)
216    context.tocontext(totable(tex.box[n],flat,verbose,noattributes),name)
217end
218
219function nodes.list(head,n) -- name might change to nodes.type -- to be checked .. will move to module anyway
220    head = tonode(head)
221    if not n then
222        context.starttyping(true)
223    end
224    while head do
225        local id = head.id
226        context(rep(" ",n or 0) .. tostring(head) .. "\n")
227        if id == hlist_code or id == vlist_code then
228            nodes.list(head.list,(n or 0)+1)
229        end
230        head = head.next
231    end
232    if not n then
233        context.stoptyping(true)
234    end
235end
236
237function nodes.print(head,n)
238    head = tonode(head)
239    while head do
240        local id = head.id
241        logs.writer(string.formatters["%w%S"],n or 0,head)
242        if id == hlist_code or id == vlist_code then
243            nodes.print(head.list,(n or 0)+1)
244        end
245        head = head.next
246    end
247end
248
249-- quick hack, nicer is to have a proper expand per node type already prepared
250
251local function apply(n,action)
252    while n do
253        action(n)
254        local id = n.id
255        if id == hlist_code or id == vlist_code then
256            apply(n.list,action)
257        end
258        n = n.next
259    end
260end
261
262nodes.apply = apply
263
264local nuts    = nodes.nuts
265local getid   = nuts.getid
266local getlist = nuts.getlist
267local getnext = nuts.getnext
268
269local function apply(n,action)
270    while n do
271        action(n)
272        local id = getid(n)
273        if id == hlist_code or id == vlist_code then
274            local list = getlist(n,action)
275            if list then
276                apply(list,action)
277            end
278        end
279        n = getnext(n)
280    end
281end
282
283nuts.apply = apply
284