summaryrefslogtreecommitdiff
path: root/compat53/module.lua
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--compat53/module.lua827
1 files changed, 827 insertions, 0 deletions
diff --git a/compat53/module.lua b/compat53/module.lua
new file mode 100644
index 0000000..30be6b5
--- /dev/null
+++ b/compat53/module.lua
@@ -0,0 +1,827 @@
1local _G, _VERSION = _G, _VERSION
2local lua_version = _VERSION:sub(-3)
3
4
5local M = _G
6
7if lua_version < "5.3" then
8
9 -- cache globals in upvalues
10 local error, ipairs, pairs, pcall, require, select, setmetatable, type =
11 error, ipairs, pairs, pcall, require, select, setmetatable, type
12 local debug, io, math, package, string, table =
13 debug, io, math, package, string, table
14 local io_lines = io.lines
15 local io_read = io.read
16 local unpack = lua_version == "5.1" and unpack or table.unpack
17
18 -- create module table
19 M = {}
20 local M_meta = {
21 __index = _G,
22 -- __newindex is set at the end
23 }
24 setmetatable(M, M_meta)
25
26 -- create subtables
27 M.io = setmetatable({}, { __index = io })
28 M.math = setmetatable({}, { __index = math })
29 M.string = setmetatable({}, { __index = string })
30 M.table = setmetatable({}, { __index = table })
31 M.utf8 = {}
32
33
34 -- select the most powerful getmetatable function available
35 local gmt = type(debug) == "table" and debug.getmetatable or
36 getmetatable or function() return false end
37
38 -- type checking functions
39 local checkinteger -- forward declararation
40
41 local function argcheck(cond, i, f, extra)
42 if not cond then
43 error("bad argument #"..i.." to '"..f.."' ("..extra..")", 0)
44 end
45 end
46
47
48 -- load utf8 library
49 local utf8_ok, utf8lib = pcall(require, "compat53.utf8")
50 if utf8_ok then
51 if lua_version == "5.1" then
52 utf8lib.charpattern = "[%z\1-\127\194-\244][\128-\191]*"
53 end
54 for k,v in pairs(utf8lib) do
55 M.utf8[k] = v
56 end
57 package.loaded["utf8"] = M.utf8
58 end
59
60
61 -- load table library
62 local table_ok, tablib = pcall(require, "compat53.table")
63 if table_ok then
64 for k,v in pairs(tablib) do
65 M.table[k] = v
66 end
67 end
68
69
70 -- load string packing functions
71 local str_ok, strlib = pcall(require, "compat53.string")
72 if str_ok then
73 for k,v in pairs(strlib) do
74 M.string[k] = v
75 end
76 end
77
78
79 -- try Roberto's struct module for string packing/unpacking if
80 -- compat53.string is unavailable
81 if not str_ok then
82 local struct_ok, struct = pcall(require, "struct")
83 if struct_ok then
84 M.string.pack = struct.pack
85 M.string.packsize = struct.size
86 M.string.unpack = struct.unpack
87 end
88 end
89
90
91 -- update math library
92 do
93 local maxint, minint = 1
94
95 while maxint+1 > maxint and 2*maxint > maxint do
96 maxint = maxint * 2
97 end
98 if 2*maxint <= maxint then
99 maxint = 2*maxint-1
100 minint = -maxint-1
101 else
102 maxint = maxint
103 minint = -maxint
104 end
105 M.math.maxinteger = maxint
106 M.math.mininteger = minint
107
108 function M.math.tointeger(n)
109 if type(n) == "number" and n <= maxint and n >= minint and n % 1 == 0 then
110 return n
111 end
112 return nil
113 end
114
115 function M.math.type(n)
116 if type(n) == "number" then
117 if n <= maxint and n >= minint and n % 1 == 0 then
118 return "integer"
119 else
120 return "float"
121 end
122 else
123 return nil
124 end
125 end
126
127 function checkinteger(x, i, f)
128 local t = type(x)
129 if t ~= "number" then
130 error("bad argument #"..i.." to '"..f..
131 "' (number expected, got "..t..")", 0)
132 elseif x > maxint or x < minint or x % 1 ~= 0 then
133 error("bad argument #"..i.." to '"..f..
134 "' (number has no integer representation)", 0)
135 else
136 return x
137 end
138 end
139
140 function M.math.ult(m, n)
141 m = checkinteger(m, "1", "math.ult")
142 n = checkinteger(n, "2", "math.ult")
143 if m >= 0 and n < 0 then
144 return true
145 elseif m < 0 and n >= 0 then
146 return false
147 else
148 return m < n
149 end
150 end
151 end
152
153
154 -- assert should allow non-string error objects
155 function M.assert(cond, ...)
156 if cond then
157 return cond, ...
158 elseif select('#', ...) > 0 then
159 error((...), 0)
160 else
161 error("assertion failed!", 0)
162 end
163 end
164
165
166 -- ipairs should respect __index metamethod
167 do
168 local function ipairs_iterator(st, var)
169 var = var + 1
170 local val = st[var]
171 if val ~= nil then
172 return var, st[var]
173 end
174 end
175 function M.ipairs(t)
176 if gmt(t) ~= nil then -- t has metatable
177 return ipairs_iterator, t, 0
178 else
179 return ipairs(t)
180 end
181 end
182 end
183
184
185 -- make '*' optional for io.read and io.lines
186 do
187 local function addasterisk(fmt)
188 if type(fmt) == "string" and fmt:sub(1, 1) ~= "*" then
189 return "*"..fmt
190 else
191 return fmt
192 end
193 end
194
195 function M.io.read(...)
196 local n = select('#', ...)
197 for i = 1, n do
198 local a = select(i, ...)
199 local b = addasterisk(a)
200 -- as an optimization we only allocate a table for the
201 -- modified format arguments when we have a '*' somewhere.
202 if a ~= b then
203 local args = { ... }
204 args[i] = b
205 for j = i+1, n do
206 args[j] = addasterisk(args[j])
207 end
208 return io_read(unpack(args, 1, n))
209 end
210 end
211 return io_read(...)
212 end
213
214 -- PUC-Rio Lua 5.1 uses a different implementation for io.lines!
215 function M.io.lines(...)
216 local n = select('#', ...)
217 for i = 2, n do
218 local a = select(i, ...)
219 local b = addasterisk(a)
220 -- as an optimization we only allocate a table for the
221 -- modified format arguments when we have a '*' somewhere.
222 if a ~= b then
223 local args = { ... }
224 args[i] = b
225 for j = i+1, n do
226 args[j] = addasterisk(args[j])
227 end
228 return io_lines(unpack(args, 1, n))
229 end
230 end
231 return io_lines(...)
232 end
233 end
234
235
236 -- update table library (if C module not available)
237 if not table_ok then
238 local table_concat = table.concat
239 local table_insert = table.insert
240 local table_remove = table.remove
241 local table_sort = table.sort
242
243 function M.table.concat(list, sep, i, j)
244 local mt = gmt(list)
245 if type(mt) == "table" and type(mt.__len) == "function" then
246 local src = list
247 list, i, j = {}, i or 1, j or mt.__len(src)
248 for k = i, j do
249 list[k] = src[k]
250 end
251 end
252 return table_concat(list, sep, i, j)
253 end
254
255 function M.table.insert(list, ...)
256 local mt = gmt(list)
257 local has_mt = type(mt) == "table"
258 local has_len = has_mt and type(mt.__len) == "function"
259 if has_mt and (has_len or mt.__index or mt.__newindex) then
260 local e = (has_len and mt.__len(list) or #list)+1
261 local nargs, pos, value = select('#', ...), ...
262 if nargs == 1 then
263 pos, value = e, pos
264 elseif nargs == 2 then
265 pos = checkinteger(pos, "2", "table.insert")
266 argcheck(1 <= pos and pos <= e, "2", "table.insert",
267 "position out of bounds" )
268 else
269 error("wrong number of arguments to 'insert'", 0)
270 end
271 for i = e-1, pos, -1 do
272 list[i+1] = list[i]
273 end
274 list[pos] = value
275 else
276 return table_insert(list, ...)
277 end
278 end
279
280 function M.table.move(a1, f, e, t, a2)
281 a2 = a2 or a1
282 f = checkinteger(f, "2", "table.move")
283 argcheck(f > 0, "2", "table.move",
284 "initial position must be positive")
285 e = checkinteger(e, "3", "table.move")
286 t = checkinteger(t, "4", "table.move")
287 if e >= f then
288 local m, n, d = 0, e-f, 1
289 if t > f then m, n, d = n, m, -1 end
290 for i = m, n, d do
291 a2[t+i] = a1[f+i]
292 end
293 end
294 return a2
295 end
296
297 function M.table.remove(list, pos)
298 local mt = gmt(list)
299 local has_mt = type(mt) == "table"
300 local has_len = has_mt and type(mt.__len) == "function"
301 if has_mt and (has_len or mt.__index or mt.__newindex) then
302 local e = (has_len and mt.__len(list) or #list)
303 pos = pos ~= nil and checkinteger(pos, "2", "table.remove") or e
304 if pos ~= e then
305 argcheck(1 <= pos and pos <= e+1, "2", "table.remove",
306 "position out of bounds" )
307 end
308 local result = list[pos]
309 while pos < e do
310 list[pos] = list[pos+1]
311 pos = pos + 1
312 end
313 list[pos] = nil
314 return result
315 else
316 return table_remove(list, pos)
317 end
318 end
319
320 do
321 local function pivot(list, cmp, a, b)
322 local m = b - a
323 if m > 2 then
324 local c = a + (m-m%2)/2
325 local x, y, z = list[a], list[b], list[c]
326 if not cmp(x, y) then
327 x, y, a, b = y, x, b, a
328 end
329 if not cmp(y, z) then
330 y, b = z, c
331 end
332 if not cmp(x, y) then
333 y, b = x, a
334 end
335 return b, y
336 else
337 return b, list[b]
338 end
339 end
340
341 local function lt_cmp(a, b)
342 return a < b
343 end
344
345 local function qsort(list, cmp, b, e)
346 if b < e then
347 local i, j, k, val = b, e, pivot(list, cmp, b, e)
348 while i < j do
349 while i < j and cmp(list[i], val) do
350 i = i + 1
351 end
352 while i < j and not cmp(list[j], val) do
353 j = j - 1
354 end
355 if i < j then
356 list[i], list[j] = list[j], list[i]
357 if i == k then k = j end -- update pivot position
358 i, j = i+1, j-1
359 end
360 end
361 if i ~= k and not cmp(list[i], val) then
362 list[i], list[k] = val, list[i]
363 k = i -- update pivot position
364 end
365 qsort(list, cmp, b, i == k and i-1 or i)
366 return qsort(list, cmp, i+1, e)
367 end
368 end
369
370 function M.table.sort(list, cmp)
371 local mt = gmt(list)
372 local has_mt = type(mt) == "table"
373 local has_len = has_mt and type(mt.__len) == "function"
374 if has_len then
375 cmp = cmp or lt_cmp
376 local len = mt.__len(list)
377 return qsort(list, cmp, 1, len)
378 else
379 return table_sort(list, cmp)
380 end
381 end
382 end
383
384 local function unpack_helper(list, i, j, ...)
385 if j < i then
386 return ...
387 else
388 return unpack_helper(list, i, j-1, list[j], ...)
389 end
390 end
391 function M.table.unpack(list, i, j)
392 local mt = gmt(list)
393 local has_mt = type(mt) == "table"
394 local has_len = has_mt and type(mt.__len) == "function"
395 if has_mt and (has_len or mt.__index) then
396 i, j = i or 1, j or (has_len and mt.__len(list)) or #list
397 return unpack_helper(list, i, j)
398 else
399 return unpack(list, i, j)
400 end
401 end
402 end -- update table library
403
404
405 -- bring Lua 5.1 (and LuaJIT) up to speed with Lua 5.2
406 if lua_version == "5.1" then
407 -- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag)
408 local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ"
409 local is_luajit52 = is_luajit and
410 #setmetatable({}, { __len = function() return 1 end }) == 1
411
412 -- cache globals in upvalues
413 local load, loadfile, loadstring, setfenv, xpcall =
414 load, loadfile, loadstring, setfenv, xpcall
415 local coroutine, os = coroutine, os
416 local coroutine_create = coroutine.create
417 local coroutine_resume = coroutine.resume
418 local coroutine_running = coroutine.running
419 local coroutine_status = coroutine.status
420 local coroutine_yield = coroutine.yield
421 local io_input = io.input
422 local io_open = io.open
423 local io_output = io.output
424 local io_write = io.write
425 local math_log = math.log
426 local os_execute = os.execute
427 local string_find = string.find
428 local string_format = string.format
429 local string_gmatch = string.gmatch
430 local string_gsub = string.gsub
431 local string_match = string.match
432 local string_rep = string.rep
433 local table_concat = table.concat
434
435 -- create subtables
436 M.coroutine = setmetatable({}, { __index = coroutine })
437 M.os = setmetatable({}, { __index = os })
438 M.package = setmetatable({}, { __index = package })
439
440 -- handle debug functions
441 if type(debug) == "table" then
442 local debug_setfenv = debug.setfenv
443 local debug_getfenv = debug.getfenv
444 local debug_setmetatable = debug.setmetatable
445
446 M.debug = setmetatable({}, { __index = debug })
447
448 if not is_luajit52 then
449 function M.debug.setuservalue(obj, value)
450 if type(obj) ~= "userdata" then
451 error("bad argument #1 to 'setuservalue' (userdata expected, got "..
452 type(obj)..")", 2)
453 end
454 if value == nil then value = _G end
455 if type(value) ~= "table" then
456 error("bad argument #2 to 'setuservalue' (table expected, got "..
457 type(value)..")", 2)
458 end
459 return debug_setfenv(obj, value)
460 end
461
462 function M.debug.getuservalue(obj)
463 if type(obj) ~= "userdata" then
464 return nil
465 else
466 local v = debug_getfenv(obj)
467 if v == _G or v == package then
468 return nil
469 end
470 return v
471 end
472 end
473
474 function M.debug.setmetatable(value, tab)
475 debug_setmetatable(value, tab)
476 return value
477 end
478 end -- not luajit with compat52 enabled
479 end -- debug table available
480
481
482 if not is_luajit52 then
483 function M.pairs(t)
484 local mt = gmt(t)
485 if type(mt) == "table" and type(mt.__pairs) == "function" then
486 return mt.__pairs(t)
487 else
488 return pairs(t)
489 end
490 end
491 end
492
493
494 if not is_luajit then
495 local function check_mode(mode, prefix)
496 local has = { text = false, binary = false }
497 for i = 1,#mode do
498 local c = mode:sub(i, i)
499 if c == "t" then has.text = true end
500 if c == "b" then has.binary = true end
501 end
502 local t = prefix:sub(1, 1) == "\27" and "binary" or "text"
503 if not has[t] then
504 return "attempt to load a "..t.." chunk (mode is '"..mode.."')"
505 end
506 end
507
508 function M.load(ld, source, mode, env)
509 mode = mode or "bt"
510 local chunk, msg
511 if type( ld ) == "string" then
512 if mode ~= "bt" then
513 local merr = check_mode(mode, ld)
514 if merr then return nil, merr end
515 end
516 chunk, msg = loadstring(ld, source)
517 else
518 local ld_type = type(ld)
519 if ld_type ~= "function" then
520 error("bad argument #1 to 'load' (function expected, got "..
521 ld_type..")", 2)
522 end
523 if mode ~= "bt" then
524 local checked, merr = false, nil
525 local function checked_ld()
526 if checked then
527 return ld()
528 else
529 checked = true
530 local v = ld()
531 merr = check_mode(mode, v or "")
532 if merr then return nil end
533 return v
534 end
535 end
536 chunk, msg = load(checked_ld, source)
537 if merr then return nil, merr end
538 else
539 chunk, msg = load(ld, source)
540 end
541 end
542 if not chunk then
543 return chunk, msg
544 end
545 if env ~= nil then
546 setfenv(chunk, env)
547 end
548 return chunk
549 end
550
551 M.loadstring = M.load
552
553 function M.loadfile(file, mode, env)
554 mode = mode or "bt"
555 if mode ~= "bt" then
556 local f = io_open(file, "rb")
557 if f then
558 local prefix = f:read(1)
559 f:close()
560 if prefix then
561 local merr = check_mode(mode, prefix)
562 if merr then return nil, merr end
563 end
564 end
565 end
566 local chunk, msg = loadfile(file)
567 if not chunk then
568 return chunk, msg
569 end
570 if env ~= nil then
571 setfenv(chunk, env)
572 end
573 return chunk
574 end
575 end -- not luajit
576
577
578 if not is_luajit52 then
579 function M.rawlen(v)
580 local t = type(v)
581 if t ~= "string" and t ~= "table" then
582 error("bad argument #1 to 'rawlen' (table or string expected)", 2)
583 end
584 return #v
585 end
586 end
587
588
589 if not is_luajit then
590 function M.xpcall(f, msgh, ...)
591 local args, n = { ... }, select('#', ...)
592 return xpcall(function() return f(unpack(args, 1, n)) end, msgh)
593 end
594 end
595
596
597 if not is_luajit52 then
598 function M.os.execute(cmd)
599 local code = os_execute(cmd)
600 -- Lua 5.1 does not report exit by signal.
601 if code == 0 then
602 return true, "exit", code
603 else
604 if package.config:sub(1, 1) == '/' then
605 code = code/256 -- only correct on Linux!
606 end
607 return nil, "exit", code
608 end
609 end
610 end
611
612
613 if not table_ok and not is_luajit52 then
614 M.table.pack = function(...)
615 return { n = select('#', ...), ... }
616 end
617 end
618
619
620 local main_coroutine = coroutine_create(function() end)
621
622 function M.coroutine.create(func)
623 local success, result = pcall(coroutine_create, func)
624 if not success then
625 if type(func) ~= "function" then
626 error("bad argument #1 (function expected)", 0)
627 end
628 result = coroutine_create(function(...) return func(...) end)
629 end
630 return result
631 end
632
633 if not is_luajit52 then
634 function M.coroutine.running()
635 local co = coroutine_running()
636 if co then
637 return co, false
638 else
639 return main_coroutine, true
640 end
641 end
642 end
643
644 function M.coroutine.yield(...)
645 local co, flag = coroutine_running()
646 if co and not flag then
647 return coroutine_yield(...)
648 else
649 error("attempt to yield from outside a coroutine", 0)
650 end
651 end
652
653 if not is_luajit then
654 function M.coroutine.resume(co, ...)
655 if co == main_coroutine then
656 return false, "cannot resume non-suspended coroutine"
657 else
658 return coroutine_resume(co, ...)
659 end
660 end
661
662 function M.coroutine.status(co)
663 local notmain = coroutine_running()
664 if co == main_coroutine then
665 return notmain and "normal" or "running"
666 else
667 return coroutine_status(co)
668 end
669 end
670 end -- not luajit
671
672
673 if not is_luajit then
674 M.math.log = function(x, base)
675 if base ~= nil then
676 return math_log(x)/math_log(base)
677 else
678 return math_log(x)
679 end
680 end
681 end
682
683
684 if not is_luajit then
685 function M.package.searchpath(name, path, sep, rep)
686 sep = (sep or "."):gsub("(%p)", "%%%1")
687 rep = (rep or package.config:sub(1, 1)):gsub("(%%)", "%%%1")
688 local pname = name:gsub(sep, rep):gsub("(%%)", "%%%1")
689 local msg = {}
690 for subpath in path:gmatch("[^;]+") do
691 local fpath = subpath:gsub("%?", pname)
692 local f = io_open(fpath, "r")
693 if f then
694 f:close()
695 return fpath
696 end
697 msg[#msg+1] = "\n\tno file '" .. fpath .. "'"
698 end
699 return nil, table_concat(msg)
700 end
701 end
702
703
704 local function fix_pattern(pattern)
705 return (string_gsub(pattern, "%z", "%%z"))
706 end
707
708 function M.string.find(s, pattern, ...)
709 return string_find(s, fix_pattern(pattern), ...)
710 end
711
712 function M.string.gmatch(s, pattern)
713 return string_gmatch(s, fix_pattern(pattern))
714 end
715
716 function M.string.gsub(s, pattern, ...)
717 return string_gsub(s, fix_pattern(pattern), ...)
718 end
719
720 function M.string.match(s, pattern, ...)
721 return string_match(s, fix_pattern(pattern), ...)
722 end
723
724 if not is_luajit then
725 function M.string.rep(s, n, sep)
726 if sep ~= nil and sep ~= "" and n >= 2 then
727 return s .. string_rep(sep..s, n-1)
728 else
729 return string_rep(s, n)
730 end
731 end
732 end
733
734 if not is_luajit then
735 do
736 local addqt = {
737 ["\n"] = "\\\n",
738 ["\\"] = "\\\\",
739 ["\""] = "\\\""
740 }
741
742 local function addquoted(c, d)
743 return (addqt[c] or string_format(d~="" and "\\%03d" or "\\%d", c:byte()))..d
744 end
745
746 function M.string.format(fmt, ...)
747 local args, n = { ... }, select('#', ...)
748 local i = 0
749 local function adjust_fmt(lead, mods, kind)
750 if #lead % 2 == 0 then
751 i = i + 1
752 if kind == "s" then
753 args[i] = _G.tostring(args[i])
754 elseif kind == "q" then
755 args[i] = '"'..string_gsub(args[i], "([%z%c\\\"\n])(%d?)", addquoted)..'"'
756 return lead.."%"..mods.."s"
757 end
758 end
759 end
760 fmt = string_gsub(fmt, "(%%*)%%([%d%.%-%+%# ]*)(%a)", adjust_fmt)
761 return string_format(fmt, unpack(args, 1, n))
762 end
763 end
764 end
765
766
767 function M.io.write(...)
768 local res, msg, errno = io_write(...)
769 if res then
770 return io_output()
771 else
772 return nil, msg, errno
773 end
774 end
775
776 if not is_luajit then
777 local function helper(st, var_1, ...)
778 if var_1 == nil then
779 if st.doclose then st.f:close() end
780 if (...) ~= nil then
781 error((...), 2)
782 end
783 end
784 return var_1, ...
785 end
786
787 local function lines_iterator(st)
788 return helper(st, st.f:read(unpack(st, 1, st.n)))
789 end
790
791 function M.io.lines(fname, ...)
792 local doclose, file, msg
793 if fname ~= nil then
794 doclose, file, msg = true, io_open(fname, "r")
795 if not file then error(msg, 2) end
796 else
797 doclose, file = false, io_input()
798 end
799 local st = { f=file, doclose=doclose, n=select('#', ...), ... }
800 for i = 1, st.n do
801 local t = type(st[i])
802 if t == "string" then
803 local fmt = st[i]:match("^%*?([aln])")
804 if not fmt then
805 error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2)
806 end
807 st[i] = "*"..fmt
808 elseif t ~= "number" then
809 error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2)
810 end
811 end
812 return lines_iterator, st
813 end
814 end -- not luajit
815
816 end -- lua 5.1
817
818 -- further write should be forwarded to _G
819 M_meta.__newindex = _G
820
821end -- lua < 5.3
822
823
824-- return module table
825return M
826
827-- vi: set expandtab softtabstop=3 shiftwidth=3 :