back-out.lmt /size: 8102 b    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['back-out'] = {
2    version   = 1.001,
3    comment   = "companion to back-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
9local type = type
10local loadstring = loadstring
11
12local context             = context
13
14-- tokens.scanners.....
15
16local get                 = token.get_index
17local scanners            = tokens.scanners
18local scaninteger         = scanners.integer
19local scanstring          = scanners.string
20local scankeyword         = scanners.keyword
21local scantokenlist       = scanners.tokenlist
22
23local serialize           = token.serialize
24
25local logwriter           = logs.writer
26local openfile            = io.open
27local flushio             = io.flush
28
29local nuts                = nodes.nuts
30local tonode              = nuts.tonode
31local copynode            = nuts.copy
32local nodepool            = nuts.pool
33
34local getdata             = nuts.getdata
35
36local whatsit_code        = nodes.nodecodes.whatsit
37
38local whatsitcodes        = nodes.whatsitcodes
39
40local literalvalues       = nodes.literalvalues
41local originliteral_code  = literalvalues.origin
42local pageliteral_code    = literalvalues.page
43local directliteral_code  = literalvalues.direct
44local rawliteral_code     = literalvalues.raw
45
46local immediate_code      = tex.flagcodes.immediate
47
48local nodeproperties      = nodes.properties.data
49
50local channels            = { }
51
52local register            = nodepool.register
53local newnut              = nuts.new
54
55local opennode            = register(newnut(whatsit_code,whatsitcodes.open))
56local writenode           = register(newnut(whatsit_code,whatsitcodes.write))
57local closenode           = register(newnut(whatsit_code,whatsitcodes.close))
58local lateluanode         = register(newnut(whatsit_code,whatsitcodes.latelua))
59local literalnode         = register(newnut(whatsit_code,whatsitcodes.literal))
60local savenode            = register(newnut(whatsit_code,whatsitcodes.save))
61local restorenode         = register(newnut(whatsit_code,whatsitcodes.restore))
62local setmatrixnode       = register(newnut(whatsit_code,whatsitcodes.setmatrix))
63
64local open_command, write_command, close_command
65
66backends = backends or { }
67
68local function immediately(prefix)
69    return prefix and (prefix & immediate_code) ~= 0
70end
71
72local function openout(prefix)
73    local channel = scaninteger()
74    scankeyword("=") -- hack
75    local filename = scanstring()
76    if not immediately(prefix) then
77        local n = copynode(opennode)
78        nodeproperties[n] = { channel = channel, filename = filename } -- action = "open"
79        return context(tonode(n))
80    elseif not channels[channel] then
81        local handle = openfile(filename,"wb") or false
82        if handle then
83            channels[channel] = handle
84        else
85            -- error
86        end
87    end
88end
89
90function backends.openout(n)
91    local p = nodeproperties[n]
92    if p then
93        local handle = openfile(p.filename,"wb") or false
94        if handle then
95            channels[p.channel] = handle
96        else
97            -- error
98        end
99    end
100end
101
102local function write(prefix)
103    local channel = scaninteger()
104    if not immediately(prefix) then
105        local t = scantokenlist()
106        local n = copynode(writenode)
107        nodeproperties[n] = { channel = channel, data = t } -- action = "write"
108        return context(tonode(n))
109    else
110        local content = scanstring()
111        local handle  = channels[channel]
112        if handle then
113            handle:write(content,"\n")
114        else
115           logwriter(content,"\n")
116        end
117    end
118end
119
120function backends.writeout(n)
121    local p = nodeproperties[n]
122    if p then
123        local handle  = channels[p.channel]
124        local content = serialize(p.data)
125        if handle then
126            handle:write(content,"\n")
127        else
128           logwriter(content,"\n")
129        end
130    end
131end
132
133local function closeout(prefix)
134    local channel = scaninteger()
135    if not immediately(prefix) then
136        local n = copynode(closenode)
137        nodeproperties[n] = { channel = channel } -- action = "close"
138        return context(tonode(n))
139    else
140        local handle = channels[channel]
141        if handle then
142            handle:close()
143            channels[channel] = false
144            flushio()
145        else
146            -- error
147        end
148    end
149end
150
151function backends.closeout(n)
152    local p = nodeproperties[n]
153    if p then
154        local channel = p.channel
155        local handle  = channels[channel]
156        if handle then
157            handle:close()
158            channels[channel] = false
159            flushio()
160        else
161            -- error
162        end
163    end
164end
165
166local noflatelua = 0
167
168local function latelua()
169    local node = copynode(lateluanode)
170    local name = "latelua"
171    if scankeyword("name") then
172        name = scanstring()
173    end
174    local data = scantokenlist()
175    nodeproperties[node] = { name = name, data = data }
176    return context(tonode(node))
177end
178
179function backends.latelua(current,pos_h,pos_v) -- todo: pass pos_h and pos_v (more efficient in lmtx)
180    local p = nodeproperties[current]
181    if p then
182        data = p.data
183    else
184        data = getdata(current)
185    end
186    noflatelua = noflatelua + 1
187    local kind = type(data)
188    if kind == "table" then
189        data.action(data.specification or data)
190    elseif kind == "function" then
191        data()
192    else
193        if kind ~= "string" then
194            data = serialize(data)
195        end
196        if #data ~= "" then
197            local code = loadstring(data)
198            if code then
199                code()
200            end
201        end
202    end
203end
204
205function backends.getcallbackstate()
206    return { count = noflatelua }
207end
208
209function nodepool.originliteral(str) local t = copynode(literalnode) nodeproperties[t] = { data = str, mode = originliteral_code } return t end
210function nodepool.pageliteral  (str) local t = copynode(literalnode) nodeproperties[t] = { data = str, mode = pageliteral_code   } return t end
211function nodepool.directliteral(str) local t = copynode(literalnode) nodeproperties[t] = { data = str, mode = directliteral_code } return t end
212function nodepool.rawliteral   (str) local t = copynode(literalnode) nodeproperties[t] = { data = str, mode = rawliteral_code    } return t end
213
214local pdfliterals = {
215    [originliteral_code] = originliteral_code, [literalvalues[originliteral_code]] = originliteral_code,
216    [pageliteral_code]   = pageliteral_code,   [literalvalues[pageliteral_code]]   = pageliteral_code,
217    [directliteral_code] = directliteral_code, [literalvalues[directliteral_code]] = directliteral_code,
218    [rawliteral_code]    = rawliteral_code,    [literalvalues[rawliteral_code]]    = rawliteral_code,
219}
220
221function nodepool.literal(mode,str)
222    local t = copynode(literalnode)
223    if str then
224        nodeproperties[t] = { data = str, mode = pdfliterals[mode] or pageliteral_code }
225    else
226        nodeproperties[t] = { data = mode, mode = pageliteral_code }
227    end
228    return t
229end
230
231--- these 3 will move to node-res as they are generic
232
233function nodepool.save()
234    return copynode(savenode)
235end
236
237function nodepool.restore()
238    return copynode(restorenode)
239end
240
241function nodepool.setmatrix(rx,sx,sy,ry,tx,ty)
242    local t = copynode(setmatrixnode)
243    nodeproperties[t] = { matrix = { rx, sx, sy, ry, tx, ty } }
244    return t
245end
246
247interfaces.implement { name = "openout",   actions = openout,    public = true, usage = "value" }
248interfaces.implement { name = "write",     actions = write,      public = true, usage = "value" }
249interfaces.implement { name = "closeout",  actions = closeout,   public = true, usage = "value" }
250interfaces.implement { name = "latelua",   actions = latelua,    public = true, protected = true }
251interfaces.implement { name = "special",   actions = scanstring, public = true, protected = true }
252
253open_command  = get(token.create("openout"))
254write_command = get(token.create("write"))
255close_command = get(token.create("closeout"))
256