summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/data-tex.lua
blob: e4795d09d186f4fffc545ef6910059ad7f3c9b5d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
if not modules then modules = { } end modules ['data-tex'] = {
    version   = 1.001,
    comment   = "companion to luat-lib.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

local tostring, tonumber, type = tostring, tonumber, type
local char, find = string.char, string.find

local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v end)

local report_tex = logs.reporter("resolvers","tex")


local sequencers        = utilities.sequencers
local utffiletype       = utf.filetype
local setmetatableindex = table.setmetatableindex
local loaddata          = io.loaddata
----- readall           = io.readall

local resolvers         = resolvers
local methodhandler     = resolvers.methodhandler
local loadbinfile       = resolvers.loadbinfile
local pushinputname     = resolvers.pushinputname
local popinputname      = resolvers.popinputname

-- local fileprocessor = nil
-- local lineprocessor = nil

local textfileactions = sequencers.new {
    arguments    = "str,filename,coding",
    returnvalues = "str",
    results      = "str",
}

local textlineactions = sequencers.new {
    arguments    = "str,filename,linenumber,noflines,coding",
    returnvalues = "str",
    results      = "str",
}

local helpers      = resolvers.openers.helpers
local appendgroup  = sequencers.appendgroup
local appendaction = sequencers.appendaction

helpers.textfileactions = textfileactions
helpers.textlineactions = textlineactions

appendgroup(textfileactions,"before") -- user
appendgroup(textfileactions,"system") -- private
appendgroup(textfileactions,"after" ) -- user

appendgroup(textlineactions,"before") -- user
appendgroup(textlineactions,"system") -- private
appendgroup(textlineactions,"after" ) -- user

local ctrl_d = char( 4) -- unix
local ctrl_z = char(26) -- windows

----------------------------------------

local lpegmatch  = lpeg.match
local newline    = lpeg.patterns.newline
local tsplitat   = lpeg.tsplitat

local linesplitters = {
    tsplitat(newline),                       -- default since we started
    tsplitat(lpeg.S(" ")^0 * newline),
    tsplitat(lpeg.S(" \t")^0 * newline),
    tsplitat(lpeg.S(" \f\t")^0 * newline),   -- saves a bit of space at the cost of runtime
 -- tsplitat(lpeg.S(" \v\f\t")^0 * newline),
 -- tsplitat(lpeg.R("\0\31")^0 * newline),
}

local linesplitter = linesplitters[1]

directives.register("system.linesplitmethod",function(v)
    linesplitter = linesplitters[tonumber(v) or 1] or linesplitters[1]
end)

local function splitlines(str)
    return lpegmatch(linesplitter,str)
end

-- not really a bottleneck, but it might become:
--
-- local splitlines = string.splitlines or function(str)
--     return lpegmatch(linesplitter,str)
-- end
--
-- directives.register("system.linesplitmethod",function(v)
--     linesplitter = linesplitters[tonumber(v) or 1] or linesplitters[1]
--     splitlines = function(str)
--         return lpegmatch(linesplitter,str)
--     end
-- end)

-----------------------------------------

local wideutfcoding = {
    ["utf-16-be"] = utf.utf16_to_utf8_be_t,
    ["utf-16-le"] = utf.utf16_to_utf8_le_t,
    ["utf-32-be"] = utf.utf32_to_utf8_be_t,
    ["utf-32-le"] = utf.utf32_to_utf8_le_t,
}

local function textopener(tag,filename,filehandle,coding)
    local lines
    local t_filehandle = type(filehandle)
    if not filehandle then
        lines = loaddata(filename)
    elseif t_filehandle == "string" then
        lines = filehandle
    elseif t_filehandle == "table" then
        lines = filehandle
    else
        lines = filehandle:read("*a") -- readall(filehandle) ... but never that large files anyway
     -- lines = readall(filehandle)
        filehandle:close()
    end
    if type(lines) == "string" then
        local coding = coding or utffiletype(lines) -- so we can signal no regime
        if trace_locating then
            report_tex("%a opener: %a opened using method %a",tag,filename,coding)
        end
        local wideutf = wideutfcoding[coding]
        if wideutf then
            lines = wideutf(lines)
        else -- utf8 or unknown (could be a mkvi file)
            local runner = textfileactions.runner
            if runner then
                lines = runner(lines,filename,coding) or lines
            end
            lines = splitlines(lines)
        end
    elseif trace_locating then
        report_tex("%a opener: %a opened",tag,filename)
    end
    local noflines = #lines
    if lines[noflines] == "" then -- maybe some special check is needed
        lines[noflines] = nil
    end
    pushinputname(filename)
    local currentline, noflines = 0, noflines
    local t = {
        filename    = filename,
        noflines    = noflines,
     -- currentline = 0,
        close       = function()
            local usedname = popinputname() -- should match filename
            if trace_locating then
                report_tex("%a closer: %a closed",tag,filename)
            end
            t = nil
        end,
        reader      = function(self)
            self = self or t
         -- local currentline, noflines = self.currentline, self.noflines
            if currentline >= noflines then
                return nil
            else
                currentline = currentline + 1
             -- self.currentline = currentline
                local content = lines[currentline]
                if content == "" then
                    return ""
             -- elseif content == ctrl_d or ctrl_z then
             --     return nil -- we need this as \endinput does not work in prints
                elseif content then
                    local runner = textlineactions.runner
                    if runner then
                        return runner(content,filename,currentline,noflines,coding) or content
                    else
                        return content
                    end
                else
                    return nil
                end
            end
        end
    }
    setmetatableindex(t,function(t,k)
        if k == "currentline" then
            return currentline
        else
            -- no such key
        end
    end)
    return t
end

helpers.settextopener(textopener) -- can only be done once

function resolvers.findtexfile(filename,filetype)
    return methodhandler('finders',filename,filetype)
end

function resolvers.opentexfile(filename)
    return methodhandler('openers',filename)
end

function resolvers.openfile(filename)
    local fullname = methodhandler('finders',filename)
    return fullname and fullname ~= "" and methodhandler('openers',fullname) or nil
end

function resolvers.loadtexfile(filename,filetype)
    -- todo: optionally apply filters
    local ok, data, size = loadbinfile(filename, filetype)
    return data or ""
end

resolvers.texdatablob = resolvers.loadtexfile

local function installhandler(namespace,what,where,func)
    if not func then
        where, func = "after", where
    end
    if where == "before" or where == "after" then
        appendaction(namespace,where,func)
    else
        report_tex("installing input %a handlers in %a is not possible",what,tostring(where))
    end
end

function resolvers.installinputlinehandler(...) installhandler(textlineactions,"line",...) end
function resolvers.installinputfilehandler(...) installhandler(textfileactions,"file",...) end

-- local basename = file.basename
-- resolvers.installinputlinehandler(function(str,filename,linenumber,noflines)
--     report_tex("[lc] file %a, line %a of %a, length %a",basename(filename),linenumber,noflines,#str)
-- end)
-- resolvers.installinputfilehandler(function(str,filename)
--     report_tex("[fc] file %a, length %a",basename(filename),#str)
-- end)