summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/penlight/pl/compat.lua
blob: 1707ef684953f3d1dc8f83a986337cdc25a66897 (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
238
239
240
241
242
243
244
245
246
247
248
249
250
----------------
--- Lua 5.1/5.2/5.3 compatibility.
-- Injects `table.pack`, `table.unpack`, and `package.searchpath` in the global
-- environment, to make sure they are available for Lua 5.1 and LuaJIT.
--
-- All other functions are exported as usual in the returned module table.
--
-- NOTE: everything in this module is also available in `pl.utils`.
-- @module pl.compat
local compat = {}

--- boolean flag this is Lua 5.1 (or LuaJIT).
-- @field lua51
compat.lua51 = _VERSION == 'Lua 5.1'

--- boolean flag this is LuaJIT.
-- @field jit
compat.jit = (tostring(assert):match('builtin') ~= nil)

--- boolean flag this is LuaJIT with 5.2 compatibility compiled in.
-- @field jit52
if compat.jit then
    -- 'goto' is a keyword when 52 compatibility is enabled in LuaJit
    compat.jit52 = not loadstring("local goto = 1")
end

--- the directory separator character for the current platform.
-- @field dir_separator
compat.dir_separator = _G.package.config:sub(1,1)

--- boolean flag this is a Windows platform.
-- @field is_windows
compat.is_windows = compat.dir_separator == '\\'

--- execute a shell command, in a compatible and platform independent way.
-- This is a compatibility function that returns the same for Lua 5.1 and
-- Lua 5.2+.
--
-- NOTE: Windows systems can use signed 32bit integer exitcodes. Posix systems
-- only use exitcodes 0-255, anything else is undefined.
--
-- NOTE2: In Lua 5.2 and 5.3 a Windows exitcode of -1 would not properly be
-- returned, this function will return it properly for all versions.
-- @param cmd a shell command
-- @return true if successful
-- @return actual return code
function compat.execute(cmd)
    local res1,res2,res3 = os.execute(cmd)
    if res2 == "No error" and res3 == 0 and compat.is_windows then
      -- os.execute bug in Lua 5.2/5.3 not reporting -1 properly on Windows
      -- this was fixed in 5.4
      res3 = -1
    end
    if compat.lua51 and not compat.jit52 then
        if compat.is_windows then
            return res1==0,res1
        else
            res1 = res1 > 255 and res1 / 256 or res1
            return res1==0,res1
        end
    else
        if compat.is_windows then
            return res3==0,res3
        else
            return not not res1,res3
        end
    end
end

----------------
-- Load Lua code as a text or binary chunk (in a Lua 5.2 compatible way).
-- @param ld code string or loader
-- @param[opt] source name of chunk for errors
-- @param[opt] mode 'b', 't' or 'bt'
-- @param[opt] env environment to load the chunk in
-- @function compat.load

---------------
-- Get environment of a function (in a Lua 5.1 compatible way).
-- Not 100% compatible, so with Lua 5.2 it may return nil for a function with no
-- global references!
-- Based on code by [Sergey Rozhenko](http://lua-users.org/lists/lua-l/2010-06/msg00313.html)
-- @param f a function or a call stack reference
-- @function compat.getfenv

---------------
-- Set environment of a function (in a Lua 5.1 compatible way).
-- @param f a function or a call stack reference
-- @param env a table that becomes the new environment of `f`
-- @function compat.setfenv

if compat.lua51 then -- define Lua 5.2 style load()
    if not compat.jit then -- but LuaJIT's load _is_ compatible
        local lua51_load = load
        function compat.load(str,src,mode,env)
            local chunk,err
            if type(str) == 'string' then
                if str:byte(1) == 27 and not (mode or 'bt'):find 'b' then
                    return nil,"attempt to load a binary chunk"
                end
                chunk,err = loadstring(str,src)
            else
                chunk,err = lua51_load(str,src)
            end
            if chunk and env then setfenv(chunk,env) end
            return chunk,err
        end
    else
        compat.load = load
    end
    compat.setfenv, compat.getfenv = setfenv, getfenv
else
    compat.load = load
    -- setfenv/getfenv replacements for Lua 5.2
    -- by Sergey Rozhenko
    -- http://lua-users.org/lists/lua-l/2010-06/msg00313.html
    -- Roberto Ierusalimschy notes that it is possible for getfenv to return nil
    -- in the case of a function with no globals:
    -- http://lua-users.org/lists/lua-l/2010-06/msg00315.html
    function compat.setfenv(f, t)
        f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
        local name
        local up = 0
        repeat
            up = up + 1
            name = debug.getupvalue(f, up)
        until name == '_ENV' or name == nil
        if name then
            debug.upvaluejoin(f, up, function() return name end, 1) -- use unique upvalue
            debug.setupvalue(f, up, t)
        end
        if f ~= 0 then return f end
    end

    function compat.getfenv(f)
        local f = f or 0
        f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
        local name, val
        local up = 0
        repeat
            up = up + 1
            name, val = debug.getupvalue(f, up)
        until name == '_ENV' or name == nil
        return val
    end
end


--- Global exported functions (for Lua 5.1 & LuaJIT)
-- @section lua52

--- pack an argument list into a table.
-- @param ... any arguments
-- @return a table with field n set to the length
-- @function table.pack
if not table.pack then
    function table.pack (...)       -- luacheck: ignore
        return {n=select('#',...); ...}
    end
end

--- unpack a table and return the elements.
--
-- NOTE: this version does NOT honor the n field, and hence it is not nil-safe.
-- See `utils.unpack` for a version that is nil-safe.
-- @param t table to unpack
-- @param[opt] i index from which to start unpacking, defaults to 1
-- @param[opt] t index of the last element to unpack, defaults to #t
-- @return multiple return values from the table
-- @function table.unpack
-- @see utils.unpack
if not table.unpack then
    table.unpack = unpack           -- luacheck: ignore
end

--- return the full path where a file name would be matched.
-- This function was introduced in Lua 5.2, so this compatibility version
-- will be injected in Lua 5.1 engines.
-- @string name file name, possibly dotted
-- @string path a path-template in the same form as package.path or package.cpath
-- @string[opt] sep template separate character to be replaced by path separator. Default: "."
-- @string[opt] rep the path separator to use, defaults to system separator. Default; "/" on Unixes, "\" on Windows.
-- @see path.package_path
-- @function package.searchpath
-- @return on success: path of the file
-- @return on failure: nil, error string listing paths tried
if not package.searchpath then
    function package.searchpath (name,path,sep,rep)    -- luacheck: ignore
        if type(name) ~= "string" then
            error(("bad argument #1 to 'searchpath' (string expected, got %s)"):format(type(path)), 2)
        end
        if type(path) ~= "string" then
            error(("bad argument #2 to 'searchpath' (string expected, got %s)"):format(type(path)), 2)
        end
        if sep ~= nil and type(sep) ~= "string" then
            error(("bad argument #3 to 'searchpath' (string expected, got %s)"):format(type(path)), 2)
        end
        if rep ~= nil and type(rep) ~= "string" then
            error(("bad argument #4 to 'searchpath' (string expected, got %s)"):format(type(path)), 2)
        end
        sep = sep or "."
        rep = rep or compat.dir_separator
        do
          local s, e = name:find(sep, nil, true)
          while s do
            name = name:sub(1, s-1) .. rep .. name:sub(e+1, -1)
            s, e = name:find(sep, s + #rep + 1, true)
          end
        end
        local tried = {}
        for m in path:gmatch('[^;]+') do
            local nm = m:gsub('?', name)
            tried[#tried+1] = nm
            local f = io.open(nm,'r')
            if f then f:close(); return nm end
        end
        return nil, "\tno file '" .. table.concat(tried, "'\n\tno file '") .. "'"
    end
end

--- Global exported functions (for Lua < 5.4)
-- @section lua54

--- raise a warning message.
-- This functions mimics the `warn` function added in Lua 5.4.
-- @function warn
-- @param ... any arguments
if not warn then  -- luacheck: ignore
    local enabled = false
    function warn(arg1, ...)  -- luacheck: ignore
        if type(arg1) == "string" and arg1:sub(1, 1) == "@" then
            -- control message
            if arg1 == "@on" then
                enabled = true
                return
            end
            if arg1 == "@off" then
                enabled = false
                return
            end
            return -- ignore unknown control messages
        end
        if enabled then
          io.stderr:write("Lua warning: ", arg1, ...)
          io.stderr:write("\n")
        end
    end
end

return compat