aboutsummaryrefslogtreecommitdiff
path: root/compat53.lua
diff options
context:
space:
mode:
Diffstat (limited to 'compat53.lua')
-rw-r--r--compat53.lua662
1 files changed, 658 insertions, 4 deletions
diff --git a/compat53.lua b/compat53.lua
index 761d554..03158f0 100644
--- a/compat53.lua
+++ b/compat53.lua
@@ -214,7 +214,70 @@ if lua_version < "5.3" then
214 end 214 end
215 end 215 end
216 216
217 -- TODO: table.sort 217 do
218 local function pivot(list, cmp, a, b)
219 local m = b - a
220 if m > 2 then
221 local c = a + (m-m%2)/2
222 local x, y, z = list[a], list[b], list[c]
223 if not cmp(x, y) then
224 x, y, a, b = y, x, b, a
225 end
226 if not cmp(y, z) then
227 y, z, b, c = z, y, c, b
228 end
229 if not cmp(x, y) then
230 x, y, a, b = y, x, b, a
231 end
232 return b, y
233 else
234 return b, list[b]
235 end
236 end
237
238 local function lt_cmp(a, b)
239 return a < b
240 end
241
242 local function qsort(list, cmp, b, e)
243 if b < e then
244 local i, j, k, val = b, e, pivot(list, cmp, b, e)
245 while i < j do
246 while i < j and cmp(list[i], val) do
247 i = i + 1
248 end
249 while i < j and not cmp(list[j], val) do
250 j = j - 1
251 end
252 if i < j then
253 list[i], list[j] = list[j], list[i]
254 if i == k then k = j end -- update pivot position
255 i, j = i+1, j-1
256 end
257 end
258 if i ~= k and not cmp(list[i], val) then
259 list[i], list[k] = val, list[i]
260 k = i -- update pivot position
261 end
262 qsort(list, cmp, b, i == k and i-1 or i)
263 return qsort(list, cmp, i+1, e)
264 end
265 end
266
267 local table_sort = table.sort
268 function table.sort(list, cmp)
269 local mt = gmt(list)
270 local has_mt = type(mt) == "table"
271 local has_len = has_mt and type(mt.__len) == "function"
272 if has_len then
273 cmp = cmp or lt_cmp
274 local len = mt.__len(list)
275 return qsort(list, cmp, 1, len)
276 else
277 return table_sort(list, cmp)
278 end
279 end
280 end
218 281
219 local table_unpack = lua_version == "5.1" and unpack or table.unpack 282 local table_unpack = lua_version == "5.1" and unpack or table.unpack
220 local function unpack_helper(list, i, j, ...) 283 local function unpack_helper(list, i, j, ...)
@@ -238,16 +301,607 @@ if lua_version < "5.3" then
238 end 301 end
239 302
240 303
304
305 -- bring Lua 5.1 (and LuaJIT) up to speed with Lua 5.2
241 if lua_version == "5.1" then 306 if lua_version == "5.1" then
242 -- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag) 307 -- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag)
243 local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ" 308 local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ"
244 local is_luajit52 = is_luajit and 309 local is_luajit52 = is_luajit and
245 #setmetatable({}, { __len = function() return 1 end }) == 1 310 #setmetatable({}, { __len = function() return 1 end }) == 1
246 311
247 -- TODO: add functions from lua-compat-5.2
248 312
249 end 313 -- table that maps each running coroutine to the coroutine that resumed it
314 -- this is used to build complete tracebacks when "coroutine-friendly" pcall
315 -- is used.
316 local pcall_previous = {}
317 local pcall_callOf = {}
318 local xpcall_running = {}
319 local coroutine_running = coroutine.running
320
321 -- handle debug functions
322 if type(debug) == "table" then
323
324 if not is_luajit52 then
325 local _G, package = _G, package
326 local debug_setfenv = debug.setfenv
327 function debug.setuservalue(obj, value)
328 if type(obj) ~= "userdata" then
329 error("bad argument #1 to 'setuservalue' (userdata expected, got "..
330 type(obj)..")", 0)
331 end
332 if value == nil then value = _G end
333 if type(value) ~= "table" then
334 error("bad argument #2 to 'setuservalue' (table expected, got "..
335 type(value)..")", 0)
336 end
337 return debug_setfenv(obj, value)
338 end
339
340 local debug_getfenv = debug.getfenv
341 function debug.getuservalue(obj)
342 if type(obj) ~= "userdata" then
343 return nil
344 else
345 local v = debug_getfenv(obj)
346 if v == _G or v == package then
347 return nil
348 end
349 return v
350 end
351 end
352
353 local debug_setmetatable = debug.setmetatable
354 function debug.setmetatable(value, tab)
355 debug_setmetatable(value, tab)
356 return value
357 end
358 end -- not luajit with compat52 enabled
359
360 local debug_getinfo = debug.getinfo
361 local function calculate_trace_level(co, level)
362 if level ~= nil then
363 for out = 1, 1/0 do
364 local info = (co==nil) and debug_getinfo(out, "") or debug_getinfo(co, out, "")
365 if info == nil then
366 local max = out-1
367 if level <= max then
368 return level
369 end
370 return nil, level-max
371 end
372 end
373 end
374 return 1
375 end
376
377 local stack_pattern = "\nstack traceback:"
378 local stack_replace = ""
379 local debug_traceback = debug.traceback
380 function debug.traceback(co, msg, level)
381 local lvl
382 local nilmsg
383 if type(co) ~= "thread" then
384 co, msg, level = coroutine_running(), co, msg
385 end
386 if msg == nil then
387 msg = ""
388 nilmsg = true
389 elseif type(msg) ~= "string" then
390 return msg
391 end
392 if co == nil then
393 msg = debug_traceback(msg, level or 1)
394 else
395 local xpco = xpcall_running[co]
396 if xpco ~= nil then
397 lvl, level = calculate_trace_level(xpco, level)
398 if lvl then
399 msg = debug_traceback(xpco, msg, lvl)
400 else
401 msg = msg..stack_pattern
402 end
403 lvl, level = calculate_trace_level(co, level)
404 if lvl then
405 local trace = debug_traceback(co, "", lvl)
406 msg = msg..trace:gsub(stack_pattern, stack_replace)
407 end
408 else
409 co = pcall_callOf[co] or co
410 lvl, level = calculate_trace_level(co, level)
411 if lvl then
412 msg = debug_traceback(co, msg, lvl)
413 else
414 msg = msg..stack_pattern
415 end
416 end
417 co = pcall_previous[co]
418 while co ~= nil do
419 lvl, level = calculate_trace_level(co, level)
420 if lvl then
421 local trace = debug_traceback(co, "", lvl)
422 msg = msg..trace:gsub(stack_pattern, stack_replace)
423 end
424 co = pcall_previous[co]
425 end
426 end
427 if nilmsg then
428 msg = msg:gsub("^\n", "")
429 end
430 msg = msg:gsub("\n\t%(tail call%): %?", "\000")
431 msg = msg:gsub("\n\t%.%.%.\n", "\001\n")
432 msg = msg:gsub("\n\t%.%.%.$", "\001")
433 msg = msg:gsub("(%z+)\001(%z+)", function(some, other)
434 return "\n\t(..."..#some+#other.."+ tail call(s)...)"
435 end)
436 msg = msg:gsub("\001(%z+)", function(zeros)
437 return "\n\t(..."..#zeros.."+ tail call(s)...)"
438 end)
439 msg = msg:gsub("(%z+)\001", function(zeros)
440 return "\n\t(..."..#zeros.."+ tail call(s)...)"
441 end)
442 msg = msg:gsub("%z+", function(zeros)
443 return "\n\t(..."..#zeros.." tail call(s)...)"
444 end)
445 msg = msg:gsub("\001", function(zeros)
446 return "\n\t..."
447 end)
448 return msg
449 end
450 end -- debug table available
451
452
453 if not is_luajit52 then
454 local _pairs = pairs
455 function pairs(t)
456 local mt = gmt(t)
457 if type(mt) == "table" and type(mt.__pairs) == "function" then
458 return mt.__pairs(t)
459 else
460 return _pairs(t)
461 end
462 end
463 end
464
465
466 if not is_luajit then
467 local function check_mode(mode, prefix)
468 local has = { text = false, binary = false }
469 for i = 1,#mode do
470 local c = mode:sub(i, i)
471 if c == "t" then has.text = true end
472 if c == "b" then has.binary = true end
473 end
474 local t = prefix:sub(1, 1) == "\27" and "binary" or "text"
475 if not has[t] then
476 return "attempt to load a "..t.." chunk (mode is '"..mode.."')"
477 end
478 end
479
480 local setfenv = setfenv
481 local _load, _loadstring = load, loadstring
482 function load(ld, source, mode, env)
483 mode = mode or "bt"
484 local chunk, msg
485 if type( ld ) == "string" then
486 if mode ~= "bt" then
487 local merr = check_mode(mode, ld)
488 if merr then return nil, merr end
489 end
490 chunk, msg = _loadstring(ld, source)
491 else
492 local ld_type = type(ld)
493 if ld_type ~= "function" then
494 error("bad argument #1 to 'load' (function expected, got "..
495 ld_type..")", 0)
496 end
497 if mode ~= "bt" then
498 local checked, merr = false, nil
499 local function checked_ld()
500 if checked then
501 return ld()
502 else
503 checked = true
504 local v = ld()
505 merr = check_mode(mode, v or "")
506 if merr then return nil end
507 return v
508 end
509 end
510 chunk, msg = _load(checked_ld, source)
511 if merr then return nil, merr end
512 else
513 chunk, msg = _load(ld, source)
514 end
515 end
516 if not chunk then
517 return chunk, msg
518 end
519 if env ~= nil then
520 setfenv(chunk, env)
521 end
522 return chunk
523 end
524
525 loadstring = load
526
527 local _loadfile = loadfile
528 local io_open = io.open
529 function loadfile(file, mode, env)
530 mode = mode or "bt"
531 if mode ~= "bt" then
532 local f = io_open(file, "rb")
533 if f then
534 local prefix = f:read(1)
535 f:close()
536 if prefix then
537 local merr = check_mode(mode, prefix)
538 if merr then return nil, merr end
539 end
540 end
541 end
542 local chunk, msg = _loadfile(file)
543 if not chunk then
544 return chunk, msg
545 end
546 if env ~= nil then
547 setfenv(chunk, env)
548 end
549 return chunk
550 end
551 end -- not luajit
552
553
554 if not is_luajit52 then
555 function rawlen(v)
556 local t = type(v)
557 if t ~= "string" and t ~= "table" then
558 error("bad argument #1 to 'rawlen' (table or string expected)", 0)
559 end
560 return #v
561 end
562 end
563
564
565 if not is_luajit52 then
566 local os_execute = os.execute
567 function os.execute(cmd)
568 local code = os_execute(cmd)
569 -- Lua 5.1 does not report exit by signal.
570 if code == 0 then
571 return true, "exit", code
572 else
573 return nil, "exit", code/256 -- only correct on POSIX!
574 end
575 end
576 end
577
578
579 if not is_luajit52 then
580 table.pack = function(...)
581 return { n = select('#', ...), ... }
582 end
583 end
584
585
586 local main_coroutine = coroutine.create(function() end)
587
588 local _pcall = pcall
589 local coroutine_create = coroutine.create
590 function coroutine.create(func)
591 local success, result = _pcall(coroutine_create, func)
592 if not success then
593 if type(func) ~= "function" then
594 error("bad argument #1 (function expected)", 0)
595 end
596 result = coroutine_create(function(...) return func(...) end)
597 end
598 return result
599 end
600
601 local pcall_mainOf = {}
602
603 if not is_luajit52 then
604 function coroutine.running()
605 local co = coroutine_running()
606 if co then
607 return pcall_mainOf[co] or co, false
608 else
609 return main_coroutine, true
610 end
611 end
612 end
613
614 local coroutine_yield = coroutine.yield
615 function coroutine.yield(...)
616 local co = coroutine_running()
617 if co then
618 return coroutine_yield(...)
619 else
620 error("attempt to yield from outside a coroutine", 0)
621 end
622 end
623
624 local coroutine_resume = coroutine.resume
625 function coroutine.resume(co, ...)
626 if co == main_coroutine then
627 return false, "cannot resume non-suspended coroutine"
628 else
629 return coroutine_resume(co, ...)
630 end
631 end
632
633 local coroutine_status = coroutine.status
634 function coroutine.status(co)
635 local notmain = coroutine_running()
636 if co == main_coroutine then
637 return notmain and "normal" or "running"
638 else
639 return coroutine_status(co)
640 end
641 end
642
643 local function pcall_results(current, call, success, ...)
644 if coroutine_status(call) == "suspended" then
645 return pcall_results(current, call, coroutine_resume(call, coroutine_yield(...)))
646 end
647 if pcall_previous then
648 pcall_previous[call] = nil
649 local main = pcall_mainOf[call]
650 if main == current then current = nil end
651 pcall_callOf[main] = current
652 end
653 pcall_mainOf[call] = nil
654 return success, ...
655 end
656 local function pcall_exec(current, call, ...)
657 local main = pcall_mainOf[current] or current
658 pcall_mainOf[call] = main
659 if pcall_previous then
660 pcall_previous[call] = current
661 pcall_callOf[main] = call
662 end
663 return pcall_results(current, call, coroutine_resume(call, ...))
664 end
665 local coroutine_create52 = coroutine.create
666 local function pcall_coroutine(func)
667 if type(func) ~= "function" then
668 local callable = func
669 func = function (...) return callable(...) end
670 end
671 return coroutine_create52(func)
672 end
673 function pcall(func, ...)
674 local current = coroutine_running()
675 if not current then return _pcall(func, ...) end
676 return pcall_exec(current, pcall_coroutine(func), ...)
677 end
678
679 local function xpcall_catch(current, call, msgh, success, ...)
680 if not success then
681 xpcall_running[current] = call
682 local ok, result = _pcall(msgh, ...)
683 xpcall_running[current] = nil
684 if not ok then
685 return false, "error in error handling ("..tostring(result)..")"
686 end
687 return false, result
688 end
689 return true, ...
690 end
691 local _xpcall = xpcall
692 local _unpack = unpack
693 function xpcall(f, msgh, ...)
694 local current = coroutine_running()
695 if not current then
696 local args, n = { ... }, select('#', ...)
697 return _xpcall(function() return f(_unpack(args, 1, n)) end, msgh)
698 end
699 local call = pcall_coroutine(f)
700 return xpcall_catch(current, call, msgh, pcall_exec(current, call, ...))
701 end
702
703
704 if not is_luajit then
705 local math_log = math.log
706 math.log = function(x, base)
707 if base ~= nil then
708 return math_log(x)/math_log(base)
709 else
710 return math_log(x)
711 end
712 end
713 end
714
715
716 if not is_luajit then
717 local io_open = io.open
718 local table_concat = table.concat
719 function package.searchpath(name, path, sep, rep)
720 sep = (sep or "."):gsub("(%p)", "%%%1")
721 rep = (rep or package.config:sub(1, 1)):gsub("(%%)", "%%%1")
722 local pname = name:gsub(sep, rep):gsub("(%%)", "%%%1")
723 local msg = {}
724 for subpath in path:gmatch("[^;]+") do
725 local fpath = subpath:gsub("%?", pname)
726 local f = io_open(fpath, "r")
727 if f then
728 f:close()
729 return fpath
730 end
731 msg[#msg+1] = "\n\tno file '" .. fpath .. "'"
732 end
733 return nil, table_concat(msg)
734 end
735 end
736
737 local p_index = { searchers = package.loaders }
738 local rawset = rawset
739 setmetatable(package, {
740 __index = p_index,
741 __newindex = function(p, k, v)
742 if k == "searchers" then
743 rawset(p, "loaders", v)
744 p_index.searchers = v
745 else
746 rawset(p, k, v)
747 end
748 end
749 })
750
751
752 local string_gsub = string.gsub
753 local function fix_pattern(pattern)
754 return (string_gsub(pattern, "%z", "%%z"))
755 end
756
757 local string_find = string.find
758 function string.find(s, pattern, ...)
759 return string_find(s, fix_pattern(pattern), ...)
760 end
761
762 local string_gmatch = string.gmatch
763 function string.gmatch(s, pattern)
764 return string_gmatch(s, fix_pattern(pattern))
765 end
766
767 function string.gsub(s, pattern, ...)
768 return string_gsub(s, fix_pattern(pattern), ...)
769 end
770
771 local string_match = string.match
772 function string.match(s, pattern, ...)
773 return string_match(s, fix_pattern(pattern), ...)
774 end
775
776 if not is_luajit then
777 local string_rep = string.rep
778 function string.rep(s, n, sep)
779 if sep ~= nil and sep ~= "" and n >= 2 then
780 return s .. string_rep(sep..s, n-1)
781 else
782 return string_rep(s, n)
783 end
784 end
785 end
786
787 if not is_luajit then
788 local string_format = string.format
789 do
790 local addqt = {
791 ["\n"] = "\\\n",
792 ["\\"] = "\\\\",
793 ["\""] = "\\\""
794 }
795
796 local function addquoted(c)
797 return addqt[c] or string_format("\\%03d", c:byte())
798 end
799
800 function string.format(fmt, ...)
801 local args, n = { ... }, select('#', ...)
802 local i = 0
803 local function adjust_fmt(lead, mods, kind)
804 if #lead % 2 == 0 then
805 i = i + 1
806 if kind == "s" then
807 args[i] = tostring(args[i])
808 elseif kind == "q" then
809 args[i] = '"'..string_gsub(args[i], "[%z%c\\\"\n]", addquoted)..'"'
810 return lead.."%"..mods.."s"
811 end
812 end
813 end
814 fmt = string_gsub(fmt, "(%%*)%%([%d%.%-%+%# ]*)(%a)", adjust_fmt)
815 return string_format(fmt, _unpack(args, 1, n))
816 end
817 end
818 end
819
820
821 local io_open = io.open
822 local io_write = io.write
823 local io_output = io.output
824 function io.write(...)
825 local res, msg, errno = io_write(...)
826 if res then
827 return io_output()
828 else
829 return nil, msg, errno
830 end
831 end
832
833 if not is_luajit then
834 local lines_iterator
835 do
836 local function helper( st, var_1, ... )
837 if var_1 == nil then
838 if st.doclose then st.f:close() end
839 if (...) ~= nil then
840 error((...), 0)
841 end
842 end
843 return var_1, ...
844 end
845
846 function lines_iterator(st)
847 return helper(st, st.f:read(_unpack(st, 1, st.n)))
848 end
849 end
850
851 local valid_format = { ["*l"] = true, ["*n"] = true, ["*a"] = true }
852
853 local io_input = io.input
854 function io.lines(fname, ...)
855 local doclose, file, msg
856 if fname ~= nil then
857 doclose, file, msg = true, io_open(fname, "r")
858 if not file then error(msg, 0) end
859 else
860 doclose, file = false, io_input()
861 end
862 local st = { f=file, doclose=doclose, n=select('#', ...), ... }
863 for i = 1, st.n do
864 if type(st[i]) ~= "number" and not valid_format[st[i]] then
865 error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 0)
866 end
867 end
868 return lines_iterator, st
869 end
870
871 do
872 local io_stdout = io.stdout
873 local io_type = io.type
874 local file_meta = gmt(io_stdout)
875 if type(file_meta) == "table" and type(file_meta.__index) == "table" then
876 local file_write = file_meta.__index.write
877 file_meta.__index.write = function(self, ...)
878 local res, msg, errno = file_write(self, ...)
879 if res then
880 return self
881 else
882 return nil, msg, errno
883 end
884 end
885
886 file_meta.__index.lines = function(self, ...)
887 if io_type(self) == "closed file" then
888 error("attempt to use a closed file", 0)
889 end
890 local st = { f=self, doclose=false, n=select('#', ...), ... }
891 for i = 1, st.n do
892 if type(st[i]) ~= "number" and not valid_format[st[i]] then
893 error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 0)
894 end
895 end
896 return lines_iterator, st
897 end
898 end
899 end
900 end -- not luajit
901
902
903 end -- lua 5.1
250 904
251end 905end -- lua < 5.3
252 906
253-- vi: set expandtab softtabstop=3 shiftwidth=3 : 907-- vi: set expandtab softtabstop=3 shiftwidth=3 :