Tradução: Sérgio Queiroz de Medeiros", 1)
+ header = string.gsub(header, "Lua (%d+.%d+) Reference Manual",
+ "Manual de Referência de Lua %1")
+ header = string.gsub(header, "All rights reserved",
+ "Todos os direitos reservados")
+end
+
+
+---------------------------------------------------------------
+
+local function compose (f,g)
+ assert(f and g)
+ return function (s) return g(f(s)) end
+end
+
+local function concat (f, g)
+ assert(f and g)
+ return function (s) return f(s) .. g(s) end
+end
+
+
+local Tag = {}
+
+
+setmetatable(Tag, {
+ __index = function (t, tag)
+ local v = function (n, att)
+ local e = ""
+ if type(att) == "table" then
+ for k,v in pairs(att) do e = string.format('%s %s="%s"', e, k, v) end
+ end
+ if n then
+ return string.format("<%s%s>%s%s>", tag, e, n, tag)
+ else
+ return string.format("<%s%s>", tag, e)
+ end
+ end
+ t[tag] = v
+ return v
+ end
+})
+
+
+
+---------------------------------------------------------------
+local labels = {}
+
+
+local function anchor (text, label, link, textlink)
+ if labels[label] then
+ error("label " .. label .. " already defined")
+ end
+ labels[label] = {text = textlink, link = link}
+ return Tag.a(text, {name=link})
+end
+
+local function makeref (label)
+ assert(not string.find(label, "|"))
+ return string.format("\3%s\3", label)
+end
+
+local function ref (label)
+ local l = labels[label]
+ if not l then
+ io.stderr:write("label ", label, " undefined\n")
+ return "@@@@@@@"
+ else
+ return Tag.a(l.text, {href="#"..l.link})
+ end
+end
+
+---------------------------------------------------------------
+local function nopara (t)
+ t = string.gsub(t, "\1", "\n\n")
+ t = string.gsub(t, "
%s*
", "")
+ return t
+end
+
+local function fixpara (t)
+ t = string.gsub(t, "\1", "\n
\n\n
\n")
+ t = string.gsub(t, "
%s*
", "")
+ return t
+end
+
+local function antipara (t)
+ return "
\n" .. t .. "
"
+end
+
+
+Tag.pre = compose(Tag.pre, antipara)
+Tag.ul = compose(Tag.ul, antipara)
+
+---------------------------------------------------------------
+local Gfoots = 0
+local footnotes = {}
+
+local line = Tag.hr(nil)
+
+local function dischargefoots ()
+ if #footnotes == 0 then return "" end
+ local fn = table.concat(footnotes)
+ footnotes = {}
+ return line .. Tag.h3"footnotes:" .. fn .. line
+end
+
+
+local Glists = 0
+local listings = {}
+
+local function dischargelist ()
+ if #listings == 0 then return "" end
+ local l = listings
+ listings = {}
+ return line .. table.concat(l, line..line) .. line
+end
+
+---------------------------------------------------------------
+local counters = {
+h1 = {val = 1},
+h2 = {father = "h1", val = 1},
+h3 = {father = "h2", val = 1},
+listing = {father = "h1", val = 1},
+}
+
+local function inccounter (count)
+ counters[count].val = counters[count].val + 1
+ for c, v in pairs(counters) do
+ if v.father == count then v.val = 1 end
+ end
+end
+
+local function getcounter (count)
+ local c = counters[count]
+ if c.father then
+ return getcounter(c.father) .. "." .. c.val
+ else
+ return c.val .. ""
+ end
+end
+---------------------------------------------------------------
+
+
+local function fixed (x)
+ return function () return x end
+end
+
+local function id (x) return x end
+
+
+local function prepos (x, y)
+ assert(x and y)
+ return function (s) return string.format("%s%s%s", x, s, y) end
+end
+
+
+local rw = Tag.b
+
+
+
+
+local function LuaName (name)
+ return Tag.code(name)
+end
+
+
+local function getparam (s)
+ local i, e = string.find(s, "^[^%s@|]+|")
+ if not i then return nil, s
+ else return string.sub(s, i, e - 1), string.sub(s, e + 1)
+ end
+end
+
+
+local function gettitle (h)
+ local title, p = assert(string.match(h, "
(.-)()"))
+ return title, string.sub(h, p)
+end
+
+local function getparamtitle (what, h, nonum)
+ local label, title, c, count
+ label, h = getparam(h)
+ title, h = gettitle(h)
+ if not nonum then
+ count = getcounter(what)
+ inccounter(what)
+ c = string.format("%s – ", count)
+ else
+ c = ""
+ end
+ label = label or count
+ if label then
+ title = anchor(title, label, count, "§"..count)
+ end
+ title = string.format("%s%s", c, title)
+ return title, h
+end
+
+local function section (what, nonum)
+ return function (h)
+ local title
+ title, h = getparamtitle(what, h, nonum)
+ local fn = what == "h1" and dischargefoots() or ""
+ h = fixpara(Tag.p(h))
+ return "\n" .. Tag[what](title) .. h .. fn ..
+ dischargelist() .. "
"
+ end
+end
+
+
+local function verbatim (s)
+ s = nopara(s)
+ s = string.gsub(s, "\n", "\n ")
+ s = string.gsub(s, "\n%s*$", "\n")
+ return Tag.pre(s)
+end
+
+
+local function verb (s)
+ return Tag.code(s)
+end
+
+
+local function lua2link (e)
+ return string.find(e, "luaL?_") and e or "pdf-"..e
+end
+
+
+local verbfixed = verb
+
+
+local Tex = {
+
+ANSI = function (func)
+ return "ISO C function " .. Tag.code(func)
+ end,
+At = fixed"@",
+B = Tag.b,
+bigskip = fixed"",
+bignum = id,
+C = fixed"",
+Ci = prepos(""),
+CId = function (func)
+ return "C function " .. Tag.code(func)
+ end,
+chapter = section"h1",
+Char = compose(verbfixed, prepos("'", "'")),
+Cdots = fixed"···",
+Close = fixed"}",
+col = Tag.td,
+defid = function (name)
+ local l = lua2link(name)
+ local c = Tag.code(name)
+ return anchor(c, l, l, c)
+ end,
+def = Tag.em,
+description = compose(nopara, Tag.ul),
+Em = fixed("\4" .. "—" .. "\4"),
+emph = Tag.em,
+emphx = Tag.em, -- emphasis plus index (if there was an index)
+En = fixed("–"),
+format = fixed"",
+["false"] = fixed(Tag.b"false"),
+id = Tag.code,
+idx = Tag.code,
+index = fixed"",
+Lidx = fixed"", -- Tag.code,
+ldots = fixed"...",
+x = id,
+itemize = compose(nopara, Tag.ul),
+leq = fixed"≤",
+Lid = function (s)
+ return makeref(lua2link(s))
+ end,
+M = Tag.em,
+N = function (s) return (string.gsub(s, " ", " ")) end,
+NE = id, -- tag"foreignphrase",
+num = id,
+["nil"] = fixed(Tag.b"nil"),
+Open = fixed"{",
+part = section("h1", true),
+Pat = compose(verbfixed, prepos("'", "'")),
+preface = section("h1", true),
+psect = section("h2", true),
+Q = prepos('"', '"'),
+refchp = makeref,
+refcode = makeref,
+refsec = makeref,
+
+pi = fixed"π",
+rep = Tag.em, -- compose(prepos("<", ">"), Tag.em),
+Rw = rw,
+rw = rw,
+sb = Tag.sub,
+sp = Tag.sup,
+St = compose(verbfixed, prepos('"', '"')),
+sect1 = section"h1",
+sect2 = section"h2",
+sect3 = section"h3",
+sect4 = section("h4", true),
+simplesect = id,
+Tab2 = function (s) return Tag.table(s, {border=1}) end,
+row = Tag.tr,
+title = Tag.title,
+todo = Tag.todo,
+["true"] = fixed(Tag.b"true"),
+T = verb,
+
+item = function (s)
+ local t, p = string.match(s, "^([^\n|]+)|()")
+ if t then
+ s = string.sub(s, p)
+ s = Tag.b(t..": ") .. s
+ end
+ return Tag.li(fixpara(s))
+ end,
+
+verbatim = verbatim,
+
+manual = id,
+
+
+-- for the manual
+
+link =function (s)
+ local l, t = getparam(s)
+ assert(l)
+ return string.format("%s (%s)", t, makeref(l))
+end,
+
+see = function (s) return string.format(seefmt, makeref(s)) end,
+See = makeref,
+seeC = function (s)
+ return string.format(seefmt, makeref(s))
+ end,
+
+seeF = function (s)
+ return string.format(seefmt, makeref(lua2link(s)))
+ end,
+
+APIEntry = function (e)
+ local h, name
+ h, e = string.match(e, "^%s*(.-)%s*|(.*)$")
+ name = string.match(h, "(luaL?_[%w_]+)%)? +%(") or
+ string.match(h, "luaL?_[%w_]+")
+ local a = anchor(Tag.code(name), name, name, Tag.code(name))
+ local apiicmd, ne = string.match(e, "^(.-)(.*)")
+--io.stderr:write(e)
+ if not apiicmd then
+ return antipara(Tag.hr() .. Tag.h3(a)) .. Tag.pre(h) .. e
+ else
+ return antipara(Tag.hr() .. Tag.h3(a)) .. apiicmd .. Tag.pre(h) .. ne
+ end
+end,
+
+LibEntry = function (e)
+ local h, name
+ h, e = string.match(e, "^(.-)|(.*)$")
+ name = string.gsub(h, " (.+", "")
+ local l = lua2link(name)
+ local a = anchor(Tag.code(h), l, l, Tag.code(name))
+ return Tag.hr() .. Tag.h3(a) .. e
+end,
+
+Produc = compose(nopara, Tag.pre),
+producname = prepos("\t", " ::= "),
+Or = fixed" | ",
+VerBar = fixed"|", -- vertical bar
+OrNL = fixed" | \4",
+bnfNter = prepos("", ""),
+bnfopt = prepos("[", "]"),
+bnfrep = prepos("{", "}"),
+bnfter = compose(Tag.b, prepos("‘", "’")),
+producbody = function (s)
+ s = string.gsub(s, "%s+", " ")
+ s = string.gsub(s, "\4", "\n\t\t")
+ return s
+ end,
+
+apii = function (s)
+ local pop,push,err = string.match(s, "^(.-),(.-),(.*)$")
+ if pop ~= "?" and string.find(pop, "%W") then
+ pop = "(" .. pop .. ")"
+ end
+ if push ~= "?" and string.find(push, "%W") then
+ push = "(" .. push .. ")"
+ end
+ err = (err == "-") and "–" or Tag.em(err)
+ return Tag.span(
+ string.format("[-%s, +%s, %s]", pop, push, err),
+ {class="apii"}
+ )
+ end,
+}
+
+local others = prepos("?? "," ??")
+
+local function trata (t)
+ t = string.gsub(t, "@(%w+)(%b{})", function (w, f)
+ f = trata(string.sub(f, 2, -2))
+ if type(Tex[w]) ~= "function" then
+ io.stderr:write(w .. "\n")
+ return others(f)
+ else
+ return Tex[w](f, w)
+ end
+ end)
+ return t
+end
+
+
+---------------------------------------------------------------------
+---------------------------------------------------------------------
+
+-- read whole book
+t = io.read"*a"
+
+t = string.gsub(t, "[<>&\128-\255]",
+ {["<"] = "<",
+ [">"] = ">",
+ ["&"] = "&",
+ ["\170"] = "ª",
+ ["\186"] = "º",
+ ["\192"] = "À",
+ ["\193"] = "Á",
+ ["\194"] = "Â",
+ ["\195"] = "Ã",
+ ["\199"] = "Ç",
+ ["\201"] = "É",
+ ["\202"] = "Ê",
+ ["\205"] = "Í",
+ ["\211"] = "Ó",
+ ["\212"] = "Ô",
+ ["\218"] = "Ú",
+ ["\224"] = "à",
+ ["\225"] = "á",
+ ["\226"] = "â",
+ ["\227"] = "ã",
+ ["\231"] = "ç",
+ ["\233"] = "é",
+ ["\234"] = "ê",
+ ["\237"] = "í",
+ ["\243"] = "ó",
+ ["\244"] = "ô",
+ ["\245"] = "õ",
+ ["\250"] = "ú",
+ ["\252"] = "ü"
+ })
+
+t = string.gsub(t, "\n\n+", "\1")
+
+
+
+-- complete macros with no arguments
+t = string.gsub(t, "(@%w+)([^{%w])", "%1{}%2")
+
+t = trata(t)
+
+-- correct references
+t = string.gsub(t, "\3(.-)\3", ref)
+
+-- remove extra space (??)
+t = string.gsub(t, "%s*\4%s*", "")
+
+t = nopara(t)
+
+-- HTML 3.2 does not need
(but complains when it is in wrong places :)
+t = string.gsub(t, "", "")
+
+io.write(header, t, footer)
+
diff --git a/manual/manual.of b/manual/manual.of
new file mode 100644
index 0000000000..935990d0ed
--- /dev/null
+++ b/manual/manual.of
@@ -0,0 +1,8704 @@
+@Ci{$Id: manual.of,v 1.175 2018/06/18 19:17:35 roberto Exp $}
+@C{[(-------------------------------------------------------------------------}
+@manual{
+
+@sect1{@title{Introduction}
+
+Lua is a powerful, efficient, lightweight, embeddable scripting language.
+It supports procedural programming,
+object-oriented programming, functional programming,
+data-driven programming, and data description.
+
+Lua combines simple procedural syntax with powerful data description
+constructs based on associative arrays and extensible semantics.
+Lua is dynamically typed,
+runs by interpreting bytecode with a register-based
+virtual machine,
+and has automatic memory management with
+incremental garbage collection,
+making it ideal for configuration, scripting,
+and rapid prototyping.
+
+Lua is implemented as a library, written in @emphx{clean C},
+the common subset of @N{Standard C} and C++.
+The Lua distribution includes a host program called @id{lua},
+which uses the Lua library to offer a complete,
+standalone Lua interpreter,
+for interactive or batch use.
+Lua is intended to be used both as a powerful, lightweight,
+embeddable scripting language for any program that needs one,
+and as a powerful but lightweight and efficient stand-alone language.
+
+As an extension language, Lua has no notion of a @Q{main} program:
+it works @emph{embedded} in a host client,
+called the @emph{embedding program} or simply the @emphx{host}.
+(Frequently, this host is the stand-alone @id{lua} program.)
+The host program can invoke functions to execute a piece of Lua code,
+can write and read Lua variables,
+and can register @N{C functions} to be called by Lua code.
+Through the use of @N{C functions}, Lua can be augmented to cope with
+a wide range of different domains,
+thus creating customized programming languages sharing a syntactical framework.
+
+Lua is free software,
+and is provided as usual with no guarantees,
+as stated in its license.
+The implementation described in this manual is available
+at Lua's official web site, @id{www.lua.org}.
+
+Like any other reference manual,
+this document is dry in places.
+For a discussion of the decisions behind the design of Lua,
+see the technical papers available at Lua's web site.
+For a detailed introduction to programming in Lua,
+see Roberto's book, @emphx{Programming in Lua}.
+
+}
+
+
+@C{-------------------------------------------------------------------------}
+@sect1{basic| @title{Basic Concepts}
+
+This section describes the basic concepts of the language.
+
+@sect2{TypesSec| @title{Values and Types}
+
+Lua is a dynamically typed language.
+This means that
+variables do not have types; only values do.
+There are no type definitions in the language.
+All values carry their own type.
+
+All values in Lua are first-class values.
+This means that all values can be stored in variables,
+passed as arguments to other functions, and returned as results.
+
+There are eight @x{basic types} in Lua:
+@def{nil}, @def{boolean}, @def{number},
+@def{string}, @def{function}, @def{userdata},
+@def{thread}, and @def{table}.
+The type @emph{nil} has one single value, @nil,
+whose main property is to be different from any other value;
+it usually represents the absence of a useful value.
+The type @emph{boolean} has two values, @false and @true.
+Both @nil and @false make a condition false;
+any other value makes it true.
+The type @emph{number} represents both
+integer numbers and real (floating-point) numbers.
+The type @emph{string} represents immutable sequences of bytes.
+@index{eight-bit clean}
+Lua is 8-bit clean:
+strings can contain any 8-bit value,
+including @x{embedded zeros} (@Char{\0}).
+Lua is also encoding-agnostic;
+it makes no assumptions about the contents of a string.
+
+The type @emph{number} uses two internal representations,
+or two @x{subtypes},
+one called @def{integer} and the other called @def{float}.
+Lua has explicit rules about when each representation is used,
+but it also converts between them automatically as needed @see{coercion}.
+Therefore,
+the programmer may choose to mostly ignore the difference
+between integers and floats
+or to assume complete control over the representation of each number.
+Standard Lua uses 64-bit integers and double-precision (64-bit) floats,
+but you can also compile Lua so that it
+uses 32-bit integers and/or single-precision (32-bit) floats.
+The option with 32 bits for both integers and floats
+is particularly attractive
+for small machines and embedded systems.
+(See macro @id{LUA_32BITS} in file @id{luaconf.h}.)
+
+Lua can call (and manipulate) functions written in Lua and
+functions written in C @see{functioncall}.
+Both are represented by the type @emph{function}.
+
+The type @emph{userdata} is provided to allow arbitrary @N{C data} to
+be stored in Lua variables.
+A userdata value represents a block of raw memory.
+There are two kinds of userdata:
+@emphx{full userdata},
+which is an object with a block of memory managed by Lua,
+and @emphx{light userdata},
+which is simply a @N{C pointer} value.
+Userdata has no predefined operations in Lua,
+except assignment and identity test.
+By using @emph{metatables},
+the programmer can define operations for full userdata values
+@see{metatable}.
+Userdata values cannot be created or modified in Lua,
+only through the @N{C API}.
+This guarantees the integrity of data owned by the host program.
+
+The type @def{thread} represents independent threads of execution
+and it is used to implement coroutines @see{coroutine}.
+Lua threads are not related to operating-system threads.
+Lua supports coroutines on all systems,
+even those that do not support threads natively.
+
+The type @emph{table} implements @x{associative arrays},
+that is, @x{arrays} that can have as indices not only numbers,
+but any Lua value except @nil and @x{NaN}.
+(@emphx{Not a Number} is a special floating-point value
+used by the @x{IEEE 754} standard to represent
+undefined or unrepresentable numerical results, such as @T{0/0}.)
+Tables can be @emph{heterogeneous};
+that is, they can contain values of all types (except @nil).
+Any key with value @nil is not considered part of the table.
+Conversely, any key that is not part of a table has
+an associated value @nil.
+
+Tables are the sole data-structuring mechanism in Lua;
+they can be used to represent ordinary arrays, lists,
+symbol tables, sets, records, graphs, trees, etc.
+To represent @x{records}, Lua uses the field name as an index.
+The language supports this representation by
+providing @id{a.name} as syntactic sugar for @T{a["name"]}.
+There are several convenient ways to create tables in Lua
+@see{tableconstructor}.
+
+Like indices,
+the values of table fields can be of any type.
+In particular,
+because functions are first-class values,
+table fields can contain functions.
+Thus tables can also carry @emph{methods} @see{func-def}.
+
+The indexing of tables follows
+the definition of raw equality in the language.
+The expressions @T{a[i]} and @T{a[j]}
+denote the same table element
+if and only if @id{i} and @id{j} are raw equal
+(that is, equal without metamethods).
+In particular, floats with integral values
+are equal to their respective integers
+(e.g., @T{1.0 == 1}).
+To avoid ambiguities,
+any float with integral value used as a key
+is converted to its respective integer.
+For instance, if you write @T{a[2.0] = true},
+the actual key inserted into the table will be the
+integer @T{2}.
+(On the other hand,
+2 and @St{2} are different Lua values and therefore
+denote different table entries.)
+
+
+Tables, functions, threads, and (full) userdata values are @emph{objects}:
+variables do not actually @emph{contain} these values,
+only @emph{references} to them.
+Assignment, parameter passing, and function returns
+always manipulate references to such values;
+these operations do not imply any kind of copy.
+
+The library function @Lid{type} returns a string describing the type
+of a given value @see{predefined}.
+
+}
+
+@sect2{globalenv| @title{Environments and the Global Environment}
+
+As will be discussed in @refsec{variables} and @refsec{assignment},
+any reference to a free name
+(that is, a name not bound to any declaration) @id{var}
+is syntactically translated to @T{_ENV.var}.
+Moreover, every chunk is compiled in the scope of
+an external local variable named @id{_ENV} @see{chunks},
+so @id{_ENV} itself is never a free name in a chunk.
+
+Despite the existence of this external @id{_ENV} variable and
+the translation of free names,
+@id{_ENV} is a completely regular name.
+In particular,
+you can define new variables and parameters with that name.
+Each reference to a free name uses the @id{_ENV} that is
+visible at that point in the program,
+following the usual visibility rules of Lua @see{visibility}.
+
+Any table used as the value of @id{_ENV} is called an @def{environment}.
+
+Lua keeps a distinguished environment called the @def{global environment}.
+This value is kept at a special index in the C registry @see{registry}.
+In Lua, the global variable @Lid{_G} is initialized with this same value.
+(@Lid{_G} is never used internally.)
+
+When Lua loads a chunk,
+the default value for its @id{_ENV} upvalue
+is the global environment @seeF{load}.
+Therefore, by default,
+free names in Lua code refer to entries in the global environment
+(and, therefore, they are also called @def{global variables}).
+Moreover, all standard libraries are loaded in the global environment
+and some functions there operate on that environment.
+You can use @Lid{load} (or @Lid{loadfile})
+to load a chunk with a different environment.
+(In C, you have to load the chunk and then change the value
+of its first upvalue.)
+
+}
+
+@sect2{error| @title{Error Handling}
+
+Because Lua is an embedded extension language,
+all Lua actions start from @N{C code} in the host program
+calling a function from the Lua library.
+(When you use Lua standalone,
+the @id{lua} application is the host program.)
+Whenever an error occurs during
+the compilation or execution of a Lua chunk,
+control returns to the host,
+which can take appropriate measures
+(such as printing an error message).
+
+Lua code can explicitly generate an error by calling the
+@Lid{error} function.
+If you need to catch errors in Lua,
+you can use @Lid{pcall} or @Lid{xpcall}
+to call a given function in @emphx{protected mode}.
+
+Whenever there is an error,
+an @def{error object} (also called an @def{error message})
+is propagated with information about the error.
+Lua itself only generates errors whose error object is a string,
+but programs may generate errors with
+any value as the error object.
+It is up to the Lua program or its host to handle such error objects.
+
+
+When you use @Lid{xpcall} or @Lid{lua_pcall},
+you may give a @def{message handler}
+to be called in case of errors.
+This function is called with the original error object
+and returns a new error object.
+It is called before the error unwinds the stack,
+so that it can gather more information about the error,
+for instance by inspecting the stack and creating a stack traceback.
+This message handler is still protected by the protected call;
+so, an error inside the message handler
+will call the message handler again.
+If this loop goes on for too long,
+Lua breaks it and returns an appropriate message.
+(The message handler is called only for regular runtime errors.
+It is not called for memory-allocation errors
+nor for errors while running finalizers.)
+
+}
+
+@sect2{metatable| @title{Metatables and Metamethods}
+
+Every value in Lua can have a @emph{metatable}.
+This @def{metatable} is an ordinary Lua table
+that defines the behavior of the original value
+under certain special operations.
+You can change several aspects of the behavior
+of operations over a value by setting specific fields in its metatable.
+For instance, when a non-numeric value is the operand of an addition,
+Lua checks for a function in the field @St{__add} of the value's metatable.
+If it finds one,
+Lua calls this function to perform the addition.
+
+The key for each event in a metatable is a string
+with the event name prefixed by two underscores;
+the corresponding values are called @def{metamethods}.
+In the previous example, the key is @St{__add}
+and the metamethod is the function that performs the addition.
+Unless stated otherwise,
+metamethods should be function values.
+
+You can query the metatable of any value
+using the @Lid{getmetatable} function.
+Lua queries metamethods in metatables using a raw access @seeF{rawget}.
+So, to retrieve the metamethod for event @id{ev} in object @id{o},
+Lua does the equivalent to the following code:
+@verbatim{
+rawget(getmetatable(@rep{o}) or {}, "__@rep{ev}")
+}
+
+You can replace the metatable of tables
+using the @Lid{setmetatable} function.
+You cannot change the metatable of other types from Lua code
+(except by using the @link{debuglib|debug library});
+you should use the @N{C API} for that.
+
+Tables and full userdata have individual metatables
+(although multiple tables and userdata can share their metatables).
+Values of all other types share one single metatable per type;
+that is, there is one single metatable for all numbers,
+one for all strings, etc.
+By default, a value has no metatable,
+but the string library sets a metatable for the string type @see{strlib}.
+
+A metatable controls how an object behaves in
+arithmetic operations, bitwise operations,
+order comparisons, concatenation, length operation, calls, and indexing.
+A metatable also can define a function to be called
+when a userdata or a table is @link{GC|garbage collected}.
+
+For the unary operators (negation, length, and bitwise NOT),
+the metamethod is computed and called with a dummy second operand,
+equal to the first one.
+This extra operand is only to simplify Lua's internals
+(by making these operators behave like a binary operation)
+and may be removed in future versions.
+(For most uses this extra operand is irrelevant.)
+
+A detailed list of events controlled by metatables is given next.
+Each operation is identified by its corresponding key.
+
+@description{
+
+@item{@idx{__add}|
+the addition (@T{+}) operation.
+If any operand for an addition is not a number
+(nor a string coercible to a number),
+Lua will try to call a metamethod.
+First, Lua will check the first operand (even if it is valid).
+If that operand does not define a metamethod for @idx{__add},
+then Lua will check the second operand.
+If Lua can find a metamethod,
+it calls the metamethod with the two operands as arguments,
+and the result of the call
+(adjusted to one value)
+is the result of the operation.
+Otherwise,
+it raises an error.
+}
+
+@item{@idx{__sub}|
+the subtraction (@T{-}) operation.
+Behavior similar to the addition operation.
+}
+
+@item{@idx{__mul}|
+the multiplication (@T{*}) operation.
+Behavior similar to the addition operation.
+}
+
+@item{@idx{__div}|
+the division (@T{/}) operation.
+Behavior similar to the addition operation.
+}
+
+@item{@idx{__mod}|
+the modulo (@T{%}) operation.
+Behavior similar to the addition operation.
+}
+
+@item{@idx{__pow}|
+the exponentiation (@T{^}) operation.
+Behavior similar to the addition operation.
+}
+
+@item{@idx{__unm}|
+the negation (unary @T{-}) operation.
+Behavior similar to the addition operation.
+}
+
+@item{@idx{__idiv}|
+the floor division (@T{//}) operation.
+Behavior similar to the addition operation.
+}
+
+@item{@idx{__band}|
+the bitwise AND (@T{&}) operation.
+Behavior similar to the addition operation,
+except that Lua will try a metamethod
+if any operand is neither an integer
+nor a value coercible to an integer @see{coercion}.
+}
+
+@item{@idx{__bor}|
+the bitwise OR (@T{|}) operation.
+Behavior similar to the bitwise AND operation.
+}
+
+@item{@idx{__bxor}|
+the bitwise exclusive OR (binary @T{~}) operation.
+Behavior similar to the bitwise AND operation.
+}
+
+@item{@idx{__bnot}|
+the bitwise NOT (unary @T{~}) operation.
+Behavior similar to the bitwise AND operation.
+}
+
+@item{@idx{__shl}|
+the bitwise left shift (@T{<<}) operation.
+Behavior similar to the bitwise AND operation.
+}
+
+@item{@idx{__shr}|
+the bitwise right shift (@T{>>}) operation.
+Behavior similar to the bitwise AND operation.
+}
+
+@item{@idx{__concat}|
+the concatenation (@T{..}) operation.
+Behavior similar to the addition operation,
+except that Lua will try a metamethod
+if any operand is neither a string nor a number
+(which is always coercible to a string).
+}
+
+@item{@idx{__len}|
+the length (@T{#}) operation.
+If the object is not a string,
+Lua will try its metamethod.
+If there is a metamethod,
+Lua calls it with the object as argument,
+and the result of the call
+(always adjusted to one value)
+is the result of the operation.
+If there is no metamethod but the object is a table,
+then Lua uses the table length operation @see{len-op}.
+Otherwise, Lua raises an error.
+}
+
+@item{@idx{__eq}|
+the equal (@T{==}) operation.
+Behavior similar to the addition operation,
+except that Lua will try a metamethod only when the values
+being compared are either both tables or both full userdata
+and they are not primitively equal.
+The result of the call is always converted to a boolean.
+}
+
+@item{@idx{__lt}|
+the less than (@T{<}) operation.
+Behavior similar to the addition operation,
+except that Lua will try a metamethod only when the values
+being compared are neither both numbers nor both strings.
+The result of the call is always converted to a boolean.
+}
+
+@item{@idx{__le}|
+the less equal (@T{<=}) operation.
+Unlike other operations,
+the less-equal operation can use two different events.
+First, Lua looks for the @idx{__le} metamethod in both operands,
+like in the less than operation.
+If it cannot find such a metamethod,
+then it will try the @idx{__lt} metamethod,
+assuming that @T{a <= b} is equivalent to @T{not (b < a)}.
+As with the other comparison operators,
+the result is always a boolean.
+(This use of the @idx{__lt} event can be removed in future versions;
+it is also slower than a real @idx{__le} metamethod.)
+}
+
+@item{@idx{__index}|
+The indexing access operation @T{table[key]}.
+This event happens when @id{table} is not a table or
+when @id{key} is not present in @id{table}.
+The metamethod is looked up in @id{table}.
+
+Despite the name,
+the metamethod for this event can be either a function or a table.
+If it is a function,
+it is called with @id{table} and @id{key} as arguments,
+and the result of the call
+(adjusted to one value)
+is the result of the operation.
+If it is a table,
+the final result is the result of indexing this table with @id{key}.
+(This indexing is regular, not raw,
+and therefore can trigger another metamethod.)
+}
+
+@item{@idx{__newindex}|
+The indexing assignment @T{table[key] = value}.
+Like the index event,
+this event happens when @id{table} is not a table or
+when @id{key} is not present in @id{table}.
+The metamethod is looked up in @id{table}.
+
+Like with indexing,
+the metamethod for this event can be either a function or a table.
+If it is a function,
+it is called with @id{table}, @id{key}, and @id{value} as arguments.
+If it is a table,
+Lua does an indexing assignment to this table with the same key and value.
+(This assignment is regular, not raw,
+and therefore can trigger another metamethod.)
+
+Whenever there is a @idx{__newindex} metamethod,
+Lua does not perform the primitive assignment.
+(If necessary,
+the metamethod itself can call @Lid{rawset}
+to do the assignment.)
+}
+
+@item{@idx{__call}|
+The call operation @T{func(args)}.
+This event happens when Lua tries to call a non-function value
+(that is, @id{func} is not a function).
+The metamethod is looked up in @id{func}.
+If present,
+the metamethod is called with @id{func} as its first argument,
+followed by the arguments of the original call (@id{args}).
+All results of the call
+are the result of the operation.
+(This is the only metamethod that allows multiple results.)
+}
+
+}
+
+It is a good practice to add all needed metamethods to a table
+before setting it as a metatable of some object.
+In particular, the @idx{__gc} metamethod works only when this order
+is followed @see{finalizers}.
+
+Because metatables are regular tables,
+they can contain arbitrary fields,
+not only the event names defined above.
+Some functions in the standard library
+(e.g., @Lid{tostring})
+use other fields in metatables for their own purposes.
+
+}
+
+@sect2{GC| @title{Garbage Collection}
+
+Lua performs automatic memory management.
+This means that
+you do not have to worry about allocating memory for new objects
+or freeing it when the objects are no longer needed.
+Lua manages memory automatically by running
+a @def{garbage collector} to collect all @emph{dead objects}
+(that is, objects that are no longer accessible from Lua).
+All memory used by Lua is subject to automatic management:
+strings, tables, userdata, functions, threads, internal structures, etc.
+
+The garbage collector (GC) in Lua can work in two modes:
+incremental and generational.
+
+The default GC mode with the default parameters
+are adequate for most uses.
+Programs that waste a large proportion of its time
+allocating and freeing memory can benefit from other settings.
+Keep in mind that the GC behavior is non-portable
+both across platforms and across different Lua releases;
+therefore, optimal settings are also non-portable.
+
+You can change the GC mode and parameters by calling
+@Lid{lua_gc} in C
+or @Lid{collectgarbage} in Lua.
+You can also use these functions to control
+the collector directly (e.g., stop and restart it).
+
+@sect3{@title{Incremental Garbage Collection}
+
+In incremental mode,
+each GC cycle performs a mark-and-sweep collection in small steps
+interleaved with the program's execution.
+In this mode,
+the collector uses three numbers to control its garbage-collection cycles:
+the @def{garbage-collector pause},
+the @def{garbage-collector step multiplier},
+and the @def{garbage-collector step size}.
+
+The garbage-collector pause
+controls how long the collector waits before starting a new cycle.
+The collector starts a new cycle when the use of memory
+hits @M{n%} of the use after the previous collection.
+Larger values make the collector less aggressive.
+Values smaller than 100 mean the collector will not wait to
+start a new cycle.
+A value of 200 means that the collector waits for the total memory in use
+to double before starting a new cycle.
+The default value is 200; the maximum value is 1000.
+
+The garbage-collector step multiplier
+controls the relative speed of the collector relative to
+memory allocation,
+that is,
+how many elements it marks or sweeps for each
+kilobyte of memory allocated.
+Larger values make the collector more aggressive but also increase
+the size of each incremental step.
+You should not use values smaller than 100,
+because they make the collector too slow and
+can result in the collector never finishing a cycle.
+The default value is 100; the maximum value is 1000.
+
+The garbage-collector step size controls the
+size of each incremental step,
+specifically how many bytes the interpreter allocates
+before performing a step.
+This parameter is logarithmic:
+A value of @M{n} means the interpreter will allocate @M{2@sp{n}}
+bytes between steps and perform equivalent work during the step.
+A large value (e.g., 60) makes the collector a stop-the-world
+(non-incremental) collector.
+The default value is 13,
+which makes for steps of approximately @N{8 Kbytes}.
+
+}
+
+@sect3{@title{Generational Garbage Collection}
+
+In generational mode,
+the collector does frequent @emph{minor} collections,
+which traverses only objects recently created.
+If after a minor collection the use of memory is still above a limit,
+the collector does a @emph{major} collection,
+which traverses all objects.
+The generational mode uses two parameters:
+the @def{major multiplier} and the @def{the minor multiplier}.
+
+The major multiplier controls the frequency of major collections.
+For a major multiplier @M{x},
+a new major collection will be done when memory
+grows @M{x%} larger than the memory in use after the previous major
+collection.
+For instance, for a multiplier of 100,
+the collector will do a major collection when the use of memory
+gets larger than twice the use after the previous collection.
+The default value is 100; the maximum value is 1000.
+
+The minor multiplier controls the frequency of minor collections.
+For a minor multiplier @M{x},
+a new minor collection will be done when memory
+grows @M{x%} larger than the memory in use after the previous major
+collection.
+For instance, for a multiplier of 20,
+the collector will do a minor collection when the use of memory
+gets 20% larger than the use after the previous major collection.
+The default value is 20; the maximum value is 200.
+
+}
+
+@sect3{finalizers| @title{Garbage-Collection Metamethods}
+
+You can set garbage-collector metamethods for tables
+and, using the @N{C API},
+for full userdata @see{metatable}.
+These metamethods are also called @def{finalizers}.
+Finalizers allow you to coordinate Lua's garbage collection
+with external resource management
+(such as closing files, network or database connections,
+or freeing your own memory).
+
+For an object (table or userdata) to be finalized when collected,
+you must @emph{mark} it for finalization.
+@index{mark (for finalization)}
+You mark an object for finalization when you set its metatable
+and the metatable has a field indexed by the string @St{__gc}.
+Note that if you set a metatable without a @idx{__gc} field
+and later create that field in the metatable,
+the object will not be marked for finalization.
+
+When a marked object becomes garbage,
+it is not collected immediately by the garbage collector.
+Instead, Lua puts it in a list.
+After the collection,
+Lua goes through that list.
+For each object in the list,
+it checks the object's @idx{__gc} metamethod:
+If it is a function,
+Lua calls it with the object as its single argument;
+if the metamethod is not a function,
+Lua simply ignores it.
+
+At the end of each garbage-collection cycle,
+the finalizers for objects are called in
+the reverse order that the objects were marked for finalization,
+among those collected in that cycle;
+that is, the first finalizer to be called is the one associated
+with the object marked last in the program.
+The execution of each finalizer may occur at any point during
+the execution of the regular code.
+
+Because the object being collected must still be used by the finalizer,
+that object (and other objects accessible only through it)
+must be @emph{resurrected} by Lua.@index{resurrection}
+Usually, this resurrection is transient,
+and the object memory is freed in the next garbage-collection cycle.
+However, if the finalizer stores the object in some global place
+(e.g., a global variable),
+then the resurrection is permanent.
+Moreover, if the finalizer marks a finalizing object for finalization again,
+its finalizer will be called again in the next cycle where the
+object is unreachable.
+In any case,
+the object memory is freed only in a GC cycle where
+the object is unreachable and not marked for finalization.
+
+When you close a state @seeF{lua_close},
+Lua calls the finalizers of all objects marked for finalization,
+following the reverse order that they were marked.
+If any finalizer marks objects for collection during that phase,
+these marks have no effect.
+
+}
+
+@sect3{weak-table| @title{Weak Tables}
+
+A @def{weak table} is a table whose elements are
+@def{weak references}.
+A weak reference is ignored by the garbage collector.
+In other words,
+if the only references to an object are weak references,
+then the garbage collector will collect that object.
+
+A weak table can have weak keys, weak values, or both.
+A table with weak values allows the collection of its values,
+but prevents the collection of its keys.
+A table with both weak keys and weak values allows the collection of
+both keys and values.
+In any case, if either the key or the value is collected,
+the whole pair is removed from the table.
+The weakness of a table is controlled by the
+@idx{__mode} field of its metatable.
+This field, if present, must be one of the following strings:
+@St{k}, for a table with weak keys;
+@St{v}, for a table with weak values;
+or @St{kv}, for a table with both weak keys and values.
+
+A table with weak keys and strong values
+is also called an @def{ephemeron table}.
+In an ephemeron table,
+a value is considered reachable only if its key is reachable.
+In particular,
+if the only reference to a key comes through its value,
+the pair is removed.
+
+Any change in the weakness of a table may take effect only
+at the next collect cycle.
+In particular, if you change the weakness to a stronger mode,
+Lua may still collect some items from that table
+before the change takes effect.
+
+Only objects that have an explicit construction
+are removed from weak tables.
+Values, such as numbers and @x{light @N{C functions}},
+are not subject to garbage collection,
+and therefore are not removed from weak tables
+(unless their associated values are collected).
+Although strings are subject to garbage collection,
+they do not have an explicit construction,
+and therefore are not removed from weak tables.
+
+Resurrected objects
+(that is, objects being finalized
+and objects accessible only through objects being finalized)
+have a special behavior in weak tables.
+They are removed from weak values before running their finalizers,
+but are removed from weak keys only in the next collection
+after running their finalizers, when such objects are actually freed.
+This behavior allows the finalizer to access properties
+associated with the object through weak tables.
+
+If a weak table is among the resurrected objects in a collection cycle,
+it may not be properly cleared until the next cycle.
+
+}
+
+}
+
+@sect2{coroutine| @title{Coroutines}
+
+Lua supports coroutines,
+also called @emphx{collaborative multithreading}.
+A coroutine in Lua represents an independent thread of execution.
+Unlike threads in multithread systems, however,
+a coroutine only suspends its execution by explicitly calling
+a yield function.
+
+You create a coroutine by calling @Lid{coroutine.create}.
+Its sole argument is a function
+that is the main function of the coroutine.
+The @id{create} function only creates a new coroutine and
+returns a handle to it (an object of type @emph{thread});
+it does not start the coroutine.
+
+You execute a coroutine by calling @Lid{coroutine.resume}.
+When you first call @Lid{coroutine.resume},
+passing as its first argument
+a thread returned by @Lid{coroutine.create},
+the coroutine starts its execution by
+calling its main function.
+Extra arguments passed to @Lid{coroutine.resume} are passed
+as arguments to that function.
+After the coroutine starts running,
+it runs until it terminates or @emph{yields}.
+
+A coroutine can terminate its execution in two ways:
+normally, when its main function returns
+(explicitly or implicitly, after the last instruction);
+and abnormally, if there is an unprotected error.
+In case of normal termination,
+@Lid{coroutine.resume} returns @true,
+plus any values returned by the coroutine main function.
+In case of errors, @Lid{coroutine.resume} returns @false
+plus an error object.
+
+A coroutine yields by calling @Lid{coroutine.yield}.
+When a coroutine yields,
+the corresponding @Lid{coroutine.resume} returns immediately,
+even if the yield happens inside nested function calls
+(that is, not in the main function,
+but in a function directly or indirectly called by the main function).
+In the case of a yield, @Lid{coroutine.resume} also returns @true,
+plus any values passed to @Lid{coroutine.yield}.
+The next time you resume the same coroutine,
+it continues its execution from the point where it yielded,
+with the call to @Lid{coroutine.yield} returning any extra
+arguments passed to @Lid{coroutine.resume}.
+
+Like @Lid{coroutine.create},
+the @Lid{coroutine.wrap} function also creates a coroutine,
+but instead of returning the coroutine itself,
+it returns a function that, when called, resumes the coroutine.
+Any arguments passed to this function
+go as extra arguments to @Lid{coroutine.resume}.
+@Lid{coroutine.wrap} returns all the values returned by @Lid{coroutine.resume},
+except the first one (the boolean error code).
+Unlike @Lid{coroutine.resume},
+@Lid{coroutine.wrap} does not catch errors;
+any error is propagated to the caller.
+
+As an example of how coroutines work,
+consider the following code:
+@verbatim{
+function foo (a)
+ print("foo", a)
+ return coroutine.yield(2*a)
+end
+
+co = coroutine.create(function (a,b)
+ print("co-body", a, b)
+ local r = foo(a+1)
+ print("co-body", r)
+ local r, s = coroutine.yield(a+b, a-b)
+ print("co-body", r, s)
+ return b, "end"
+end)
+
+print("main", coroutine.resume(co, 1, 10))
+print("main", coroutine.resume(co, "r"))
+print("main", coroutine.resume(co, "x", "y"))
+print("main", coroutine.resume(co, "x", "y"))
+}
+When you run it, it produces the following output:
+@verbatim{
+co-body 1 10
+foo 2
+main true 4
+co-body r
+main true 11 -9
+co-body x y
+main true 10 end
+main false cannot resume dead coroutine
+}
+
+You can also create and manipulate coroutines through the C API:
+see functions @Lid{lua_newthread}, @Lid{lua_resume},
+and @Lid{lua_yield}.
+
+}
+
+}
+
+
+@C{-------------------------------------------------------------------------}
+@sect1{language| @title{The Language}
+
+This section describes the lexis, the syntax, and the semantics of Lua.
+In other words,
+this section describes
+which tokens are valid,
+how they can be combined,
+and what their combinations mean.
+
+Language constructs will be explained using the usual extended BNF notation,
+in which
+@N{@bnfrep{@rep{a}} means 0} or more @rep{a}'s, and
+@N{@bnfopt{@rep{a}} means} an optional @rep{a}.
+Non-terminals are shown like @bnfNter{non-terminal},
+keywords are shown like @rw{kword},
+and other terminal symbols are shown like @bnfter{=}.
+The complete syntax of Lua can be found in @refsec{BNF}
+at the end of this manual.
+
+@sect2{lexical| @title{Lexical Conventions}
+
+Lua is a @x{free-form} language.
+It ignores spaces (including new lines) and comments
+between lexical elements (@x{tokens}),
+except as delimiters between @x{names} and @x{keywords}.
+
+@def{Names}
+(also called @def{identifiers})
+in Lua can be any string of letters,
+digits, and underscores,
+not beginning with a digit and
+not being a reserved word.
+Identifiers are used to name variables, table fields, and labels.
+
+The following @def{keywords} are reserved
+and cannot be used as names:
+@index{reserved words}
+@verbatim{
+and break do else elseif end
+false for function goto if in
+local nil not or repeat return
+then true until while
+}
+
+Lua is a case-sensitive language:
+@id{and} is a reserved word, but @id{And} and @id{AND}
+are two different, valid names.
+As a convention,
+programs should avoid creating
+names that start with an underscore followed by
+one or more uppercase letters (such as @Lid{_VERSION}).
+
+The following strings denote other @x{tokens}:
+@verbatim{
++ - * / % ^ #
+& ~ | << >> //
+== ~= <= >= < > =
+( ) { } [ ] ::
+; : , . .. ...
+}
+
+A @def{short literal string}
+can be delimited by matching single or double quotes,
+and can contain the following C-like escape sequences:
+@Char{\a} (bell),
+@Char{\b} (backspace),
+@Char{\f} (form feed),
+@Char{\n} (newline),
+@Char{\r} (carriage return),
+@Char{\t} (horizontal tab),
+@Char{\v} (vertical tab),
+@Char{\\} (backslash),
+@Char{\"} (quotation mark [double quote]),
+and @Char{\'} (apostrophe [single quote]).
+A backslash followed by a line break
+results in a newline in the string.
+The escape sequence @Char{\z} skips the following span
+of white-space characters,
+including line breaks;
+it is particularly useful to break and indent a long literal string
+into multiple lines without adding the newlines and spaces
+into the string contents.
+A short literal string cannot contain unescaped line breaks
+nor escapes not forming a valid escape sequence.
+
+We can specify any byte in a short literal string,
+including @x{embedded zeros},
+by its numeric value.
+This can be done
+with the escape sequence @T{\x@rep{XX}},
+where @rep{XX} is a sequence of exactly two hexadecimal digits,
+or with the escape sequence @T{\@rep{ddd}},
+where @rep{ddd} is a sequence of up to three decimal digits.
+(Note that if a decimal escape sequence is to be followed by a digit,
+it must be expressed using exactly three digits.)
+
+The @x{UTF-8} encoding of a @x{Unicode} character
+can be inserted in a literal string with
+the escape sequence @T{\u{@rep{XXX}}}
+(note the mandatory enclosing brackets),
+where @rep{XXX} is a sequence of one or more hexadecimal digits
+representing the character code point.
+
+Literal strings can also be defined using a long format
+enclosed by @def{long brackets}.
+We define an @def{opening long bracket of level @rep{n}} as an opening
+square bracket followed by @rep{n} equal signs followed by another
+opening square bracket.
+So, an opening long bracket of @N{level 0} is written as @T{[[}, @C{]]}
+an opening long bracket of @N{level 1} is written as @T{[=[}, @C{]]}
+and so on.
+A @emph{closing long bracket} is defined similarly;
+for instance,
+a closing long bracket of @N{level 4} is written as @C{[[} @T{]====]}.
+A @def{long literal} starts with an opening long bracket of any level and
+ends at the first closing long bracket of the same level.
+It can contain any text except a closing bracket of the same level.
+Literals in this bracketed form can run for several lines,
+do not interpret any escape sequences,
+and ignore long brackets of any other level.
+Any kind of end-of-line sequence
+(carriage return, newline, carriage return followed by newline,
+or newline followed by carriage return)
+is converted to a simple newline.
+
+For convenience,
+when the opening long bracket is immediately followed by a newline,
+the newline is not included in the string.
+As an example, in a system using ASCII
+(in which @Char{a} is coded @N{as 97},
+newline is coded @N{as 10}, and @Char{1} is coded @N{as 49}),
+the five literal strings below denote the same string:
+@verbatim{
+a = 'alo\n123"'
+a = "alo\n123\""
+a = '\97lo\10\04923"'
+a = [[alo
+123"]]
+a = [==[
+alo
+123"]==]
+}
+
+Any byte in a literal string not
+explicitly affected by the previous rules represents itself.
+However, Lua opens files for parsing in text mode,
+and the system file functions may have problems with
+some control characters.
+So, it is safer to represent
+non-text data as a quoted literal with
+explicit escape sequences for the non-text characters.
+
+A @def{numeric constant} (or @def{numeral})
+can be written with an optional fractional part
+and an optional decimal exponent,
+marked by a letter @Char{e} or @Char{E}.
+Lua also accepts @x{hexadecimal constants},
+which start with @T{0x} or @T{0X}.
+Hexadecimal constants also accept an optional fractional part
+plus an optional binary exponent,
+marked by a letter @Char{p} or @Char{P}.
+A numeric constant with a radix point or an exponent
+denotes a float;
+otherwise,
+if its value fits in an integer,
+it denotes an integer.
+Examples of valid integer constants are
+@verbatim{
+3 345 0xff 0xBEBADA
+}
+Examples of valid float constants are
+@verbatim{
+3.0 3.1416 314.16e-2 0.31416E1 34e1
+0x0.1E 0xA23p-4 0X1.921FB54442D18P+1
+}
+
+A @def{comment} starts with a double hyphen (@T{--})
+anywhere outside a string.
+If the text immediately after @T{--} is not an opening long bracket,
+the comment is a @def{short comment},
+which runs until the end of the line.
+Otherwise, it is a @def{long comment},
+which runs until the corresponding closing long bracket.
+
+}
+
+@sect2{variables| @title{Variables}
+
+Variables are places that store values.
+There are three kinds of variables in Lua:
+global variables, local variables, and table fields.
+
+A single name can denote a global variable or a local variable
+(or a function's formal parameter,
+which is a particular kind of local variable):
+@Produc{
+@producname{var}@producbody{@bnfNter{Name}}
+}
+@bnfNter{Name} denotes identifiers, as defined in @See{lexical}.
+
+Any variable name is assumed to be global unless explicitly declared
+as a local @see{localvar}.
+@x{Local variables} are @emph{lexically scoped}:
+local variables can be freely accessed by functions
+defined inside their scope @see{visibility}.
+
+Before the first assignment to a variable, its value is @nil.
+
+Square brackets are used to index a table:
+@Produc{
+@producname{var}@producbody{prefixexp @bnfter{[} exp @bnfter{]}}
+}
+The meaning of accesses to table fields can be changed via metatables
+@see{metatable}.
+
+The syntax @id{var.Name} is just syntactic sugar for
+@T{var["Name"]}:
+@Produc{
+@producname{var}@producbody{prefixexp @bnfter{.} @bnfNter{Name}}
+}
+
+An access to a global variable @id{x}
+is equivalent to @id{_ENV.x}.
+Due to the way that chunks are compiled,
+the variable @id{_ENV} itself is never global @see{globalenv}.
+
+}
+
+@sect2{stats| @title{Statements}
+
+Lua supports an almost conventional set of @x{statements},
+similar to those in Pascal or C.
+This set includes
+assignments, control structures, function calls,
+and variable declarations.
+
+@sect3{@title{Blocks}
+
+A @x{block} is a list of statements,
+which are executed sequentially:
+@Produc{
+@producname{block}@producbody{@bnfrep{stat}}
+}
+Lua has @def{empty statements}
+that allow you to separate statements with semicolons,
+start a block with a semicolon
+or write two semicolons in sequence:
+@Produc{
+@producname{stat}@producbody{@bnfter{;}}
+}
+
+Function calls and assignments
+can start with an open parenthesis.
+This possibility leads to an ambiguity in Lua's grammar.
+Consider the following fragment:
+@verbatim{
+a = b + c
+(print or io.write)('done')
+}
+The grammar could see it in two ways:
+@verbatim{
+a = b + c(print or io.write)('done')
+
+a = b + c; (print or io.write)('done')
+}
+The current parser always sees such constructions
+in the first way,
+interpreting the open parenthesis
+as the start of the arguments to a call.
+To avoid this ambiguity,
+it is a good practice to always precede with a semicolon
+statements that start with a parenthesis:
+@verbatim{
+;(print or io.write)('done')
+}
+
+A block can be explicitly delimited to produce a single statement:
+@Produc{
+@producname{stat}@producbody{@Rw{do} block @Rw{end}}
+}
+Explicit blocks are useful
+to control the scope of variable declarations.
+Explicit blocks are also sometimes used to
+add a @Rw{return} statement in the middle
+of another block @see{control}.
+
+}
+
+@sect3{chunks| @title{Chunks}
+
+The unit of compilation of Lua is called a @def{chunk}.
+Syntactically,
+a chunk is simply a block:
+@Produc{
+@producname{chunk}@producbody{block}
+}
+
+Lua handles a chunk as the body of an anonymous function
+with a variable number of arguments
+@see{func-def}.
+As such, chunks can define local variables,
+receive arguments, and return values.
+Moreover, such anonymous function is compiled as in the
+scope of an external local variable called @id{_ENV} @see{globalenv}.
+The resulting function always has @id{_ENV} as its only upvalue,
+even if it does not use that variable.
+
+A chunk can be stored in a file or in a string inside the host program.
+To execute a chunk,
+Lua first @emph{loads} it,
+precompiling the chunk's code into instructions for a virtual machine,
+and then Lua executes the compiled code
+with an interpreter for the virtual machine.
+
+Chunks can also be precompiled into binary form;
+see program @idx{luac} and function @Lid{string.dump} for details.
+Programs in source and compiled forms are interchangeable;
+Lua automatically detects the file type and acts accordingly @seeF{load}.
+
+}
+
+@sect3{assignment| @title{Assignment}
+
+Lua allows @x{multiple assignments}.
+Therefore, the syntax for assignment
+defines a list of variables on the left side
+and a list of expressions on the right side.
+The elements in both lists are separated by commas:
+@Produc{
+@producname{stat}@producbody{varlist @bnfter{=} explist}
+@producname{varlist}@producbody{var @bnfrep{@bnfter{,} var}}
+@producname{explist}@producbody{exp @bnfrep{@bnfter{,} exp}}
+}
+Expressions are discussed in @See{expressions}.
+
+Before the assignment,
+the list of values is @emph{adjusted} to the length of
+the list of variables.@index{adjustment}
+If there are more values than needed,
+the excess values are thrown away.
+If there are fewer values than needed,
+the list is extended with as many @nil's as needed.
+If the list of expressions ends with a function call,
+then all values returned by that call enter the list of values,
+before the adjustment
+(except when the call is enclosed in parentheses; see @See{expressions}).
+
+The assignment statement first evaluates all its expressions
+and only then the assignments are performed.
+Thus the code
+@verbatim{
+i = 3
+i, a[i] = i+1, 20
+}
+sets @T{a[3]} to 20, without affecting @T{a[4]}
+because the @id{i} in @T{a[i]} is evaluated (to 3)
+before it is @N{assigned 4}.
+Similarly, the line
+@verbatim{
+x, y = y, x
+}
+exchanges the values of @id{x} and @id{y},
+and
+@verbatim{
+x, y, z = y, z, x
+}
+cyclically permutes the values of @id{x}, @id{y}, and @id{z}.
+
+An assignment to a global name @T{x = val}
+is equivalent to the assignment
+@T{_ENV.x = val} @see{globalenv}.
+
+The meaning of assignments to table fields and
+global variables (which are actually table fields, too)
+can be changed via metatables @see{metatable}.
+
+}
+
+@sect3{control| @title{Control Structures}
+The control structures
+@Rw{if}, @Rw{while}, and @Rw{repeat} have the usual meaning and
+familiar syntax:
+@index{while-do statement}
+@index{repeat-until statement}
+@index{if-then-else statement}
+@Produc{
+@producname{stat}@producbody{@Rw{while} exp @Rw{do} block @Rw{end}}
+@producname{stat}@producbody{@Rw{repeat} block @Rw{until} exp}
+@producname{stat}@producbody{@Rw{if} exp @Rw{then} block
+ @bnfrep{@Rw{elseif} exp @Rw{then} block}
+ @bnfopt{@Rw{else} block} @Rw{end}}
+}
+Lua also has a @Rw{for} statement, in two flavors @see{for}.
+
+The @x{condition expression} of a
+control structure can return any value.
+Both @false and @nil test false.
+All values different from @nil and @false test true.
+(In particular, the number 0 and the empty string also test true).
+
+In the @Rw{repeat}@En@Rw{until} loop,
+the inner block does not end at the @Rw{until} keyword,
+but only after the condition.
+So, the condition can refer to local variables
+declared inside the loop block.
+
+The @Rw{goto} statement transfers the program control to a label.
+For syntactical reasons,
+labels in Lua are considered statements too:
+@index{goto statement}
+@index{label}
+@Produc{
+@producname{stat}@producbody{@Rw{goto} Name}
+@producname{stat}@producbody{label}
+@producname{label}@producbody{@bnfter{::} Name @bnfter{::}}
+}
+
+A label is visible in the entire block where it is defined,
+except
+inside nested blocks where a label with the same name is defined and
+inside nested functions.
+A goto may jump to any visible label as long as it does not
+enter into the scope of a local variable.
+
+Labels and empty statements are called @def{void statements},
+as they perform no actions.
+
+The @Rw{break} statement terminates the execution of a
+@Rw{while}, @Rw{repeat}, or @Rw{for} loop,
+skipping to the next statement after the loop:
+@index{break statement}
+@Produc{
+@producname{stat}@producbody{@Rw{break}}
+}
+A @Rw{break} ends the innermost enclosing loop.
+
+The @Rw{return} statement is used to return values
+from a function or a chunk
+(which is an anonymous function).
+@index{return statement}
+Functions can return more than one value,
+so the syntax for the @Rw{return} statement is
+@Produc{
+@producname{stat}@producbody{@Rw{return} @bnfopt{explist} @bnfopt{@bnfter{;}}}
+}
+
+The @Rw{return} statement can only be written
+as the last statement of a block.
+If it is really necessary to @Rw{return} in the middle of a block,
+then an explicit inner block can be used,
+as in the idiom @T{do return end},
+because now @Rw{return} is the last statement in its (inner) block.
+
+}
+
+@sect3{for| @title{For Statement}
+
+@index{for statement}
+The @Rw{for} statement has two forms:
+one numerical and one generic.
+
+The numerical @Rw{for} loop repeats a block of code while a
+control variable runs through an arithmetic progression.
+It has the following syntax:
+@Produc{
+@producname{stat}@producbody{@Rw{for} @bnfNter{Name} @bnfter{=}
+ exp @bnfter{,} exp @bnfopt{@bnfter{,} exp} @Rw{do} block @Rw{end}}
+}
+The @emph{block} is repeated for @emph{name} starting at the value of
+the first @emph{exp}, until it passes the second @emph{exp} by steps of the
+third @emph{exp}.
+More precisely, a @Rw{for} statement like
+@verbatim{
+for v = @rep{e1}, @rep{e2}, @rep{e3} do @rep{block} end
+}
+is equivalent to the code:
+@verbatim{
+do
+ local @rep{var}, @rep{limit}, @rep{step} = tonumber(@rep{e1}), tonumber(@rep{e2}), tonumber(@rep{e3})
+ if not (@rep{var} and @rep{limit} and @rep{step}) then error() end
+ @rep{var} = @rep{var} - @rep{step}
+ while true do
+ @rep{var} = @rep{var} + @rep{step}
+ if (@rep{step} >= 0 and @rep{var} > @rep{limit}) or (@rep{step} < 0 and @rep{var} < @rep{limit}) then
+ break
+ end
+ local v = @rep{var}
+ @rep{block}
+ end
+end
+}
+
+Note the following:
+@itemize{
+
+@item{
+All three control expressions are evaluated only once,
+before the loop starts.
+They must all result in numbers.
+}
+
+@item{
+@T{@rep{var}}, @T{@rep{limit}}, and @T{@rep{step}} are invisible variables.
+The names shown here are for explanatory purposes only.
+}
+
+@item{
+If the third expression (the step) is absent,
+then a step @N{of 1} is used.
+}
+
+@item{
+You can use @Rw{break} and @Rw{goto} to exit a @Rw{for} loop.
+}
+
+@item{
+The loop variable @T{v} is local to the loop body.
+If you need its value after the loop,
+assign it to another variable before exiting the loop.
+}
+
+@item{
+The values in @rep{var}, @rep{limit}, and @rep{step}
+can be integers or floats.
+All operations on them respect the usual rules in Lua.
+}
+
+}
+
+The generic @Rw{for} statement works over functions,
+called @def{iterators}.
+On each iteration, the iterator function is called to produce a new value,
+stopping when this new value is @nil.
+The generic @Rw{for} loop has the following syntax:
+@Produc{
+@producname{stat}@producbody{@Rw{for} namelist @Rw{in} explist
+ @Rw{do} block @Rw{end}}
+@producname{namelist}@producbody{@bnfNter{Name} @bnfrep{@bnfter{,} @bnfNter{Name}}}
+}
+A @Rw{for} statement like
+@verbatim{
+for @rep{var_1}, @Cdots, @rep{var_n} in @rep{explist} do @rep{block} end
+}
+is equivalent to the code:
+@verbatim{
+do
+ local @rep{f}, @rep{s}, @rep{var} = @rep{explist}
+ while true do
+ local @rep{var_1}, @Cdots, @rep{var_n} = @rep{f}(@rep{s}, @rep{var})
+ if @rep{var_1} == nil then break end
+ @rep{var} = @rep{var_1}
+ @rep{block}
+ end
+end
+}
+Note the following:
+@itemize{
+
+@item{
+@T{@rep{explist}} is evaluated only once.
+Its results are an @emph{iterator} function,
+a @emph{state},
+and an initial value for the first @emph{iterator variable}.
+}
+
+@item{
+@T{@rep{f}}, @T{@rep{s}}, and @T{@rep{var}} are invisible variables.
+The names are here for explanatory purposes only.
+}
+
+@item{
+You can use @Rw{break} to exit a @Rw{for} loop.
+}
+
+@item{
+The loop variables @T{@rep{var_i}} are local to the loop;
+you cannot use their values after the @Rw{for} ends.
+If you need these values,
+then assign them to other variables before breaking or exiting the loop.
+}
+
+}
+
+}
+
+@sect3{funcstat| @title{Function Calls as Statements}
+To allow possible side-effects,
+function calls can be executed as statements:
+@Produc{
+@producname{stat}@producbody{functioncall}
+}
+In this case, all returned values are thrown away.
+Function calls are explained in @See{functioncall}.
+
+}
+
+@sect3{localvar| @title{Local Declarations}
+@x{Local variables} can be declared anywhere inside a block.
+The declaration can include an initial assignment:
+@Produc{
+@producname{stat}@producbody{@Rw{local} namelist @bnfopt{@bnfter{=} explist}}
+}
+If present, an initial assignment has the same semantics
+of a multiple assignment @see{assignment}.
+Otherwise, all variables are initialized with @nil.
+
+A chunk is also a block @see{chunks},
+and so local variables can be declared in a chunk outside any explicit block.
+
+The visibility rules for local variables are explained in @See{visibility}.
+
+}
+
+}
+
+@sect2{expressions| @title{Expressions}
+
+The basic expressions in Lua are the following:
+@Produc{
+@producname{exp}@producbody{prefixexp}
+@producname{exp}@producbody{@Rw{nil} @Or @Rw{false} @Or @Rw{true}}
+@producname{exp}@producbody{@bnfNter{Numeral}}
+@producname{exp}@producbody{@bnfNter{LiteralString}}
+@producname{exp}@producbody{functiondef}
+@producname{exp}@producbody{tableconstructor}
+@producname{exp}@producbody{@bnfter{...}}
+@producname{exp}@producbody{exp binop exp}
+@producname{exp}@producbody{unop exp}
+@producname{prefixexp}@producbody{var @Or functioncall @Or
+ @bnfter{(} exp @bnfter{)}}
+}
+
+Numerals and literal strings are explained in @See{lexical};
+variables are explained in @See{variables};
+function definitions are explained in @See{func-def};
+function calls are explained in @See{functioncall};
+table constructors are explained in @See{tableconstructor}.
+Vararg expressions,
+denoted by three dots (@Char{...}), can only be used when
+directly inside a vararg function;
+they are explained in @See{func-def}.
+
+Binary operators comprise arithmetic operators @see{arith},
+bitwise operators @see{bitwise},
+relational operators @see{rel-ops}, logical operators @see{logic},
+and the concatenation operator @see{concat}.
+Unary operators comprise the unary minus @see{arith},
+the unary bitwise NOT @see{bitwise},
+the unary logical @Rw{not} @see{logic},
+and the unary @def{length operator} @see{len-op}.
+
+Both function calls and vararg expressions can result in multiple values.
+If a function call is used as a statement @see{funcstat},
+then its return list is adjusted to zero elements,
+thus discarding all returned values.
+If an expression is used as the last (or the only) element
+of a list of expressions,
+then no adjustment is made
+(unless the expression is enclosed in parentheses).
+In all other contexts,
+Lua adjusts the result list to one element,
+either discarding all values except the first one
+or adding a single @nil if there are no values.
+
+Here are some examples:
+@verbatim{
+f() -- adjusted to 0 results
+g(f(), x) -- f() is adjusted to 1 result
+g(x, f()) -- g gets x plus all results from f()
+a,b,c = f(), x -- f() is adjusted to 1 result (c gets nil)
+a,b = ... -- a gets the first vararg argument, b gets
+ -- the second (both a and b can get nil if there
+ -- is no corresponding vararg argument)
+
+a,b,c = x, f() -- f() is adjusted to 2 results
+a,b,c = f() -- f() is adjusted to 3 results
+return f() -- returns all results from f()
+return ... -- returns all received vararg arguments
+return x,y,f() -- returns x, y, and all results from f()
+{f()} -- creates a list with all results from f()
+{...} -- creates a list with all vararg arguments
+{f(), nil} -- f() is adjusted to 1 result
+}
+
+Any expression enclosed in parentheses always results in only one value.
+Thus,
+@T{(f(x,y,z))} is always a single value,
+even if @id{f} returns several values.
+(The value of @T{(f(x,y,z))} is the first value returned by @id{f}
+or @nil if @id{f} does not return any values.)
+
+
+
+@sect3{arith| @title{Arithmetic Operators}
+Lua supports the following @x{arithmetic operators}:
+@description{
+@item{@T{+}|addition}
+@item{@T{-}|subtraction}
+@item{@T{*}|multiplication}
+@item{@T{/}|float division}
+@item{@T{//}|floor division}
+@item{@T{%}|modulo}
+@item{@T{^}|exponentiation}
+@item{@T{-}|unary minus}
+}
+
+With the exception of exponentiation and float division,
+the arithmetic operators work as follows:
+If both operands are integers,
+the operation is performed over integers and the result is an integer.
+Otherwise, if both operands are numbers,
+then they are converted to floats,
+the operation is performed following the usual rules
+for floating-point arithmetic
+(usually the @x{IEEE 754} standard),
+and the result is a float.
+(The string library coerces strings to numbers in
+arithmetic operations; see @See{coercion} for details.)
+
+Exponentiation and float division (@T{/})
+always convert their operands to floats
+and the result is always a float.
+Exponentiation uses the @ANSI{pow},
+so that it works for non-integer exponents too.
+
+Floor division (@T{//}) is a division
+that rounds the quotient towards minus infinity,
+that is, the floor of the division of its operands.
+
+Modulo is defined as the remainder of a division
+that rounds the quotient towards minus infinity (floor division).
+
+In case of overflows in integer arithmetic,
+all operations @emphx{wrap around},
+according to the usual rules of two-complement arithmetic.
+(In other words,
+they return the unique representable integer
+that is equal modulo @M{2@sp{64}} to the mathematical result.)
+}
+
+@sect3{bitwise| @title{Bitwise Operators}
+Lua supports the following @x{bitwise operators}:
+@description{
+@item{@T{&}|bitwise AND}
+@item{@T{@VerBar}|bitwise OR}
+@item{@T{~}|bitwise exclusive OR}
+@item{@T{>>}|right shift}
+@item{@T{<<}|left shift}
+@item{@T{~}|unary bitwise NOT}
+}
+
+All bitwise operations convert its operands to integers
+@see{coercion},
+operate on all bits of those integers,
+and result in an integer.
+
+Both right and left shifts fill the vacant bits with zeros.
+Negative displacements shift to the other direction;
+displacements with absolute values equal to or higher than
+the number of bits in an integer
+result in zero (as all bits are shifted out).
+
+}
+
+@sect3{coercion| @title{Coercions and Conversions}
+Lua provides some automatic conversions between some
+types and representations at run time.
+Bitwise operators always convert float operands to integers.
+Exponentiation and float division
+always convert integer operands to floats.
+All other arithmetic operations applied to mixed numbers
+(integers and floats) convert the integer operand to a float.
+The C API also converts both integers to floats and
+floats to integers, as needed.
+Moreover, string concatenation accepts numbers as arguments,
+besides strings.
+
+In a conversion from integer to float,
+if the integer value has an exact representation as a float,
+that is the result.
+Otherwise,
+the conversion gets the nearest higher or
+the nearest lower representable value.
+This kind of conversion never fails.
+
+The conversion from float to integer
+checks whether the float has an exact representation as an integer
+(that is, the float has an integral value and
+it is in the range of integer representation).
+If it does, that representation is the result.
+Otherwise, the conversion fails.
+
+The string library uses metamethods that try to coerce
+strings to numbers in all arithmetic operations.
+Any string operator is converted to an integer or a float,
+following its syntax and the rules of the Lua lexer.
+(The string may have also leading and trailing spaces and a sign.)
+All conversions from strings to numbers
+accept both a dot and the current locale mark
+as the radix character.
+(The Lua lexer, however, accepts only a dot.)
+
+The conversion from numbers to strings uses a
+non-specified human-readable format.
+For complete control over how numbers are converted to strings,
+use the @id{format} function from the string library
+@seeF{string.format}.
+
+}
+
+@sect3{rel-ops| @title{Relational Operators}
+Lua supports the following @x{relational operators}:
+@description{
+@item{@T{==}|equality}
+@item{@T{~=}|inequality}
+@item{@T{<}|less than}
+@item{@T{>}|greater than}
+@item{@T{<=}|less or equal}
+@item{@T{>=}|greater or equal}
+}
+These operators always result in @false or @true.
+
+Equality (@T{==}) first compares the type of its operands.
+If the types are different, then the result is @false.
+Otherwise, the values of the operands are compared.
+Strings are compared in the obvious way.
+Numbers are equal if they denote the same mathematical value.
+
+Tables, userdata, and threads
+are compared by reference:
+two objects are considered equal only if they are the same object.
+Every time you create a new object
+(a table, userdata, or thread),
+this new object is different from any previously existing object.
+A closure is always equal to itself.
+Closures with any detectable difference
+(different behavior, different definition) are always different.
+Closures created at different times but with no detectable differences
+may be classified as equal or not
+(depending on internal cashing details).
+
+You can change the way that Lua compares tables and userdata
+by using the @idx{__eq} metamethod @see{metatable}.
+
+Equality comparisons do not convert strings to numbers
+or vice versa.
+Thus, @T{"0"==0} evaluates to @false,
+and @T{t[0]} and @T{t["0"]} denote different
+entries in a table.
+
+The operator @T{~=} is exactly the negation of equality (@T{==}).
+
+The order operators work as follows.
+If both arguments are numbers,
+then they are compared according to their mathematical values
+(regardless of their subtypes).
+Otherwise, if both arguments are strings,
+then their values are compared according to the current locale.
+Otherwise, Lua tries to call the @idx{__lt} or the @idx{__le}
+metamethod @see{metatable}.
+A comparison @T{a > b} is translated to @T{b < a}
+and @T{a >= b} is translated to @T{b <= a}.
+
+Following the @x{IEEE 754} standard,
+@x{NaN} is considered neither smaller than,
+nor equal to, nor greater than any value (including itself).
+
+}
+
+@sect3{logic| @title{Logical Operators}
+The @x{logical operators} in Lua are
+@Rw{and}, @Rw{or}, and @Rw{not}.
+Like the control structures @see{control},
+all logical operators consider both @false and @nil as false
+and anything else as true.
+
+The negation operator @Rw{not} always returns @false or @true.
+The conjunction operator @Rw{and} returns its first argument
+if this value is @false or @nil;
+otherwise, @Rw{and} returns its second argument.
+The disjunction operator @Rw{or} returns its first argument
+if this value is different from @nil and @false;
+otherwise, @Rw{or} returns its second argument.
+Both @Rw{and} and @Rw{or} use @x{short-circuit evaluation};
+that is,
+the second operand is evaluated only if necessary.
+Here are some examples:
+@verbatim{
+10 or 20 --> 10
+10 or error() --> 10
+nil or "a" --> "a"
+nil and 10 --> nil
+false and error() --> false
+false and nil --> false
+false or nil --> nil
+10 and 20 --> 20
+}
+
+}
+
+@sect3{concat| @title{Concatenation}
+The string @x{concatenation} operator in Lua is
+denoted by two dots (@Char{..}).
+If both operands are strings or numbers, then they are converted to
+strings according to the rules described in @See{coercion}.
+Otherwise, the @idx{__concat} metamethod is called @see{metatable}.
+
+}
+
+@sect3{len-op| @title{The Length Operator}
+
+The length operator is denoted by the unary prefix operator @T{#}.
+
+The length of a string is its number of bytes
+(that is, the usual meaning of string length when each
+character is one byte).
+
+The length operator applied on a table
+returns a @x{border} in that table.
+A @def{border} in a table @id{t} is any natural number
+that satisfies the following condition:
+@verbatim{
+(border == 0 or t[border] ~= nil) and t[border + 1] == nil
+}
+In words,
+a border is any (natural) index present in the table
+that is followed by an absent index
+(or zero, when index 1 is absent).
+
+A table with exactly one border is called a @def{sequence}.
+For instance, the table @T{{10, 20, 30, 40, 50}} is a sequence,
+as it has only one border (5).
+The table @T{{10, 20, 30, nil, 50}} has two borders (3 and 5),
+and therefore it is not a sequence.
+The table @T{{nil, 20, 30, nil, nil, 60, nil}}
+has three borders (0, 3, and 6),
+so it is not a sequence, too.
+The table @T{{}} is a sequence with border 0.
+Note that non-natural keys do not interfere
+with whether a table is a sequence.
+
+When @id{t} is a sequence,
+@T{#t} returns its only border,
+which corresponds to the intuitive notion of the length of the sequence.
+When @id{t} is not a sequence,
+@T{#t} can return any of its borders.
+(The exact one depends on details of
+the internal representation of the table,
+which in turn can depend on how the table was populated and
+the memory addresses of its non-numeric keys.)
+
+The computation of the length of a table
+has a guaranteed worst time of @M{O(log n)},
+where @M{n} is the largest natural key in the table.
+
+A program can modify the behavior of the length operator for
+any value but strings through the @idx{__len} metamethod @see{metatable}.
+
+}
+
+@sect3{prec| @title{Precedence}
+@x{Operator precedence} in Lua follows the table below,
+from lower to higher priority:
+@verbatim{
+or
+and
+< > <= >= ~= ==
+|
+~
+&
+<< >>
+..
++ -
+* / // %
+unary operators (not # - ~)
+^
+}
+As usual,
+you can use parentheses to change the precedences of an expression.
+The concatenation (@Char{..}) and exponentiation (@Char{^})
+operators are right associative.
+All other binary operators are left associative.
+
+}
+
+@sect3{tableconstructor| @title{Table Constructors}
+Table @x{constructors} are expressions that create tables.
+Every time a constructor is evaluated, a new table is created.
+A constructor can be used to create an empty table
+or to create a table and initialize some of its fields.
+The general syntax for constructors is
+@Produc{
+@producname{tableconstructor}@producbody{@bnfter{@Open} @bnfopt{fieldlist} @bnfter{@Close}}
+@producname{fieldlist}@producbody{field @bnfrep{fieldsep field} @bnfopt{fieldsep}}
+@producname{field}@producbody{@bnfter{[} exp @bnfter{]} @bnfter{=} exp @Or
+ @bnfNter{Name} @bnfter{=} exp @Or exp}
+@producname{fieldsep}@producbody{@bnfter{,} @Or @bnfter{;}}
+}
+
+Each field of the form @T{[exp1] = exp2} adds to the new table an entry
+with key @id{exp1} and value @id{exp2}.
+A field of the form @T{name = exp} is equivalent to
+@T{["name"] = exp}.
+Finally, fields of the form @id{exp} are equivalent to
+@T{[i] = exp}, where @id{i} are consecutive integers
+starting with 1.
+Fields in the other formats do not affect this counting.
+For example,
+@verbatim{
+a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 }
+}
+is equivalent to
+@verbatim{
+do
+ local t = {}
+ t[f(1)] = g
+ t[1] = "x" -- 1st exp
+ t[2] = "y" -- 2nd exp
+ t.x = 1 -- t["x"] = 1
+ t[3] = f(x) -- 3rd exp
+ t[30] = 23
+ t[4] = 45 -- 4th exp
+ a = t
+end
+}
+
+The order of the assignments in a constructor is undefined.
+(This order would be relevant only when there are repeated keys.)
+
+If the last field in the list has the form @id{exp}
+and the expression is a function call or a vararg expression,
+then all values returned by this expression enter the list consecutively
+@see{functioncall}.
+
+The field list can have an optional trailing separator,
+as a convenience for machine-generated code.
+
+}
+
+@sect3{functioncall| @title{Function Calls}
+A @x{function call} in Lua has the following syntax:
+@Produc{
+@producname{functioncall}@producbody{prefixexp args}
+}
+In a function call,
+first @bnfNter{prefixexp} and @bnfNter{args} are evaluated.
+If the value of @bnfNter{prefixexp} has type @emph{function},
+then this function is called
+with the given arguments.
+Otherwise, the @bnfNter{prefixexp} @idx{__call} metamethod is called,
+having as first argument the value of @bnfNter{prefixexp},
+followed by the original call arguments
+@see{metatable}.
+
+The form
+@Produc{
+@producname{functioncall}@producbody{prefixexp @bnfter{:} @bnfNter{Name} args}
+}
+can be used to call @Q{methods}.
+A call @T{v:name(@rep{args})}
+is syntactic sugar for @T{v.name(v,@rep{args})},
+except that @id{v} is evaluated only once.
+
+Arguments have the following syntax:
+@Produc{
+@producname{args}@producbody{@bnfter{(} @bnfopt{explist} @bnfter{)}}
+@producname{args}@producbody{tableconstructor}
+@producname{args}@producbody{@bnfNter{LiteralString}}
+}
+All argument expressions are evaluated before the call.
+A call of the form @T{f{@rep{fields}}} is
+syntactic sugar for @T{f({@rep{fields}})};
+that is, the argument list is a single new table.
+A call of the form @T{f'@rep{string}'}
+(or @T{f"@rep{string}"} or @T{f[[@rep{string}]]})
+is syntactic sugar for @T{f('@rep{string}')};
+that is, the argument list is a single literal string.
+
+A call of the form @T{return @rep{functioncall}} is called
+a @def{tail call}.
+Lua implements @def{proper tail calls}
+(or @emph{proper tail recursion}):
+in a tail call,
+the called function reuses the stack entry of the calling function.
+Therefore, there is no limit on the number of nested tail calls that
+a program can execute.
+However, a tail call erases any debug information about the
+calling function.
+Note that a tail call only happens with a particular syntax,
+where the @Rw{return} has one single function call as argument;
+this syntax makes the calling function return exactly
+the returns of the called function.
+So, none of the following examples are tail calls:
+@verbatim{
+return (f(x)) -- results adjusted to 1
+return 2 * f(x)
+return x, f(x) -- additional results
+f(x); return -- results discarded
+return x or f(x) -- results adjusted to 1
+}
+
+}
+
+@sect3{func-def| @title{Function Definitions}
+
+The syntax for function definition is
+@Produc{
+@producname{functiondef}@producbody{@Rw{function} funcbody}
+@producname{funcbody}@producbody{@bnfter{(} @bnfopt{parlist} @bnfter{)} block @Rw{end}}
+}
+
+The following syntactic sugar simplifies function definitions:
+@Produc{
+@producname{stat}@producbody{@Rw{function} funcname funcbody}
+@producname{stat}@producbody{@Rw{local} @Rw{function} @bnfNter{Name} funcbody}
+@producname{funcname}@producbody{@bnfNter{Name} @bnfrep{@bnfter{.} @bnfNter{Name}} @bnfopt{@bnfter{:} @bnfNter{Name}}}
+}
+The statement
+@verbatim{
+function f () @rep{body} end
+}
+translates to
+@verbatim{
+f = function () @rep{body} end
+}
+The statement
+@verbatim{
+function t.a.b.c.f () @rep{body} end
+}
+translates to
+@verbatim{
+t.a.b.c.f = function () @rep{body} end
+}
+The statement
+@verbatim{
+local function f () @rep{body} end
+}
+translates to
+@verbatim{
+local f; f = function () @rep{body} end
+}
+not to
+@verbatim{
+local f = function () @rep{body} end
+}
+(This only makes a difference when the body of the function
+contains references to @id{f}.)
+
+A function definition is an executable expression,
+whose value has type @emph{function}.
+When Lua precompiles a chunk,
+all its function bodies are precompiled too.
+Then, whenever Lua executes the function definition,
+the function is @emph{instantiated} (or @emph{closed}).
+This function instance (or @emphx{closure})
+is the final value of the expression.
+
+Parameters act as local variables that are
+initialized with the argument values:
+@Produc{
+@producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} @bnfter{...}} @Or
+ @bnfter{...}}
+}
+When a Lua function is called,
+it adjusts its list of @x{arguments} to
+the length of its list of parameters,
+unless the function is a @def{vararg function},
+which is indicated by three dots (@Char{...})
+at the end of its parameter list.
+A vararg function does not adjust its argument list;
+instead, it collects all extra arguments and supplies them
+to the function through a @def{vararg expression},
+which is also written as three dots.
+The value of this expression is a list of all actual extra arguments,
+similar to a function with multiple results.
+If a vararg expression is used inside another expression
+or in the middle of a list of expressions,
+then its return list is adjusted to one element.
+If the expression is used as the last element of a list of expressions,
+then no adjustment is made
+(unless that last expression is enclosed in parentheses).
+
+
+As an example, consider the following definitions:
+@verbatim{
+function f(a, b) end
+function g(a, b, ...) end
+function r() return 1,2,3 end
+}
+Then, we have the following mapping from arguments to parameters and
+to the vararg expression:
+@verbatim{
+CALL PARAMETERS
+
+f(3) a=3, b=nil
+f(3, 4) a=3, b=4
+f(3, 4, 5) a=3, b=4
+f(r(), 10) a=1, b=10
+f(r()) a=1, b=2
+
+g(3) a=3, b=nil, ... --> (nothing)
+g(3, 4) a=3, b=4, ... --> (nothing)
+g(3, 4, 5, 8) a=3, b=4, ... --> 5 8
+g(5, r()) a=5, b=1, ... --> 2 3
+}
+
+Results are returned using the @Rw{return} statement @see{control}.
+If control reaches the end of a function
+without encountering a @Rw{return} statement,
+then the function returns with no results.
+
+@index{multiple return}
+There is a system-dependent limit on the number of values
+that a function may return.
+This limit is guaranteed to be larger than 1000.
+
+The @emphx{colon} syntax
+is used for defining @def{methods},
+that is, functions that have an implicit extra parameter @idx{self}.
+Thus, the statement
+@verbatim{
+function t.a.b.c:f (@rep{params}) @rep{body} end
+}
+is syntactic sugar for
+@verbatim{
+t.a.b.c.f = function (self, @rep{params}) @rep{body} end
+}
+
+}
+
+}
+
+@sect2{visibility| @title{Visibility Rules}
+
+@index{visibility}
+Lua is a lexically scoped language.
+The scope of a local variable begins at the first statement after
+its declaration and lasts until the last non-void statement
+of the innermost block that includes the declaration.
+Consider the following example:
+@verbatim{
+x = 10 -- global variable
+do -- new block
+ local x = x -- new 'x', with value 10
+ print(x) --> 10
+ x = x+1
+ do -- another block
+ local x = x+1 -- another 'x'
+ print(x) --> 12
+ end
+ print(x) --> 11
+end
+print(x) --> 10 (the global one)
+}
+
+Notice that, in a declaration like @T{local x = x},
+the new @id{x} being declared is not in scope yet,
+and so the second @id{x} refers to the outside variable.
+
+Because of the @x{lexical scoping} rules,
+local variables can be freely accessed by functions
+defined inside their scope.
+A local variable used by an inner function is called
+an @def{upvalue}, or @emphx{external local variable},
+inside the inner function.
+
+Notice that each execution of a @Rw{local} statement
+defines new local variables.
+Consider the following example:
+@verbatim{
+a = {}
+local x = 20
+for i=1,10 do
+ local y = 0
+ a[i] = function () y=y+1; return x+y end
+end
+}
+The loop creates ten closures
+(that is, ten instances of the anonymous function).
+Each of these closures uses a different @id{y} variable,
+while all of them share the same @id{x}.
+
+}
+
+}
+
+
+@C{-------------------------------------------------------------------------}
+@sect1{API| @title{The Application Program Interface}
+
+@index{C API}
+This section describes the @N{C API} for Lua, that is,
+the set of @N{C functions} available to the host program to communicate
+with Lua.
+All API functions and related types and constants
+are declared in the header file @defid{lua.h}.
+
+Even when we use the term @Q{function},
+any facility in the API may be provided as a macro instead.
+Except where stated otherwise,
+all such macros use each of their arguments exactly once
+(except for the first argument, which is always a Lua state),
+and so do not generate any hidden side-effects.
+
+As in most @N{C libraries},
+the Lua API functions do not check their arguments
+for validity or consistency.
+However, you can change this behavior by compiling Lua
+with the macro @defid{LUA_USE_APICHECK} defined.
+
+The Lua library is fully reentrant:
+it has no global variables.
+It keeps all information it needs in a dynamic structure,
+called the @def{Lua state}.
+
+Each Lua state has one or more threads,
+which correspond to independent, cooperative lines of execution.
+The type @Lid{lua_State} (despite its name) refers to a thread.
+(Indirectly, through the thread, it also refers to the
+Lua state associated to the thread.)
+
+A pointer to a thread must be passed as the first argument to
+every function in the library, except to @Lid{lua_newstate},
+which creates a Lua state from scratch and returns a pointer
+to the @emph{main thread} in the new state.
+
+
+@sect2{@title{The Stack}
+
+Lua uses a @emph{virtual stack} to pass values to and from C.
+Each element in this stack represents a Lua value
+(@nil, number, string, etc.).
+Functions in the API can access this stack through the
+Lua state parameter that they receive.
+
+Whenever Lua calls C, the called function gets a new stack,
+which is independent of previous stacks and of stacks of
+@N{C functions} that are still active.
+This stack initially contains any arguments to the @N{C function}
+and it is where the @N{C function} can store temporary
+Lua values and must push its results
+to be returned to the caller @seeC{lua_CFunction}.
+
+For convenience,
+most query operations in the API do not follow a strict stack discipline.
+Instead, they can refer to any element in the stack
+by using an @emph{index}:@index{index (API stack)}
+A positive index represents an absolute stack position
+(starting @N{at 1});
+a negative index represents an offset relative to the top of the stack.
+More specifically, if the stack has @rep{n} elements,
+then @N{index 1} represents the first element
+(that is, the element that was pushed onto the stack first)
+and
+@N{index @rep{n}} represents the last element;
+@N{index @num{-1}} also represents the last element
+(that is, the element at @N{the top})
+and index @M{-n} represents the first element.
+
+}
+
+@sect2{stacksize| @title{Stack Size}
+
+When you interact with the Lua API,
+you are responsible for ensuring consistency.
+In particular,
+@emph{you are responsible for controlling stack overflow}.
+You can use the function @Lid{lua_checkstack}
+to ensure that the stack has enough space for pushing new elements.
+
+Whenever Lua calls C,
+it ensures that the stack has space for
+at least @defid{LUA_MINSTACK} extra slots.
+@id{LUA_MINSTACK} is defined as 20,
+so that usually you do not have to worry about stack space
+unless your code has loops pushing elements onto the stack.
+
+When you call a Lua function
+without a fixed number of results @seeF{lua_call},
+Lua ensures that the stack has enough space for all results,
+but it does not ensure any extra space.
+So, before pushing anything in the stack after such a call
+you should use @Lid{lua_checkstack}.
+
+}
+
+@sect2{@title{Valid and Acceptable Indices}
+
+Any function in the API that receives stack indices
+works only with @emphx{valid indices} or @emphx{acceptable indices}.
+
+A @def{valid index} is an index that refers to a
+position that stores a modifiable Lua value.
+It comprises stack indices @N{between 1} and the stack top
+(@T{1 @leq abs(index) @leq top})
+@index{stack index}
+plus @def{pseudo-indices},
+which represent some positions that are accessible to @N{C code}
+but that are not in the stack.
+Pseudo-indices are used to access the registry @see{registry}
+and the upvalues of a @N{C function} @see{c-closure}.
+
+Functions that do not need a specific mutable position,
+but only a value (e.g., query functions),
+can be called with acceptable indices.
+An @def{acceptable index} can be any valid index,
+but it also can be any positive index after the stack top
+within the space allocated for the stack,
+that is, indices up to the stack size.
+(Note that 0 is never an acceptable index.)
+Indices to upvalues @see{c-closure} larger than the real number
+of upvalues in the current @N{C function} are also acceptable (but invalid).
+Except when noted otherwise,
+functions in the API work with acceptable indices.
+
+Acceptable indices serve to avoid extra tests
+against the stack top when querying the stack.
+For instance, a @N{C function} can query its third argument
+without the need to first check whether there is a third argument,
+that is, without the need to check whether 3 is a valid index.
+
+For functions that can be called with acceptable indices,
+any non-valid index is treated as if it
+contains a value of a virtual type @defid{LUA_TNONE},
+which behaves like a nil value.
+
+}
+
+@sect2{c-closure| @title{C Closures}
+
+When a @N{C function} is created,
+it is possible to associate some values with it,
+thus creating a @def{@N{C closure}}
+@seeC{lua_pushcclosure};
+these values are called @def{upvalues} and are
+accessible to the function whenever it is called.
+
+Whenever a @N{C function} is called,
+its upvalues are located at specific pseudo-indices.
+These pseudo-indices are produced by the macro
+@Lid{lua_upvalueindex}.
+The first upvalue associated with a function is at index
+@T{lua_upvalueindex(1)}, and so on.
+Any access to @T{lua_upvalueindex(@rep{n})},
+where @rep{n} is greater than the number of upvalues of the
+current function
+(but not greater than 256,
+which is one plus the maximum number of upvalues in a closure),
+produces an acceptable but invalid index.
+
+A @N{C closure} can also change the values of its corresponding upvalues.
+
+}
+
+@sect2{registry| @title{Registry}
+
+Lua provides a @def{registry},
+a predefined table that can be used by any @N{C code} to
+store whatever Lua values it needs to store.
+The registry table is always located at pseudo-index
+@defid{LUA_REGISTRYINDEX}.
+Any @N{C library} can store data into this table,
+but it must take care to choose keys
+that are different from those used
+by other libraries, to avoid collisions.
+Typically, you should use as key a string containing your library name,
+or a light userdata with the address of a @N{C object} in your code,
+or any Lua object created by your code.
+As with variable names,
+string keys starting with an underscore followed by
+uppercase letters are reserved for Lua.
+
+The integer keys in the registry are used
+by the reference mechanism @seeC{luaL_ref}
+and by some predefined values.
+Therefore, integer keys must not be used for other purposes.
+
+When you create a new Lua state,
+its registry comes with some predefined values.
+These predefined values are indexed with integer keys
+defined as constants in @id{lua.h}.
+The following constants are defined:
+@description{
+@item{@defid{LUA_RIDX_MAINTHREAD}| At this index the registry has
+the main thread of the state.
+(The main thread is the one created together with the state.)
+}
+
+@item{@defid{LUA_RIDX_GLOBALS}| At this index the registry has
+the @x{global environment}.
+}
+}
+
+}
+
+@sect2{C-error|@title{Error Handling in C}
+
+Internally, Lua uses the C @id{longjmp} facility to handle errors.
+(Lua will use exceptions if you compile it as C++;
+search for @id{LUAI_THROW} in the source code for details.)
+When Lua faces any error
+(such as a @x{memory allocation error} or a type error)
+it @emph{raises} an error;
+that is, it does a long jump.
+A @emphx{protected environment} uses @id{setjmp}
+to set a recovery point;
+any error jumps to the most recent active recovery point.
+
+Inside a @N{C function} you can raise an error by calling @Lid{lua_error}.
+
+Most functions in the API can raise an error,
+for instance due to a @x{memory allocation error}.
+The documentation for each function indicates whether
+it can raise errors.
+
+If an error happens outside any protected environment,
+Lua calls a @def{panic function} (see @Lid{lua_atpanic})
+and then calls @T{abort},
+thus exiting the host application.
+Your panic function can avoid this exit by
+never returning
+(e.g., doing a long jump to your own recovery point outside Lua).
+
+The panic function,
+as its name implies,
+is a mechanism of last resort.
+Programs should avoid it.
+As a general rule,
+when a @N{C function} is called by Lua with a Lua state,
+it can do whatever it wants on that Lua state,
+as it should be already protected.
+However,
+when C code operates on other Lua states
+(e.g., a Lua parameter to the function,
+a Lua state stored in the registry, or
+the result of @Lid{lua_newthread}),
+it should use them only in API calls that cannot raise errors.
+
+The panic function runs as if it were a @x{message handler} @see{error};
+in particular, the error object is at the top of the stack.
+However, there is no guarantee about stack space.
+To push anything on the stack,
+the panic function must first check the available space @see{stacksize}.
+
+}
+
+@sect2{continuations|@title{Handling Yields in C}
+
+Internally, Lua uses the C @id{longjmp} facility to yield a coroutine.
+Therefore, if a @N{C function} @id{foo} calls an API function
+and this API function yields
+(directly or indirectly by calling another function that yields),
+Lua cannot return to @id{foo} any more,
+because the @id{longjmp} removes its frame from the C stack.
+
+To avoid this kind of problem,
+Lua raises an error whenever it tries to yield across an API call,
+except for three functions:
+@Lid{lua_yieldk}, @Lid{lua_callk}, and @Lid{lua_pcallk}.
+All those functions receive a @def{continuation function}
+(as a parameter named @id{k}) to continue execution after a yield.
+
+We need to set some terminology to explain continuations.
+We have a @N{C function} called from Lua which we will call
+the @emph{original function}.
+This original function then calls one of those three functions in the C API,
+which we will call the @emph{callee function},
+that then yields the current thread.
+(This can happen when the callee function is @Lid{lua_yieldk},
+or when the callee function is either @Lid{lua_callk} or @Lid{lua_pcallk}
+and the function called by them yields.)
+
+Suppose the running thread yields while executing the callee function.
+After the thread resumes,
+it eventually will finish running the callee function.
+However,
+the callee function cannot return to the original function,
+because its frame in the C stack was destroyed by the yield.
+Instead, Lua calls a @def{continuation function},
+which was given as an argument to the callee function.
+As the name implies,
+the continuation function should continue the task
+of the original function.
+
+As an illustration, consider the following function:
+@verbatim{
+int original_function (lua_State *L) {
+ ... /* code 1 */
+ status = lua_pcall(L, n, m, h); /* calls Lua */
+ ... /* code 2 */
+}
+}
+Now we want to allow
+the Lua code being run by @Lid{lua_pcall} to yield.
+First, we can rewrite our function like here:
+@verbatim{
+int k (lua_State *L, int status, lua_KContext ctx) {
+ ... /* code 2 */
+}
+
+int original_function (lua_State *L) {
+ ... /* code 1 */
+ return k(L, lua_pcall(L, n, m, h), ctx);
+}
+}
+In the above code,
+the new function @id{k} is a
+@emph{continuation function} (with type @Lid{lua_KFunction}),
+which should do all the work that the original function
+was doing after calling @Lid{lua_pcall}.
+Now, we must inform Lua that it must call @id{k} if the Lua code
+being executed by @Lid{lua_pcall} gets interrupted in some way
+(errors or yielding),
+so we rewrite the code as here,
+replacing @Lid{lua_pcall} by @Lid{lua_pcallk}:
+@verbatim{
+int original_function (lua_State *L) {
+ ... /* code 1 */
+ return k(L, lua_pcallk(L, n, m, h, ctx2, k), ctx1);
+}
+}
+Note the external, explicit call to the continuation:
+Lua will call the continuation only if needed, that is,
+in case of errors or resuming after a yield.
+If the called function returns normally without ever yielding,
+@Lid{lua_pcallk} (and @Lid{lua_callk}) will also return normally.
+(Of course, instead of calling the continuation in that case,
+you can do the equivalent work directly inside the original function.)
+
+Besides the Lua state,
+the continuation function has two other parameters:
+the final status of the call plus the context value (@id{ctx}) that
+was passed originally to @Lid{lua_pcallk}.
+(Lua does not use this context value;
+it only passes this value from the original function to the
+continuation function.)
+For @Lid{lua_pcallk},
+the status is the same value that would be returned by @Lid{lua_pcallk},
+except that it is @Lid{LUA_YIELD} when being executed after a yield
+(instead of @Lid{LUA_OK}).
+For @Lid{lua_yieldk} and @Lid{lua_callk},
+the status is always @Lid{LUA_YIELD} when Lua calls the continuation.
+(For these two functions,
+Lua will not call the continuation in case of errors,
+because they do not handle errors.)
+Similarly, when using @Lid{lua_callk},
+you should call the continuation function
+with @Lid{LUA_OK} as the status.
+(For @Lid{lua_yieldk}, there is not much point in calling
+directly the continuation function,
+because @Lid{lua_yieldk} usually does not return.)
+
+Lua treats the continuation function as if it were the original function.
+The continuation function receives the same Lua stack
+from the original function,
+in the same state it would be if the callee function had returned.
+(For instance,
+after a @Lid{lua_callk} the function and its arguments are
+removed from the stack and replaced by the results from the call.)
+It also has the same upvalues.
+Whatever it returns is handled by Lua as if it were the return
+of the original function.
+
+}
+
+@sect2{@title{Functions and Types}
+
+Here we list all functions and types from the @N{C API} in
+alphabetical order.
+Each function has an indicator like this:
+@apii{o,p,x}
+
+The first field, @T{o},
+is how many elements the function pops from the stack.
+The second field, @T{p},
+is how many elements the function pushes onto the stack.
+(Any function always pushes its results after popping its arguments.)
+A field in the form @T{x|y} means the function can push (or pop)
+@T{x} or @T{y} elements,
+depending on the situation;
+an interrogation mark @Char{?} means that
+we cannot know how many elements the function pops/pushes
+by looking only at its arguments
+(e.g., they may depend on what is on the stack).
+The third field, @T{x},
+tells whether the function may raise errors:
+@Char{-} means the function never raises any error;
+@Char{m} means the function may raise out-of-memory errors
+and errors running a finalizer;
+@Char{v} means the function may raise the errors explained in the text;
+@Char{e} means the function may raise any errors
+(because it can run arbitrary Lua code,
+either directly or through metamethods).
+
+
+@APIEntry{int lua_absindex (lua_State *L, int idx);|
+@apii{0,0,-}
+
+Converts the @x{acceptable index} @id{idx}
+into an equivalent @x{absolute index}
+(that is, one that does not depend on the stack top).
+
+}
+
+
+@APIEntry{
+typedef void * (*lua_Alloc) (void *ud,
+ void *ptr,
+ size_t osize,
+ size_t nsize);|
+
+The type of the @x{memory-allocation function} used by Lua states.
+The allocator function must provide a
+functionality similar to @id{realloc},
+but not exactly the same.
+Its arguments are
+@id{ud}, an opaque pointer passed to @Lid{lua_newstate};
+@id{ptr}, a pointer to the block being allocated/reallocated/freed;
+@id{osize}, the original size of the block or some code about what
+is being allocated;
+and @id{nsize}, the new size of the block.
+
+When @id{ptr} is not @id{NULL},
+@id{osize} is the size of the block pointed by @id{ptr},
+that is, the size given when it was allocated or reallocated.
+
+When @id{ptr} is @id{NULL},
+@id{osize} encodes the kind of object that Lua is allocating.
+@id{osize} is any of
+@Lid{LUA_TSTRING}, @Lid{LUA_TTABLE}, @Lid{LUA_TFUNCTION},
+@Lid{LUA_TUSERDATA}, or @Lid{LUA_TTHREAD} when (and only when)
+Lua is creating a new object of that type.
+When @id{osize} is some other value,
+Lua is allocating memory for something else.
+
+Lua assumes the following behavior from the allocator function:
+
+When @id{nsize} is zero,
+the allocator must behave like @id{free}
+and return @id{NULL}.
+
+When @id{nsize} is not zero,
+the allocator must behave like @id{realloc}.
+The allocator returns @id{NULL}
+if and only if it cannot fulfill the request.
+
+Here is a simple implementation for the @x{allocator function}.
+It is used in the auxiliary library by @Lid{luaL_newstate}.
+@verbatim{
+static void *l_alloc (void *ud, void *ptr, size_t osize,
+ size_t nsize) {
+ (void)ud; (void)osize; /* not used */
+ if (nsize == 0) {
+ free(ptr);
+ return NULL;
+ }
+ else
+ return realloc(ptr, nsize);
+}
+}
+Note that @N{Standard C} ensures
+that @T{free(NULL)} has no effect and that
+@T{realloc(NULL,size)} is equivalent to @T{malloc(size)}.
+
+}
+
+@APIEntry{void lua_arith (lua_State *L, int op);|
+@apii{2|1,1,e}
+
+Performs an arithmetic or bitwise operation over the two values
+(or one, in the case of negations)
+at the top of the stack,
+with the value at the top being the second operand,
+pops these values, and pushes the result of the operation.
+The function follows the semantics of the corresponding Lua operator
+(that is, it may call metamethods).
+
+The value of @id{op} must be one of the following constants:
+@description{
+
+@item{@defid{LUA_OPADD}| performs addition (@T{+})}
+@item{@defid{LUA_OPSUB}| performs subtraction (@T{-})}
+@item{@defid{LUA_OPMUL}| performs multiplication (@T{*})}
+@item{@defid{LUA_OPDIV}| performs float division (@T{/})}
+@item{@defid{LUA_OPIDIV}| performs floor division (@T{//})}
+@item{@defid{LUA_OPMOD}| performs modulo (@T{%})}
+@item{@defid{LUA_OPPOW}| performs exponentiation (@T{^})}
+@item{@defid{LUA_OPUNM}| performs mathematical negation (unary @T{-})}
+@item{@defid{LUA_OPBNOT}| performs bitwise NOT (@T{~})}
+@item{@defid{LUA_OPBAND}| performs bitwise AND (@T{&})}
+@item{@defid{LUA_OPBOR}| performs bitwise OR (@T{|})}
+@item{@defid{LUA_OPBXOR}| performs bitwise exclusive OR (@T{~})}
+@item{@defid{LUA_OPSHL}| performs left shift (@T{<<})}
+@item{@defid{LUA_OPSHR}| performs right shift (@T{>>})}
+
+}
+
+}
+
+@APIEntry{lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf);|
+@apii{0,0,-}
+
+Sets a new panic function and returns the old one @see{C-error}.
+
+}
+
+@APIEntry{void lua_call (lua_State *L, int nargs, int nresults);|
+@apii{nargs+1,nresults,e}
+
+Calls a function.
+
+To do a call you must use the following protocol:
+first, the value to be called is pushed onto the stack;
+then, the arguments to the call are pushed
+in direct order;
+that is, the first argument is pushed first.
+Finally you call @Lid{lua_call};
+@id{nargs} is the number of arguments that you pushed onto the stack.
+All arguments and the function value are popped from the stack
+when the function is called.
+The function results are pushed onto the stack when the function returns.
+The number of results is adjusted to @id{nresults},
+unless @id{nresults} is @defid{LUA_MULTRET}.
+In this case, all results from the function are pushed;
+Lua takes care that the returned values fit into the stack space,
+but it does not ensure any extra space in the stack.
+The function results are pushed onto the stack in direct order
+(the first result is pushed first),
+so that after the call the last result is on the top of the stack.
+
+Any error while calling and running the function is propagated upwards
+(with a @id{longjmp}).
+Like regular Lua calls,
+this function respects the @idx{__call} metamethod.
+
+The following example shows how the host program can do the
+equivalent to this Lua code:
+@verbatim{
+a = f("how", t.x, 14)
+}
+Here it is @N{in C}:
+@verbatim{
+lua_getglobal(L, "f"); /* function to be called */
+lua_pushliteral(L, "how"); /* 1st argument */
+lua_getglobal(L, "t"); /* table to be indexed */
+lua_getfield(L, -1, "x"); /* push result of t.x (2nd arg) */
+lua_remove(L, -2); /* remove 't' from the stack */
+lua_pushinteger(L, 14); /* 3rd argument */
+lua_call(L, 3, 1); /* call 'f' with 3 arguments and 1 result */
+lua_setglobal(L, "a"); /* set global 'a' */
+}
+Note that the code above is @emph{balanced}:
+at its end, the stack is back to its original configuration.
+This is considered good programming practice.
+
+}
+
+@APIEntry{
+void lua_callk (lua_State *L,
+ int nargs,
+ int nresults,
+ lua_KContext ctx,
+ lua_KFunction k);|
+@apii{nargs + 1,nresults,e}
+
+This function behaves exactly like @Lid{lua_call},
+but allows the called function to yield @see{continuations}.
+
+}
+
+@APIEntry{typedef int (*lua_CFunction) (lua_State *L);|
+
+Type for @N{C functions}.
+
+In order to communicate properly with Lua,
+a @N{C function} must use the following protocol,
+which defines the way parameters and results are passed:
+a @N{C function} receives its arguments from Lua in its stack
+in direct order (the first argument is pushed first).
+So, when the function starts,
+@T{lua_gettop(L)} returns the number of arguments received by the function.
+The first argument (if any) is at index 1
+and its last argument is at index @T{lua_gettop(L)}.
+To return values to Lua, a @N{C function} just pushes them onto the stack,
+in direct order (the first result is pushed first),
+and returns the number of results.
+Any other value in the stack below the results will be properly
+discarded by Lua.
+Like a Lua function, a @N{C function} called by Lua can also return
+many results.
+
+As an example, the following function receives a variable number
+of numeric arguments and returns their average and their sum:
+@verbatim{
+static int foo (lua_State *L) {
+ int n = lua_gettop(L); /* number of arguments */
+ lua_Number sum = 0.0;
+ int i;
+ for (i = 1; i <= n; i++) {
+ if (!lua_isnumber(L, i)) {
+ lua_pushliteral(L, "incorrect argument");
+ lua_error(L);
+ }
+ sum += lua_tonumber(L, i);
+ }
+ lua_pushnumber(L, sum/n); /* first result */
+ lua_pushnumber(L, sum); /* second result */
+ return 2; /* number of results */
+}
+}
+
+
+
+}
+
+
+@APIEntry{int lua_checkstack (lua_State *L, int n);|
+@apii{0,0,-}
+
+Ensures that the stack has space for at least @id{n} extra slots
+(that is, that you can safely push up to @id{n} values into it).
+It returns false if it cannot fulfill the request,
+either because it would cause the stack
+to be larger than a fixed maximum size
+(typically at least several thousand elements) or
+because it cannot allocate memory for the extra space.
+This function never shrinks the stack;
+if the stack already has space for the extra slots,
+it is left unchanged.
+
+}
+
+@APIEntry{void lua_close (lua_State *L);|
+@apii{0,0,-}
+
+Destroys all objects in the given Lua state
+(calling the corresponding garbage-collection metamethods, if any)
+and frees all dynamic memory used by this state.
+On several platforms, you may not need to call this function,
+because all resources are naturally released when the host program ends.
+On the other hand, long-running programs that create multiple states,
+such as daemons or web servers,
+will probably need to close states as soon as they are not needed.
+
+}
+
+@APIEntry{int lua_compare (lua_State *L, int index1, int index2, int op);|
+@apii{0,0,e}
+
+Compares two Lua values.
+Returns 1 if the value at index @id{index1} satisfies @id{op}
+when compared with the value at index @id{index2},
+following the semantics of the corresponding Lua operator
+(that is, it may call metamethods).
+Otherwise @N{returns 0}.
+Also @N{returns 0} if any of the indices is not valid.
+
+The value of @id{op} must be one of the following constants:
+@description{
+
+@item{@defid{LUA_OPEQ}| compares for equality (@T{==})}
+@item{@defid{LUA_OPLT}| compares for less than (@T{<})}
+@item{@defid{LUA_OPLE}| compares for less or equal (@T{<=})}
+
+}
+
+}
+
+@APIEntry{void lua_concat (lua_State *L, int n);|
+@apii{n,1,e}
+
+Concatenates the @id{n} values at the top of the stack,
+pops them, and leaves the result at the top.
+If @N{@T{n} is 1}, the result is the single value on the stack
+(that is, the function does nothing);
+if @id{n} is 0, the result is the empty string.
+Concatenation is performed following the usual semantics of Lua
+@see{concat}.
+
+}
+
+@APIEntry{void lua_copy (lua_State *L, int fromidx, int toidx);|
+@apii{0,0,-}
+
+Copies the element at index @id{fromidx}
+into the valid index @id{toidx},
+replacing the value at that position.
+Values at other positions are not affected.
+
+}
+
+@APIEntry{void lua_createtable (lua_State *L, int narr, int nrec);|
+@apii{0,1,m}
+
+Creates a new empty table and pushes it onto the stack.
+Parameter @id{narr} is a hint for how many elements the table
+will have as a sequence;
+parameter @id{nrec} is a hint for how many other elements
+the table will have.
+Lua may use these hints to preallocate memory for the new table.
+This preallocation is useful for performance when you know in advance
+how many elements the table will have.
+Otherwise you can use the function @Lid{lua_newtable}.
+
+}
+
+@APIEntry{int lua_dump (lua_State *L,
+ lua_Writer writer,
+ void *data,
+ int strip);|
+@apii{0,0,-}
+
+Dumps a function as a binary chunk.
+Receives a Lua function on the top of the stack
+and produces a binary chunk that,
+if loaded again,
+results in a function equivalent to the one dumped.
+As it produces parts of the chunk,
+@Lid{lua_dump} calls function @id{writer} @seeC{lua_Writer}
+with the given @id{data}
+to write them.
+
+If @id{strip} is true,
+the binary representation may not include all debug information
+about the function,
+to save space.
+
+The value returned is the error code returned by the last
+call to the writer;
+@N{0 means} no errors.
+
+This function does not pop the Lua function from the stack.
+
+}
+
+@APIEntry{int lua_error (lua_State *L);|
+@apii{1,0,v}
+
+Generates a Lua error,
+using the value at the top of the stack as the error object.
+This function does a long jump,
+and therefore never returns
+@seeC{luaL_error}.
+
+}
+
+@APIEntry{int lua_gc (lua_State *L, int what, int data);|
+@apii{0,0,v}
+
+Controls the garbage collector.
+
+This function performs several tasks,
+according to the value of the parameter @id{what}:
+@description{
+
+@item{@id{LUA_GCSTOP}|
+stops the garbage collector.
+}
+
+@item{@id{LUA_GCRESTART}|
+restarts the garbage collector.
+}
+
+@item{@id{LUA_GCCOLLECT}|
+performs a full garbage-collection cycle.
+}
+
+@item{@id{LUA_GCCOUNT}|
+returns the current amount of memory (in Kbytes) in use by Lua.
+}
+
+@item{@id{LUA_GCCOUNTB}|
+returns the remainder of dividing the current amount of bytes of
+memory in use by Lua by 1024.
+}
+
+@item{@id{LUA_GCSTEP}|
+performs an incremental step of garbage collection.
+}
+
+@item{@id{LUA_GCSETPAUSE}|
+sets @id{data} as the new value
+for the @emph{pause} of the collector @see{GC}
+and returns the previous value of the pause.
+}
+
+@item{@id{LUA_GCSETSTEPMUL}|
+sets @id{data} as the new value for the @emph{step multiplier} of
+the collector @see{GC}
+and returns the previous value of the step multiplier.
+}
+
+@item{@id{LUA_GCISRUNNING}|
+returns a boolean that tells whether the collector is running
+(i.e., not stopped).
+}
+
+}
+For more details about these options,
+see @Lid{collectgarbage}.
+
+This function may raise errors when calling finalizers.
+
+}
+
+@APIEntry{lua_Alloc lua_getallocf (lua_State *L, void **ud);|
+@apii{0,0,-}
+
+Returns the @x{memory-allocation function} of a given state.
+If @id{ud} is not @id{NULL}, Lua stores in @T{*ud} the
+opaque pointer given when the memory-allocator function was set.
+
+}
+
+@APIEntry{int lua_getfield (lua_State *L, int index, const char *k);|
+@apii{0,1,e}
+
+Pushes onto the stack the value @T{t[k]},
+where @id{t} is the value at the given index.
+As in Lua, this function may trigger a metamethod
+for the @Q{index} event @see{metatable}.
+
+Returns the type of the pushed value.
+
+}
+
+@APIEntry{void *lua_getextraspace (lua_State *L);|
+@apii{0,0,-}
+
+Returns a pointer to a raw memory area associated with the
+given Lua state.
+The application can use this area for any purpose;
+Lua does not use it for anything.
+
+Each new thread has this area initialized with a copy
+of the area of the @x{main thread}.
+
+By default, this area has the size of a pointer to void,
+but you can recompile Lua with a different size for this area.
+(See @id{LUA_EXTRASPACE} in @id{luaconf.h}.)
+
+}
+
+@APIEntry{int lua_getglobal (lua_State *L, const char *name);|
+@apii{0,1,e}
+
+Pushes onto the stack the value of the global @id{name}.
+Returns the type of that value.
+
+}
+
+@APIEntry{int lua_geti (lua_State *L, int index, lua_Integer i);|
+@apii{0,1,e}
+
+Pushes onto the stack the value @T{t[i]},
+where @id{t} is the value at the given index.
+As in Lua, this function may trigger a metamethod
+for the @Q{index} event @see{metatable}.
+
+Returns the type of the pushed value.
+
+}
+
+@APIEntry{int lua_getmetatable (lua_State *L, int index);|
+@apii{0,0|1,-}
+
+If the value at the given index has a metatable,
+the function pushes that metatable onto the stack and @N{returns 1}.
+Otherwise,
+the function @N{returns 0} and pushes nothing on the stack.
+
+}
+
+@APIEntry{int lua_gettable (lua_State *L, int index);|
+@apii{1,1,e}
+
+Pushes onto the stack the value @T{t[k]},
+where @id{t} is the value at the given index
+and @id{k} is the value at the top of the stack.
+
+This function pops the key from the stack,
+pushing the resulting value in its place.
+As in Lua, this function may trigger a metamethod
+for the @Q{index} event @see{metatable}.
+
+Returns the type of the pushed value.
+
+}
+
+@APIEntry{int lua_gettop (lua_State *L);|
+@apii{0,0,-}
+
+Returns the index of the top element in the stack.
+Because indices start @N{at 1},
+this result is equal to the number of elements in the stack;
+in particular, @N{0 means} an empty stack.
+
+}
+
+@APIEntry{int lua_getiuservalue (lua_State *L, int index, int n);|
+@apii{0,1,-}
+
+Pushes onto the stack the @id{n}-th user value associated with the
+full userdata at the given index and
+returns the type of the pushed value.
+
+If the userdata does not have that value,
+pushes @nil and returns @Lid{LUA_TNONE}.
+
+}
+
+@APIEntry{void lua_insert (lua_State *L, int index);|
+@apii{1,1,-}
+
+Moves the top element into the given valid index,
+shifting up the elements above this index to open space.
+This function cannot be called with a pseudo-index,
+because a pseudo-index is not an actual stack position.
+
+}
+
+@APIEntry{typedef @ldots lua_Integer;|
+
+The type of integers in Lua.
+
+By default this type is @id{long long},
+(usually a 64-bit two-complement integer),
+but that can be changed to @id{long} or @id{int}
+(usually a 32-bit two-complement integer).
+(See @id{LUA_INT_TYPE} in @id{luaconf.h}.)
+
+Lua also defines the constants
+@defid{LUA_MININTEGER} and @defid{LUA_MAXINTEGER},
+with the minimum and the maximum values that fit in this type.
+
+}
+
+@APIEntry{int lua_isboolean (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the value at the given index is a boolean,
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_iscfunction (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the value at the given index is a @N{C function},
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_isfunction (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the value at the given index is a function
+(either C or Lua), and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_isinteger (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the value at the given index is an integer
+(that is, the value is a number and is represented as an integer),
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_islightuserdata (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the value at the given index is a light userdata,
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_isnil (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the value at the given index is @nil,
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_isnone (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the given index is not valid,
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_isnoneornil (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the given index is not valid
+or if the value at this index is @nil,
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_isnumber (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the value at the given index is a number
+or a string convertible to a number,
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_isstring (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the value at the given index is a string
+or a number (which is always convertible to a string),
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_istable (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the value at the given index is a table,
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_isthread (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the value at the given index is a thread,
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_isuserdata (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns 1 if the value at the given index is a userdata
+(either full or light), and @N{0 otherwise}.
+
+}
+
+@APIEntry{int lua_isyieldable (lua_State *L);|
+@apii{0,0,-}
+
+Returns 1 if the given coroutine can yield,
+and @N{0 otherwise}.
+
+}
+
+@APIEntry{typedef @ldots lua_KContext;|
+
+The type for continuation-function contexts.
+It must be a numeric type.
+This type is defined as @id{intptr_t}
+when @id{intptr_t} is available,
+so that it can store pointers too.
+Otherwise, it is defined as @id{ptrdiff_t}.
+
+}
+
+@APIEntry{
+typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx);|
+
+Type for continuation functions @see{continuations}.
+
+}
+
+@APIEntry{void lua_len (lua_State *L, int index);|
+@apii{0,1,e}
+
+Returns the length of the value at the given index.
+It is equivalent to the @Char{#} operator in Lua @see{len-op} and
+may trigger a metamethod for the @Q{length} event @see{metatable}.
+The result is pushed on the stack.
+
+}
+
+@APIEntry{
+int lua_load (lua_State *L,
+ lua_Reader reader,
+ void *data,
+ const char *chunkname,
+ const char *mode);|
+@apii{0,1,-}
+
+Loads a Lua chunk without running it.
+If there are no errors,
+@id{lua_load} pushes the compiled chunk as a Lua
+function on top of the stack.
+Otherwise, it pushes an error message.
+
+The return values of @id{lua_load} are:
+@description{
+
+@item{@Lid{LUA_OK}| no errors;}
+
+@item{@defid{LUA_ERRSYNTAX}|
+syntax error during precompilation;}
+
+@item{@Lid{LUA_ERRMEM}|
+@x{memory allocation (out-of-memory) error};}
+
+@item{@Lid{LUA_ERRGCMM}|
+error while running a @idx{__gc} metamethod.
+(This error has no relation with the chunk being loaded.
+It is generated by the garbage collector.)
+}
+
+}
+
+The @id{lua_load} function uses a user-supplied @id{reader} function
+to read the chunk @seeC{lua_Reader}.
+The @id{data} argument is an opaque value passed to the reader function.
+
+The @id{chunkname} argument gives a name to the chunk,
+which is used for error messages and in debug information @see{debugI}.
+
+@id{lua_load} automatically detects whether the chunk is text or binary
+and loads it accordingly (see program @idx{luac}).
+The string @id{mode} works as in function @Lid{load},
+with the addition that
+a @id{NULL} value is equivalent to the string @St{bt}.
+
+@id{lua_load} uses the stack internally,
+so the reader function must always leave the stack
+unmodified when returning.
+
+If the resulting function has upvalues,
+its first upvalue is set to the value of the @x{global environment}
+stored at index @id{LUA_RIDX_GLOBALS} in the registry @see{registry}.
+When loading main chunks,
+this upvalue will be the @id{_ENV} variable @see{globalenv}.
+Other upvalues are initialized with @nil.
+
+}
+
+@APIEntry{lua_State *lua_newstate (lua_Alloc f, void *ud);|
+@apii{0,0,-}
+
+Creates a new thread running in a new, independent state.
+Returns @id{NULL} if it cannot create the thread or the state
+(due to lack of memory).
+The argument @id{f} is the @x{allocator function};
+Lua does all memory allocation for this state
+through this function @seeF{lua_Alloc}.
+The second argument, @id{ud}, is an opaque pointer that Lua
+passes to the allocator in every call.
+
+}
+
+@APIEntry{void lua_newtable (lua_State *L);|
+@apii{0,1,m}
+
+Creates a new empty table and pushes it onto the stack.
+It is equivalent to @T{lua_createtable(L, 0, 0)}.
+
+}
+
+@APIEntry{lua_State *lua_newthread (lua_State *L);|
+@apii{0,1,m}
+
+Creates a new thread, pushes it on the stack,
+and returns a pointer to a @Lid{lua_State} that represents this new thread.
+The new thread returned by this function shares with the original thread
+its global environment,
+but has an independent execution stack.
+
+There is no explicit function to close or to destroy a thread.
+Threads are subject to garbage collection,
+like any Lua object.
+
+}
+
+@APIEntry{void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue);|
+@apii{0,1,m}
+
+This function creates and pushes on the stack a new full userdata,
+with @id{nuvalue} associated Lua values (called @id{user values})
+plus an associated block of raw memory with @id{size} bytes.
+(The user values can be set and read with the functions
+@Lid{lua_setiuservalue} and @Lid{lua_getiuservalue}.)
+
+The function returns the address of the block of memory.
+
+}
+
+@APIEntry{int lua_next (lua_State *L, int index);|
+@apii{1,2|0,v}
+
+Pops a key from the stack,
+and pushes a key@En{}value pair from the table at the given index
+(the @Q{next} pair after the given key).
+If there are no more elements in the table,
+then @Lid{lua_next} returns 0 (and pushes nothing).
+
+A typical traversal looks like this:
+@verbatim{
+/* table is in the stack at index 't' */
+lua_pushnil(L); /* first key */
+while (lua_next(L, t) != 0) {
+ /* uses 'key' (at index -2) and 'value' (at index -1) */
+ printf("%s - %s\n",
+ lua_typename(L, lua_type(L, -2)),
+ lua_typename(L, lua_type(L, -1)));
+ /* removes 'value'; keeps 'key' for next iteration */
+ lua_pop(L, 1);
+}
+}
+
+While traversing a table,
+do not call @Lid{lua_tolstring} directly on a key,
+unless you know that the key is actually a string.
+Recall that @Lid{lua_tolstring} may change
+the value at the given index;
+this confuses the next call to @Lid{lua_next}.
+
+This function may raise an error if the given key
+is neither @nil nor present in the table.
+See function @Lid{next} for the caveats of modifying
+the table during its traversal.
+
+}
+
+@APIEntry{typedef @ldots lua_Number;|
+
+The type of floats in Lua.
+
+By default this type is double,
+but that can be changed to a single float or a long double.
+(See @id{LUA_FLOAT_TYPE} in @id{luaconf.h}.)
+
+}
+
+@APIEntry{int lua_numbertointeger (lua_Number n, lua_Integer *p);|
+
+Converts a Lua float to a Lua integer.
+This macro assumes that @id{n} has an integral value.
+If that value is within the range of Lua integers,
+it is converted to an integer and assigned to @T{*p}.
+The macro results in a boolean indicating whether the
+conversion was successful.
+(Note that this range test can be tricky to do
+correctly without this macro,
+due to roundings.)
+
+This macro may evaluate its arguments more than once.
+
+}
+
+@APIEntry{int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);|
+@apii{nargs + 1,nresults|1,-}
+
+Calls a function (or a callable object) in protected mode.
+
+Both @id{nargs} and @id{nresults} have the same meaning as
+in @Lid{lua_call}.
+If there are no errors during the call,
+@Lid{lua_pcall} behaves exactly like @Lid{lua_call}.
+However, if there is any error,
+@Lid{lua_pcall} catches it,
+pushes a single value on the stack (the error object),
+and returns an error code.
+Like @Lid{lua_call},
+@Lid{lua_pcall} always removes the function
+and its arguments from the stack.
+
+If @id{msgh} is 0,
+then the error object returned on the stack
+is exactly the original error object.
+Otherwise, @id{msgh} is the stack index of a
+@emph{message handler}.
+(This index cannot be a pseudo-index.)
+In case of runtime errors,
+this function will be called with the error object
+and its return value will be the object
+returned on the stack by @Lid{lua_pcall}.
+
+Typically, the message handler is used to add more debug
+information to the error object, such as a stack traceback.
+Such information cannot be gathered after the return of @Lid{lua_pcall},
+since by then the stack has unwound.
+
+The @Lid{lua_pcall} function returns one of the following constants
+(defined in @id{lua.h}):
+@description{
+
+@item{@defid{LUA_OK} (0)|
+success.}
+
+@item{@defid{LUA_ERRRUN}|
+a runtime error.
+}
+
+@item{@defid{LUA_ERRMEM}|
+@x{memory allocation error}.
+For such errors, Lua does not call the @x{message handler}.
+}
+
+@item{@defid{LUA_ERRERR}|
+error while running the @x{message handler}.
+}
+
+@item{@defid{LUA_ERRGCMM}|
+error while running a @idx{__gc} metamethod.
+For such errors, Lua does not call the @x{message handler}
+(as this kind of error typically has no relation
+with the function being called).
+}
+
+}
+
+}
+
+@APIEntry{
+int lua_pcallk (lua_State *L,
+ int nargs,
+ int nresults,
+ int msgh,
+ lua_KContext ctx,
+ lua_KFunction k);|
+@apii{nargs + 1,nresults|1,-}
+
+This function behaves exactly like @Lid{lua_pcall},
+but allows the called function to yield @see{continuations}.
+
+}
+
+@APIEntry{void lua_pop (lua_State *L, int n);|
+@apii{n,0,-}
+
+Pops @id{n} elements from the stack.
+
+}
+
+@APIEntry{void lua_pushboolean (lua_State *L, int b);|
+@apii{0,1,-}
+
+Pushes a boolean value with value @id{b} onto the stack.
+
+}
+
+@APIEntry{void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);|
+@apii{n,1,m}
+
+Pushes a new @N{C closure} onto the stack.
+This function receives a pointer to a @N{C function}
+and pushes onto the stack a Lua value of type @id{function} that,
+when called, invokes the corresponding @N{C function}.
+The parameter @id{n} tells how many upvalues this function will have
+@see{c-closure}.
+
+Any function to be callable by Lua must
+follow the correct protocol to receive its parameters
+and return its results @seeC{lua_CFunction}.
+
+When a @N{C function} is created,
+it is possible to associate some values with it,
+thus creating a @x{@N{C closure}} @see{c-closure};
+these values are then accessible to the function whenever it is called.
+To associate values with a @N{C function},
+first these values must be pushed onto the stack
+(when there are multiple values, the first value is pushed first).
+Then @Lid{lua_pushcclosure}
+is called to create and push the @N{C function} onto the stack,
+with the argument @id{n} telling how many values will be
+associated with the function.
+@Lid{lua_pushcclosure} also pops these values from the stack.
+
+The maximum value for @id{n} is 255.
+
+When @id{n} is zero,
+this function creates a @def{light @N{C function}},
+which is just a pointer to the @N{C function}.
+In that case, it never raises a memory error.
+
+}
+
+@APIEntry{void lua_pushcfunction (lua_State *L, lua_CFunction f);|
+@apii{0,1,-}
+
+Pushes a @N{C function} onto the stack.
+
+}
+
+@APIEntry{const char *lua_pushfstring (lua_State *L, const char *fmt, ...);|
+@apii{0,1,v}
+
+Pushes onto the stack a formatted string
+and returns a pointer to this string.
+It is similar to the @ANSI{sprintf},
+but has two important differences.
+First,
+you do not have to allocate space for the result;
+the result is a Lua string and Lua takes care of memory allocation
+(and deallocation, through garbage collection).
+Second,
+the conversion specifiers are quite restricted.
+There are no flags, widths, or precisions.
+The conversion specifiers can only be
+@Char{%%} (inserts the character @Char{%}),
+@Char{%s} (inserts a zero-terminated string, with no size restrictions),
+@Char{%f} (inserts a @Lid{lua_Number}),
+@Char{%I} (inserts a @Lid{lua_Integer}),
+@Char{%p} (inserts a pointer as a hexadecimal numeral),
+@Char{%d} (inserts an @T{int}),
+@Char{%c} (inserts an @T{int} as a one-byte character), and
+@Char{%U} (inserts a @T{long int} as a @x{UTF-8} byte sequence).
+
+This function may raise errors due to memory overflow
+or an invalid conversion specifier.
+
+}
+
+@APIEntry{void lua_pushglobaltable (lua_State *L);|
+@apii{0,1,-}
+
+Pushes the @x{global environment} onto the stack.
+
+}
+
+@APIEntry{void lua_pushinteger (lua_State *L, lua_Integer n);|
+@apii{0,1,-}
+
+Pushes an integer with value @id{n} onto the stack.
+
+}
+
+@APIEntry{void lua_pushlightuserdata (lua_State *L, void *p);|
+@apii{0,1,-}
+
+Pushes a light userdata onto the stack.
+
+Userdata represent @N{C values} in Lua.
+A @def{light userdata} represents a pointer, a @T{void*}.
+It is a value (like a number):
+you do not create it, it has no individual metatable,
+and it is not collected (as it was never created).
+A light userdata is equal to @Q{any}
+light userdata with the same @N{C address}.
+
+}
+
+@APIEntry{const char *lua_pushliteral (lua_State *L, const char *s);|
+@apii{0,1,m}
+
+This macro is equivalent to @Lid{lua_pushstring},
+but should be used only when @id{s} is a literal string.
+
+}
+
+@APIEntry{const char *lua_pushlstring (lua_State *L, const char *s, size_t len);|
+@apii{0,1,m}
+
+Pushes the string pointed to by @id{s} with size @id{len}
+onto the stack.
+Lua makes (or reuses) an internal copy of the given string,
+so the memory at @id{s} can be freed or reused immediately after
+the function returns.
+The string can contain any binary data,
+including @x{embedded zeros}.
+
+Returns a pointer to the internal copy of the string.
+
+}
+
+@APIEntry{void lua_pushnil (lua_State *L);|
+@apii{0,1,-}
+
+Pushes a nil value onto the stack.
+
+}
+
+@APIEntry{void lua_pushnumber (lua_State *L, lua_Number n);|
+@apii{0,1,-}
+
+Pushes a float with value @id{n} onto the stack.
+
+}
+
+@APIEntry{const char *lua_pushstring (lua_State *L, const char *s);|
+@apii{0,1,m}
+
+Pushes the zero-terminated string pointed to by @id{s}
+onto the stack.
+Lua makes (or reuses) an internal copy of the given string,
+so the memory at @id{s} can be freed or reused immediately after
+the function returns.
+
+Returns a pointer to the internal copy of the string.
+
+If @id{s} is @id{NULL}, pushes @nil and returns @id{NULL}.
+
+}
+
+@APIEntry{int lua_pushthread (lua_State *L);|
+@apii{0,1,-}
+
+Pushes the thread represented by @id{L} onto the stack.
+Returns 1 if this thread is the @x{main thread} of its state.
+
+}
+
+@APIEntry{void lua_pushvalue (lua_State *L, int index);|
+@apii{0,1,-}
+
+Pushes a copy of the element at the given index
+onto the stack.
+
+}
+
+@APIEntry{
+const char *lua_pushvfstring (lua_State *L,
+ const char *fmt,
+ va_list argp);|
+@apii{0,1,v}
+
+Equivalent to @Lid{lua_pushfstring}, except that it receives a @id{va_list}
+instead of a variable number of arguments.
+
+}
+
+@APIEntry{int lua_rawequal (lua_State *L, int index1, int index2);|
+@apii{0,0,-}
+
+Returns 1 if the two values in indices @id{index1} and
+@id{index2} are primitively equal
+(that is, without calling the @idx{__eq} metamethod).
+Otherwise @N{returns 0}.
+Also @N{returns 0} if any of the indices are not valid.
+
+}
+
+@APIEntry{int lua_rawget (lua_State *L, int index);|
+@apii{1,1,-}
+
+Similar to @Lid{lua_gettable}, but does a raw access
+(i.e., without metamethods).
+
+}
+
+@APIEntry{int lua_rawgeti (lua_State *L, int index, lua_Integer n);|
+@apii{0,1,-}
+
+Pushes onto the stack the value @T{t[n]},
+where @id{t} is the table at the given index.
+The access is raw,
+that is, it does not invoke the @idx{__index} metamethod.
+
+Returns the type of the pushed value.
+
+}
+
+@APIEntry{int lua_rawgetp (lua_State *L, int index, const void *p);|
+@apii{0,1,-}
+
+Pushes onto the stack the value @T{t[k]},
+where @id{t} is the table at the given index and
+@id{k} is the pointer @id{p} represented as a light userdata.
+The access is raw;
+that is, it does not invoke the @idx{__index} metamethod.
+
+Returns the type of the pushed value.
+
+}
+
+@APIEntry{lua_Unsigned lua_rawlen (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns the raw @Q{length} of the value at the given index:
+for strings, this is the string length;
+for tables, this is the result of the length operator (@Char{#})
+with no metamethods;
+for userdata, this is the size of the block of memory allocated
+for the userdata;
+for other values, it @N{is 0}.
+
+}
+
+@APIEntry{void lua_rawset (lua_State *L, int index);|
+@apii{2,0,m}
+
+Similar to @Lid{lua_settable}, but does a raw assignment
+(i.e., without metamethods).
+
+}
+
+@APIEntry{void lua_rawseti (lua_State *L, int index, lua_Integer i);|
+@apii{1,0,m}
+
+Does the equivalent of @T{t[i] = v},
+where @id{t} is the table at the given index
+and @id{v} is the value at the top of the stack.
+
+This function pops the value from the stack.
+The assignment is raw,
+that is, it does not invoke the @idx{__newindex} metamethod.
+
+}
+
+@APIEntry{void lua_rawsetp (lua_State *L, int index, const void *p);|
+@apii{1,0,m}
+
+Does the equivalent of @T{t[p] = v},
+where @id{t} is the table at the given index,
+@id{p} is encoded as a light userdata,
+and @id{v} is the value at the top of the stack.
+
+This function pops the value from the stack.
+The assignment is raw,
+that is, it does not invoke @idx{__newindex} metamethod.
+
+}
+
+@APIEntry{
+typedef const char * (*lua_Reader) (lua_State *L,
+ void *data,
+ size_t *size);|
+
+The reader function used by @Lid{lua_load}.
+Every time it needs another piece of the chunk,
+@Lid{lua_load} calls the reader,
+passing along its @id{data} parameter.
+The reader must return a pointer to a block of memory
+with a new piece of the chunk
+and set @id{size} to the block size.
+The block must exist until the reader function is called again.
+To signal the end of the chunk,
+the reader must return @id{NULL} or set @id{size} to zero.
+The reader function may return pieces of any size greater than zero.
+
+}
+
+@APIEntry{void lua_register (lua_State *L, const char *name, lua_CFunction f);|
+@apii{0,0,e}
+
+Sets the @N{C function} @id{f} as the new value of global @id{name}.
+It is defined as a macro:
+@verbatim{
+#define lua_register(L,n,f) \
+ (lua_pushcfunction(L, f), lua_setglobal(L, n))
+}
+
+}
+
+@APIEntry{void lua_remove (lua_State *L, int index);|
+@apii{1,0,-}
+
+Removes the element at the given valid index,
+shifting down the elements above this index to fill the gap.
+This function cannot be called with a pseudo-index,
+because a pseudo-index is not an actual stack position.
+
+}
+
+@APIEntry{void lua_replace (lua_State *L, int index);|
+@apii{1,0,-}
+
+Moves the top element into the given valid index
+without shifting any element
+(therefore replacing the value at that given index),
+and then pops the top element.
+
+}
+
+@APIEntry{int lua_resume (lua_State *L, lua_State *from, int nargs,
+ int *nresults);|
+@apii{?,?,-}
+
+Starts and resumes a coroutine in the given thread @id{L}.
+
+To start a coroutine,
+you push onto the thread stack the main function plus any arguments;
+then you call @Lid{lua_resume},
+with @id{nargs} being the number of arguments.
+This call returns when the coroutine suspends or finishes its execution.
+When it returns,
+@id{nresults} is updated and
+the top of the stack contains
+the @id{nresults} values passed to @Lid{lua_yield}
+or returned by the body function.
+@Lid{lua_resume} returns
+@Lid{LUA_YIELD} if the coroutine yields,
+@Lid{LUA_OK} if the coroutine finishes its execution
+without errors,
+or an error code in case of errors @seeC{lua_pcall}.
+
+In case of errors,
+the stack is not unwound,
+so you can use the debug API over it.
+The error object is on the top of the stack.
+
+To resume a coroutine,
+you remove all results from the last @Lid{lua_yield},
+put on its stack only the values to
+be passed as results from @id{yield},
+and then call @Lid{lua_resume}.
+
+The parameter @id{from} represents the coroutine that is resuming @id{L}.
+If there is no such coroutine,
+this parameter can be @id{NULL}.
+
+}
+
+@APIEntry{void lua_rotate (lua_State *L, int idx, int n);|
+@apii{0,0,-}
+
+Rotates the stack elements between the valid index @id{idx}
+and the top of the stack.
+The elements are rotated @id{n} positions in the direction of the top,
+for a positive @id{n},
+or @T{-n} positions in the direction of the bottom,
+for a negative @id{n}.
+The absolute value of @id{n} must not be greater than the size
+of the slice being rotated.
+This function cannot be called with a pseudo-index,
+because a pseudo-index is not an actual stack position.
+
+}
+
+@APIEntry{void lua_setallocf (lua_State *L, lua_Alloc f, void *ud);|
+@apii{0,0,-}
+
+Changes the @x{allocator function} of a given state to @id{f}
+with user data @id{ud}.
+
+}
+
+@APIEntry{void lua_setfield (lua_State *L, int index, const char *k);|
+@apii{1,0,e}
+
+Does the equivalent to @T{t[k] = v},
+where @id{t} is the value at the given index
+and @id{v} is the value at the top of the stack.
+
+This function pops the value from the stack.
+As in Lua, this function may trigger a metamethod
+for the @Q{newindex} event @see{metatable}.
+
+}
+
+@APIEntry{void lua_setglobal (lua_State *L, const char *name);|
+@apii{1,0,e}
+
+Pops a value from the stack and
+sets it as the new value of global @id{name}.
+
+}
+
+@APIEntry{void lua_seti (lua_State *L, int index, lua_Integer n);|
+@apii{1,0,e}
+
+Does the equivalent to @T{t[n] = v},
+where @id{t} is the value at the given index
+and @id{v} is the value at the top of the stack.
+
+This function pops the value from the stack.
+As in Lua, this function may trigger a metamethod
+for the @Q{newindex} event @see{metatable}.
+
+}
+
+@APIEntry{void lua_setmetatable (lua_State *L, int index);|
+@apii{1,0,-}
+
+Pops a table from the stack and
+sets it as the new metatable for the value at the given index.
+
+}
+
+@APIEntry{void lua_settable (lua_State *L, int index);|
+@apii{2,0,e}
+
+Does the equivalent to @T{t[k] = v},
+where @id{t} is the value at the given index,
+@id{v} is the value at the top of the stack,
+and @id{k} is the value just below the top.
+
+This function pops both the key and the value from the stack.
+As in Lua, this function may trigger a metamethod
+for the @Q{newindex} event @see{metatable}.
+
+}
+
+@APIEntry{void lua_settop (lua_State *L, int index);|
+@apii{?,?,-}
+
+Accepts any index, @N{or 0},
+and sets the stack top to this index.
+If the new top is larger than the old one,
+then the new elements are filled with @nil.
+If @id{index} @N{is 0}, then all stack elements are removed.
+
+}
+
+@APIEntry{int lua_setiuservalue (lua_State *L, int index, int n);|
+@apii{1,0,-}
+
+Pops a value from the stack and sets it as
+the new @id{n}-th user value associated to the
+full userdata at the given index.
+Returns 0 if the userdata does not have that value.
+
+}
+
+@APIEntry{typedef struct lua_State lua_State;|
+
+An opaque structure that points to a thread and indirectly
+(through the thread) to the whole state of a Lua interpreter.
+The Lua library is fully reentrant:
+it has no global variables.
+All information about a state is accessible through this structure.
+
+A pointer to this structure must be passed as the first argument to
+every function in the library, except to @Lid{lua_newstate},
+which creates a Lua state from scratch.
+
+}
+
+@APIEntry{int lua_status (lua_State *L);|
+@apii{0,0,-}
+
+Returns the status of the thread @id{L}.
+
+The status can be 0 (@Lid{LUA_OK}) for a normal thread,
+an error code if the thread finished the execution
+of a @Lid{lua_resume} with an error,
+or @defid{LUA_YIELD} if the thread is suspended.
+
+You can only call functions in threads with status @Lid{LUA_OK}.
+You can resume threads with status @Lid{LUA_OK}
+(to start a new coroutine) or @Lid{LUA_YIELD}
+(to resume a coroutine).
+
+}
+
+@APIEntry{size_t lua_stringtonumber (lua_State *L, const char *s);|
+@apii{0,1,-}
+
+Converts the zero-terminated string @id{s} to a number,
+pushes that number into the stack,
+and returns the total size of the string,
+that is, its length plus one.
+The conversion can result in an integer or a float,
+according to the lexical conventions of Lua @see{lexical}.
+The string may have leading and trailing spaces and a sign.
+If the string is not a valid numeral,
+returns 0 and pushes nothing.
+(Note that the result can be used as a boolean,
+true if the conversion succeeds.)
+
+}
+
+@APIEntry{int lua_toboolean (lua_State *L, int index);|
+@apii{0,0,-}
+
+Converts the Lua value at the given index to a @N{C boolean}
+value (@N{0 or 1}).
+Like all tests in Lua,
+@Lid{lua_toboolean} returns true for any Lua value
+different from @false and @nil;
+otherwise it returns false.
+(If you want to accept only actual boolean values,
+use @Lid{lua_isboolean} to test the value's type.)
+
+}
+
+@APIEntry{lua_CFunction lua_tocfunction (lua_State *L, int index);|
+@apii{0,0,-}
+
+Converts a value at the given index to a @N{C function}.
+That value must be a @N{C function};
+otherwise, returns @id{NULL}.
+
+}
+
+@APIEntry{lua_Integer lua_tointeger (lua_State *L, int index);|
+@apii{0,0,-}
+
+Equivalent to @Lid{lua_tointegerx} with @id{isnum} equal to @id{NULL}.
+
+}
+
+@APIEntry{lua_Integer lua_tointegerx (lua_State *L, int index, int *isnum);|
+@apii{0,0,-}
+
+Converts the Lua value at the given index
+to the signed integral type @Lid{lua_Integer}.
+The Lua value must be an integer,
+or a number or string convertible to an integer @see{coercion};
+otherwise, @id{lua_tointegerx} @N{returns 0}.
+
+If @id{isnum} is not @id{NULL},
+its referent is assigned a boolean value that
+indicates whether the operation succeeded.
+
+}
+
+@APIEntry{const char *lua_tolstring (lua_State *L, int index, size_t *len);|
+@apii{0,0,m}
+
+Converts the Lua value at the given index to a @N{C string}.
+If @id{len} is not @id{NULL},
+it sets @T{*len} with the string length.
+The Lua value must be a string or a number;
+otherwise, the function returns @id{NULL}.
+If the value is a number,
+then @id{lua_tolstring} also
+@emph{changes the actual value in the stack to a string}.
+(This change confuses @Lid{lua_next}
+when @id{lua_tolstring} is applied to keys during a table traversal.)
+
+@id{lua_tolstring} returns a pointer
+to a string inside the Lua state.
+This string always has a zero (@Char{\0})
+after its last character (as @N{in C}),
+but can contain other zeros in its body.
+
+Because Lua has garbage collection,
+there is no guarantee that the pointer returned by @id{lua_tolstring}
+will be valid after the corresponding Lua value is removed from the stack.
+
+}
+
+@APIEntry{lua_Number lua_tonumber (lua_State *L, int index);|
+@apii{0,0,-}
+
+Equivalent to @Lid{lua_tonumberx} with @id{isnum} equal to @id{NULL}.
+
+}
+
+@APIEntry{lua_Number lua_tonumberx (lua_State *L, int index, int *isnum);|
+@apii{0,0,-}
+
+Converts the Lua value at the given index
+to the @N{C type} @Lid{lua_Number} @seeC{lua_Number}.
+The Lua value must be a number or a string convertible to a number
+@see{coercion};
+otherwise, @Lid{lua_tonumberx} @N{returns 0}.
+
+If @id{isnum} is not @id{NULL},
+its referent is assigned a boolean value that
+indicates whether the operation succeeded.
+
+}
+
+@APIEntry{const void *lua_topointer (lua_State *L, int index);|
+@apii{0,0,-}
+
+Converts the value at the given index to a generic
+@N{C pointer} (@T{void*}).
+The value can be a userdata, a table, a thread, or a function;
+otherwise, @id{lua_topointer} returns @id{NULL}.
+Different objects will give different pointers.
+There is no way to convert the pointer back to its original value.
+
+Typically this function is used only for hashing and debug information.
+
+}
+
+@APIEntry{const char *lua_tostring (lua_State *L, int index);|
+@apii{0,0,m}
+
+Equivalent to @Lid{lua_tolstring} with @id{len} equal to @id{NULL}.
+
+}
+
+@APIEntry{lua_State *lua_tothread (lua_State *L, int index);|
+@apii{0,0,-}
+
+Converts the value at the given index to a Lua thread
+(represented as @T{lua_State*}).
+This value must be a thread;
+otherwise, the function returns @id{NULL}.
+
+}
+
+@APIEntry{void *lua_touserdata (lua_State *L, int index);|
+@apii{0,0,-}
+
+If the value at the given index is a full userdata,
+returns its memory-block address.
+If the value is a light userdata,
+returns its pointer.
+Otherwise, returns @id{NULL}.
+
+}
+
+@APIEntry{int lua_type (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns the type of the value in the given valid index,
+or @id{LUA_TNONE} for a non-valid (but acceptable) index.
+The types returned by @Lid{lua_type} are coded by the following constants
+defined in @id{lua.h}:
+@defid{LUA_TNIL},
+@defid{LUA_TNUMBER},
+@defid{LUA_TBOOLEAN},
+@defid{LUA_TSTRING},
+@defid{LUA_TTABLE},
+@defid{LUA_TFUNCTION},
+@defid{LUA_TUSERDATA},
+@defid{LUA_TTHREAD},
+and
+@defid{LUA_TLIGHTUSERDATA}.
+
+}
+
+@APIEntry{const char *lua_typename (lua_State *L, int tp);|
+@apii{0,0,-}
+
+Returns the name of the type encoded by the value @id{tp},
+which must be one the values returned by @Lid{lua_type}.
+
+}
+
+@APIEntry{typedef @ldots lua_Unsigned;|
+
+The unsigned version of @Lid{lua_Integer}.
+
+}
+
+@APIEntry{int lua_upvalueindex (int i);|
+@apii{0,0,-}
+
+Returns the pseudo-index that represents the @id{i}-th upvalue of
+the running function @see{c-closure}.
+
+}
+
+@APIEntry{lua_Number lua_version (lua_State *L);|
+@apii{0,0,-}
+
+Returns the version number of this core.
+
+}
+
+@APIEntry{
+typedef int (*lua_Writer) (lua_State *L,
+ const void* p,
+ size_t sz,
+ void* ud);|
+
+The type of the writer function used by @Lid{lua_dump}.
+Every time it produces another piece of chunk,
+@Lid{lua_dump} calls the writer,
+passing along the buffer to be written (@id{p}),
+its size (@id{sz}),
+and the @id{data} parameter supplied to @Lid{lua_dump}.
+
+The writer returns an error code:
+@N{0 means} no errors;
+any other value means an error and stops @Lid{lua_dump} from
+calling the writer again.
+
+}
+
+@APIEntry{void lua_xmove (lua_State *from, lua_State *to, int n);|
+@apii{?,?,-}
+
+Exchange values between different threads of the same state.
+
+This function pops @id{n} values from the stack @id{from},
+and pushes them onto the stack @id{to}.
+
+}
+
+@APIEntry{int lua_yield (lua_State *L, int nresults);|
+@apii{?,?,v}
+
+This function is equivalent to @Lid{lua_yieldk},
+but it has no continuation @see{continuations}.
+Therefore, when the thread resumes,
+it continues the function that called
+the function calling @id{lua_yield}.
+To avoid surprises,
+this function should be called only in a tail call.
+
+}
+
+
+@APIEntry{
+int lua_yieldk (lua_State *L,
+ int nresults,
+ lua_KContext ctx,
+ lua_KFunction k);|
+@apii{?,?,v}
+
+Yields a coroutine (thread).
+
+When a @N{C function} calls @Lid{lua_yieldk},
+the running coroutine suspends its execution,
+and the call to @Lid{lua_resume} that started this coroutine returns.
+The parameter @id{nresults} is the number of values from the stack
+that will be passed as results to @Lid{lua_resume}.
+
+When the coroutine is resumed again,
+Lua calls the given @x{continuation function} @id{k} to continue
+the execution of the @N{C function} that yielded @see{continuations}.
+This continuation function receives the same stack
+from the previous function,
+with the @id{n} results removed and
+replaced by the arguments passed to @Lid{lua_resume}.
+Moreover,
+the continuation function receives the value @id{ctx}
+that was passed to @Lid{lua_yieldk}.
+
+Usually, this function does not return;
+when the coroutine eventually resumes,
+it continues executing the continuation function.
+However, there is one special case,
+which is when this function is called
+from inside a line or a count hook @see{debugI}.
+In that case, @id{lua_yieldk} should be called with no continuation
+(probably in the form of @Lid{lua_yield}) and no results,
+and the hook should return immediately after the call.
+Lua will yield and,
+when the coroutine resumes again,
+it will continue the normal execution
+of the (Lua) function that triggered the hook.
+
+This function can raise an error if it is called from a thread
+with a pending C call with no continuation function
+(what is called a @emphx{C-call boundary},
+or it is called from a thread that is not running inside a resume
+(typically the main thread).
+
+}
+
+}
+
+@sect2{debugI| @title{The Debug Interface}
+
+Lua has no built-in debugging facilities.
+Instead, it offers a special interface
+by means of functions and @emph{hooks}.
+This interface allows the construction of different
+kinds of debuggers, profilers, and other tools
+that need @Q{inside information} from the interpreter.
+
+
+@APIEntry{
+typedef struct lua_Debug {
+ int event;
+ const char *name; /* (n) */
+ const char *namewhat; /* (n) */
+ const char *what; /* (S) */
+ const char *source; /* (S) */
+ int currentline; /* (l) */
+ int linedefined; /* (S) */
+ int lastlinedefined; /* (S) */
+ unsigned char nups; /* (u) number of upvalues */
+ unsigned char nparams; /* (u) number of parameters */
+ char isvararg; /* (u) */
+ char istailcall; /* (t) */
+ unsigned short ftransfer; /* (r) index of first value transferred */
+ unsigned short ntransfer; /* (r) number of transferred values */
+ char short_src[LUA_IDSIZE]; /* (S) */
+ /* private part */
+ @rep{other fields}
+} lua_Debug;
+|
+
+A structure used to carry different pieces of
+information about a function or an activation record.
+@Lid{lua_getstack} fills only the private part
+of this structure, for later use.
+To fill the other fields of @Lid{lua_Debug} with useful information,
+call @Lid{lua_getinfo}.
+
+The fields of @Lid{lua_Debug} have the following meaning:
+@description{
+
+@item{@id{source}|
+the name of the chunk that created the function.
+If @T{source} starts with a @Char{@At},
+it means that the function was defined in a file where
+the file name follows the @Char{@At}.
+If @T{source} starts with a @Char{=},
+the remainder of its contents describe the source in a user-dependent manner.
+Otherwise,
+the function was defined in a string where
+@T{source} is that string.
+}
+
+@item{@id{short_src}|
+a @Q{printable} version of @T{source}, to be used in error messages.
+}
+
+@item{@id{linedefined}|
+the line number where the definition of the function starts.
+}
+
+@item{@id{lastlinedefined}|
+the line number where the definition of the function ends.
+}
+
+@item{@id{what}|
+the string @T{"Lua"} if the function is a Lua function,
+@T{"C"} if it is a @N{C function},
+@T{"main"} if it is the main part of a chunk.
+}
+
+@item{@id{currentline}|
+the current line where the given function is executing.
+When no line information is available,
+@T{currentline} is set to @num{-1}.
+}
+
+@item{@id{name}|
+a reasonable name for the given function.
+Because functions in Lua are first-class values,
+they do not have a fixed name:
+some functions can be the value of multiple global variables,
+while others can be stored only in a table field.
+The @T{lua_getinfo} function checks how the function was
+called to find a suitable name.
+If it cannot find a name,
+then @id{name} is set to @id{NULL}.
+}
+
+@item{@id{namewhat}|
+explains the @T{name} field.
+The value of @T{namewhat} can be
+@T{"global"}, @T{"local"}, @T{"method"},
+@T{"field"}, @T{"upvalue"}, or @T{""} (the empty string),
+according to how the function was called.
+(Lua uses the empty string when no other option seems to apply.)
+}
+
+@item{@id{istailcall}|
+true if this function invocation was called by a tail call.
+In this case, the caller of this level is not in the stack.
+}
+
+@item{@id{nups}|
+the number of upvalues of the function.
+}
+
+@item{@id{nparams}|
+the number of parameters of the function
+(always @N{0 for} @N{C functions}).
+}
+
+@item{@id{isvararg}|
+true if the function is a vararg function
+(always true for @N{C functions}).
+}
+
+@item{@id{ftransfer}|
+the index on the stack of the first value being @Q{transferred},
+that is, parameters in a call or return values in a return.
+(The other values are in consecutive indices.)
+Using this index, you can access and modify these values
+through @Lid{lua_getlocal} and @Lid{lua_setlocal}.
+This field is only meaningful during a
+call hook, denoting the first parameter,
+or a return hook, denoting the first value being returned.
+(For call hooks, this value is always 1.)
+}
+
+@item{@id{ntransfer}|
+The number of values being transferred (see previous item).
+(For calls of Lua functions,
+this value is always equal to @id{nparams}.)
+}
+
+}
+
+}
+
+@APIEntry{lua_Hook lua_gethook (lua_State *L);|
+@apii{0,0,-}
+
+Returns the current hook function.
+
+}
+
+@APIEntry{int lua_gethookcount (lua_State *L);|
+@apii{0,0,-}
+
+Returns the current hook count.
+
+}
+
+@APIEntry{int lua_gethookmask (lua_State *L);|
+@apii{0,0,-}
+
+Returns the current hook mask.
+
+}
+
+@APIEntry{int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar);|
+@apii{0|1,0|1|2,m}
+
+Gets information about a specific function or function invocation.
+
+To get information about a function invocation,
+the parameter @id{ar} must be a valid activation record that was
+filled by a previous call to @Lid{lua_getstack} or
+given as argument to a hook @seeC{lua_Hook}.
+
+To get information about a function, you push it onto the stack
+and start the @id{what} string with the character @Char{>}.
+(In that case,
+@id{lua_getinfo} pops the function from the top of the stack.)
+For instance, to know in which line a function @id{f} was defined,
+you can write the following code:
+@verbatim{
+lua_Debug ar;
+lua_getglobal(L, "f"); /* get global 'f' */
+lua_getinfo(L, ">S", &ar);
+printf("%d\n", ar.linedefined);
+}
+
+Each character in the string @id{what}
+selects some fields of the structure @id{ar} to be filled or
+a value to be pushed on the stack:
+@description{
+
+@item{@Char{n}| fills in the field @id{name} and @id{namewhat};
+}
+
+@item{@Char{S}|
+fills in the fields @id{source}, @id{short_src},
+@id{linedefined}, @id{lastlinedefined}, and @id{what};
+}
+
+@item{@Char{l}| fills in the field @id{currentline};
+}
+
+@item{@Char{t}| fills in the field @id{istailcall};
+}
+
+@item{@Char{u}| fills in the fields
+@id{nups}, @id{nparams}, and @id{isvararg};
+}
+
+@item{@Char{f}|
+pushes onto the stack the function that is
+running at the given level;
+}
+
+@item{@Char{L}|
+pushes onto the stack a table whose indices are the
+numbers of the lines that are valid on the function.
+(A @emph{valid line} is a line with some associated code,
+that is, a line where you can put a break point.
+Non-valid lines include empty lines and comments.)
+
+If this option is given together with option @Char{f},
+its table is pushed after the function.
+
+This is the only option that can raise a memory error.
+}
+
+}
+
+This function returns 0 if given an invalid option in @id{what}.
+
+}
+
+@APIEntry{const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n);|
+@apii{0,0|1,-}
+
+Gets information about a local variable or a temporary value
+of a given activation record or a given function.
+
+In the first case,
+the parameter @id{ar} must be a valid activation record that was
+filled by a previous call to @Lid{lua_getstack} or
+given as argument to a hook @seeC{lua_Hook}.
+The index @id{n} selects which local variable to inspect;
+see @Lid{debug.getlocal} for details about variable indices
+and names.
+
+@Lid{lua_getlocal} pushes the variable's value onto the stack
+and returns its name.
+
+In the second case, @id{ar} must be @id{NULL} and the function
+to be inspected must be at the top of the stack.
+In this case, only parameters of Lua functions are visible
+(as there is no information about what variables are active)
+and no values are pushed onto the stack.
+
+Returns @id{NULL} (and pushes nothing)
+when the index is greater than
+the number of active local variables.
+
+}
+
+@APIEntry{int lua_getstack (lua_State *L, int level, lua_Debug *ar);|
+@apii{0,0,-}
+
+Gets information about the interpreter runtime stack.
+
+This function fills parts of a @Lid{lua_Debug} structure with
+an identification of the @emph{activation record}
+of the function executing at a given level.
+@N{Level 0} is the current running function,
+whereas level @M{n+1} is the function that has called level @M{n}
+(except for tail calls, which do not count on the stack).
+When there are no errors, @Lid{lua_getstack} returns 1;
+when called with a level greater than the stack depth,
+it returns 0.
+
+}
+
+@APIEntry{const char *lua_getupvalue (lua_State *L, int funcindex, int n);|
+@apii{0,0|1,-}
+
+Gets information about the @id{n}-th upvalue
+of the closure at index @id{funcindex}.
+It pushes the upvalue's value onto the stack
+and returns its name.
+Returns @id{NULL} (and pushes nothing)
+when the index @id{n} is greater than the number of upvalues.
+
+For @N{C functions}, this function uses the empty string @T{""}
+as a name for all upvalues.
+(For Lua functions,
+upvalues are the external local variables that the function uses,
+and that are consequently included in its closure.)
+
+Upvalues have no particular order,
+as they are active through the whole function.
+They are numbered in an arbitrary order.
+
+}
+
+@APIEntry{typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);|
+
+Type for debugging hook functions.
+
+Whenever a hook is called, its @id{ar} argument has its field
+@id{event} set to the specific event that triggered the hook.
+Lua identifies these events with the following constants:
+@defid{LUA_HOOKCALL}, @defid{LUA_HOOKRET},
+@defid{LUA_HOOKTAILCALL}, @defid{LUA_HOOKLINE},
+and @defid{LUA_HOOKCOUNT}.
+Moreover, for line events, the field @id{currentline} is also set.
+To get the value of any other field in @id{ar},
+the hook must call @Lid{lua_getinfo}.
+
+For call events, @id{event} can be @id{LUA_HOOKCALL},
+the normal value, or @id{LUA_HOOKTAILCALL}, for a tail call;
+in this case, there will be no corresponding return event.
+
+While Lua is running a hook, it disables other calls to hooks.
+Therefore, if a hook calls back Lua to execute a function or a chunk,
+this execution occurs without any calls to hooks.
+
+Hook functions cannot have continuations,
+that is, they cannot call @Lid{lua_yieldk},
+@Lid{lua_pcallk}, or @Lid{lua_callk} with a non-null @id{k}.
+
+Hook functions can yield under the following conditions:
+Only count and line events can yield;
+to yield, a hook function must finish its execution
+calling @Lid{lua_yield} with @id{nresults} equal to zero
+(that is, with no values).
+
+}
+
+@APIEntry{void lua_sethook (lua_State *L, lua_Hook f, int mask, int count);|
+@apii{0,0,-}
+
+Sets the debugging hook function.
+
+Argument @id{f} is the hook function.
+@id{mask} specifies on which events the hook will be called:
+it is formed by a bitwise OR of the constants
+@defid{LUA_MASKCALL},
+@defid{LUA_MASKRET},
+@defid{LUA_MASKLINE},
+and @defid{LUA_MASKCOUNT}.
+The @id{count} argument is only meaningful when the mask
+includes @id{LUA_MASKCOUNT}.
+For each event, the hook is called as explained below:
+@description{
+
+@item{The call hook| is called when the interpreter calls a function.
+The hook is called just after Lua enters the new function,
+before the function gets its arguments.
+}
+
+@item{The return hook| is called when the interpreter returns from a function.
+The hook is called just before Lua leaves the function.
+There is no standard way to access the values
+to be returned by the function.
+}
+
+@item{The line hook| is called when the interpreter is about to
+start the execution of a new line of code,
+or when it jumps back in the code (even to the same line).
+(This event only happens while Lua is executing a Lua function.)
+}
+
+@item{The count hook| is called after the interpreter executes every
+@T{count} instructions.
+(This event only happens while Lua is executing a Lua function.)
+}
+
+}
+
+A hook is disabled by setting @id{mask} to zero.
+
+}
+
+@APIEntry{const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n);|
+@apii{0|1,0,-}
+
+Sets the value of a local variable of a given activation record.
+It assigns the value at the top of the stack
+to the variable and returns its name.
+It also pops the value from the stack.
+
+Returns @id{NULL} (and pops nothing)
+when the index is greater than
+the number of active local variables.
+
+Parameters @id{ar} and @id{n} are as in function @Lid{lua_getlocal}.
+
+}
+
+@APIEntry{const char *lua_setupvalue (lua_State *L, int funcindex, int n);|
+@apii{0|1,0,-}
+
+Sets the value of a closure's upvalue.
+It assigns the value at the top of the stack
+to the upvalue and returns its name.
+It also pops the value from the stack.
+
+Returns @id{NULL} (and pops nothing)
+when the index @id{n} is greater than the number of upvalues.
+
+Parameters @id{funcindex} and @id{n} are as in function @Lid{lua_getupvalue}.
+
+}
+
+@APIEntry{void *lua_upvalueid (lua_State *L, int funcindex, int n);|
+@apii{0,0,-}
+
+Returns a unique identifier for the upvalue numbered @id{n}
+from the closure at index @id{funcindex}.
+
+These unique identifiers allow a program to check whether different
+closures share upvalues.
+Lua closures that share an upvalue
+(that is, that access a same external local variable)
+will return identical ids for those upvalue indices.
+
+Parameters @id{funcindex} and @id{n} are as in function @Lid{lua_getupvalue},
+but @id{n} cannot be greater than the number of upvalues.
+
+}
+
+@APIEntry{
+void lua_upvaluejoin (lua_State *L, int funcindex1, int n1,
+ int funcindex2, int n2);|
+@apii{0,0,-}
+
+Make the @id{n1}-th upvalue of the Lua closure at index @id{funcindex1}
+refer to the @id{n2}-th upvalue of the Lua closure at index @id{funcindex2}.
+
+}
+
+}
+
+}
+
+
+@C{-------------------------------------------------------------------------}
+@sect1{@title{The Auxiliary Library}
+
+@index{lauxlib.h}
+The @def{auxiliary library} provides several convenient functions
+to interface C with Lua.
+While the basic API provides the primitive functions for all
+interactions between C and Lua,
+the auxiliary library provides higher-level functions for some
+common tasks.
+
+All functions and types from the auxiliary library
+are defined in header file @id{lauxlib.h} and
+have a prefix @id{luaL_}.
+
+All functions in the auxiliary library are built on
+top of the basic API,
+and so they provide nothing that cannot be done with that API.
+Nevertheless, the use of the auxiliary library ensures
+more consistency to your code.
+
+
+Several functions in the auxiliary library use internally some
+extra stack slots.
+When a function in the auxiliary library uses less than five slots,
+it does not check the stack size;
+it simply assumes that there are enough slots.
+
+Several functions in the auxiliary library are used to
+check @N{C function} arguments.
+Because the error message is formatted for arguments
+(e.g., @St{bad argument #1}),
+you should not use these functions for other stack values.
+
+Functions called @id{luaL_check*}
+always raise an error if the check is not satisfied.
+
+@sect2{@title{Functions and Types}
+
+Here we list all functions and types from the auxiliary library
+in alphabetical order.
+
+
+@APIEntry{void luaL_addchar (luaL_Buffer *B, char c);|
+@apii{?,?,m}
+
+Adds the byte @id{c} to the buffer @id{B}
+@seeC{luaL_Buffer}.
+
+}
+
+@APIEntry{void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l);|
+@apii{?,?,m}
+
+Adds the string pointed to by @id{s} with length @id{l} to
+the buffer @id{B}
+@seeC{luaL_Buffer}.
+The string can contain @x{embedded zeros}.
+
+}
+
+@APIEntry{void luaL_addsize (luaL_Buffer *B, size_t n);|
+@apii{?,?,-}
+
+Adds to the buffer @id{B} @seeC{luaL_Buffer}
+a string of length @id{n} previously copied to the
+buffer area @seeC{luaL_prepbuffer}.
+
+}
+
+@APIEntry{void luaL_addstring (luaL_Buffer *B, const char *s);|
+@apii{?,?,m}
+
+Adds the zero-terminated string pointed to by @id{s}
+to the buffer @id{B}
+@seeC{luaL_Buffer}.
+
+}
+
+@APIEntry{void luaL_addvalue (luaL_Buffer *B);|
+@apii{1,?,m}
+
+Adds the value at the top of the stack
+to the buffer @id{B}
+@seeC{luaL_Buffer}.
+Pops the value.
+
+This is the only function on string buffers that can (and must)
+be called with an extra element on the stack,
+which is the value to be added to the buffer.
+
+}
+
+@APIEntry{
+void luaL_argcheck (lua_State *L,
+ int cond,
+ int arg,
+ const char *extramsg);|
+@apii{0,0,v}
+
+Checks whether @id{cond} is true.
+If it is not, raises an error with a standard message @seeF{luaL_argerror}.
+
+}
+
+@APIEntry{int luaL_argerror (lua_State *L, int arg, const char *extramsg);|
+@apii{0,0,v}
+
+Raises an error reporting a problem with argument @id{arg}
+of the @N{C function} that called it,
+using a standard message
+that includes @id{extramsg} as a comment:
+@verbatim{
+bad argument #@rep{arg} to '@rep{funcname}' (@rep{extramsg})
+}
+This function never returns.
+
+}
+
+@APIEntry{typedef struct luaL_Buffer luaL_Buffer;|
+
+Type for a @def{string buffer}.
+
+A string buffer allows @N{C code} to build Lua strings piecemeal.
+Its pattern of use is as follows:
+@itemize{
+
+@item{First declare a variable @id{b} of type @Lid{luaL_Buffer}.}
+
+@item{Then initialize it with a call @T{luaL_buffinit(L, &b)}.}
+
+@item{
+Then add string pieces to the buffer calling any of
+the @id{luaL_add*} functions.
+}
+
+@item{
+Finish by calling @T{luaL_pushresult(&b)}.
+This call leaves the final string on the top of the stack.
+}
+
+}
+
+If you know beforehand the total size of the resulting string,
+you can use the buffer like this:
+@itemize{
+
+@item{First declare a variable @id{b} of type @Lid{luaL_Buffer}.}
+
+@item{Then initialize it and preallocate a space of
+size @id{sz} with a call @T{luaL_buffinitsize(L, &b, sz)}.}
+
+@item{Then produce the string into that space.}
+
+@item{
+Finish by calling @T{luaL_pushresultsize(&b, sz)},
+where @id{sz} is the total size of the resulting string
+copied into that space.
+}
+
+}
+
+During its normal operation,
+a string buffer uses a variable number of stack slots.
+So, while using a buffer, you cannot assume that you know where
+the top of the stack is.
+You can use the stack between successive calls to buffer operations
+as long as that use is balanced;
+that is,
+when you call a buffer operation,
+the stack is at the same level
+it was immediately after the previous buffer operation.
+(The only exception to this rule is @Lid{luaL_addvalue}.)
+After calling @Lid{luaL_pushresult} the stack is back to its
+level when the buffer was initialized,
+plus the final string on its top.
+
+}
+
+@APIEntry{void luaL_buffinit (lua_State *L, luaL_Buffer *B);|
+@apii{0,0,-}
+
+Initializes a buffer @id{B}.
+This function does not allocate any space;
+the buffer must be declared as a variable
+@seeC{luaL_Buffer}.
+
+}
+
+@APIEntry{char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz);|
+@apii{?,?,m}
+
+Equivalent to the sequence
+@Lid{luaL_buffinit}, @Lid{luaL_prepbuffsize}.
+
+}
+
+@APIEntry{int luaL_callmeta (lua_State *L, int obj, const char *e);|
+@apii{0,0|1,e}
+
+Calls a metamethod.
+
+If the object at index @id{obj} has a metatable and this
+metatable has a field @id{e},
+this function calls this field passing the object as its only argument.
+In this case this function returns true and pushes onto the
+stack the value returned by the call.
+If there is no metatable or no metamethod,
+this function returns false (without pushing any value on the stack).
+
+}
+
+@APIEntry{void luaL_checkany (lua_State *L, int arg);|
+@apii{0,0,v}
+
+Checks whether the function has an argument
+of any type (including @nil) at position @id{arg}.
+
+}
+
+@APIEntry{lua_Integer luaL_checkinteger (lua_State *L, int arg);|
+@apii{0,0,v}
+
+Checks whether the function argument @id{arg} is an integer
+(or can be converted to an integer)
+and returns this integer cast to a @Lid{lua_Integer}.
+
+}
+
+@APIEntry{const char *luaL_checklstring (lua_State *L, int arg, size_t *l);|
+@apii{0,0,v}
+
+Checks whether the function argument @id{arg} is a string
+and returns this string;
+if @id{l} is not @id{NULL} fills @T{*l}
+with the string's length.
+
+This function uses @Lid{lua_tolstring} to get its result,
+so all conversions and caveats of that function apply here.
+
+}
+
+@APIEntry{lua_Number luaL_checknumber (lua_State *L, int arg);|
+@apii{0,0,v}
+
+Checks whether the function argument @id{arg} is a number
+and returns this number.
+
+}
+
+@APIEntry{
+int luaL_checkoption (lua_State *L,
+ int arg,
+ const char *def,
+ const char *const lst[]);|
+@apii{0,0,v}
+
+Checks whether the function argument @id{arg} is a string and
+searches for this string in the array @id{lst}
+(which must be NULL-terminated).
+Returns the index in the array where the string was found.
+Raises an error if the argument is not a string or
+if the string cannot be found.
+
+If @id{def} is not @id{NULL},
+the function uses @id{def} as a default value when
+there is no argument @id{arg} or when this argument is @nil.
+
+This is a useful function for mapping strings to @N{C enums}.
+(The usual convention in Lua libraries is
+to use strings instead of numbers to select options.)
+
+}
+
+@APIEntry{void luaL_checkstack (lua_State *L, int sz, const char *msg);|
+@apii{0,0,v}
+
+Grows the stack size to @T{top + sz} elements,
+raising an error if the stack cannot grow to that size.
+@id{msg} is an additional text to go into the error message
+(or @id{NULL} for no additional text).
+
+}
+
+@APIEntry{const char *luaL_checkstring (lua_State *L, int arg);|
+@apii{0,0,v}
+
+Checks whether the function argument @id{arg} is a string
+and returns this string.
+
+This function uses @Lid{lua_tolstring} to get its result,
+so all conversions and caveats of that function apply here.
+
+}
+
+@APIEntry{void luaL_checktype (lua_State *L, int arg, int t);|
+@apii{0,0,v}
+
+Checks whether the function argument @id{arg} has type @id{t}.
+See @Lid{lua_type} for the encoding of types for @id{t}.
+
+}
+
+@APIEntry{void *luaL_checkudata (lua_State *L, int arg, const char *tname);|
+@apii{0,0,v}
+
+Checks whether the function argument @id{arg} is a userdata
+of the type @id{tname} @seeC{luaL_newmetatable} and
+returns the userdata's memory-block address @seeC{lua_touserdata}.
+
+}
+
+@APIEntry{void luaL_checkversion (lua_State *L);|
+@apii{0,0,v}
+
+Checks whether the code making the call and the Lua library being called
+are using the same version of Lua and the same numeric types.
+
+}
+
+@APIEntry{int luaL_dofile (lua_State *L, const char *filename);|
+@apii{0,?,m}
+
+Loads and runs the given file.
+It is defined as the following macro:
+@verbatim{
+(luaL_loadfile(L, filename) || lua_pcall(L, 0, LUA_MULTRET, 0))
+}
+It returns false if there are no errors
+or true in case of errors.
+
+}
+
+@APIEntry{int luaL_dostring (lua_State *L, const char *str);|
+@apii{0,?,-}
+
+Loads and runs the given string.
+It is defined as the following macro:
+@verbatim{
+(luaL_loadstring(L, str) || lua_pcall(L, 0, LUA_MULTRET, 0))
+}
+It returns false if there are no errors
+or true in case of errors.
+
+}
+
+@APIEntry{int luaL_error (lua_State *L, const char *fmt, ...);|
+@apii{0,0,v}
+
+Raises an error.
+The error message format is given by @id{fmt}
+plus any extra arguments,
+following the same rules of @Lid{lua_pushfstring}.
+It also adds at the beginning of the message the file name and
+the line number where the error occurred,
+if this information is available.
+
+This function never returns,
+but it is an idiom to use it in @N{C functions}
+as @T{return luaL_error(@rep{args})}.
+
+}
+
+@APIEntry{int luaL_execresult (lua_State *L, int stat);|
+@apii{0,3,m}
+
+This function produces the return values for
+process-related functions in the standard library
+(@Lid{os.execute} and @Lid{io.close}).
+
+}
+
+@APIEntry{
+int luaL_fileresult (lua_State *L, int stat, const char *fname);|
+@apii{0,1|3,m}
+
+This function produces the return values for
+file-related functions in the standard library
+(@Lid{io.open}, @Lid{os.rename}, @Lid{file:seek}, etc.).
+
+}
+
+@APIEntry{int luaL_getmetafield (lua_State *L, int obj, const char *e);|
+@apii{0,0|1,m}
+
+Pushes onto the stack the field @id{e} from the metatable
+of the object at index @id{obj} and returns the type of the pushed value.
+If the object does not have a metatable,
+or if the metatable does not have this field,
+pushes nothing and returns @id{LUA_TNIL}.
+
+}
+
+@APIEntry{int luaL_getmetatable (lua_State *L, const char *tname);|
+@apii{0,1,m}
+
+Pushes onto the stack the metatable associated with the name @id{tname}
+in the registry @seeC{luaL_newmetatable},
+or @nil if there is no metatable associated with that name.
+Returns the type of the pushed value.
+
+}
+
+@APIEntry{int luaL_getsubtable (lua_State *L, int idx, const char *fname);|
+@apii{0,1,e}
+
+Ensures that the value @T{t[fname]},
+where @id{t} is the value at index @id{idx},
+is a table,
+and pushes that table onto the stack.
+Returns true if it finds a previous table there
+and false if it creates a new table.
+
+}
+
+@APIEntry{
+const char *luaL_gsub (lua_State *L,
+ const char *s,
+ const char *p,
+ const char *r);|
+@apii{0,1,m}
+
+Creates a copy of string @id{s} by replacing
+any occurrence of the string @id{p}
+with the string @id{r}.
+Pushes the resulting string on the stack and returns it.
+
+}
+
+@APIEntry{lua_Integer luaL_len (lua_State *L, int index);|
+@apii{0,0,e}
+
+Returns the @Q{length} of the value at the given index
+as a number;
+it is equivalent to the @Char{#} operator in Lua @see{len-op}.
+Raises an error if the result of the operation is not an integer.
+(This case only can happen through metamethods.)
+
+}
+
+@APIEntry{
+int luaL_loadbuffer (lua_State *L,
+ const char *buff,
+ size_t sz,
+ const char *name);|
+@apii{0,1,-}
+
+Equivalent to @Lid{luaL_loadbufferx} with @id{mode} equal to @id{NULL}.
+
+}
+
+
+@APIEntry{
+int luaL_loadbufferx (lua_State *L,
+ const char *buff,
+ size_t sz,
+ const char *name,
+ const char *mode);|
+@apii{0,1,-}
+
+Loads a buffer as a Lua chunk.
+This function uses @Lid{lua_load} to load the chunk in the
+buffer pointed to by @id{buff} with size @id{sz}.
+
+This function returns the same results as @Lid{lua_load}.
+@id{name} is the chunk name,
+used for debug information and error messages.
+The string @id{mode} works as in function @Lid{lua_load}.
+
+}
+
+
+@APIEntry{int luaL_loadfile (lua_State *L, const char *filename);|
+@apii{0,1,m}
+
+Equivalent to @Lid{luaL_loadfilex} with @id{mode} equal to @id{NULL}.
+
+}
+
+@APIEntry{int luaL_loadfilex (lua_State *L, const char *filename,
+ const char *mode);|
+@apii{0,1,m}
+
+Loads a file as a Lua chunk.
+This function uses @Lid{lua_load} to load the chunk in the file
+named @id{filename}.
+If @id{filename} is @id{NULL},
+then it loads from the standard input.
+The first line in the file is ignored if it starts with a @T{#}.
+
+The string @id{mode} works as in function @Lid{lua_load}.
+
+This function returns the same results as @Lid{lua_load},
+but it has an extra error code @defid{LUA_ERRFILE}
+for file-related errors
+(e.g., it cannot open or read the file).
+
+As @Lid{lua_load}, this function only loads the chunk;
+it does not run it.
+
+}
+
+@APIEntry{int luaL_loadstring (lua_State *L, const char *s);|
+@apii{0,1,-}
+
+Loads a string as a Lua chunk.
+This function uses @Lid{lua_load} to load the chunk in
+the zero-terminated string @id{s}.
+
+This function returns the same results as @Lid{lua_load}.
+
+Also as @Lid{lua_load}, this function only loads the chunk;
+it does not run it.
+
+}
+
+
+@APIEntry{void luaL_newlib (lua_State *L, const luaL_Reg l[]);|
+@apii{0,1,m}
+
+Creates a new table and registers there
+the functions in list @id{l}.
+
+It is implemented as the following macro:
+@verbatim{
+(luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
+}
+The array @id{l} must be the actual array,
+not a pointer to it.
+
+}
+
+@APIEntry{void luaL_newlibtable (lua_State *L, const luaL_Reg l[]);|
+@apii{0,1,m}
+
+Creates a new table with a size optimized
+to store all entries in the array @id{l}
+(but does not actually store them).
+It is intended to be used in conjunction with @Lid{luaL_setfuncs}
+@seeF{luaL_newlib}.
+
+It is implemented as a macro.
+The array @id{l} must be the actual array,
+not a pointer to it.
+
+}
+
+@APIEntry{int luaL_newmetatable (lua_State *L, const char *tname);|
+@apii{0,1,m}
+
+If the registry already has the key @id{tname},
+returns 0.
+Otherwise,
+creates a new table to be used as a metatable for userdata,
+adds to this new table the pair @T{__name = tname},
+adds to the registry the pair @T{[tname] = new table},
+and returns 1.
+(The entry @idx{__name} is used by some error-reporting functions.)
+
+In both cases pushes onto the stack the final value associated
+with @id{tname} in the registry.
+
+}
+
+@APIEntry{lua_State *luaL_newstate (void);|
+@apii{0,0,-}
+
+Creates a new Lua state.
+It calls @Lid{lua_newstate} with an
+allocator based on the @N{standard C} @id{realloc} function
+and then sets a panic function @see{C-error} that prints
+an error message to the standard error output in case of fatal
+errors.
+
+Returns the new state,
+or @id{NULL} if there is a @x{memory allocation error}.
+
+}
+
+@APIEntry{void luaL_openlibs (lua_State *L);|
+@apii{0,0,e}
+
+Opens all standard Lua libraries into the given state.
+
+}
+
+@APIEntry{
+T luaL_opt (L, func, arg, dflt);|
+@apii{0,0,-}
+
+This macro is defined as follows:
+@verbatim{
+(lua_isnoneornil(L,(arg)) ? (dflt) : func(L,(arg)))
+}
+In words, if the argument @id{arg} is nil or absent,
+the macro results in the default @id{dflt}.
+Otherwise, it results in the result of calling @id{func}
+with the state @id{L} and the argument index @id{arg} as
+parameters.
+Note that it evaluates the expression @id{dflt} only if needed.
+
+}
+
+@APIEntry{
+lua_Integer luaL_optinteger (lua_State *L,
+ int arg,
+ lua_Integer d);|
+@apii{0,0,v}
+
+If the function argument @id{arg} is an integer
+(or convertible to an integer),
+returns this integer.
+If this argument is absent or is @nil,
+returns @id{d}.
+Otherwise, raises an error.
+
+}
+
+@APIEntry{
+const char *luaL_optlstring (lua_State *L,
+ int arg,
+ const char *d,
+ size_t *l);|
+@apii{0,0,v}
+
+If the function argument @id{arg} is a string,
+returns this string.
+If this argument is absent or is @nil,
+returns @id{d}.
+Otherwise, raises an error.
+
+If @id{l} is not @id{NULL},
+fills the position @T{*l} with the result's length.
+If the result is @id{NULL}
+(only possible when returning @id{d} and @T{d == NULL}),
+its length is considered zero.
+
+This function uses @Lid{lua_tolstring} to get its result,
+so all conversions and caveats of that function apply here.
+
+}
+
+@APIEntry{lua_Number luaL_optnumber (lua_State *L, int arg, lua_Number d);|
+@apii{0,0,v}
+
+If the function argument @id{arg} is a number,
+returns this number.
+If this argument is absent or is @nil,
+returns @id{d}.
+Otherwise, raises an error.
+
+}
+
+@APIEntry{
+const char *luaL_optstring (lua_State *L,
+ int arg,
+ const char *d);|
+@apii{0,0,v}
+
+If the function argument @id{arg} is a string,
+returns this string.
+If this argument is absent or is @nil,
+returns @id{d}.
+Otherwise, raises an error.
+
+}
+
+@APIEntry{char *luaL_prepbuffer (luaL_Buffer *B);|
+@apii{?,?,m}
+
+Equivalent to @Lid{luaL_prepbuffsize}
+with the predefined size @defid{LUAL_BUFFERSIZE}.
+
+}
+
+@APIEntry{char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz);|
+@apii{?,?,m}
+
+Returns an address to a space of size @id{sz}
+where you can copy a string to be added to buffer @id{B}
+@seeC{luaL_Buffer}.
+After copying the string into this space you must call
+@Lid{luaL_addsize} with the size of the string to actually add
+it to the buffer.
+
+}
+
+@APIEntry{void luaL_pushresult (luaL_Buffer *B);|
+@apii{?,1,m}
+
+Finishes the use of buffer @id{B} leaving the final string on
+the top of the stack.
+
+}
+
+@APIEntry{void luaL_pushresultsize (luaL_Buffer *B, size_t sz);|
+@apii{?,1,m}
+
+Equivalent to the sequence @Lid{luaL_addsize}, @Lid{luaL_pushresult}.
+
+}
+
+@APIEntry{int luaL_ref (lua_State *L, int t);|
+@apii{1,0,m}
+
+Creates and returns a @def{reference},
+in the table at index @id{t},
+for the object at the top of the stack (and pops the object).
+
+A reference is a unique integer key.
+As long as you do not manually add integer keys into table @id{t},
+@Lid{luaL_ref} ensures the uniqueness of the key it returns.
+You can retrieve an object referred by reference @id{r}
+by calling @T{lua_rawgeti(L, t, r)}.
+Function @Lid{luaL_unref} frees a reference and its associated object.
+
+If the object at the top of the stack is @nil,
+@Lid{luaL_ref} returns the constant @defid{LUA_REFNIL}.
+The constant @defid{LUA_NOREF} is guaranteed to be different
+from any reference returned by @Lid{luaL_ref}.
+
+}
+
+@APIEntry{
+typedef struct luaL_Reg {
+ const char *name;
+ lua_CFunction func;
+} luaL_Reg;
+|
+
+Type for arrays of functions to be registered by
+@Lid{luaL_setfuncs}.
+@id{name} is the function name and @id{func} is a pointer to
+the function.
+Any array of @Lid{luaL_Reg} must end with a sentinel entry
+in which both @id{name} and @id{func} are @id{NULL}.
+
+}
+
+@APIEntry{
+void luaL_requiref (lua_State *L, const char *modname,
+ lua_CFunction openf, int glb);|
+@apii{0,1,e}
+
+If @T{package.loaded[modname]} is not true,
+calls function @id{openf} with string @id{modname} as an argument
+and sets the call result to @T{package.loaded[modname]},
+as if that function has been called through @Lid{require}.
+
+If @id{glb} is true,
+also stores the module into global @id{modname}.
+
+Leaves a copy of the module on the stack.
+
+}
+
+@APIEntry{void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup);|
+@apii{nup,0,m}
+
+Registers all functions in the array @id{l}
+@seeC{luaL_Reg} into the table on the top of the stack
+(below optional upvalues, see next).
+
+When @id{nup} is not zero,
+all functions are created with @id{nup} upvalues,
+initialized with copies of the @id{nup} values
+previously pushed on the stack
+on top of the library table.
+These values are popped from the stack after the registration.
+
+}
+
+@APIEntry{void luaL_setmetatable (lua_State *L, const char *tname);|
+@apii{0,0,-}
+
+Sets the metatable of the object at the top of the stack
+as the metatable associated with name @id{tname}
+in the registry @seeC{luaL_newmetatable}.
+
+}
+
+@APIEntry{
+typedef struct luaL_Stream {
+ FILE *f;
+ lua_CFunction closef;
+} luaL_Stream;
+|
+
+The standard representation for @x{file handles},
+which is used by the standard I/O library.
+
+A file handle is implemented as a full userdata,
+with a metatable called @id{LUA_FILEHANDLE}
+(where @id{LUA_FILEHANDLE} is a macro with the actual metatable's name).
+The metatable is created by the I/O library
+@seeF{luaL_newmetatable}.
+
+This userdata must start with the structure @id{luaL_Stream};
+it can contain other data after this initial structure.
+Field @id{f} points to the corresponding C stream
+(or it can be @id{NULL} to indicate an incompletely created handle).
+Field @id{closef} points to a Lua function
+that will be called to close the stream
+when the handle is closed or collected;
+this function receives the file handle as its sole argument and
+must return either @true (in case of success)
+or @nil plus an error message (in case of error).
+Once Lua calls this field,
+it changes the field value to @id{NULL}
+to signal that the handle is closed.
+
+}
+
+@APIEntry{void *luaL_testudata (lua_State *L, int arg, const char *tname);|
+@apii{0,0,m}
+
+This function works like @Lid{luaL_checkudata},
+except that, when the test fails,
+it returns @id{NULL} instead of raising an error.
+
+}
+
+@APIEntry{const char *luaL_tolstring (lua_State *L, int idx, size_t *len);|
+@apii{0,1,e}
+
+Converts any Lua value at the given index to a @N{C string}
+in a reasonable format.
+The resulting string is pushed onto the stack and also
+returned by the function.
+If @id{len} is not @id{NULL},
+the function also sets @T{*len} with the string length.
+
+If the value has a metatable with a @idx{__tostring} field,
+then @id{luaL_tolstring} calls the corresponding metamethod
+with the value as argument,
+and uses the result of the call as its result.
+
+}
+
+@APIEntry{
+void luaL_traceback (lua_State *L, lua_State *L1, const char *msg,
+ int level);|
+@apii{0,1,m}
+
+Creates and pushes a traceback of the stack @id{L1}.
+If @id{msg} is not @id{NULL} it is appended
+at the beginning of the traceback.
+The @id{level} parameter tells at which level
+to start the traceback.
+
+}
+
+@APIEntry{const char *luaL_typename (lua_State *L, int index);|
+@apii{0,0,-}
+
+Returns the name of the type of the value at the given index.
+
+}
+
+@APIEntry{void luaL_unref (lua_State *L, int t, int ref);|
+@apii{0,0,-}
+
+Releases reference @id{ref} from the table at index @id{t}
+@seeC{luaL_ref}.
+The entry is removed from the table,
+so that the referred object can be collected.
+The reference @id{ref} is also freed to be used again.
+
+If @id{ref} is @Lid{LUA_NOREF} or @Lid{LUA_REFNIL},
+@Lid{luaL_unref} does nothing.
+
+}
+
+@APIEntry{void luaL_where (lua_State *L, int lvl);|
+@apii{0,1,m}
+
+Pushes onto the stack a string identifying the current position
+of the control at level @id{lvl} in the call stack.
+Typically this string has the following format:
+@verbatim{
+@rep{chunkname}:@rep{currentline}:
+}
+@N{Level 0} is the running function,
+@N{level 1} is the function that called the running function,
+etc.
+
+This function is used to build a prefix for error messages.
+
+}
+
+}
+
+}
+
+
+@C{-------------------------------------------------------------------------}
+@sect1{libraries| @title{Standard Libraries}
+
+The standard Lua libraries provide useful functions
+that are implemented directly through the @N{C API}.
+Some of these functions provide essential services to the language
+(e.g., @Lid{type} and @Lid{getmetatable});
+others provide access to @Q{outside} services (e.g., I/O);
+and others could be implemented in Lua itself,
+but are quite useful or have critical performance requirements that
+deserve an implementation in C (e.g., @Lid{table.sort}).
+
+All libraries are implemented through the official @N{C API}
+and are provided as separate @N{C modules}.
+Unless otherwise noted,
+these library functions do not adjust its number of arguments
+to its expected parameters.
+For instance, a function documented as @T{foo(arg)}
+should not be called without an argument.
+
+Currently, Lua has the following standard libraries:
+@itemize{
+
+@item{@link{predefined|basic library};}
+
+@item{@link{corolib|coroutine library};}
+
+@item{@link{packlib|package library};}
+
+@item{@link{strlib|string manipulation};}
+
+@item{@link{utf8|basic UTF-8 support};}
+
+@item{@link{tablib|table manipulation};}
+
+@item{@link{mathlib|mathematical functions} (sin, log, etc.);}
+
+@item{@link{iolib|input and output};}
+
+@item{@link{oslib|operating system facilities};}
+
+@item{@link{debuglib|debug facilities}.}
+
+}
+Except for the basic and the package libraries,
+each library provides all its functions as fields of a global table
+or as methods of its objects.
+
+To have access to these libraries,
+the @N{C host} program should call the @Lid{luaL_openlibs} function,
+which opens all standard libraries.
+Alternatively,
+the host program can open them individually by using
+@Lid{luaL_requiref} to call
+@defid{luaopen_base} (for the basic library),
+@defid{luaopen_package} (for the package library),
+@defid{luaopen_coroutine} (for the coroutine library),
+@defid{luaopen_string} (for the string library),
+@defid{luaopen_utf8} (for the UTF8 library),
+@defid{luaopen_table} (for the table library),
+@defid{luaopen_math} (for the mathematical library),
+@defid{luaopen_io} (for the I/O library),
+@defid{luaopen_os} (for the operating system library),
+and @defid{luaopen_debug} (for the debug library).
+These functions are declared in @defid{lualib.h}.
+
+@sect2{predefined| @title{Basic Functions}
+
+The basic library provides core functions to Lua.
+If you do not include this library in your application,
+you should check carefully whether you need to provide
+implementations for some of its facilities.
+
+
+@LibEntry{assert (v [, message])|
+
+Calls @Lid{error} if
+the value of its argument @id{v} is false (i.e., @nil or @false);
+otherwise, returns all its arguments.
+In case of error,
+@id{message} is the error object;
+when absent, it defaults to @St{assertion failed!}
+
+}
+
+@LibEntry{collectgarbage ([opt [, arg]])|
+
+This function is a generic interface to the garbage collector.
+It performs different functions according to its first argument, @id{opt}:
+@description{
+
+@item{@St{collect}|
+performs a full garbage-collection cycle.
+This is the default option.
+}
+
+@item{@St{stop}|
+stops automatic execution of the garbage collector.
+The collector will run only when explicitly invoked,
+until a call to restart it.
+}
+
+@item{@St{restart}|
+restarts automatic execution of the garbage collector.
+}
+
+@item{@St{count}|
+returns the total memory in use by Lua in Kbytes.
+The value has a fractional part,
+so that it multiplied by 1024
+gives the exact number of bytes in use by Lua
+(except for overflows).
+}
+
+@item{@St{step}|
+performs a garbage-collection step.
+The step @Q{size} is controlled by @id{arg}.
+With a zero value,
+the collector will perform one basic (indivisible) step.
+For non-zero values,
+the collector will perform as if that amount of memory
+(in KBytes) had been allocated by Lua.
+Returns @true if the step finished a collection cycle.
+}
+
+@item{@St{setpause}|
+sets @id{arg} as the new value for the @emph{pause} of
+the collector @see{GC}.
+Returns the previous value for @emph{pause}.
+}
+
+@item{@St{incremental}|
+Change the collector mode to incremental.
+This option can be followed by three numbers:
+the garbage-collector pause,
+the step multiplier,
+and the step size.
+}
+
+@item{@St{generational}|
+Change the collector mode to generational.
+This option can be followed by two numbers:
+the garbage-collector minor multiplier
+and the major multiplier.
+}
+
+@item{@St{isrunning}|
+returns a boolean that tells whether the collector is running
+(i.e., not stopped).
+}
+
+}
+
+}
+
+@LibEntry{dofile ([filename])|
+Opens the named file and executes its contents as a Lua chunk.
+When called without arguments,
+@id{dofile} executes the contents of the standard input (@id{stdin}).
+Returns all values returned by the chunk.
+In case of errors, @id{dofile} propagates the error
+to its caller (that is, @id{dofile} does not run in protected mode).
+
+}
+
+@LibEntry{error (message [, level])|
+Terminates the last protected function called
+and returns @id{message} as the error object.
+Function @id{error} never returns.
+
+Usually, @id{error} adds some information about the error position
+at the beginning of the message, if the message is a string.
+The @id{level} argument specifies how to get the error position.
+With @N{level 1} (the default), the error position is where the
+@id{error} function was called.
+@N{Level 2} points the error to where the function
+that called @id{error} was called; and so on.
+Passing a @N{level 0} avoids the addition of error position information
+to the message.
+
+}
+
+@LibEntry{_G|
+A global variable (not a function) that
+holds the @x{global environment} @see{globalenv}.
+Lua itself does not use this variable;
+changing its value does not affect any environment,
+nor vice versa.
+
+}
+
+@LibEntry{getmetatable (object)|
+
+If @id{object} does not have a metatable, returns @nil.
+Otherwise,
+if the object's metatable has a @idx{__metatable} field,
+returns the associated value.
+Otherwise, returns the metatable of the given object.
+
+}
+
+@LibEntry{ipairs (t)|
+
+Returns three values (an iterator function, the table @id{t}, and 0)
+so that the construction
+@verbatim{
+for i,v in ipairs(t) do @rep{body} end
+}
+will iterate over the key@En{}value pairs
+(@T{1,t[1]}), (@T{2,t[2]}), @ldots,
+up to the first absent index.
+
+}
+
+@LibEntry{load (chunk [, chunkname [, mode [, env]]])|
+
+Loads a chunk.
+
+If @id{chunk} is a string, the chunk is this string.
+If @id{chunk} is a function,
+@id{load} calls it repeatedly to get the chunk pieces.
+Each call to @id{chunk} must return a string that concatenates
+with previous results.
+A return of an empty string, @nil, or no value signals the end of the chunk.
+
+If there are no syntactic errors,
+returns the compiled chunk as a function;
+otherwise, returns @nil plus the error message.
+
+If the resulting function has upvalues,
+the first upvalue is set to the value of @id{env},
+if that parameter is given,
+or to the value of the @x{global environment}.
+Other upvalues are initialized with @nil.
+(When you load a main chunk,
+the resulting function will always have exactly one upvalue,
+the @id{_ENV} variable @see{globalenv}.
+However,
+when you load a binary chunk created from a function @seeF{string.dump},
+the resulting function can have an arbitrary number of upvalues.)
+All upvalues are fresh, that is,
+they are not shared with any other function.
+
+@id{chunkname} is used as the name of the chunk for error messages
+and debug information @see{debugI}.
+When absent,
+it defaults to @id{chunk}, if @id{chunk} is a string,
+or to @St{=(load)} otherwise.
+
+The string @id{mode} controls whether the chunk can be text or binary
+(that is, a precompiled chunk).
+It may be the string @St{b} (only @x{binary chunk}s),
+@St{t} (only text chunks),
+or @St{bt} (both binary and text).
+The default is @St{bt}.
+
+Lua does not check the consistency of binary chunks.
+Maliciously crafted binary chunks can crash
+the interpreter.
+
+}
+
+@LibEntry{loadfile ([filename [, mode [, env]]])|
+
+Similar to @Lid{load},
+but gets the chunk from file @id{filename}
+or from the standard input,
+if no file name is given.
+
+}
+
+@LibEntry{next (table [, index])|
+
+Allows a program to traverse all fields of a table.
+Its first argument is a table and its second argument
+is an index in this table.
+@id{next} returns the next index of the table
+and its associated value.
+When called with @nil as its second argument,
+@id{next} returns an initial index
+and its associated value.
+When called with the last index,
+or with @nil in an empty table,
+@id{next} returns @nil.
+If the second argument is absent, then it is interpreted as @nil.
+In particular,
+you can use @T{next(t)} to check whether a table is empty.
+
+The order in which the indices are enumerated is not specified,
+@emph{even for numeric indices}.
+(To traverse a table in numerical order,
+use a numerical @Rw{for}.)
+
+The behavior of @id{next} is undefined if,
+during the traversal,
+you assign any value to a non-existent field in the table.
+You may however modify existing fields.
+In particular, you may set existing fields to nil.
+
+}
+
+@LibEntry{pairs (t)|
+
+If @id{t} has a metamethod @idx{__pairs},
+calls it with @id{t} as argument and returns the first three
+results from the call.
+
+Otherwise,
+returns three values: the @Lid{next} function, the table @id{t}, and @nil,
+so that the construction
+@verbatim{
+for k,v in pairs(t) do @rep{body} end
+}
+will iterate over all key@En{}value pairs of table @id{t}.
+
+See function @Lid{next} for the caveats of modifying
+the table during its traversal.
+
+}
+
+@LibEntry{pcall (f [, arg1, @Cdots])|
+
+Calls function @id{f} with
+the given arguments in @def{protected mode}.
+This means that any error @N{inside @T{f}} is not propagated;
+instead, @id{pcall} catches the error
+and returns a status code.
+Its first result is the status code (a boolean),
+which is true if the call succeeds without errors.
+In such case, @id{pcall} also returns all results from the call,
+after this first result.
+In case of any error, @id{pcall} returns @false plus the error message.
+
+}
+
+@LibEntry{print (@Cdots)|
+Receives any number of arguments
+and prints their values to @id{stdout},
+using the @Lid{tostring} function to convert each argument to a string.
+@id{print} is not intended for formatted output,
+but only as a quick way to show a value,
+for instance for debugging.
+For complete control over the output,
+use @Lid{string.format} and @Lid{io.write}.
+
+}
+
+@LibEntry{rawequal (v1, v2)|
+Checks whether @id{v1} is equal to @id{v2},
+without invoking the @idx{__eq} metamethod.
+Returns a boolean.
+
+}
+
+@LibEntry{rawget (table, index)|
+Gets the real value of @T{table[index]},
+without invoking the @idx{__index} metamethod.
+@id{table} must be a table;
+@id{index} may be any value.
+
+}
+
+@LibEntry{rawlen (v)|
+Returns the length of the object @id{v},
+which must be a table or a string,
+without invoking the @idx{__len} metamethod.
+Returns an integer.
+
+}
+
+@LibEntry{rawset (table, index, value)|
+Sets the real value of @T{table[index]} to @id{value},
+without invoking the @idx{__newindex} metamethod.
+@id{table} must be a table,
+@id{index} any value different from @nil and @x{NaN},
+and @id{value} any Lua value.
+
+This function returns @id{table}.
+
+}
+
+@LibEntry{select (index, @Cdots)|
+
+If @id{index} is a number,
+returns all arguments after argument number @id{index};
+a negative number indexes from the end (@num{-1} is the last argument).
+Otherwise, @id{index} must be the string @T{"#"},
+and @id{select} returns the total number of extra arguments it received.
+
+}
+
+@LibEntry{setmetatable (table, metatable)|
+
+Sets the metatable for the given table.
+(To change the metatable of other types from Lua code,
+you must use the @link{debuglib|debug library}.)
+If @id{metatable} is @nil,
+removes the metatable of the given table.
+If the original metatable has a @idx{__metatable} field,
+raises an error.
+
+This function returns @id{table}.
+
+}
+
+@LibEntry{tonumber (e [, base])|
+
+When called with no @id{base},
+@id{tonumber} tries to convert its argument to a number.
+If the argument is already a number or
+a string convertible to a number,
+then @id{tonumber} returns this number;
+otherwise, it returns @nil.
+
+The conversion of strings can result in integers or floats,
+according to the lexical conventions of Lua @see{lexical}.
+(The string may have leading and trailing spaces and a sign.)
+
+When called with @id{base},
+then @id{e} must be a string to be interpreted as
+an integer numeral in that base.
+The base may be any integer between 2 and 36, inclusive.
+In bases @N{above 10}, the letter @Char{A} (in either upper or lower case)
+@N{represents 10}, @Char{B} @N{represents 11}, and so forth,
+with @Char{Z} representing 35.
+If the string @id{e} is not a valid numeral in the given base,
+the function returns @nil.
+
+}
+
+@LibEntry{tostring (v)|
+Receives a value of any type and
+converts it to a string in a human-readable format.
+(For complete control of how numbers are converted,
+use @Lid{string.format}.)
+
+If the metatable of @id{v} has a @idx{__tostring} field,
+then @id{tostring} calls the corresponding value
+with @id{v} as argument,
+and uses the result of the call as its result.
+
+}
+
+@LibEntry{type (v)|
+Returns the type of its only argument, coded as a string.
+The possible results of this function are
+@St{nil} (a string, not the value @nil),
+@St{number},
+@St{string},
+@St{boolean},
+@St{table},
+@St{function},
+@St{thread},
+and @St{userdata}.
+
+}
+
+@LibEntry{_VERSION|
+
+A global variable (not a function) that
+holds a string containing the running Lua version.
+The current value of this variable is @St{Lua 5.4}.
+
+}
+
+@LibEntry{xpcall (f, msgh [, arg1, @Cdots])|
+
+This function is similar to @Lid{pcall},
+except that it sets a new @x{message handler} @id{msgh}.
+
+}
+
+}
+
+@sect2{corolib| @title{Coroutine Manipulation}
+
+This library comprises the operations to manipulate coroutines,
+which come inside the table @defid{coroutine}.
+See @See{coroutine} for a general description of coroutines.
+
+
+@LibEntry{coroutine.create (f)|
+
+Creates a new coroutine, with body @id{f}.
+@id{f} must be a function.
+Returns this new coroutine,
+an object with type @T{"thread"}.
+
+}
+
+@LibEntry{coroutine.isyieldable ()|
+
+Returns true when the running coroutine can yield.
+
+A running coroutine is yieldable if it is not the main thread and
+it is not inside a non-yieldable @N{C function}.
+
+}
+
+@LibEntry{coroutine.resume (co [, val1, @Cdots])|
+
+Starts or continues the execution of coroutine @id{co}.
+The first time you resume a coroutine,
+it starts running its body.
+The values @id{val1}, @ldots are passed
+as the arguments to the body function.
+If the coroutine has yielded,
+@id{resume} restarts it;
+the values @id{val1}, @ldots are passed
+as the results from the yield.
+
+If the coroutine runs without any errors,
+@id{resume} returns @true plus any values passed to @id{yield}
+(when the coroutine yields) or any values returned by the body function
+(when the coroutine terminates).
+If there is any error,
+@id{resume} returns @false plus the error message.
+
+}
+
+@LibEntry{coroutine.running ()|
+
+Returns the running coroutine plus a boolean,
+true when the running coroutine is the main one.
+
+}
+
+@LibEntry{coroutine.status (co)|
+
+Returns the status of coroutine @id{co}, as a string:
+@T{"running"},
+if the coroutine is running (that is, it called @id{status});
+@T{"suspended"}, if the coroutine is suspended in a call to @id{yield},
+or if it has not started running yet;
+@T{"normal"} if the coroutine is active but not running
+(that is, it has resumed another coroutine);
+and @T{"dead"} if the coroutine has finished its body function,
+or if it has stopped with an error.
+
+}
+
+@LibEntry{coroutine.wrap (f)|
+
+Creates a new coroutine, with body @id{f}.
+@id{f} must be a function.
+Returns a function that resumes the coroutine each time it is called.
+Any arguments passed to the function behave as the
+extra arguments to @id{resume}.
+Returns the same values returned by @id{resume},
+except the first boolean.
+In case of error, propagates the error.
+
+}
+
+@LibEntry{coroutine.yield (@Cdots)|
+
+Suspends the execution of the calling coroutine.
+Any arguments to @id{yield} are passed as extra results to @id{resume}.
+
+}
+
+}
+
+@sect2{packlib| @title{Modules}
+
+The package library provides basic
+facilities for loading modules in Lua.
+It exports one function directly in the global environment:
+@Lid{require}.
+Everything else is exported in a table @defid{package}.
+
+
+@LibEntry{require (modname)|
+
+Loads the given module.
+The function starts by looking into the @Lid{package.loaded} table
+to determine whether @id{modname} is already loaded.
+If it is, then @id{require} returns the value stored
+at @T{package.loaded[modname]}.
+Otherwise, it tries to find a @emph{loader} for the module.
+
+To find a loader,
+@id{require} is guided by the @Lid{package.searchers} sequence.
+By changing this sequence,
+we can change how @id{require} looks for a module.
+The following explanation is based on the default configuration
+for @Lid{package.searchers}.
+
+First @id{require} queries @T{package.preload[modname]}.
+If it has a value,
+this value (which must be a function) is the loader.
+Otherwise @id{require} searches for a Lua loader using the
+path stored in @Lid{package.path}.
+If that also fails, it searches for a @N{C loader} using the
+path stored in @Lid{package.cpath}.
+If that also fails,
+it tries an @emph{all-in-one} loader @seeF{package.searchers}.
+
+Once a loader is found,
+@id{require} calls the loader with two arguments:
+@id{modname} and an extra value dependent on how it got the loader.
+(If the loader came from a file,
+this extra value is the file name.)
+If the loader returns any non-nil value,
+@id{require} assigns the returned value to @T{package.loaded[modname]}.
+If the loader does not return a non-nil value and
+has not assigned any value to @T{package.loaded[modname]},
+then @id{require} assigns @Rw{true} to this entry.
+In any case, @id{require} returns the
+final value of @T{package.loaded[modname]}.
+
+If there is any error loading or running the module,
+or if it cannot find any loader for the module,
+then @id{require} raises an error.
+
+}
+
+@LibEntry{package.config|
+
+A string describing some compile-time configurations for packages.
+This string is a sequence of lines:
+@itemize{
+
+@item{The first line is the @x{directory separator} string.
+Default is @Char{\} for @x{Windows} and @Char{/} for all other systems.}
+
+@item{The second line is the character that separates templates in a path.
+Default is @Char{;}.}
+
+@item{The third line is the string that marks the
+substitution points in a template.
+Default is @Char{?}.}
+
+@item{The fourth line is a string that, in a path in @x{Windows},
+is replaced by the executable's directory.
+Default is @Char{!}.}
+
+@item{The fifth line is a mark to ignore all text after it
+when building the @id{luaopen_} function name.
+Default is @Char{-}.}
+
+}
+
+}
+
+@LibEntry{package.cpath|
+
+The path used by @Lid{require} to search for a @N{C loader}.
+
+Lua initializes the @N{C path} @Lid{package.cpath} in the same way
+it initializes the Lua path @Lid{package.path},
+using the environment variable @defid{LUA_CPATH_5_4},
+or the environment variable @defid{LUA_CPATH},
+or a default path defined in @id{luaconf.h}.
+
+}
+
+@LibEntry{package.loaded|
+
+A table used by @Lid{require} to control which
+modules are already loaded.
+When you require a module @id{modname} and
+@T{package.loaded[modname]} is not false,
+@Lid{require} simply returns the value stored there.
+
+This variable is only a reference to the real table;
+assignments to this variable do not change the
+table used by @Lid{require}.
+
+}
+
+@LibEntry{package.loadlib (libname, funcname)|
+
+Dynamically links the host program with the @N{C library} @id{libname}.
+
+If @id{funcname} is @St{*},
+then it only links with the library,
+making the symbols exported by the library
+available to other dynamically linked libraries.
+Otherwise,
+it looks for a function @id{funcname} inside the library
+and returns this function as a @N{C function}.
+So, @id{funcname} must follow the @Lid{lua_CFunction} prototype
+@seeC{lua_CFunction}.
+
+This is a low-level function.
+It completely bypasses the package and module system.
+Unlike @Lid{require},
+it does not perform any path searching and
+does not automatically adds extensions.
+@id{libname} must be the complete file name of the @N{C library},
+including if necessary a path and an extension.
+@id{funcname} must be the exact name exported by the @N{C library}
+(which may depend on the @N{C compiler} and linker used).
+
+This function is not supported by @N{Standard C}.
+As such, it is only available on some platforms
+(Windows, Linux, Mac OS X, Solaris, BSD,
+plus other Unix systems that support the @id{dlfcn} standard).
+
+}
+
+@LibEntry{package.path|
+
+The path used by @Lid{require} to search for a Lua loader.
+
+At start-up, Lua initializes this variable with
+the value of the environment variable @defid{LUA_PATH_5_4} or
+the environment variable @defid{LUA_PATH} or
+with a default path defined in @id{luaconf.h},
+if those environment variables are not defined.
+Any @St{;;} in the value of the environment variable
+is replaced by the default path.
+
+}
+
+@LibEntry{package.preload|
+
+A table to store loaders for specific modules
+@seeF{require}.
+
+This variable is only a reference to the real table;
+assignments to this variable do not change the
+table used by @Lid{require}.
+
+}
+
+@LibEntry{package.searchers|
+
+A table used by @Lid{require} to control how to load modules.
+
+Each entry in this table is a @def{searcher function}.
+When looking for a module,
+@Lid{require} calls each of these searchers in ascending order,
+with the module name (the argument given to @Lid{require}) as its
+sole parameter.
+The function can return another function (the module @def{loader})
+plus an extra value that will be passed to that loader,
+or a string explaining why it did not find that module
+(or @nil if it has nothing to say).
+
+Lua initializes this table with four searcher functions.
+
+The first searcher simply looks for a loader in the
+@Lid{package.preload} table.
+
+The second searcher looks for a loader as a Lua library,
+using the path stored at @Lid{package.path}.
+The search is done as described in function @Lid{package.searchpath}.
+
+The third searcher looks for a loader as a @N{C library},
+using the path given by the variable @Lid{package.cpath}.
+Again,
+the search is done as described in function @Lid{package.searchpath}.
+For instance,
+if the @N{C path} is the string
+@verbatim{
+"./?.so;./?.dll;/usr/local/?/init.so"
+}
+the searcher for module @id{foo}
+will try to open the files @T{./foo.so}, @T{./foo.dll},
+and @T{/usr/local/foo/init.so}, in that order.
+Once it finds a @N{C library},
+this searcher first uses a dynamic link facility to link the
+application with the library.
+Then it tries to find a @N{C function} inside the library to
+be used as the loader.
+The name of this @N{C function} is the string @St{luaopen_}
+concatenated with a copy of the module name where each dot
+is replaced by an underscore.
+Moreover, if the module name has a hyphen,
+its suffix after (and including) the first hyphen is removed.
+For instance, if the module name is @id{a.b.c-v2.1},
+the function name will be @id{luaopen_a_b_c}.
+
+The fourth searcher tries an @def{all-in-one loader}.
+It searches the @N{C path} for a library for
+the root name of the given module.
+For instance, when requiring @id{a.b.c},
+it will search for a @N{C library} for @id{a}.
+If found, it looks into it for an open function for
+the submodule;
+in our example, that would be @id{luaopen_a_b_c}.
+With this facility, a package can pack several @N{C submodules}
+into one single library,
+with each submodule keeping its original open function.
+
+All searchers except the first one (preload) return as the extra value
+the file name where the module was found,
+as returned by @Lid{package.searchpath}.
+The first searcher returns no extra value.
+
+}
+
+@LibEntry{package.searchpath (name, path [, sep [, rep]])|
+
+Searches for the given @id{name} in the given @id{path}.
+
+A path is a string containing a sequence of
+@emph{templates} separated by semicolons.
+For each template,
+the function replaces each interrogation mark (if any)
+in the template with a copy of @id{name}
+wherein all occurrences of @id{sep}
+(a dot, by default)
+were replaced by @id{rep}
+(the system's directory separator, by default),
+and then tries to open the resulting file name.
+
+For instance, if the path is the string
+@verbatim{
+"./?.lua;./?.lc;/usr/local/?/init.lua"
+}
+the search for the name @id{foo.a}
+will try to open the files
+@T{./foo/a.lua}, @T{./foo/a.lc}, and
+@T{/usr/local/foo/a/init.lua}, in that order.
+
+Returns the resulting name of the first file that it can
+open in read mode (after closing the file),
+or @nil plus an error message if none succeeds.
+(This error message lists all file names it tried to open.)
+
+}
+
+}
+
+@sect2{strlib| @title{String Manipulation}
+
+This library provides generic functions for string manipulation,
+such as finding and extracting substrings, and pattern matching.
+When indexing a string in Lua, the first character is at @N{position 1}
+(not @N{at 0}, as in C).
+Indices are allowed to be negative and are interpreted as indexing backwards,
+from the end of the string.
+Thus, the last character is at position @num{-1}, and so on.
+
+The string library provides all its functions inside the table
+@defid{string}.
+It also sets a @x{metatable for strings}
+where the @idx{__index} field points to the @id{string} table.
+Therefore, you can use the string functions in object-oriented style.
+For instance, @T{string.byte(s,i)}
+can be written as @T{s:byte(i)}.
+
+The string library assumes one-byte character encodings.
+
+
+@LibEntry{string.byte (s [, i [, j]])|
+Returns the internal numeric codes of the characters @T{s[i]},
+@T{s[i+1]}, @ldots, @T{s[j]}.
+The default value for @id{i} @N{is 1};
+the default value for @id{j} @N{is @id{i}}.
+These indices are corrected
+following the same rules of function @Lid{string.sub}.
+
+Numeric codes are not necessarily portable across platforms.
+
+}
+
+@LibEntry{string.char (@Cdots)|
+Receives zero or more integers.
+Returns a string with length equal to the number of arguments,
+in which each character has the internal numeric code equal
+to its corresponding argument.
+
+Numeric codes are not necessarily portable across platforms.
+
+}
+
+@LibEntry{string.dump (function [, strip])|
+
+Returns a string containing a binary representation
+(a @emph{binary chunk})
+of the given function,
+so that a later @Lid{load} on this string returns
+a copy of the function (but with new upvalues).
+If @id{strip} is a true value,
+the binary representation may not include all debug information
+about the function,
+to save space.
+
+Functions with upvalues have only their number of upvalues saved.
+When (re)loaded,
+those upvalues receive fresh instances containing @nil.
+(You can use the debug library to serialize
+and reload the upvalues of a function
+in a way adequate to your needs.)
+
+}
+
+@LibEntry{string.find (s, pattern [, init [, plain]])|
+
+Looks for the first match of
+@id{pattern} @see{pm} in the string @id{s}.
+If it finds a match, then @id{find} returns the indices @N{of @T{s}}
+where this occurrence starts and ends;
+otherwise, it returns @nil.
+A third, optional numeric argument @id{init} specifies
+where to start the search;
+its default value @N{is 1} and can be negative.
+A value of @true as a fourth, optional argument @id{plain}
+turns off the pattern matching facilities,
+so the function does a plain @Q{find substring} operation,
+with no characters in @id{pattern} being considered magic.
+Note that if @id{plain} is given, then @id{init} must be given as well.
+
+If the pattern has captures,
+then in a successful match
+the captured values are also returned,
+after the two indices.
+
+}
+
+@LibEntry{string.format (formatstring, @Cdots)|
+
+Returns a formatted version of its variable number of arguments
+following the description given in its first argument (which must be a string).
+The format string follows the same rules as the @ANSI{sprintf}.
+The only differences are that the options/modifiers
+@T{*}, @id{h}, @id{L}, @id{l}, @id{n},
+and @id{p} are not supported
+and that there is an extra option, @id{q}.
+
+The @id{q} option formats booleans, nil, numbers, and strings
+in a way that the result is a valid constant in Lua source code.
+Booleans and nil are written in the obvious way
+(@id{true}, @id{false}, @id{nil}).
+Floats are written in hexadecimal,
+to preserve full precision.
+A string is written between double quotes,
+using escape sequences when necessary to ensure that
+it can safely be read back by the Lua interpreter.
+For instance, the call
+@verbatim{
+string.format('%q', 'a string with "quotes" and \n new line')
+}
+may produce the string:
+@verbatim{
+"a string with \"quotes\" and \
+ new line"
+}
+
+Options
+@id{A}, @id{a}, @id{E}, @id{e}, @id{f},
+@id{G}, and @id{g} all expect a number as argument.
+Options @id{c}, @id{d},
+@id{i}, @id{o}, @id{u}, @id{X}, and @id{x}
+expect an integer.
+When Lua is compiled with a C89 compiler,
+options @id{A} and @id{a} (hexadecimal floats)
+do not support any modifier (flags, width, length).
+
+Option @id{s} expects a string;
+if its argument is not a string,
+it is converted to one following the same rules of @Lid{tostring}.
+If the option has any modifier (flags, width, length),
+the string argument should not contain @x{embedded zeros}.
+
+}
+
+@LibEntry{string.gmatch (s, pattern)|
+Returns an iterator function that,
+each time it is called,
+returns the next captures from @id{pattern} @see{pm}
+over the string @id{s}.
+If @id{pattern} specifies no captures,
+then the whole match is produced in each call.
+
+As an example, the following loop
+will iterate over all the words from string @id{s},
+printing one per line:
+@verbatim{
+s = "hello world from Lua"
+for w in string.gmatch(s, "%a+") do
+ print(w)
+end
+}
+The next example collects all pairs @T{key=value} from the
+given string into a table:
+@verbatim{
+t = {}
+s = "from=world, to=Lua"
+for k, v in string.gmatch(s, "(%w+)=(%w+)") do
+ t[k] = v
+end
+}
+
+For this function, a caret @Char{^} at the start of a pattern does not
+work as an anchor, as this would prevent the iteration.
+
+}
+
+@LibEntry{string.gsub (s, pattern, repl [, n])|
+Returns a copy of @id{s}
+in which all (or the first @id{n}, if given)
+occurrences of the @id{pattern} @see{pm} have been
+replaced by a replacement string specified by @id{repl},
+which can be a string, a table, or a function.
+@id{gsub} also returns, as its second value,
+the total number of matches that occurred.
+The name @id{gsub} comes from @emph{Global SUBstitution}.
+
+If @id{repl} is a string, then its value is used for replacement.
+The @N{character @T{%}} works as an escape character:
+any sequence in @id{repl} of the form @T{%@rep{d}},
+with @rep{d} between 1 and 9,
+stands for the value of the @rep{d}-th captured substring.
+The sequence @T{%0} stands for the whole match.
+The sequence @T{%%} stands for a @N{single @T{%}}.
+
+If @id{repl} is a table, then the table is queried for every match,
+using the first capture as the key.
+
+If @id{repl} is a function, then this function is called every time a
+match occurs, with all captured substrings passed as arguments,
+in order.
+
+In any case,
+if the pattern specifies no captures,
+then it behaves as if the whole pattern was inside a capture.
+
+If the value returned by the table query or by the function call
+is a string or a number,
+then it is used as the replacement string;
+otherwise, if it is @Rw{false} or @nil,
+then there is no replacement
+(that is, the original match is kept in the string).
+
+Here are some examples:
+@verbatim{
+x = string.gsub("hello world", "(%w+)", "%1 %1")
+--> x="hello hello world world"
+
+x = string.gsub("hello world", "%w+", "%0 %0", 1)
+--> x="hello hello world"
+
+x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
+--> x="world hello Lua from"
+
+x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv)
+--> x="home = /home/roberto, user = roberto"
+
+x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s)
+ return load(s)()
+ end)
+--> x="4+5 = 9"
+
+local t = {name="lua", version="5.4"}
+x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
+--> x="lua-5.4.tar.gz"
+}
+
+}
+
+@LibEntry{string.len (s)|
+Receives a string and returns its length.
+The empty string @T{""} has length 0.
+Embedded zeros are counted,
+so @T{"a\000bc\000"} has length 5.
+
+}
+
+@LibEntry{string.lower (s)|
+Receives a string and returns a copy of this string with all
+uppercase letters changed to lowercase.
+All other characters are left unchanged.
+The definition of what an uppercase letter is depends on the current locale.
+
+}
+
+@LibEntry{string.match (s, pattern [, init])|
+Looks for the first @emph{match} of
+@id{pattern} @see{pm} in the string @id{s}.
+If it finds one, then @id{match} returns
+the captures from the pattern;
+otherwise it returns @nil.
+If @id{pattern} specifies no captures,
+then the whole match is returned.
+A third, optional numeric argument @id{init} specifies
+where to start the search;
+its default value @N{is 1} and can be negative.
+
+}
+
+@LibEntry{string.pack (fmt, v1, v2, @Cdots)|
+
+Returns a binary string containing the values @id{v1}, @id{v2}, etc.
+packed (that is, serialized in binary form)
+according to the format string @id{fmt} @see{pack}.
+
+}
+
+@LibEntry{string.packsize (fmt)|
+
+Returns the size of a string resulting from @Lid{string.pack}
+with the given format.
+The format string cannot have the variable-length options
+@Char{s} or @Char{z} @see{pack}.
+
+}
+
+@LibEntry{string.rep (s, n [, sep])|
+Returns a string that is the concatenation of @id{n} copies of
+the string @id{s} separated by the string @id{sep}.
+The default value for @id{sep} is the empty string
+(that is, no separator).
+Returns the empty string if @id{n} is not positive.
+
+(Note that it is very easy to exhaust the memory of your machine
+with a single call to this function.)
+
+}
+
+@LibEntry{string.reverse (s)|
+Returns a string that is the string @id{s} reversed.
+
+}
+
+@LibEntry{string.sub (s, i [, j])|
+Returns the substring of @id{s} that
+starts at @id{i} and continues until @id{j};
+@id{i} and @id{j} can be negative.
+If @id{j} is absent, then it is assumed to be equal to @num{-1}
+(which is the same as the string length).
+In particular,
+the call @T{string.sub(s,1,j)} returns a prefix of @id{s}
+with length @id{j},
+and @T{string.sub(s, -i)} (for a positive @id{i})
+returns a suffix of @id{s}
+with length @id{i}.
+
+If, after the translation of negative indices,
+@id{i} is less than 1,
+it is corrected to 1.
+If @id{j} is greater than the string length,
+it is corrected to that length.
+If, after these corrections,
+@id{i} is greater than @id{j},
+the function returns the empty string.
+
+}
+
+@LibEntry{string.unpack (fmt, s [, pos])|
+
+Returns the values packed in string @id{s} @seeF{string.pack}
+according to the format string @id{fmt} @see{pack}.
+An optional @id{pos} marks where
+to start reading in @id{s} (default is 1).
+After the read values,
+this function also returns the index of the first unread byte in @id{s}.
+
+}
+
+@LibEntry{string.upper (s)|
+Receives a string and returns a copy of this string with all
+lowercase letters changed to uppercase.
+All other characters are left unchanged.
+The definition of what a lowercase letter is depends on the current locale.
+
+}
+
+
+@sect3{pm| @title{Patterns}
+
+Patterns in Lua are described by regular strings,
+which are interpreted as patterns by the pattern-matching functions
+@Lid{string.find},
+@Lid{string.gmatch},
+@Lid{string.gsub},
+and @Lid{string.match}.
+This section describes the syntax and the meaning
+(that is, what they match) of these strings.
+
+@sect4{@title{Character Class:}
+A @def{character class} is used to represent a set of characters.
+The following combinations are allowed in describing a character class:
+@description{
+
+@item{@rep{x}|
+(where @rep{x} is not one of the @emphx{magic characters}
+@T{^$()%.[]*+-?})
+represents the character @emph{x} itself.
+}
+
+@item{@T{.}| (a dot) represents all characters.}
+
+@item{@T{%a}| represents all letters.}
+
+@item{@T{%c}| represents all control characters.}
+
+@item{@T{%d}| represents all digits.}
+
+@item{@T{%g}| represents all printable characters except space.}
+
+@item{@T{%l}| represents all lowercase letters.}
+
+@item{@T{%p}| represents all punctuation characters.}
+
+@item{@T{%s}| represents all space characters.}
+
+@item{@T{%u}| represents all uppercase letters.}
+
+@item{@T{%w}| represents all alphanumeric characters.}
+
+@item{@T{%x}| represents all hexadecimal digits.}
+
+@item{@T{%@rep{x}}| (where @rep{x} is any non-alphanumeric character)
+represents the character @rep{x}.
+This is the standard way to escape the magic characters.
+Any non-alphanumeric character
+(including all punctuation characters, even the non-magical)
+can be preceded by a @Char{%}
+when used to represent itself in a pattern.
+}
+
+@item{@T{[@rep{set}]}|
+represents the class which is the union of all
+characters in @rep{set}.
+A range of characters can be specified by
+separating the end characters of the range,
+in ascending order, with a @Char{-}.
+All classes @T{%}@emph{x} described above can also be used as
+components in @rep{set}.
+All other characters in @rep{set} represent themselves.
+For example, @T{[%w_]} (or @T{[_%w]})
+represents all alphanumeric characters plus the underscore,
+@T{[0-7]} represents the octal digits,
+and @T{[0-7%l%-]} represents the octal digits plus
+the lowercase letters plus the @Char{-} character.
+
+You can put a closing square bracket in a set
+by positioning it as the first character in the set.
+You can put a hyphen in a set
+by positioning it as the first or the last character in the set.
+(You can also use an escape for both cases.)
+
+The interaction between ranges and classes is not defined.
+Therefore, patterns like @T{[%a-z]} or @T{[a-%%]}
+have no meaning.
+}
+
+@item{@T{[^@rep{set}]}|
+represents the complement of @rep{set},
+where @rep{set} is interpreted as above.
+}
+
+}
+For all classes represented by single letters (@T{%a}, @T{%c}, etc.),
+the corresponding uppercase letter represents the complement of the class.
+For instance, @T{%S} represents all non-space characters.
+
+The definitions of letter, space, and other character groups
+depend on the current locale.
+In particular, the class @T{[a-z]} may not be equivalent to @T{%l}.
+
+}
+
+@sect4{@title{Pattern Item:}
+A @def{pattern item} can be
+@itemize{
+
+@item{
+a single character class,
+which matches any single character in the class;
+}
+
+@item{
+a single character class followed by @Char{*},
+which matches zero or more repetitions of characters in the class.
+These repetition items will always match the longest possible sequence;
+}
+
+@item{
+a single character class followed by @Char{+},
+which matches one or more repetitions of characters in the class.
+These repetition items will always match the longest possible sequence;
+}
+
+@item{
+a single character class followed by @Char{-},
+which also matches zero or more repetitions of characters in the class.
+Unlike @Char{*},
+these repetition items will always match the shortest possible sequence;
+}
+
+@item{
+a single character class followed by @Char{?},
+which matches zero or one occurrence of a character in the class.
+It always matches one occurrence if possible;
+}
+
+@item{
+@T{%@rep{n}}, for @rep{n} between 1 and 9;
+such item matches a substring equal to the @rep{n}-th captured string
+(see below);
+}
+
+@item{
+@T{%b@rep{xy}}, where @rep{x} and @rep{y} are two distinct characters;
+such item matches strings that start @N{with @rep{x}}, end @N{with @rep{y}},
+and where the @rep{x} and @rep{y} are @emph{balanced}.
+This means that, if one reads the string from left to right,
+counting @M{+1} for an @rep{x} and @M{-1} for a @rep{y},
+the ending @rep{y} is the first @rep{y} where the count reaches 0.
+For instance, the item @T{%b()} matches expressions with
+balanced parentheses.
+}
+
+@item{
+@T{%f[@rep{set}]}, a @def{frontier pattern};
+such item matches an empty string at any position such that
+the next character belongs to @rep{set}
+and the previous character does not belong to @rep{set}.
+The set @rep{set} is interpreted as previously described.
+The beginning and the end of the subject are handled as if
+they were the character @Char{\0}.
+}
+
+}
+
+}
+
+@sect4{@title{Pattern:}
+A @def{pattern} is a sequence of pattern items.
+A caret @Char{^} at the beginning of a pattern anchors the match at the
+beginning of the subject string.
+A @Char{$} at the end of a pattern anchors the match at the
+end of the subject string.
+At other positions,
+@Char{^} and @Char{$} have no special meaning and represent themselves.
+
+}
+
+@sect4{@title{Captures:}
+A pattern can contain sub-patterns enclosed in parentheses;
+they describe @def{captures}.
+When a match succeeds, the substrings of the subject string
+that match captures are stored (@emph{captured}) for future use.
+Captures are numbered according to their left parentheses.
+For instance, in the pattern @T{"(a*(.)%w(%s*))"},
+the part of the string matching @T{"a*(.)%w(%s*)"} is
+stored as the first capture (and therefore has @N{number 1});
+the character matching @St{.} is captured with @N{number 2},
+and the part matching @St{%s*} has @N{number 3}.
+
+As a special case, the empty capture @T{()} captures
+the current string position (a number).
+For instance, if we apply the pattern @T{"()aa()"} on the
+string @T{"flaaap"}, there will be two captures: @N{3 and 5}.
+
+}
+
+@sect4{@title{Multiple matches:}
+The function @Lid{string.gsub} and the iterator @Lid{string.gmatch}
+match multiple occurrences of the given pattern in the subject.
+For these functions,
+a new match is considered valid only
+if it ends at least one byte after the previous match.
+In other words, the pattern machine never accepts the
+empty string as a match immediately after another match.
+As an example,
+consider the results of the following code:
+@verbatim{
+> string.gsub("abc", "()a*()", print)
+--> 1 2
+--> 3 3
+--> 4 4
+}
+The second and third results come from Lua matching an empty
+string after @Char{b} and another one after @Char{c}.
+Lua does not match an empty string after @Char{a},
+because it would end at the same position of the previous match.
+
+}
+
+}
+
+@sect3{pack| @title{Format Strings for Pack and Unpack}
+
+The first argument to @Lid{string.pack},
+@Lid{string.packsize}, and @Lid{string.unpack}
+is a format string,
+which describes the layout of the structure being created or read.
+
+A format string is a sequence of conversion options.
+The conversion options are as follows:
+@description{
+@item{@T{<}|sets little endian}
+@item{@T{>}|sets big endian}
+@item{@T{=}|sets native endian}
+@item{@T{![@rep{n}]}|sets maximum alignment to @id{n}
+(default is native alignment)}
+@item{@T{b}|a signed byte (@id{char})}
+@item{@T{B}|an unsigned byte (@id{char})}
+@item{@T{h}|a signed @id{short} (native size)}
+@item{@T{H}|an unsigned @id{short} (native size)}
+@item{@T{l}|a signed @id{long} (native size)}
+@item{@T{L}|an unsigned @id{long} (native size)}
+@item{@T{j}|a @id{lua_Integer}}
+@item{@T{J}|a @id{lua_Unsigned}}
+@item{@T{T}|a @id{size_t} (native size)}
+@item{@T{i[@rep{n}]}|a signed @id{int} with @id{n} bytes
+(default is native size)}
+@item{@T{I[@rep{n}]}|an unsigned @id{int} with @id{n} bytes
+(default is native size)}
+@item{@T{f}|a @id{float} (native size)}
+@item{@T{d}|a @id{double} (native size)}
+@item{@T{n}|a @id{lua_Number}}
+@item{@T{c@rep{n}}|a fixed-sized string with @id{n} bytes}
+@item{@T{z}|a zero-terminated string}
+@item{@T{s[@emph{n}]}|a string preceded by its length
+coded as an unsigned integer with @id{n} bytes
+(default is a @id{size_t})}
+@item{@T{x}|one byte of padding}
+@item{@T{X@rep{op}}|an empty item that aligns
+according to option @id{op}
+(which is otherwise ignored)}
+@item{@Char{ }|(empty space) ignored}
+}
+(A @St{[@rep{n}]} means an optional integral numeral.)
+Except for padding, spaces, and configurations
+(options @St{xX <=>!}),
+each option corresponds to an argument (in @Lid{string.pack})
+or a result (in @Lid{string.unpack}).
+
+For options @St{!@rep{n}}, @St{s@rep{n}}, @St{i@rep{n}}, and @St{I@rep{n}},
+@id{n} can be any integer between 1 and 16.
+All integral options check overflows;
+@Lid{string.pack} checks whether the given value fits in the given size;
+@Lid{string.unpack} checks whether the read value fits in a Lua integer.
+
+Any format string starts as if prefixed by @St{!1=},
+that is,
+with maximum alignment of 1 (no alignment)
+and native endianness.
+
+Alignment works as follows:
+For each option,
+the format gets extra padding until the data starts
+at an offset that is a multiple of the minimum between the
+option size and the maximum alignment;
+this minimum must be a power of 2.
+Options @St{c} and @St{z} are not aligned;
+option @St{s} follows the alignment of its starting integer.
+
+All padding is filled with zeros by @Lid{string.pack}
+(and ignored by @Lid{string.unpack}).
+
+}
+
+}
+
+@sect2{utf8| @title{UTF-8 Support}
+
+This library provides basic support for @x{UTF-8} encoding.
+It provides all its functions inside the table @defid{utf8}.
+This library does not provide any support for @x{Unicode} other
+than the handling of the encoding.
+Any operation that needs the meaning of a character,
+such as character classification, is outside its scope.
+
+Unless stated otherwise,
+all functions that expect a byte position as a parameter
+assume that the given position is either the start of a byte sequence
+or one plus the length of the subject string.
+As in the string library,
+negative indices count from the end of the string.
+
+
+@LibEntry{utf8.char (@Cdots)|
+Receives zero or more integers,
+converts each one to its corresponding UTF-8 byte sequence
+and returns a string with the concatenation of all these sequences.
+
+}
+
+@LibEntry{utf8.charpattern|
+The pattern (a string, not a function) @St{[\0-\x7F\xC2-\xF4][\x80-\xBF]*}
+@see{pm},
+which matches exactly one UTF-8 byte sequence,
+assuming that the subject is a valid UTF-8 string.
+
+}
+
+@LibEntry{utf8.codes (s)|
+
+Returns values so that the construction
+@verbatim{
+for p, c in utf8.codes(s) do @rep{body} end
+}
+will iterate over all characters in string @id{s},
+with @id{p} being the position (in bytes) and @id{c} the code point
+of each character.
+It raises an error if it meets any invalid byte sequence.
+
+}
+
+@LibEntry{utf8.codepoint (s [, i [, j]])|
+Returns the codepoints (as integers) from all characters in @id{s}
+that start between byte position @id{i} and @id{j} (both included).
+The default for @id{i} is 1 and for @id{j} is @id{i}.
+It raises an error if it meets any invalid byte sequence.
+
+}
+
+@LibEntry{utf8.len (s [, i [, j]])|
+Returns the number of UTF-8 characters in string @id{s}
+that start between positions @id{i} and @id{j} (both inclusive).
+The default for @id{i} is @num{1} and for @id{j} is @num{-1}.
+If it finds any invalid byte sequence,
+returns a false value plus the position of the first invalid byte.
+
+}
+
+@LibEntry{utf8.offset (s, n [, i])|
+Returns the position (in bytes) where the encoding of the
+@id{n}-th character of @id{s}
+(counting from position @id{i}) starts.
+A negative @id{n} gets characters before position @id{i}.
+The default for @id{i} is 1 when @id{n} is non-negative
+and @T{#s + 1} otherwise,
+so that @T{utf8.offset(s, -n)} gets the offset of the
+@id{n}-th character from the end of the string.
+If the specified character is neither in the subject
+nor right after its end,
+the function returns @nil.
+
+As a special case,
+when @id{n} is 0 the function returns the start of the encoding
+of the character that contains the @id{i}-th byte of @id{s}.
+
+This function assumes that @id{s} is a valid UTF-8 string.
+
+}
+
+}
+
+@sect2{tablib| @title{Table Manipulation}
+
+This library provides generic functions for table manipulation.
+It provides all its functions inside the table @defid{table}.
+
+Remember that, whenever an operation needs the length of a table,
+all caveats about the length operator apply @see{len-op}.
+All functions ignore non-numeric keys
+in the tables given as arguments.
+
+
+@LibEntry{table.concat (list [, sep [, i [, j]]])|
+
+Given a list where all elements are strings or numbers,
+returns the string @T{list[i]..sep..list[i+1] @Cdots sep..list[j]}.
+The default value for @id{sep} is the empty string,
+the default for @id{i} is 1,
+and the default for @id{j} is @T{#list}.
+If @id{i} is greater than @id{j}, returns the empty string.
+
+}
+
+@LibEntry{table.insert (list, [pos,] value)|
+
+Inserts element @id{value} at position @id{pos} in @id{list},
+shifting up the elements
+@T{list[pos], list[pos+1], @Cdots, list[#list]}.
+The default value for @id{pos} is @T{#list+1},
+so that a call @T{table.insert(t,x)} inserts @id{x} at the end
+of list @id{t}.
+
+}
+
+@LibEntry{table.move (a1, f, e, t [,a2])|
+
+Moves elements from table @id{a1} to table @id{a2},
+performing the equivalent to the following
+multiple assignment:
+@T{a2[t],@Cdots = a1[f],@Cdots,a1[e]}.
+The default for @id{a2} is @id{a1}.
+The destination range can overlap with the source range.
+The number of elements to be moved must fit in a Lua integer.
+
+Returns the destination table @id{a2}.
+
+}
+
+@LibEntry{table.pack (@Cdots)|
+
+Returns a new table with all arguments stored into keys 1, 2, etc.
+and with a field @St{n} with the total number of arguments.
+Note that the resulting table may not be a sequence,
+if some arguments are @nil.
+
+}
+
+@LibEntry{table.remove (list [, pos])|
+
+Removes from @id{list} the element at position @id{pos},
+returning the value of the removed element.
+When @id{pos} is an integer between 1 and @T{#list},
+it shifts down the elements
+@T{list[pos+1], list[pos+2], @Cdots, list[#list]}
+and erases element @T{list[#list]};
+The index @id{pos} can also be 0 when @T{#list} is 0,
+or @T{#list + 1}.
+
+The default value for @id{pos} is @T{#list},
+so that a call @T{table.remove(l)} removes the last element
+of list @id{l}.
+
+}
+
+@LibEntry{table.sort (list [, comp])|
+
+Sorts list elements in a given order, @emph{in-place},
+from @T{list[1]} to @T{list[#list]}.
+If @id{comp} is given,
+then it must be a function that receives two list elements
+and returns true when the first element must come
+before the second in the final order
+(so that, after the sort,
+@T{i < j} implies @T{not comp(list[j],list[i])}).
+If @id{comp} is not given,
+then the standard Lua operator @T{<} is used instead.
+
+Note that the @id{comp} function must define
+a strict partial order over the elements in the list;
+that is, it must be asymmetric and transitive.
+Otherwise, no valid sort may be possible.
+
+The sort algorithm is not stable:
+elements considered equal by the given order
+may have their relative positions changed by the sort.
+
+}
+
+@LibEntry{table.unpack (list [, i [, j]])|
+
+Returns the elements from the given list.
+This function is equivalent to
+@verbatim{
+return list[i], list[i+1], @Cdots, list[j]
+}
+By default, @id{i} @N{is 1} and @id{j} is @T{#list}.
+
+}
+
+}
+
+@sect2{mathlib| @title{Mathematical Functions}
+
+This library provides basic mathematical functions.
+It provides all its functions and constants inside the table @defid{math}.
+Functions with the annotation @St{integer/float} give
+integer results for integer arguments
+and float results for float (or mixed) arguments.
+Rounding functions
+(@Lid{math.ceil}, @Lid{math.floor}, and @Lid{math.modf})
+return an integer when the result fits in the range of an integer,
+or a float otherwise.
+
+@LibEntry{math.abs (x)|
+
+Returns the absolute value of @id{x}. (integer/float)
+
+}
+
+@LibEntry{math.acos (x)|
+
+Returns the arc cosine of @id{x} (in radians).
+
+}
+
+@LibEntry{math.asin (x)|
+
+Returns the arc sine of @id{x} (in radians).
+
+}
+
+@LibEntry{math.atan (y [, x])|
+
+@index{atan2}
+Returns the arc tangent of @T{y/x} (in radians),
+but uses the signs of both parameters to find the
+quadrant of the result.
+(It also handles correctly the case of @id{x} being zero.)
+
+The default value for @id{x} is 1,
+so that the call @T{math.atan(y)}
+returns the arc tangent of @id{y}.
+
+}
+
+@LibEntry{math.ceil (x)|
+
+Returns the smallest integral value larger than or equal to @id{x}.
+
+}
+
+@LibEntry{math.cos (x)|
+
+Returns the cosine of @id{x} (assumed to be in radians).
+
+}
+
+@LibEntry{math.deg (x)|
+
+Converts the angle @id{x} from radians to degrees.
+
+}
+
+@LibEntry{math.exp (x)|
+
+Returns the value @M{e@sp{x}}
+(where @id{e} is the base of natural logarithms).
+
+}
+
+@LibEntry{math.floor (x)|
+
+Returns the largest integral value smaller than or equal to @id{x}.
+
+}
+
+@LibEntry{math.fmod (x, y)|
+
+Returns the remainder of the division of @id{x} by @id{y}
+that rounds the quotient towards zero. (integer/float)
+
+}
+
+@LibEntry{math.huge|
+
+The float value @idx{HUGE_VAL},
+a value larger than any other numeric value.
+
+}
+
+@LibEntry{math.log (x [, base])|
+
+Returns the logarithm of @id{x} in the given base.
+The default for @id{base} is @M{e}
+(so that the function returns the natural logarithm of @id{x}).
+
+}
+
+@LibEntry{math.max (x, @Cdots)|
+
+Returns the argument with the maximum value,
+according to the Lua operator @T{<}. (integer/float)
+
+}
+
+@LibEntry{math.maxinteger|
+An integer with the maximum value for an integer.
+
+}
+
+@LibEntry{math.min (x, @Cdots)|
+
+Returns the argument with the minimum value,
+according to the Lua operator @T{<}. (integer/float)
+
+}
+
+@LibEntry{math.mininteger|
+An integer with the minimum value for an integer.
+
+}
+
+@LibEntry{math.modf (x)|
+
+Returns the integral part of @id{x} and the fractional part of @id{x}.
+Its second result is always a float.
+
+}
+
+@LibEntry{math.pi|
+
+The value of @M{@pi}.
+
+}
+
+@LibEntry{math.rad (x)|
+
+Converts the angle @id{x} from degrees to radians.
+
+}
+
+@LibEntry{math.random ([m [, n]])|
+
+When called without arguments,
+returns a pseudo-random float with uniform distribution
+in the range @C{(} @M{[0,1)}. @C{]}
+When called with two integers @id{m} and @id{n},
+@id{math.random} returns a pseudo-random integer
+with uniform distribution in the range @M{[m, n]}.
+The call @T{math.random(n)}, for a positive @id{n},
+is equivalent to @T{math.random(1,n)}.
+The call @T{math.random(0)} produces an integer with
+all bits (pseudo)random.
+
+Lua initializes its pseudo-random generator with
+a weak attempt for ``randomness'',
+so that @id{math.random} should generate
+different sequences of results each time the program runs.
+To ensure a required level of randomness to the initial state
+(or contrarily, to have a deterministic sequence,
+for instance when debugging a program),
+you should call @Lid{math.randomseed} explicitly.
+
+The results from this function have good statistical qualities,
+but they are not cryptographically secure.
+(For instance, there are no garanties that it is hard
+to predict future results based on the observation of
+some number of previous results.)
+
+}
+
+@LibEntry{math.randomseed (x [, y])|
+
+Sets @id{x} and @id{y} as the @Q{seed}
+for the pseudo-random generator:
+equal seeds produce equal sequences of numbers.
+The default for @id{y} is zero.
+
+}
+
+@LibEntry{math.sin (x)|
+
+Returns the sine of @id{x} (assumed to be in radians).
+
+}
+
+@LibEntry{math.sqrt (x)|
+
+Returns the square root of @id{x}.
+(You can also use the expression @T{x^0.5} to compute this value.)
+
+}
+
+@LibEntry{math.tan (x)|
+
+Returns the tangent of @id{x} (assumed to be in radians).
+
+}
+
+@LibEntry{math.tointeger (x)|
+
+If the value @id{x} is convertible to an integer,
+returns that integer.
+Otherwise, returns @nil.
+
+}
+
+@LibEntry{math.type (x)|
+
+Returns @St{integer} if @id{x} is an integer,
+@St{float} if it is a float,
+or @nil if @id{x} is not a number.
+
+}
+
+@LibEntry{math.ult (m, n)|
+
+Returns a boolean,
+true if and only if integer @id{m} is below integer @id{n} when
+they are compared as @x{unsigned integers}.
+
+}
+
+}
+
+
+@sect2{iolib| @title{Input and Output Facilities}
+
+The I/O library provides two different styles for file manipulation.
+The first one uses implicit file handles;
+that is, there are operations to set a default input file and a
+default output file,
+and all input/output operations are over these default files.
+The second style uses explicit file handles.
+
+When using implicit file handles,
+all operations are supplied by table @defid{io}.
+When using explicit file handles,
+the operation @Lid{io.open} returns a file handle
+and then all operations are supplied as methods of the file handle.
+
+The table @id{io} also provides
+three predefined file handles with their usual meanings from C:
+@defid{io.stdin}, @defid{io.stdout}, and @defid{io.stderr}.
+The I/O library never closes these files.
+
+Unless otherwise stated,
+all I/O functions return @nil on failure
+(plus an error message as a second result and
+a system-dependent error code as a third result)
+and some value different from @nil on success.
+On non-POSIX systems,
+the computation of the error message and error code
+in case of errors
+may be not @x{thread safe},
+because they rely on the global C variable @id{errno}.
+
+@LibEntry{io.close ([file])|
+
+Equivalent to @T{file:close()}.
+Without a @id{file}, closes the default output file.
+
+}
+
+@LibEntry{io.flush ()|
+
+Equivalent to @T{io.output():flush()}.
+
+}
+
+@LibEntry{io.input ([file])|
+
+When called with a file name, it opens the named file (in text mode),
+and sets its handle as the default input file.
+When called with a file handle,
+it simply sets this file handle as the default input file.
+When called without parameters,
+it returns the current default input file.
+
+In case of errors this function raises the error,
+instead of returning an error code.
+
+}
+
+@LibEntry{io.lines ([filename, @Cdots])|
+
+Opens the given file name in read mode
+and returns an iterator function that
+works like @T{file:lines(@Cdots)} over the opened file.
+When the iterator function detects the end of file,
+it returns no values (to finish the loop) and automatically closes the file.
+
+The call @T{io.lines()} (with no file name) is equivalent
+to @T{io.input():lines("l")};
+that is, it iterates over the lines of the default input file.
+In this case, the iterator does not close the file when the loop ends.
+
+In case of errors this function raises the error,
+instead of returning an error code.
+
+}
+
+@LibEntry{io.open (filename [, mode])|
+
+This function opens a file,
+in the mode specified in the string @id{mode}.
+In case of success,
+it returns a new file handle.
+
+The @id{mode} string can be any of the following:
+@description{
+@item{@St{r}| read mode (the default);}
+@item{@St{w}| write mode;}
+@item{@St{a}| append mode;}
+@item{@St{r+}| update mode, all previous data is preserved;}
+@item{@St{w+}| update mode, all previous data is erased;}
+@item{@St{a+}| append update mode, previous data is preserved,
+ writing is only allowed at the end of file.}
+}
+The @id{mode} string can also have a @Char{b} at the end,
+which is needed in some systems to open the file in binary mode.
+
+}
+
+@LibEntry{io.output ([file])|
+
+Similar to @Lid{io.input}, but operates over the default output file.
+
+}
+
+@LibEntry{io.popen (prog [, mode])|
+
+This function is system dependent and is not available
+on all platforms.
+
+Starts program @id{prog} in a separated process and returns
+a file handle that you can use to read data from this program
+(if @id{mode} is @T{"r"}, the default)
+or to write data to this program
+(if @id{mode} is @T{"w"}).
+
+}
+
+@LibEntry{io.read (@Cdots)|
+
+Equivalent to @T{io.input():read(@Cdots)}.
+
+}
+
+@LibEntry{io.tmpfile ()|
+
+In case of success,
+returns a handle for a temporary file.
+This file is opened in update mode
+and it is automatically removed when the program ends.
+
+}
+
+@LibEntry{io.type (obj)|
+
+Checks whether @id{obj} is a valid file handle.
+Returns the string @T{"file"} if @id{obj} is an open file handle,
+@T{"closed file"} if @id{obj} is a closed file handle,
+or @nil if @id{obj} is not a file handle.
+
+}
+
+@LibEntry{io.write (@Cdots)|
+
+Equivalent to @T{io.output():write(@Cdots)}.
+
+
+}
+
+@LibEntry{file:close ()|
+
+Closes @id{file}.
+Note that files are automatically closed when
+their handles are garbage collected,
+but that takes an unpredictable amount of time to happen.
+
+When closing a file handle created with @Lid{io.popen},
+@Lid{file:close} returns the same values
+returned by @Lid{os.execute}.
+
+}
+
+@LibEntry{file:flush ()|
+
+Saves any written data to @id{file}.
+
+}
+
+@LibEntry{file:lines (@Cdots)|
+
+Returns an iterator function that,
+each time it is called,
+reads the file according to the given formats.
+When no format is given,
+uses @St{l} as a default.
+As an example, the construction
+@verbatim{
+for c in file:lines(1) do @rep{body} end
+}
+will iterate over all characters of the file,
+starting at the current position.
+Unlike @Lid{io.lines}, this function does not close the file
+when the loop ends.
+
+In case of errors this function raises the error,
+instead of returning an error code.
+
+}
+
+@LibEntry{file:read (@Cdots)|
+
+Reads the file @id{file},
+according to the given formats, which specify what to read.
+For each format,
+the function returns a string or a number with the characters read,
+or @nil if it cannot read data with the specified format.
+(In this latter case,
+the function does not read subsequent formats.)
+When called without parameters,
+it uses a default format that reads the next line
+(see below).
+
+The available formats are
+@description{
+
+@item{@St{n}|
+reads a numeral and returns it as a float or an integer,
+following the lexical conventions of Lua.
+(The numeral may have leading spaces and a sign.)
+This format always reads the longest input sequence that
+is a valid prefix for a numeral;
+if that prefix does not form a valid numeral
+(e.g., an empty string, @St{0x}, or @St{3.4e-}),
+it is discarded and the format returns @nil.
+}
+
+@item{@St{a}|
+reads the whole file, starting at the current position.
+On end of file, it returns the empty string.
+}
+
+@item{@St{l}|
+reads the next line skipping the end of line,
+returning @nil on end of file.
+This is the default format.
+}
+
+@item{@St{L}|
+reads the next line keeping the end-of-line character (if present),
+returning @nil on end of file.
+}
+
+@item{@emph{number}|
+reads a string with up to this number of bytes,
+returning @nil on end of file.
+If @id{number} is zero,
+it reads nothing and returns an empty string,
+or @nil on end of file.
+}
+
+}
+The formats @St{l} and @St{L} should be used only for text files.
+
+}
+
+@LibEntry{file:seek ([whence [, offset]])|
+
+Sets and gets the file position,
+measured from the beginning of the file,
+to the position given by @id{offset} plus a base
+specified by the string @id{whence}, as follows:
+@description{
+@item{@St{set}| base is position 0 (beginning of the file);}
+@item{@St{cur}| base is current position;}
+@item{@St{end}| base is end of file;}
+}
+In case of success, @id{seek} returns the final file position,
+measured in bytes from the beginning of the file.
+If @id{seek} fails, it returns @nil,
+plus a string describing the error.
+
+The default value for @id{whence} is @T{"cur"},
+and for @id{offset} is 0.
+Therefore, the call @T{file:seek()} returns the current
+file position, without changing it;
+the call @T{file:seek("set")} sets the position to the
+beginning of the file (and returns 0);
+and the call @T{file:seek("end")} sets the position to the
+end of the file, and returns its size.
+
+}
+
+@LibEntry{file:setvbuf (mode [, size])|
+
+Sets the buffering mode for an output file.
+There are three available modes:
+@description{
+
+@item{@St{no}|
+no buffering; the result of any output operation appears immediately.
+}
+
+@item{@St{full}|
+full buffering; output operation is performed only
+when the buffer is full or when
+you explicitly @T{flush} the file @seeF{io.flush}.
+}
+
+@item{@St{line}|
+line buffering; output is buffered until a newline is output
+or there is any input from some special files
+(such as a terminal device).
+}
+
+}
+For the last two cases, @id{size}
+specifies the size of the buffer, in bytes.
+The default is an appropriate size.
+
+}
+
+@LibEntry{file:write (@Cdots)|
+
+Writes the value of each of its arguments to @id{file}.
+The arguments must be strings or numbers.
+
+In case of success, this function returns @id{file}.
+Otherwise it returns @nil plus a string describing the error.
+
+}
+
+}
+
+@sect2{oslib| @title{Operating System Facilities}
+
+This library is implemented through table @defid{os}.
+
+
+@LibEntry{os.clock ()|
+
+Returns an approximation of the amount in seconds of CPU time
+used by the program.
+
+}
+
+@LibEntry{os.date ([format [, time]])|
+
+Returns a string or a table containing date and time,
+formatted according to the given string @id{format}.
+
+If the @id{time} argument is present,
+this is the time to be formatted
+(see the @Lid{os.time} function for a description of this value).
+Otherwise, @id{date} formats the current time.
+
+If @id{format} starts with @Char{!},
+then the date is formatted in Coordinated Universal Time.
+After this optional character,
+if @id{format} is the string @St{*t},
+then @id{date} returns a table with the following fields:
+@id{year}, @id{month} (1@En{}12), @id{day} (1@En{}31),
+@id{hour} (0@En{}23), @id{min} (0@En{}59),
+@id{sec} (0@En{}61, due to leap seconds),
+@id{wday} (weekday, 1@En{}7, Sunday @N{is 1}),
+@id{yday} (day of the year, 1@En{}366),
+and @id{isdst} (daylight saving flag, a boolean).
+This last field may be absent
+if the information is not available.
+
+If @id{format} is not @St{*t},
+then @id{date} returns the date as a string,
+formatted according to the same rules as the @ANSI{strftime}.
+
+When called without arguments,
+@id{date} returns a reasonable date and time representation that depends on
+the host system and on the current locale.
+(More specifically, @T{os.date()} is equivalent to @T{os.date("%c")}.)
+
+On non-POSIX systems,
+this function may be not @x{thread safe}
+because of its reliance on @CId{gmtime} and @CId{localtime}.
+
+}
+
+@LibEntry{os.difftime (t2, t1)|
+
+Returns the difference, in seconds,
+from time @id{t1} to time @id{t2}
+(where the times are values returned by @Lid{os.time}).
+In @x{POSIX}, @x{Windows}, and some other systems,
+this value is exactly @id{t2}@M{-}@id{t1}.
+
+}
+
+@LibEntry{os.execute ([command])|
+
+This function is equivalent to the @ANSI{system}.
+It passes @id{command} to be executed by an operating system shell.
+Its first result is @true
+if the command terminated successfully,
+or @nil otherwise.
+After this first result
+the function returns a string plus a number,
+as follows:
+@description{
+
+@item{@St{exit}|
+the command terminated normally;
+the following number is the exit status of the command.
+}
+
+@item{@St{signal}|
+the command was terminated by a signal;
+the following number is the signal that terminated the command.
+}
+
+}
+
+When called without a @id{command},
+@id{os.execute} returns a boolean that is true if a shell is available.
+
+}
+
+@LibEntry{os.exit ([code [, close]])|
+
+Calls the @ANSI{exit} to terminate the host program.
+If @id{code} is @Rw{true},
+the returned status is @idx{EXIT_SUCCESS};
+if @id{code} is @Rw{false},
+the returned status is @idx{EXIT_FAILURE};
+if @id{code} is a number,
+the returned status is this number.
+The default value for @id{code} is @Rw{true}.
+
+If the optional second argument @id{close} is true,
+closes the Lua state before exiting.
+
+}
+
+@LibEntry{os.getenv (varname)|
+
+Returns the value of the process environment variable @id{varname},
+or @nil if the variable is not defined.
+
+}
+
+@LibEntry{os.remove (filename)|
+
+Deletes the file (or empty directory, on @x{POSIX} systems)
+with the given name.
+If this function fails, it returns @nil,
+plus a string describing the error and the error code.
+Otherwise, it returns true.
+
+}
+
+@LibEntry{os.rename (oldname, newname)|
+
+Renames the file or directory named @id{oldname} to @id{newname}.
+If this function fails, it returns @nil,
+plus a string describing the error and the error code.
+Otherwise, it returns true.
+
+}
+
+@LibEntry{os.setlocale (locale [, category])|
+
+Sets the current locale of the program.
+@id{locale} is a system-dependent string specifying a locale;
+@id{category} is an optional string describing which category to change:
+@T{"all"}, @T{"collate"}, @T{"ctype"},
+@T{"monetary"}, @T{"numeric"}, or @T{"time"};
+the default category is @T{"all"}.
+The function returns the name of the new locale,
+or @nil if the request cannot be honored.
+
+If @id{locale} is the empty string,
+the current locale is set to an implementation-defined native locale.
+If @id{locale} is the string @St{C},
+the current locale is set to the standard C locale.
+
+When called with @nil as the first argument,
+this function only returns the name of the current locale
+for the given category.
+
+This function may be not @x{thread safe}
+because of its reliance on @CId{setlocale}.
+
+}
+
+@LibEntry{os.time ([table])|
+
+Returns the current time when called without arguments,
+or a time representing the local date and time specified by the given table.
+This table must have fields @id{year}, @id{month}, and @id{day},
+and may have fields
+@id{hour} (default is 12),
+@id{min} (default is 0),
+@id{sec} (default is 0),
+and @id{isdst} (default is @nil).
+Other fields are ignored.
+For a description of these fields, see the @Lid{os.date} function.
+
+When the function is called,
+the values in these fields do not need to be inside their valid ranges.
+For instance, if @id{sec} is -10,
+it means 10 seconds before the time specified by the other fields;
+if @id{hour} is 1000,
+it means 1000 hours after the time specified by the other fields.
+
+The returned value is a number, whose meaning depends on your system.
+In @x{POSIX}, @x{Windows}, and some other systems,
+this number counts the number
+of seconds since some given start time (the @Q{epoch}).
+In other systems, the meaning is not specified,
+and the number returned by @id{time} can be used only as an argument to
+@Lid{os.date} and @Lid{os.difftime}.
+
+When called with a table,
+@id{os.time} also normalizes all the fields
+documented in the @Lid{os.date} function,
+so that they represent the same time as before the call
+but with values inside their valid ranges.
+
+}
+
+@LibEntry{os.tmpname ()|
+
+Returns a string with a file name that can
+be used for a temporary file.
+The file must be explicitly opened before its use
+and explicitly removed when no longer needed.
+
+In @x{POSIX} systems,
+this function also creates a file with that name,
+to avoid security risks.
+(Someone else might create the file with wrong permissions
+in the time between getting the name and creating the file.)
+You still have to open the file to use it
+and to remove it (even if you do not use it).
+
+When possible,
+you may prefer to use @Lid{io.tmpfile},
+which automatically removes the file when the program ends.
+
+}
+
+}
+
+@sect2{debuglib| @title{The Debug Library}
+
+This library provides
+the functionality of the @link{debugI|debug interface} to Lua programs.
+You should exert care when using this library.
+Several of its functions
+violate basic assumptions about Lua code
+(e.g., that variables local to a function
+cannot be accessed from outside;
+that userdata metatables cannot be changed by Lua code;
+that Lua programs do not crash)
+and therefore can compromise otherwise secure code.
+Moreover, some functions in this library may be slow.
+
+All functions in this library are provided
+inside the @defid{debug} table.
+All functions that operate over a thread
+have an optional first argument which is the
+thread to operate over.
+The default is always the current thread.
+
+
+@LibEntry{debug.debug ()|
+
+Enters an interactive mode with the user,
+running each string that the user enters.
+Using simple commands and other debug facilities,
+the user can inspect global and local variables,
+change their values, evaluate expressions, and so on.
+A line containing only the word @id{cont} finishes this function,
+so that the caller continues its execution.
+
+Note that commands for @id{debug.debug} are not lexically nested
+within any function and so have no direct access to local variables.
+
+}
+
+@LibEntry{debug.gethook ([thread])|
+
+Returns the current hook settings of the thread, as three values:
+the current hook function, the current hook mask,
+and the current hook count
+(as set by the @Lid{debug.sethook} function).
+
+}
+
+@LibEntry{debug.getinfo ([thread,] f [, what])|
+
+Returns a table with information about a function.
+You can give the function directly
+or you can give a number as the value of @id{f},
+which means the function running at level @id{f} of the call stack
+of the given thread:
+@N{level 0} is the current function (@id{getinfo} itself);
+@N{level 1} is the function that called @id{getinfo}
+(except for tail calls, which do not count on the stack);
+and so on.
+If @id{f} is a number larger than the number of active functions,
+then @id{getinfo} returns @nil.
+
+The returned table can contain all the fields returned by @Lid{lua_getinfo},
+with the string @id{what} describing which fields to fill in.
+The default for @id{what} is to get all information available,
+except the table of valid lines.
+If present,
+the option @Char{f}
+adds a field named @id{func} with the function itself.
+If present,
+the option @Char{L}
+adds a field named @id{activelines} with the table of
+valid lines.
+
+For instance, the expression @T{debug.getinfo(1,"n").name} returns
+a name for the current function,
+if a reasonable name can be found,
+and the expression @T{debug.getinfo(print)}
+returns a table with all available information
+about the @Lid{print} function.
+
+}
+
+@LibEntry{debug.getlocal ([thread,] f, local)|
+
+This function returns the name and the value of the local variable
+with index @id{local} of the function at level @id{f} of the stack.
+This function accesses not only explicit local variables,
+but also parameters, temporaries, etc.
+
+The first parameter or local variable has @N{index 1}, and so on,
+following the order that they are declared in the code,
+counting only the variables that are active
+in the current scope of the function.
+Negative indices refer to vararg parameters;
+@num{-1} is the first vararg parameter.
+The function returns @nil if there is no variable with the given index,
+and raises an error when called with a level out of range.
+(You can call @Lid{debug.getinfo} to check whether the level is valid.)
+
+Variable names starting with @Char{(} (open parenthesis) @C{)}
+represent variables with no known names
+(internal variables such as loop control variables,
+and variables from chunks saved without debug information).
+
+The parameter @id{f} may also be a function.
+In that case, @id{getlocal} returns only the name of function parameters.
+
+}
+
+@LibEntry{debug.getmetatable (value)|
+
+Returns the metatable of the given @id{value}
+or @nil if it does not have a metatable.
+
+}
+
+@LibEntry{debug.getregistry ()|
+
+Returns the registry table @see{registry}.
+
+}
+
+@LibEntry{debug.getupvalue (f, up)|
+
+This function returns the name and the value of the upvalue
+with index @id{up} of the function @id{f}.
+The function returns @nil if there is no upvalue with the given index.
+
+Variable names starting with @Char{(} (open parenthesis) @C{)}
+represent variables with no known names
+(variables from chunks saved without debug information).
+
+}
+
+@LibEntry{debug.getuservalue (u, n)|
+
+Returns the @id{n}-th user value associated
+to the userdata @id{u} plus a boolean,
+@false if the userdata does not have that value.
+
+}
+
+@LibEntry{debug.sethook ([thread,] hook, mask [, count])|
+
+Sets the given function as a hook.
+The string @id{mask} and the number @id{count} describe
+when the hook will be called.
+The string mask may have any combination of the following characters,
+with the given meaning:
+@description{
+@item{@Char{c}| the hook is called every time Lua calls a function;}
+@item{@Char{r}| the hook is called every time Lua returns from a function;}
+@item{@Char{l}| the hook is called every time Lua enters a new line of code.}
+}
+Moreover,
+with a @id{count} different from zero,
+the hook is called also after every @id{count} instructions.
+
+When called without arguments,
+@Lid{debug.sethook} turns off the hook.
+
+When the hook is called, its first parameter is a string
+describing the event that has triggered its call:
+@T{"call"} (or @T{"tail call"}),
+@T{"return"},
+@T{"line"}, and @T{"count"}.
+For line events,
+the hook also gets the new line number as its second parameter.
+Inside a hook,
+you can call @id{getinfo} with @N{level 2} to get more information about
+the running function
+(@N{level 0} is the @id{getinfo} function,
+and @N{level 1} is the hook function).
+
+}
+
+@LibEntry{debug.setlocal ([thread,] level, local, value)|
+
+This function assigns the value @id{value} to the local variable
+with index @id{local} of the function at level @id{level} of the stack.
+The function returns @nil if there is no local
+variable with the given index,
+and raises an error when called with a @id{level} out of range.
+(You can call @id{getinfo} to check whether the level is valid.)
+Otherwise, it returns the name of the local variable.
+
+See @Lid{debug.getlocal} for more information about
+variable indices and names.
+
+}
+
+@LibEntry{debug.setmetatable (value, table)|
+
+Sets the metatable for the given @id{value} to the given @id{table}
+(which can be @nil).
+Returns @id{value}.
+
+}
+
+@LibEntry{debug.setupvalue (f, up, value)|
+
+This function assigns the value @id{value} to the upvalue
+with index @id{up} of the function @id{f}.
+The function returns @nil if there is no upvalue
+with the given index.
+Otherwise, it returns the name of the upvalue.
+
+}
+
+@LibEntry{debug.setuservalue (udata, value, n)|
+
+Sets the given @id{value} as
+the @id{n}-th user value associated to the given @id{udata}.
+@id{udata} must be a full userdata.
+
+Returns @id{udata},
+or @nil if the userdata does not have that value.
+
+}
+
+@LibEntry{debug.traceback ([thread,] [message [, level]])|
+
+If @id{message} is present but is neither a string nor @nil,
+this function returns @id{message} without further processing.
+Otherwise,
+it returns a string with a traceback of the call stack.
+The optional @id{message} string is appended
+at the beginning of the traceback.
+An optional @id{level} number tells at which level
+to start the traceback
+(default is 1, the function calling @id{traceback}).
+
+}
+
+@LibEntry{debug.upvalueid (f, n)|
+
+Returns a unique identifier (as a light userdata)
+for the upvalue numbered @id{n}
+from the given function.
+
+These unique identifiers allow a program to check whether different
+closures share upvalues.
+Lua closures that share an upvalue
+(that is, that access a same external local variable)
+will return identical ids for those upvalue indices.
+
+}
+
+@LibEntry{debug.upvaluejoin (f1, n1, f2, n2)|
+
+Make the @id{n1}-th upvalue of the Lua closure @id{f1}
+refer to the @id{n2}-th upvalue of the Lua closure @id{f2}.
+
+}
+
+}
+
+}
+
+
+@C{-------------------------------------------------------------------------}
+@sect1{lua-sa| @title{Lua Standalone}
+
+Although Lua has been designed as an extension language,
+to be embedded in a host @N{C program},
+it is also frequently used as a standalone language.
+An interpreter for Lua as a standalone language,
+called simply @id{lua},
+is provided with the standard distribution.
+The @x{standalone interpreter} includes
+all standard libraries, including the debug library.
+Its usage is:
+@verbatim{
+lua [options] [script [args]]
+}
+The options are:
+@description{
+@item{@T{-e @rep{stat}}| executes string @rep{stat};}
+@item{@T{-l @rep{mod}}| @Q{requires} @rep{mod} and assigns the
+ result to global @rep{mod};}
+@item{@T{-i}| enters interactive mode after running @rep{script};}
+@item{@T{-v}| prints version information;}
+@item{@T{-E}| ignores environment variables;}
+@item{@T{--}| stops handling options;}
+@item{@T{-}| executes @id{stdin} as a file and stops handling options.}
+}
+After handling its options, @id{lua} runs the given @emph{script}.
+When called without arguments,
+@id{lua} behaves as @T{lua -v -i}
+when the standard input (@id{stdin}) is a terminal,
+and as @T{lua -} otherwise.
+
+When called without option @T{-E},
+the interpreter checks for an environment variable @defid{LUA_INIT_5_4}
+(or @defid{LUA_INIT} if the versioned name is not defined)
+before running any argument.
+If the variable content has the format @T{@At@rep{filename}},
+then @id{lua} executes the file.
+Otherwise, @id{lua} executes the string itself.
+
+When called with option @T{-E},
+besides ignoring @id{LUA_INIT},
+Lua also ignores
+the values of @id{LUA_PATH} and @id{LUA_CPATH},
+setting the values of
+@Lid{package.path} and @Lid{package.cpath}
+with the default paths defined in @id{luaconf.h}.
+
+All options are handled in order, except @T{-i} and @T{-E}.
+For instance, an invocation like
+@verbatim{
+$ lua -e'a=1' -e 'print(a)' script.lua
+}
+will first set @id{a} to 1, then print the value of @id{a},
+and finally run the file @id{script.lua} with no arguments.
+(Here @T{$} is the shell prompt. Your prompt may be different.)
+
+Before running any code,
+@id{lua} collects all command-line arguments
+in a global table called @id{arg}.
+The script name goes to index 0,
+the first argument after the script name goes to index 1,
+and so on.
+Any arguments before the script name
+(that is, the interpreter name plus its options)
+go to negative indices.
+For instance, in the call
+@verbatim{
+$ lua -la b.lua t1 t2
+}
+the table is like this:
+@verbatim{
+arg = { [-2] = "lua", [-1] = "-la",
+ [0] = "b.lua",
+ [1] = "t1", [2] = "t2" }
+}
+If there is no script in the call,
+the interpreter name goes to index 0,
+followed by the other arguments.
+For instance, the call
+@verbatim{
+$ lua -e "print(arg[1])"
+}
+will print @St{-e}.
+If there is a script,
+the script is called with parameters
+@T{arg[1]}, @Cdots, @T{arg[#arg]}.
+(Like all chunks in Lua,
+the script is compiled as a vararg function.)
+
+In interactive mode,
+Lua repeatedly prompts and waits for a line.
+After reading a line,
+Lua first try to interpret the line as an expression.
+If it succeeds, it prints its value.
+Otherwise, it interprets the line as a statement.
+If you write an incomplete statement,
+the interpreter waits for its completion
+by issuing a different prompt.
+
+If the global variable @defid{_PROMPT} contains a string,
+then its value is used as the prompt.
+Similarly, if the global variable @defid{_PROMPT2} contains a string,
+its value is used as the secondary prompt
+(issued during incomplete statements).
+
+In case of unprotected errors in the script,
+the interpreter reports the error to the standard error stream.
+If the error object is not a string but
+has a metamethod @idx{__tostring},
+the interpreter calls this metamethod to produce the final message.
+Otherwise, the interpreter converts the error object to a string
+and adds a stack traceback to it.
+
+When finishing normally,
+the interpreter closes its main Lua state
+@seeF{lua_close}.
+The script can avoid this step by
+calling @Lid{os.exit} to terminate.
+
+To allow the use of Lua as a
+script interpreter in Unix systems,
+the standalone interpreter skips
+the first line of a chunk if it starts with @T{#}.
+Therefore, Lua scripts can be made into executable programs
+by using @T{chmod +x} and @N{the @T{#!}} form,
+as in
+@verbatim{
+#!/usr/local/bin/lua
+}
+(Of course,
+the location of the Lua interpreter may be different in your machine.
+If @id{lua} is in your @id{PATH},
+then
+@verbatim{
+#!/usr/bin/env lua
+}
+is a more portable solution.)
+
+}
+
+
+@sect1{incompat| @title{Incompatibilities with the Previous Version}
+
+Here we list the incompatibilities that you may find when moving a program
+from @N{Lua 5.3} to @N{Lua 5.4}.
+You can avoid some incompatibilities by compiling Lua with
+appropriate options (see file @id{luaconf.h}).
+However,
+all these compatibility options will be removed in the future.
+
+Lua versions can always change the C API in ways that
+do not imply source-code changes in a program,
+such as the numeric values for constants
+or the implementation of functions as macros.
+Therefore,
+you should not assume that binaries are compatible between
+different Lua versions.
+Always recompile clients of the Lua API when
+using a new version.
+
+Similarly, Lua versions can always change the internal representation
+of precompiled chunks;
+precompiled chunks are not compatible between different Lua versions.
+
+The standard paths in the official distribution may
+change between versions.
+
+@sect2{@title{Changes in the Language}
+@itemize{
+
+@item{
+The coercion of strings to numbers in
+arithmetic and bitwise operations
+has been removed from the core language.
+The string library does a similar job
+for arithmetic (but not for bitwise) operations
+using the string metamethods.
+However, unlike in previous versions,
+the new implementation preserves the implicit type of the numeral
+in the string.
+For instance, the result of @T{"1" + "2"} now is an integer,
+not a float.
+}
+
+}
+
+}
+
+@sect2{@title{Changes in the Libraries}
+@itemize{
+
+@item{
+The pseudo-random number generator used by the function @Lid{math.random}
+now starts with a somewhat random seed.
+Moreover, it uses a different algorithm.
+}
+
+}
+
+}
+
+@sect2{@title{Changes in the API}
+
+@itemize{
+
+@item{
+Full userdata now has an arbitrary number of associated user values.
+Therefore, the functions @id{lua_newuserdata},
+@id{lua_setuservalue}, and @id{lua_getuservalue} were
+replaced by @Lid{lua_newuserdatauv},
+@Lid{lua_setiuservalue}, and @Lid{lua_getiuservalue},
+which have an extra argument.
+
+(For compatibility, the old names still work as macros assuming
+one single user value.)
+}
+
+@item{
+The function @Lid{lua_resume} has an extra parameter.
+This out parameter returns the number of values on
+the top of the stack that were yielded or returned by the coroutine.
+(In older versions,
+those values were the entire stack.)
+}
+
+@item{
+The function @Lid{lua_version} returns the version number,
+instead of an address of the version number.
+(The Lua core should work correctly with libraries using their
+own static copies of the same core,
+so there is no need to check whether they are using the same
+address space.)
+}
+
+}
+
+}
+
+}
+
+
+@C{[===============================================================}
+
+@sect1{BNF| @title{The Complete Syntax of Lua}
+
+Here is the complete syntax of Lua in extended BNF.
+As usual in extended BNF,
+@bnfNter{{A}} means 0 or more @bnfNter{A}s,
+and @bnfNter{[A]} means an optional @bnfNter{A}.
+(For operator precedences, see @See{prec};
+for a description of the terminals
+@bnfNter{Name}, @bnfNter{Numeral},
+and @bnfNter{LiteralString}, see @See{lexical}.)
+@index{grammar}
+
+@Produc{
+
+@producname{chunk}@producbody{block}
+
+@producname{block}@producbody{@bnfrep{stat} @bnfopt{retstat}}
+
+@producname{stat}@producbody{
+ @bnfter{;}
+@OrNL varlist @bnfter{=} explist
+@OrNL functioncall
+@OrNL label
+@OrNL @Rw{break}
+@OrNL @Rw{goto} Name
+@OrNL @Rw{do} block @Rw{end}
+@OrNL @Rw{while} exp @Rw{do} block @Rw{end}
+@OrNL @Rw{repeat} block @Rw{until} exp
+@OrNL @Rw{if} exp @Rw{then} block
+ @bnfrep{@Rw{elseif} exp @Rw{then} block}
+ @bnfopt{@Rw{else} block} @Rw{end}
+@OrNL @Rw{for} @bnfNter{Name} @bnfter{=} exp @bnfter{,} exp @bnfopt{@bnfter{,} exp}
+ @Rw{do} block @Rw{end}
+@OrNL @Rw{for} namelist @Rw{in} explist @Rw{do} block @Rw{end}
+@OrNL @Rw{function} funcname funcbody
+@OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody
+@OrNL @Rw{local} namelist @bnfopt{@bnfter{=} explist}
+}
+
+@producname{retstat}@producbody{@Rw{return}
+ @bnfopt{explist} @bnfopt{@bnfter{;}}}
+
+@producname{label}@producbody{@bnfter{::} Name @bnfter{::}}
+
+@producname{funcname}@producbody{@bnfNter{Name} @bnfrep{@bnfter{.} @bnfNter{Name}}
+ @bnfopt{@bnfter{:} @bnfNter{Name}}}
+
+@producname{varlist}@producbody{var @bnfrep{@bnfter{,} var}}
+
+@producname{var}@producbody{
+ @bnfNter{Name}
+@Or prefixexp @bnfter{[} exp @bnfter{]}
+@Or prefixexp @bnfter{.} @bnfNter{Name}
+}
+
+@producname{namelist}@producbody{@bnfNter{Name} @bnfrep{@bnfter{,} @bnfNter{Name}}}
+
+
+@producname{explist}@producbody{exp @bnfrep{@bnfter{,} exp}}
+
+@producname{exp}@producbody{
+ @Rw{nil}
+@Or @Rw{false}
+@Or @Rw{true}
+@Or @bnfNter{Numeral}
+@Or @bnfNter{LiteralString}
+@Or @bnfter{...}
+@Or functiondef
+@OrNL prefixexp
+@Or tableconstructor
+@Or exp binop exp
+@Or unop exp
+}
+
+@producname{prefixexp}@producbody{var @Or functioncall @Or @bnfter{(} exp @bnfter{)}}
+
+@producname{functioncall}@producbody{
+ prefixexp args
+@Or prefixexp @bnfter{:} @bnfNter{Name} args
+}
+
+@producname{args}@producbody{
+ @bnfter{(} @bnfopt{explist} @bnfter{)}
+@Or tableconstructor
+@Or @bnfNter{LiteralString}
+}
+
+@producname{functiondef}@producbody{@Rw{function} funcbody}
+
+@producname{funcbody}@producbody{@bnfter{(} @bnfopt{parlist} @bnfter{)} block @Rw{end}}
+
+@producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} @bnfter{...}}
+ @Or @bnfter{...}}
+
+@producname{tableconstructor}@producbody{@bnfter{@Open} @bnfopt{fieldlist} @bnfter{@Close}}
+
+@producname{fieldlist}@producbody{field @bnfrep{fieldsep field} @bnfopt{fieldsep}}
+
+@producname{field}@producbody{@bnfter{[} exp @bnfter{]} @bnfter{=} exp @Or @bnfNter{Name} @bnfter{=} exp @Or exp}
+
+@producname{fieldsep}@producbody{@bnfter{,} @Or @bnfter{;}}
+
+@producname{binop}@producbody{
+ @bnfter{+} @Or @bnfter{-} @Or @bnfter{*} @Or @bnfter{/} @Or @bnfter{//}
+ @Or @bnfter{^} @Or @bnfter{%}
+ @OrNL
+ @bnfter{&} @Or @bnfter{~} @Or @bnfter{|} @Or @bnfter{>>} @Or @bnfter{<<}
+ @Or @bnfter{..}
+ @OrNL
+ @bnfter{<} @Or @bnfter{<=} @Or @bnfter{>} @Or @bnfter{>=}
+ @Or @bnfter{==} @Or @bnfter{~=}
+ @OrNL
+ @Rw{and} @Or @Rw{or}}
+
+@producname{unop}@producbody{@bnfter{-} @Or @Rw{not} @Or @bnfter{#} @Or
+ @bnfter{~}}
+
+}
+
+}
+
+@C{]===============================================================}
+
+}
+@C{)]-------------------------------------------------------------------------}
diff --git a/testes/all.lua b/testes/all.lua
new file mode 100755
index 0000000000..cfe2160397
--- /dev/null
+++ b/testes/all.lua
@@ -0,0 +1,294 @@
+#!../lua
+-- $Id: all.lua,v 1.100 2018/03/09 14:23:48 roberto Exp $
+-- See Copyright Notice at the end of this file
+
+
+local version = "Lua 5.4"
+if _VERSION ~= version then
+ io.stderr:write("\nThis test suite is for ", version, ", not for ", _VERSION,
+ "\nExiting tests\n")
+ return
+end
+
+
+_G.ARG = arg -- save arg for other tests
+
+
+-- next variables control the execution of some tests
+-- true means no test (so an undefined variable does not skip a test)
+-- defaults are for Linux; test everything.
+-- Make true to avoid long or memory consuming tests
+_soft = rawget(_G, "_soft") or false
+-- Make true to avoid non-portable tests
+_port = rawget(_G, "_port") or false
+-- Make true to avoid messages about tests not performed
+_nomsg = rawget(_G, "_nomsg") or false
+
+
+local usertests = rawget(_G, "_U")
+
+if usertests then
+ -- tests for sissies ;) Avoid problems
+ _soft = true
+ _port = true
+ _nomsg = true
+end
+
+-- tests should require debug when needed
+debug = nil
+
+require"bwcoercion"
+
+
+if usertests then
+ T = nil -- no "internal" tests for user tests
+else
+ T = rawget(_G, "T") -- avoid problems with 'strict' module
+end
+
+math.randomseed(0)
+
+--[=[
+ example of a long [comment],
+ [[spanning several [lines]]]
+
+]=]
+
+print("current path:\n****" .. package.path .. "****\n")
+
+
+local initclock = os.clock()
+local lastclock = initclock
+local walltime = os.time()
+
+local collectgarbage = collectgarbage
+
+do -- (
+
+-- track messages for tests not performed
+local msgs = {}
+function Message (m)
+ if not _nomsg then
+ print(m)
+ msgs[#msgs+1] = string.sub(m, 3, -3)
+ end
+end
+
+assert(os.setlocale"C")
+
+local T,print,format,write,assert,type,unpack,floor =
+ T,print,string.format,io.write,assert,type,table.unpack,math.floor
+
+-- use K for 1000 and M for 1000000 (not 2^10 -- 2^20)
+local function F (m)
+ local function round (m)
+ m = m + 0.04999
+ return format("%.1f", m) -- keep one decimal digit
+ end
+ if m < 1000 then return m
+ else
+ m = m / 1000
+ if m < 1000 then return round(m).."K"
+ else
+ return round(m/1000).."M"
+ end
+ end
+end
+
+local showmem
+if not T then
+ local max = 0
+ showmem = function ()
+ local m = collectgarbage("count") * 1024
+ max = (m > max) and m or max
+ print(format(" ---- total memory: %s, max memory: %s ----\n",
+ F(m), F(max)))
+ end
+else
+ showmem = function ()
+ T.checkmemory()
+ local total, numblocks, maxmem = T.totalmem()
+ local count = collectgarbage("count")
+ print(format(
+ "\n ---- total memory: %s (%.0fK), max use: %s, blocks: %d\n",
+ F(total), count, F(maxmem), numblocks))
+ print(format("\t(strings: %d, tables: %d, functions: %d, "..
+ "\n\tudata: %d, threads: %d)",
+ T.totalmem"string", T.totalmem"table", T.totalmem"function",
+ T.totalmem"userdata", T.totalmem"thread"))
+ end
+end
+
+
+--
+-- redefine dofile to run files through dump/undump
+--
+local function report (n) print("\n***** FILE '"..n.."'*****") end
+local olddofile = dofile
+local dofile = function (n, strip)
+ showmem()
+ local c = os.clock()
+ print(string.format("time: %g (+%g)", c - initclock, c - lastclock))
+ lastclock = c
+ report(n)
+ local f = assert(loadfile(n))
+ local b = string.dump(f, strip)
+ f = assert(load(b))
+ return f()
+end
+
+dofile('main.lua')
+
+do
+ local next, setmetatable, stderr = next, setmetatable, io.stderr
+ -- track collections
+ local mt = {}
+ -- each time a table is collected, remark it for finalization
+ -- on next cycle
+ mt.__gc = function (o)
+ stderr:write'.' -- mark progress
+ local n = setmetatable(o, mt) -- remark it
+ end
+ local n = setmetatable({}, mt) -- create object
+end
+
+report"gc.lua"
+local f = assert(loadfile('gc.lua'))
+f()
+
+dofile('db.lua')
+assert(dofile('calls.lua') == deep and deep)
+olddofile('strings.lua')
+olddofile('literals.lua')
+dofile('tpack.lua')
+assert(dofile('attrib.lua') == 27)
+
+assert(dofile('locals.lua') == 5)
+dofile('constructs.lua')
+dofile('code.lua', true)
+if not _G._soft then
+ report('big.lua')
+ local f = coroutine.wrap(assert(loadfile('big.lua')))
+ assert(f() == 'b')
+ assert(f() == 'a')
+end
+dofile('nextvar.lua')
+dofile('pm.lua')
+dofile('utf8.lua')
+dofile('api.lua')
+assert(dofile('events.lua') == 12)
+dofile('vararg.lua')
+dofile('closure.lua')
+dofile('coroutine.lua')
+dofile('goto.lua', true)
+dofile('errors.lua')
+dofile('math.lua')
+dofile('sort.lua', true)
+dofile('bitwise.lua')
+assert(dofile('verybig.lua', true) == 10); collectgarbage()
+dofile('files.lua')
+
+if #msgs > 0 then
+ print("\ntests not performed:")
+ for i=1,#msgs do
+ print(msgs[i])
+ end
+ print()
+end
+
+-- no test module should define 'debug'
+assert(debug == nil)
+
+local debug = require "debug"
+
+print(string.format("%d-bit integers, %d-bit floats",
+ string.packsize("j") * 8, string.packsize("n") * 8))
+
+debug.sethook(function (a) assert(type(a) == 'string') end, "cr")
+
+-- to survive outside block
+_G.showmem = showmem
+
+end --)
+
+local _G, showmem, print, format, clock, time, difftime, assert, open =
+ _G, showmem, print, string.format, os.clock, os.time, os.difftime,
+ assert, io.open
+
+-- file with time of last performed test
+local fname = T and "time-debug.txt" or "time.txt"
+local lasttime
+
+if not usertests then
+ -- open file with time of last performed test
+ local f = io.open(fname)
+ if f then
+ lasttime = assert(tonumber(f:read'a'))
+ f:close();
+ else -- no such file; assume it is recording time for first time
+ lasttime = nil
+ end
+end
+
+-- erase (almost) all globals
+print('cleaning all!!!!')
+for n in pairs(_G) do
+ if not ({___Glob = 1, tostring = 1})[n] then
+ _G[n] = undef
+ end
+end
+
+
+collectgarbage()
+collectgarbage()
+collectgarbage()
+collectgarbage()
+collectgarbage()
+collectgarbage();showmem()
+
+local clocktime = clock() - initclock
+walltime = difftime(time(), walltime)
+
+print(format("\n\ntotal time: %.2fs (wall time: %gs)\n", clocktime, walltime))
+
+if not usertests then
+ lasttime = lasttime or clocktime -- if no last time, ignore difference
+ -- check whether current test time differs more than 5% from last time
+ local diff = (clocktime - lasttime) / lasttime
+ local tolerance = 0.05 -- 5%
+ if (diff >= tolerance or diff <= -tolerance) then
+ print(format("WARNING: time difference from previous test: %+.1f%%",
+ diff * 100))
+ end
+ assert(open(fname, "w")):write(clocktime):close()
+end
+
+print("final OK !!!")
+
+
+
+--[[
+*****************************************************************************
+* Copyright (C) 1994-2016 Lua.org, PUC-Rio.
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*****************************************************************************
+]]
+
diff --git a/testes/api.lua b/testes/api.lua
new file mode 100644
index 0000000000..836a6070a3
--- /dev/null
+++ b/testes/api.lua
@@ -0,0 +1,1264 @@
+-- $Id: api.lua,v 1.155 2018/03/09 14:23:48 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+if T==nil then
+ (Message or print)('\n >>> testC not active: skipping API tests <<<\n')
+ return
+end
+
+local debug = require "debug"
+
+local pack = table.pack
+
+
+function tcheck (t1, t2)
+ assert(t1.n == (t2.n or #t2) + 1)
+ for i = 2, t1.n do assert(t1[i] == t2[i - 1]) end
+end
+
+
+local function checkerr (msg, f, ...)
+ local stat, err = pcall(f, ...)
+ assert(not stat and string.find(err, msg))
+end
+
+
+print('testing C API')
+
+a = T.testC("pushvalue R; return 1")
+assert(a == debug.getregistry())
+
+
+-- absindex
+assert(T.testC("settop 10; absindex -1; return 1") == 10)
+assert(T.testC("settop 5; absindex -5; return 1") == 1)
+assert(T.testC("settop 10; absindex 1; return 1") == 1)
+assert(T.testC("settop 10; absindex R; return 1") < -10)
+
+-- testing alignment
+a = T.d2s(12458954321123.0)
+assert(a == string.pack("d", 12458954321123.0))
+assert(T.s2d(a) == 12458954321123.0)
+
+a,b,c = T.testC("pushnum 1; pushnum 2; pushnum 3; return 2")
+assert(a == 2 and b == 3 and not c)
+
+f = T.makeCfunc("pushnum 1; pushnum 2; pushnum 3; return 2")
+a,b,c = f()
+assert(a == 2 and b == 3 and not c)
+
+-- test that all trues are equal
+a,b,c = T.testC("pushbool 1; pushbool 2; pushbool 0; return 3")
+assert(a == b and a == true and c == false)
+a,b,c = T.testC"pushbool 0; pushbool 10; pushnil;\
+ tobool -3; tobool -3; tobool -3; return 3"
+assert(a==false and b==true and c==false)
+
+
+a,b,c = T.testC("gettop; return 2", 10, 20, 30, 40)
+assert(a == 40 and b == 5 and not c)
+
+t = pack(T.testC("settop 5; return *", 2, 3))
+tcheck(t, {n=4,2,3})
+
+t = pack(T.testC("settop 0; settop 15; return 10", 3, 1, 23))
+assert(t.n == 10 and t[1] == nil and t[10] == nil)
+
+t = pack(T.testC("remove -2; return *", 2, 3, 4))
+tcheck(t, {n=2,2,4})
+
+t = pack(T.testC("insert -1; return *", 2, 3))
+tcheck(t, {n=2,2,3})
+
+t = pack(T.testC("insert 3; return *", 2, 3, 4, 5))
+tcheck(t, {n=4,2,5,3,4})
+
+t = pack(T.testC("replace 2; return *", 2, 3, 4, 5))
+tcheck(t, {n=3,5,3,4})
+
+t = pack(T.testC("replace -2; return *", 2, 3, 4, 5))
+tcheck(t, {n=3,2,3,5})
+
+t = pack(T.testC("remove 3; return *", 2, 3, 4, 5))
+tcheck(t, {n=3,2,4,5})
+
+t = pack(T.testC("copy 3 4; return *", 2, 3, 4, 5))
+tcheck(t, {n=4,2,3,3,5})
+
+t = pack(T.testC("copy -3 -1; return *", 2, 3, 4, 5))
+tcheck(t, {n=4,2,3,4,3})
+
+do -- testing 'rotate'
+ local t = {10, 20, 30, 40, 50, 60}
+ for i = -6, 6 do
+ local s = string.format("rotate 2 %d; return 7", i)
+ local t1 = pack(T.testC(s, 10, 20, 30, 40, 50, 60))
+ tcheck(t1, t)
+ table.insert(t, 1, table.remove(t))
+ end
+
+ t = pack(T.testC("rotate -2 1; return *", 10, 20, 30, 40))
+ tcheck(t, {10, 20, 40, 30})
+ t = pack(T.testC("rotate -2 -1; return *", 10, 20, 30, 40))
+ tcheck(t, {10, 20, 40, 30})
+
+ -- some corner cases
+ t = pack(T.testC("rotate -1 0; return *", 10, 20, 30, 40))
+ tcheck(t, {10, 20, 30, 40})
+ t = pack(T.testC("rotate -1 1; return *", 10, 20, 30, 40))
+ tcheck(t, {10, 20, 30, 40})
+ t = pack(T.testC("rotate 5 -1; return *", 10, 20, 30, 40))
+ tcheck(t, {10, 20, 30, 40})
+end
+
+-- testing message handlers
+do
+ local f = T.makeCfunc[[
+ getglobal error
+ pushstring bola
+ pcall 1 1 1 # call 'error' with given handler
+ pushstatus
+ return 2 # return error message and status
+ ]]
+
+ local msg, st = f(string.upper) -- function handler
+ assert(st == "ERRRUN" and msg == "BOLA")
+ local msg, st = f(string.len) -- function handler
+ assert(st == "ERRRUN" and msg == 4)
+
+end
+
+t = pack(T.testC("insert 3; pushvalue 3; remove 3; pushvalue 2; remove 2; \
+ insert 2; pushvalue 1; remove 1; insert 1; \
+ insert -2; pushvalue -2; remove -3; return *",
+ 2, 3, 4, 5, 10, 40, 90))
+tcheck(t, {n=7,2,3,4,5,10,40,90})
+
+t = pack(T.testC("concat 5; return *", "alo", 2, 3, "joao", 12))
+tcheck(t, {n=1,"alo23joao12"})
+
+-- testing MULTRET
+t = pack(T.testC("call 2,-1; return *",
+ function (a,b) return 1,2,3,4,a,b end, "alo", "joao"))
+tcheck(t, {n=6,1,2,3,4,"alo", "joao"})
+
+do -- test returning more results than fit in the caller stack
+ local a = {}
+ for i=1,1000 do a[i] = true end; a[999] = 10
+ local b = T.testC([[pcall 1 -1 0; pop 1; tostring -1; return 1]],
+ table.unpack, a)
+ assert(b == "10")
+end
+
+
+-- testing globals
+_G.a = 14; _G.b = "a31"
+local a = {T.testC[[
+ getglobal a;
+ getglobal b;
+ getglobal b;
+ setglobal a;
+ return *
+]]}
+assert(a[2] == 14 and a[3] == "a31" and a[4] == nil and _G.a == "a31")
+
+
+-- testing arith
+assert(T.testC("pushnum 10; pushnum 20; arith /; return 1") == 0.5)
+assert(T.testC("pushnum 10; pushnum 20; arith -; return 1") == -10)
+assert(T.testC("pushnum 10; pushnum -20; arith *; return 1") == -200)
+assert(T.testC("pushnum 10; pushnum 3; arith ^; return 1") == 1000)
+assert(T.testC("pushnum 10; pushstring 20; arith /; return 1") == 0.5)
+assert(T.testC("pushstring 10; pushnum 20; arith -; return 1") == -10)
+assert(T.testC("pushstring 10; pushstring -20; arith *; return 1") == -200)
+assert(T.testC("pushstring 10; pushstring 3; arith ^; return 1") == 1000)
+assert(T.testC("arith /; return 1", 2, 0) == 10.0/0)
+a = T.testC("pushnum 10; pushint 3; arith \\; return 1")
+assert(a == 3.0 and math.type(a) == "float")
+a = T.testC("pushint 10; pushint 3; arith \\; return 1")
+assert(a == 3 and math.type(a) == "integer")
+a = assert(T.testC("pushint 10; pushint 3; arith +; return 1"))
+assert(a == 13 and math.type(a) == "integer")
+a = assert(T.testC("pushnum 10; pushint 3; arith +; return 1"))
+assert(a == 13 and math.type(a) == "float")
+a,b,c = T.testC([[pushnum 1;
+ pushstring 10; arith _;
+ pushstring 5; return 3]])
+assert(a == 1 and b == -10 and c == "5")
+mt = {__add = function (a,b) return setmetatable({a[1] + b[1]}, mt) end,
+ __mod = function (a,b) return setmetatable({a[1] % b[1]}, mt) end,
+ __unm = function (a) return setmetatable({a[1]* 2}, mt) end}
+a,b,c = setmetatable({4}, mt),
+ setmetatable({8}, mt),
+ setmetatable({-3}, mt)
+x,y,z = T.testC("arith +; return 2", 10, a, b)
+assert(x == 10 and y[1] == 12 and z == nil)
+assert(T.testC("arith %; return 1", a, c)[1] == 4%-3)
+assert(T.testC("arith _; arith +; arith %; return 1", b, a, c)[1] ==
+ 8 % (4 + (-3)*2))
+
+-- errors in arithmetic
+checkerr("divide by zero", T.testC, "arith \\", 10, 0)
+checkerr("%%0", T.testC, "arith %", 10, 0)
+
+
+-- testing lessthan and lessequal
+assert(T.testC("compare LT 2 5, return 1", 3, 2, 2, 4, 2, 2))
+assert(T.testC("compare LE 2 5, return 1", 3, 2, 2, 4, 2, 2))
+assert(not T.testC("compare LT 3 4, return 1", 3, 2, 2, 4, 2, 2))
+assert(T.testC("compare LE 3 4, return 1", 3, 2, 2, 4, 2, 2))
+assert(T.testC("compare LT 5 2, return 1", 4, 2, 2, 3, 2, 2))
+assert(not T.testC("compare LT 2 -3, return 1", "4", "2", "2", "3", "2", "2"))
+assert(not T.testC("compare LT -3 2, return 1", "3", "2", "2", "4", "2", "2"))
+
+-- non-valid indices produce false
+assert(not T.testC("compare LT 1 4, return 1"))
+assert(not T.testC("compare LE 9 1, return 1"))
+assert(not T.testC("compare EQ 9 9, return 1"))
+
+local b = {__lt = function (a,b) return a[1] < b[1] end}
+local a1,a3,a4 = setmetatable({1}, b),
+ setmetatable({3}, b),
+ setmetatable({4}, b)
+assert(T.testC("compare LT 2 5, return 1", a3, 2, 2, a4, 2, 2))
+assert(T.testC("compare LE 2 5, return 1", a3, 2, 2, a4, 2, 2))
+assert(T.testC("compare LT 5 -6, return 1", a4, 2, 2, a3, 2, 2))
+a,b = T.testC("compare LT 5 -6, return 2", a1, 2, 2, a3, 2, 20)
+assert(a == 20 and b == false)
+a,b = T.testC("compare LE 5 -6, return 2", a1, 2, 2, a3, 2, 20)
+assert(a == 20 and b == false)
+a,b = T.testC("compare LE 5 -6, return 2", a1, 2, 2, a1, 2, 20)
+assert(a == 20 and b == true)
+
+-- testing length
+local t = setmetatable({x = 20}, {__len = function (t) return t.x end})
+a,b,c = T.testC([[
+ len 2;
+ Llen 2;
+ objsize 2;
+ return 3
+]], t)
+assert(a == 20 and b == 20 and c == 0)
+
+t.x = "234"; t[1] = 20
+a,b,c = T.testC([[
+ len 2;
+ Llen 2;
+ objsize 2;
+ return 3
+]], t)
+assert(a == "234" and b == 234 and c == 1)
+
+t.x = print; t[1] = 20
+a,c = T.testC([[
+ len 2;
+ objsize 2;
+ return 2
+]], t)
+assert(a == print and c == 1)
+
+
+-- testing __concat
+
+a = setmetatable({x="u"}, {__concat = function (a,b) return a.x..'.'..b.x end})
+x,y = T.testC([[
+ pushnum 5
+ pushvalue 2;
+ pushvalue 2;
+ concat 2;
+ pushvalue -2;
+ return 2;
+]], a, a)
+assert(x == a..a and y == 5)
+
+-- concat with 0 elements
+assert(T.testC("concat 0; return 1") == "")
+
+-- concat with 1 element
+assert(T.testC("concat 1; return 1", "xuxu") == "xuxu")
+
+
+
+-- testing lua_is
+
+function B(x) return x and 1 or 0 end
+
+function count (x, n)
+ n = n or 2
+ local prog = [[
+ isnumber %d;
+ isstring %d;
+ isfunction %d;
+ iscfunction %d;
+ istable %d;
+ isuserdata %d;
+ isnil %d;
+ isnull %d;
+ return 8
+ ]]
+ prog = string.format(prog, n, n, n, n, n, n, n, n)
+ local a,b,c,d,e,f,g,h = T.testC(prog, x)
+ return B(a)+B(b)+B(c)+B(d)+B(e)+B(f)+B(g)+(100*B(h))
+end
+
+assert(count(3) == 2)
+assert(count('alo') == 1)
+assert(count('32') == 2)
+assert(count({}) == 1)
+assert(count(print) == 2)
+assert(count(function () end) == 1)
+assert(count(nil) == 1)
+assert(count(io.stdin) == 1)
+assert(count(nil, 15) == 100)
+
+
+-- testing lua_to...
+
+function to (s, x, n)
+ n = n or 2
+ return T.testC(string.format("%s %d; return 1", s, n), x)
+end
+
+local hfunc = string.gmatch("", "") -- a "heavy C function" (with upvalues)
+assert(debug.getupvalue(hfunc, 1))
+assert(to("tostring", {}) == nil)
+assert(to("tostring", "alo") == "alo")
+assert(to("tostring", 12) == "12")
+assert(to("tostring", 12, 3) == nil)
+assert(to("objsize", {}) == 0)
+assert(to("objsize", {1,2,3}) == 3)
+assert(to("objsize", "alo\0\0a") == 6)
+assert(to("objsize", T.newuserdata(0)) == 0)
+assert(to("objsize", T.newuserdata(101)) == 101)
+assert(to("objsize", 124) == 0)
+assert(to("objsize", true) == 0)
+assert(to("tonumber", {}) == 0)
+assert(to("tonumber", "12") == 12)
+assert(to("tonumber", "s2") == 0)
+assert(to("tonumber", 1, 20) == 0)
+assert(to("topointer", 10) == 0)
+assert(to("topointer", true) == 0)
+assert(to("topointer", T.pushuserdata(20)) == 20)
+assert(to("topointer", io.read) ~= 0) -- light C function
+assert(to("topointer", hfunc) ~= 0) -- "heavy" C function
+assert(to("topointer", function () end) ~= 0) -- Lua function
+assert(to("topointer", io.stdin) ~= 0) -- full userdata
+assert(to("func2num", 20) == 0)
+assert(to("func2num", T.pushuserdata(10)) == 0)
+assert(to("func2num", io.read) ~= 0) -- light C function
+assert(to("func2num", hfunc) ~= 0) -- "heavy" C function (with upvalue)
+a = to("tocfunction", math.deg)
+assert(a(3) == math.deg(3) and a == math.deg)
+
+
+print("testing panic function")
+do
+ -- trivial error
+ assert(T.checkpanic("pushstring hi; error") == "hi")
+
+ -- using the stack inside panic
+ assert(T.checkpanic("pushstring hi; error;",
+ [[checkstack 5 XX
+ pushstring ' alo'
+ pushstring ' mundo'
+ concat 3]]) == "hi alo mundo")
+
+ -- "argerror" without frames
+ assert(T.checkpanic("loadstring 4") ==
+ "bad argument #4 (string expected, got no value)")
+
+
+ -- memory error
+ T.totalmem(T.totalmem()+10000) -- set low memory limit (+10k)
+ assert(T.checkpanic("newuserdata 20000") == "not enough memory")
+ T.totalmem(0) -- restore high limit
+
+ -- stack error
+ if not _soft then
+ local msg = T.checkpanic[[
+ pushstring "function f() f() end"
+ loadstring -1; call 0 0
+ getglobal f; call 0 0
+ ]]
+ assert(string.find(msg, "stack overflow"))
+ end
+
+end
+
+-- testing deep C stack
+if not _soft then
+ print("testing stack overflow")
+ collectgarbage("stop")
+ checkerr("XXXX", T.testC, "checkstack 1000023 XXXX") -- too deep
+ -- too deep (with no message)
+ checkerr("^stack overflow$", T.testC, "checkstack 1000023 ''")
+ local s = string.rep("pushnil;checkstack 1 XX;", 1000000)
+ checkerr("overflow", T.testC, s)
+ collectgarbage("restart")
+ print'+'
+end
+
+local lim = _soft and 500 or 12000
+local prog = {"checkstack " .. (lim * 2 + 100) .. "msg", "newtable"}
+for i = 1,lim do
+ prog[#prog + 1] = "pushnum " .. i
+ prog[#prog + 1] = "pushnum " .. i * 10
+end
+
+prog[#prog + 1] = "rawgeti R 2" -- get global table in registry
+prog[#prog + 1] = "insert " .. -(2*lim + 2)
+
+for i = 1,lim do
+ prog[#prog + 1] = "settable " .. -(2*(lim - i + 1) + 1)
+end
+
+prog[#prog + 1] = "return 2"
+
+prog = table.concat(prog, ";")
+local g, t = T.testC(prog)
+assert(g == _G)
+for i = 1,lim do assert(t[i] == i*10); t[i] = undef end
+assert(next(t) == nil)
+prog, g, t = nil
+
+-- testing errors
+
+a = T.testC([[
+ loadstring 2; pcall 0 1 0;
+ pushvalue 3; insert -2; pcall 1 1 0;
+ pcall 0 0 0;
+ return 1
+]], "x=150", function (a) assert(a==nil); return 3 end)
+
+assert(type(a) == 'string' and x == 150)
+
+function check3(p, ...)
+ local arg = {...}
+ assert(#arg == 3)
+ assert(string.find(arg[3], p))
+end
+check3(":1:", T.testC("loadstring 2; return *", "x="))
+check3("%.", T.testC("loadfile 2; return *", "."))
+check3("xxxx", T.testC("loadfile 2; return *", "xxxx"))
+
+-- test errors in non protected threads
+function checkerrnopro (code, msg)
+ local th = coroutine.create(function () end) -- create new thread
+ local stt, err = pcall(T.testC, th, code) -- run code there
+ assert(not stt and string.find(err, msg))
+end
+
+if not _soft then
+ checkerrnopro("pushnum 3; call 0 0", "attempt to call")
+ print"testing stack overflow in unprotected thread"
+ function f () f() end
+ checkerrnopro("getglobal 'f'; call 0 0;", "stack overflow")
+end
+print"+"
+
+
+-- testing table access
+
+do -- getp/setp
+ local a = {}
+ T.testC("rawsetp 2 1", a, 20)
+ assert(a[T.pushuserdata(1)] == 20)
+ assert(T.testC("rawgetp 2 1; return 1", a) == 20)
+end
+
+a = {x=0, y=12}
+x, y = T.testC("gettable 2; pushvalue 4; gettable 2; return 2",
+ a, 3, "y", 4, "x")
+assert(x == 0 and y == 12)
+T.testC("settable -5", a, 3, 4, "x", 15)
+assert(a.x == 15)
+a[a] = print
+x = T.testC("gettable 2; return 1", a) -- table and key are the same object!
+assert(x == print)
+T.testC("settable 2", a, "x") -- table and key are the same object!
+assert(a[a] == "x")
+
+b = setmetatable({p = a}, {})
+getmetatable(b).__index = function (t, i) return t.p[i] end
+k, x = T.testC("gettable 3, return 2", 4, b, 20, 35, "x")
+assert(x == 15 and k == 35)
+k = T.testC("getfield 2 y, return 1", b)
+assert(k == 12)
+getmetatable(b).__index = function (t, i) return a[i] end
+getmetatable(b).__newindex = function (t, i,v ) a[i] = v end
+y = T.testC("insert 2; gettable -5; return 1", 2, 3, 4, "y", b)
+assert(y == 12)
+k = T.testC("settable -5, return 1", b, 3, 4, "x", 16)
+assert(a.x == 16 and k == 4)
+a[b] = 'xuxu'
+y = T.testC("gettable 2, return 1", b)
+assert(y == 'xuxu')
+T.testC("settable 2", b, 19)
+assert(a[b] == 19)
+
+--
+do -- testing getfield/setfield with long keys
+ local t = {_012345678901234567890123456789012345678901234567890123456789 = 32}
+ local a = T.testC([[
+ getfield 2 _012345678901234567890123456789012345678901234567890123456789
+ return 1
+ ]], t)
+ assert(a == 32)
+ local a = T.testC([[
+ pushnum 33
+ setglobal _012345678901234567890123456789012345678901234567890123456789
+ ]])
+ assert(_012345678901234567890123456789012345678901234567890123456789 == 33)
+ _012345678901234567890123456789012345678901234567890123456789 = nil
+end
+
+-- testing next
+a = {}
+t = pack(T.testC("next; return *", a, nil))
+tcheck(t, {n=1,a})
+a = {a=3}
+t = pack(T.testC("next; return *", a, nil))
+tcheck(t, {n=3,a,'a',3})
+t = pack(T.testC("next; pop 1; next; return *", a, nil))
+tcheck(t, {n=1,a})
+
+
+
+-- testing upvalues
+
+do
+ local A = T.testC[[ pushnum 10; pushnum 20; pushcclosure 2; return 1]]
+ t, b, c = A([[pushvalue U0; pushvalue U1; pushvalue U2; return 3]])
+ assert(b == 10 and c == 20 and type(t) == 'table')
+ a, b = A([[tostring U3; tonumber U4; return 2]])
+ assert(a == nil and b == 0)
+ A([[pushnum 100; pushnum 200; replace U2; replace U1]])
+ b, c = A([[pushvalue U1; pushvalue U2; return 2]])
+ assert(b == 100 and c == 200)
+ A([[replace U2; replace U1]], {x=1}, {x=2})
+ b, c = A([[pushvalue U1; pushvalue U2; return 2]])
+ assert(b.x == 1 and c.x == 2)
+ T.checkmemory()
+end
+
+
+-- testing absent upvalues from C-function pointers
+assert(T.testC[[isnull U1; return 1]] == true)
+assert(T.testC[[isnull U100; return 1]] == true)
+assert(T.testC[[pushvalue U1; return 1]] == nil)
+
+local f = T.testC[[ pushnum 10; pushnum 20; pushcclosure 2; return 1]]
+assert(T.upvalue(f, 1) == 10 and
+ T.upvalue(f, 2) == 20 and
+ T.upvalue(f, 3) == nil)
+T.upvalue(f, 2, "xuxu")
+assert(T.upvalue(f, 2) == "xuxu")
+
+
+-- large closures
+do
+ local A = "checkstack 300 msg;" ..
+ string.rep("pushnum 10;", 255) ..
+ "pushcclosure 255; return 1"
+ A = T.testC(A)
+ for i=1,255 do
+ assert(A(("pushvalue U%d; return 1"):format(i)) == 10)
+ end
+ assert(A("isnull U256; return 1"))
+ assert(not A("isnil U256; return 1"))
+end
+
+
+
+-- testing get/setuservalue
+-- bug in 5.1.2
+checkerr("got number", debug.setuservalue, 3, {})
+checkerr("got nil", debug.setuservalue, nil, {})
+checkerr("got light userdata", debug.setuservalue, T.pushuserdata(1), {})
+
+-- testing multiple user values
+local b = T.newuserdata(0, 10)
+for i = 1, 10 do
+ local v, p = debug.getuservalue(b, i)
+ assert(v == nil and p)
+end
+do -- indices out of range
+ local v, p = debug.getuservalue(b, -2)
+ assert(v == nil and not p)
+ local v, p = debug.getuservalue(b, 11)
+ assert(v == nil and not p)
+end
+local t = {true, false, 4.56, print, {}, b, "XYZ"}
+for k, v in ipairs(t) do
+ debug.setuservalue(b, v, k)
+end
+for k, v in ipairs(t) do
+ local v1, p = debug.getuservalue(b, k)
+ assert(v1 == v and p)
+end
+
+assert(debug.getuservalue(4) == nil)
+
+debug.setuservalue(b, function () return 10 end, 10)
+collectgarbage() -- function should not be collected
+assert(debug.getuservalue(b, 10)() == 10)
+
+debug.setuservalue(b, 134)
+collectgarbage() -- number should not be a problem for collector
+assert(debug.getuservalue(b) == 134)
+
+
+-- test barrier for uservalues
+do
+ local oldmode = collectgarbage("incremental")
+ T.gcstate("atomic")
+ assert(T.gccolor(b) == "black")
+ debug.setuservalue(b, {x = 100})
+ T.gcstate("pause") -- complete collection
+ assert(debug.getuservalue(b).x == 100) -- uvalue should be there
+ collectgarbage(oldmode)
+end
+
+-- long chain of userdata
+for i = 1, 1000 do
+ local bb = T.newuserdata(0, 1)
+ debug.setuservalue(bb, b)
+ b = bb
+end
+collectgarbage() -- nothing should not be collected
+for i = 1, 1000 do
+ b = debug.getuservalue(b)
+end
+assert(debug.getuservalue(b).x == 100)
+b = nil
+
+
+-- testing locks (refs)
+
+-- reuse of references
+local i = T.ref{}
+T.unref(i)
+assert(T.ref{} == i)
+
+Arr = {}
+Lim = 100
+for i=1,Lim do -- lock many objects
+ Arr[i] = T.ref({})
+end
+
+assert(T.ref(nil) == -1 and T.getref(-1) == nil)
+T.unref(-1); T.unref(-1)
+
+for i=1,Lim do -- unlock all them
+ T.unref(Arr[i])
+end
+
+function printlocks ()
+ local f = T.makeCfunc("gettable R; return 1")
+ local n = f("n")
+ print("n", n)
+ for i=0,n do
+ print(i, f(i))
+ end
+end
+
+
+for i=1,Lim do -- lock many objects
+ Arr[i] = T.ref({})
+end
+
+for i=1,Lim,2 do -- unlock half of them
+ T.unref(Arr[i])
+end
+
+assert(type(T.getref(Arr[2])) == 'table')
+
+
+assert(T.getref(-1) == nil)
+
+
+a = T.ref({})
+
+collectgarbage()
+
+assert(type(T.getref(a)) == 'table')
+
+
+-- colect in cl the `val' of all collected userdata
+tt = {}
+cl = {n=0}
+A = nil; B = nil
+local F
+F = function (x)
+ local udval = T.udataval(x)
+ table.insert(cl, udval)
+ local d = T.newuserdata(100) -- create garbage
+ d = nil
+ assert(debug.getmetatable(x).__gc == F)
+ assert(load("table.insert({}, {})"))() -- create more garbage
+ collectgarbage() -- force a GC during GC
+ assert(debug.getmetatable(x).__gc == F) -- previous GC did not mess this?
+ local dummy = {} -- create more garbage during GC
+ if A ~= nil then
+ assert(type(A) == "userdata")
+ assert(T.udataval(A) == B)
+ debug.getmetatable(A) -- just acess it
+ end
+ A = x -- ressucita userdata
+ B = udval
+ return 1,2,3
+end
+tt.__gc = F
+
+-- test whether udate collection frees memory in the right time
+do
+ collectgarbage();
+ collectgarbage();
+ local x = collectgarbage("count");
+ local a = T.newuserdata(5001)
+ assert(T.testC("objsize 2; return 1", a) == 5001)
+ assert(collectgarbage("count") >= x+4)
+ a = nil
+ collectgarbage();
+ assert(collectgarbage("count") <= x+1)
+ -- udata without finalizer
+ x = collectgarbage("count")
+ collectgarbage("stop")
+ for i=1,1000 do T.newuserdata(0) end
+ assert(collectgarbage("count") > x+10)
+ collectgarbage()
+ assert(collectgarbage("count") <= x+1)
+ -- udata with finalizer
+ collectgarbage()
+ x = collectgarbage("count")
+ collectgarbage("stop")
+ a = {__gc = function () end}
+ for i=1,1000 do debug.setmetatable(T.newuserdata(0), a) end
+ assert(collectgarbage("count") >= x+10)
+ collectgarbage() -- this collection only calls TM, without freeing memory
+ assert(collectgarbage("count") >= x+10)
+ collectgarbage() -- now frees memory
+ assert(collectgarbage("count") <= x+1)
+ collectgarbage("restart")
+end
+
+
+collectgarbage("stop")
+
+-- create 3 userdatas with tag `tt'
+a = T.newuserdata(0); debug.setmetatable(a, tt); na = T.udataval(a)
+b = T.newuserdata(0); debug.setmetatable(b, tt); nb = T.udataval(b)
+c = T.newuserdata(0); debug.setmetatable(c, tt); nc = T.udataval(c)
+
+-- create userdata without meta table
+x = T.newuserdata(4)
+y = T.newuserdata(0)
+
+checkerr("FILE%* expected, got userdata", io.input, a)
+checkerr("FILE%* expected, got userdata", io.input, x)
+
+assert(debug.getmetatable(x) == nil and debug.getmetatable(y) == nil)
+
+d=T.ref(a);
+e=T.ref(b);
+f=T.ref(c);
+t = {T.getref(d), T.getref(e), T.getref(f)}
+assert(t[1] == a and t[2] == b and t[3] == c)
+
+t=nil; a=nil; c=nil;
+T.unref(e); T.unref(f)
+
+collectgarbage()
+
+-- check that unref objects have been collected
+assert(#cl == 1 and cl[1] == nc)
+
+x = T.getref(d)
+assert(type(x) == 'userdata' and debug.getmetatable(x) == tt)
+x =nil
+tt.b = b -- create cycle
+tt=nil -- frees tt for GC
+A = nil
+b = nil
+T.unref(d);
+n5 = T.newuserdata(0)
+debug.setmetatable(n5, {__gc=F})
+n5 = T.udataval(n5)
+collectgarbage()
+assert(#cl == 4)
+-- check order of collection
+assert(cl[2] == n5 and cl[3] == nb and cl[4] == na)
+
+collectgarbage"restart"
+
+
+a, na = {}, {}
+for i=30,1,-1 do
+ a[i] = T.newuserdata(0)
+ debug.setmetatable(a[i], {__gc=F})
+ na[i] = T.udataval(a[i])
+end
+cl = {}
+a = nil; collectgarbage()
+assert(#cl == 30)
+for i=1,30 do assert(cl[i] == na[i]) end
+na = nil
+
+
+for i=2,Lim,2 do -- unlock the other half
+ T.unref(Arr[i])
+end
+
+x = T.newuserdata(41); debug.setmetatable(x, {__gc=F})
+assert(T.testC("objsize 2; return 1", x) == 41)
+cl = {}
+a = {[x] = 1}
+x = T.udataval(x)
+collectgarbage()
+-- old `x' cannot be collected (`a' still uses it)
+assert(#cl == 0)
+for n in pairs(a) do a[n] = undef end
+collectgarbage()
+assert(#cl == 1 and cl[1] == x) -- old `x' must be collected
+
+-- testing lua_equal
+assert(T.testC("compare EQ 2 4; return 1", print, 1, print, 20))
+assert(T.testC("compare EQ 3 2; return 1", 'alo', "alo"))
+assert(T.testC("compare EQ 2 3; return 1", nil, nil))
+assert(not T.testC("compare EQ 2 3; return 1", {}, {}))
+assert(not T.testC("compare EQ 2 3; return 1"))
+assert(not T.testC("compare EQ 2 3; return 1", 3))
+
+-- testing lua_equal with fallbacks
+do
+ local map = {}
+ local t = {__eq = function (a,b) return map[a] == map[b] end}
+ local function f(x)
+ local u = T.newuserdata(0)
+ debug.setmetatable(u, t)
+ map[u] = x
+ return u
+ end
+ assert(f(10) == f(10))
+ assert(f(10) ~= f(11))
+ assert(T.testC("compare EQ 2 3; return 1", f(10), f(10)))
+ assert(not T.testC("compare EQ 2 3; return 1", f(10), f(20)))
+ t.__eq = nil
+ assert(f(10) ~= f(10))
+end
+
+print'+'
+
+
+
+-- testing changing hooks during hooks
+_G.t = {}
+T.sethook([[
+ # set a line hook after 3 count hooks
+ sethook 4 0 '
+ getglobal t;
+ pushvalue -3; append -2
+ pushvalue -2; append -2
+ ']], "c", 3)
+local a = 1 -- counting
+a = 1 -- counting
+a = 1 -- count hook (set line hook)
+a = 1 -- line hook
+a = 1 -- line hook
+debug.sethook()
+t = _G.t
+assert(t[1] == "line")
+line = t[2]
+assert(t[3] == "line" and t[4] == line + 1)
+assert(t[5] == "line" and t[6] == line + 2)
+assert(t[7] == nil)
+
+
+-------------------------------------------------------------------------
+do -- testing errors during GC
+ collectgarbage("stop")
+ local a = {}
+ for i=1,20 do
+ a[i] = T.newuserdata(i) -- creates several udata
+ end
+ for i=1,20,2 do -- mark half of them to raise errors during GC
+ debug.setmetatable(a[i], {__gc = function (x) error("error inside gc") end})
+ end
+ for i=2,20,2 do -- mark the other half to count and to create more garbage
+ debug.setmetatable(a[i], {__gc = function (x) load("A=A+1")() end})
+ end
+ _G.A = 0
+ a = 0
+ while 1 do
+ local stat, msg = pcall(collectgarbage)
+ if stat then
+ break -- stop when no more errors
+ else
+ a = a + 1
+ assert(string.find(msg, "__gc"))
+ end
+ end
+ assert(a == 10) -- number of errors
+
+ assert(A == 10) -- number of normal collections
+ collectgarbage("restart")
+end
+-------------------------------------------------------------------------
+-- test for userdata vals
+do
+ local a = {}; local lim = 30
+ for i=0,lim do a[i] = T.pushuserdata(i) end
+ for i=0,lim do assert(T.udataval(a[i]) == i) end
+ for i=0,lim do assert(T.pushuserdata(i) == a[i]) end
+ for i=0,lim do a[a[i]] = i end
+ for i=0,lim do a[T.pushuserdata(i)] = i end
+ assert(type(tostring(a[1])) == "string")
+end
+
+
+-------------------------------------------------------------------------
+-- testing multiple states
+T.closestate(T.newstate());
+L1 = T.newstate()
+assert(L1)
+
+assert(T.doremote(L1, "X='a'; return 'a'") == 'a')
+
+
+assert(#pack(T.doremote(L1, "function f () return 'alo', 3 end; f()")) == 0)
+
+a, b = T.doremote(L1, "return f()")
+assert(a == 'alo' and b == '3')
+
+T.doremote(L1, "_ERRORMESSAGE = nil")
+-- error: `sin' is not defined
+a, _, b = T.doremote(L1, "return sin(1)")
+assert(a == nil and b == 2) -- 2 == run-time error
+
+-- error: syntax error
+a, b, c = T.doremote(L1, "return a+")
+assert(a == nil and c == 3 and type(b) == "string") -- 3 == syntax error
+
+T.loadlib(L1)
+a, b, c = T.doremote(L1, [[
+ string = require'string'
+ a = require'_G'; assert(a == _G and require("_G") == a)
+ io = require'io'; assert(type(io.read) == "function")
+ assert(require("io") == io)
+ a = require'table'; assert(type(a.insert) == "function")
+ a = require'debug'; assert(type(a.getlocal) == "function")
+ a = require'math'; assert(type(a.sin) == "function")
+ return string.sub('okinama', 1, 2)
+]])
+assert(a == "ok")
+
+T.closestate(L1);
+
+
+L1 = T.newstate()
+T.loadlib(L1)
+T.doremote(L1, "a = {}")
+T.testC(L1, [[getglobal "a"; pushstring "x"; pushint 1;
+ settable -3]])
+assert(T.doremote(L1, "return a.x") == "1")
+
+T.closestate(L1)
+
+L1 = nil
+
+print('+')
+
+-------------------------------------------------------------------------
+-- testing memory limits
+-------------------------------------------------------------------------
+print("memory-allocation errors")
+
+checkerr("block too big", T.newuserdata, math.maxinteger)
+collectgarbage()
+local f = load"local a={}; for i=1,100000 do a[i]=i end"
+T.alloccount(10)
+checkerr("not enough memory", f)
+T.alloccount() -- remove limit
+
+-- test memory errors; increase limit for number of allocations one
+-- by one, so that we get memory errors in all allocations of a given
+-- task, until there is enough allocations to complete the task without
+-- errors.
+
+function testamem (s, f)
+ collectgarbage(); collectgarbage()
+ local M = 0
+ local a,b = nil
+ while true do
+ T.alloccount(M)
+ a, b = pcall(f)
+ T.alloccount() -- remove limit
+ if a and b then break end -- stop when no more errors
+ if not a and not -- `real' error?
+ (string.find(b, "memory") or string.find(b, "overflow")) then
+ error(b, 0) -- propagate it
+ end
+ M = M + 1 -- increase allocation limit
+ end
+ print(string.format("limit for %s: %d allocations", s, M))
+ return b
+end
+
+
+-- doing nothing
+b = testamem("doing nothing", function () return 10 end)
+assert(b == 10)
+
+-- testing memory errors when creating a new state
+
+b = testamem("state creation", T.newstate)
+T.closestate(b); -- close new state
+
+testamem("empty-table creation", function ()
+ return {}
+end)
+
+testamem("string creation", function ()
+ return "XXX" .. "YYY"
+end)
+
+testamem("coroutine creation", function()
+ return coroutine.create(print)
+end)
+
+
+-- testing threads
+
+-- get main thread from registry (at index LUA_RIDX_MAINTHREAD == 1)
+mt = T.testC("rawgeti R 1; return 1")
+assert(type(mt) == "thread" and coroutine.running() == mt)
+
+
+
+function expand (n,s)
+ if n==0 then return "" end
+ local e = string.rep("=", n)
+ return string.format("T.doonnewstack([%s[ %s;\n collectgarbage(); %s]%s])\n",
+ e, s, expand(n-1,s), e)
+end
+
+G=0; collectgarbage(); a =collectgarbage("count")
+load(expand(20,"G=G+1"))()
+assert(G==20); collectgarbage(); -- assert(gcinfo() <= a+1)
+
+testamem("running code on new thread", function ()
+ return T.doonnewstack("x=1") == 0 -- try to create thread
+end)
+
+
+-- testing memory x compiler
+
+testamem("loadstring", function ()
+ return load("x=1") -- try to do load a string
+end)
+
+
+local testprog = [[
+local function foo () return end
+local t = {"x"}
+a = "aaa"
+for i = 1, #t do a=a..t[i] end
+return true
+]]
+
+-- testing memory x dofile
+_G.a = nil
+local t =os.tmpname()
+local f = assert(io.open(t, "w"))
+f:write(testprog)
+f:close()
+testamem("dofile", function ()
+ local a = loadfile(t)
+ return a and a()
+end)
+assert(os.remove(t))
+assert(_G.a == "aaax")
+
+
+-- other generic tests
+
+testamem("gsub", function ()
+ local a, b = string.gsub("alo alo", "(a)", function (x) return x..'b' end)
+ return (a == 'ablo ablo')
+end)
+
+testamem("dump/undump", function ()
+ local a = load(testprog)
+ local b = a and string.dump(a)
+ a = b and load(b)
+ return a and a()
+end)
+
+local t = os.tmpname()
+testamem("file creation", function ()
+ local f = assert(io.open(t, 'w'))
+ assert (not io.open"nomenaoexistente")
+ io.close(f);
+ return not loadfile'nomenaoexistente'
+end)
+assert(os.remove(t))
+
+testamem("table creation", function ()
+ local a, lim = {}, 10
+ for i=1,lim do a[i] = i; a[i..'a'] = {} end
+ return (type(a[lim..'a']) == 'table' and a[lim] == lim)
+end)
+
+testamem("constructors", function ()
+ local a = {10, 20, 30, 40, 50; a=1, b=2, c=3, d=4, e=5}
+ return (type(a) == 'table' and a.e == 5)
+end)
+
+local a = 1
+close = nil
+testamem("closure creation", function ()
+ function close (b)
+ return function (x) return b + x end
+ end
+ return (close(2)(4) == 6)
+end)
+
+testamem("using coroutines", function ()
+ local a = coroutine.wrap(function ()
+ coroutine.yield(string.rep("a", 10))
+ return {}
+ end)
+ assert(string.len(a()) == 10)
+ return a()
+end)
+
+do -- auxiliary buffer
+ local lim = 100
+ local a = {}; for i = 1, lim do a[i] = "01234567890123456789" end
+ testamem("auxiliary buffer", function ()
+ return (#table.concat(a, ",") == 20*lim + lim - 1)
+ end)
+end
+
+testamem("growing stack", function ()
+ local function foo (n)
+ if n == 0 then return 1 else return 1 + foo(n - 1) end
+ end
+ return foo(100)
+end)
+
+do -- testing failing in 'lua_checkstack'
+ local res = T.testC([[rawcheckstack 500000; return 1]])
+ assert(res == false)
+ local L = T.newstate()
+ T.alloccount(0) -- will be unable to reallocate the stack
+ res = T.testC(L, [[rawcheckstack 5000; return 1]])
+ T.alloccount()
+ T.closestate(L)
+ assert(res == false)
+end
+
+do -- closing state with no extra memory
+ local L = T.newstate()
+ T.alloccount(0)
+ T.closestate(L)
+ T.alloccount()
+end
+
+do -- garbage collection with no extra memory
+ local L = T.newstate()
+ T.loadlib(L)
+ local res = (T.doremote(L, [[
+ _ENV = require"_G"
+ local T = require"T"
+ local a = {}
+ for i = 1, 1000 do a[i] = 'i' .. i end -- grow string table
+ local stsize, stuse = T.querystr()
+ assert(stuse > 1000)
+ local function foo (n)
+ if n > 0 then foo(n - 1) end
+ end
+ foo(180) -- grow stack
+ local _, stksize = T.stacklevel()
+ assert(stksize > 180)
+ a = nil
+ T.alloccount(0)
+ collectgarbage()
+ T.alloccount()
+ -- stack and string table could not be reallocated,
+ -- so they kept their sizes (without errors)
+ assert(select(2, T.stacklevel()) == stksize)
+ assert(T.querystr() == stsize)
+ return 'ok'
+ ]]))
+ assert(res == 'ok')
+ T.closestate(L)
+end
+
+print'+'
+
+-- testing some auxlib functions
+local function gsub (a, b, c)
+ a, b = T.testC("gsub 2 3 4; gettop; return 2", a, b, c)
+ assert(b == 5)
+ return a
+end
+
+assert(gsub("alo.alo.uhuh.", ".", "//") == "alo//alo//uhuh//")
+assert(gsub("alo.alo.uhuh.", "alo", "//") == "//.//.uhuh.")
+assert(gsub("", "alo", "//") == "")
+assert(gsub("...", ".", "/.") == "/././.")
+assert(gsub("...", "...", "") == "")
+
+
+-- testing luaL_newmetatable
+local mt_xuxu, res, top = T.testC("newmetatable xuxu; gettop; return 3")
+assert(type(mt_xuxu) == "table" and res and top == 3)
+local d, res, top = T.testC("newmetatable xuxu; gettop; return 3")
+assert(mt_xuxu == d and not res and top == 3)
+d, res, top = T.testC("newmetatable xuxu1; gettop; return 3")
+assert(mt_xuxu ~= d and res and top == 3)
+
+x = T.newuserdata(0);
+y = T.newuserdata(0);
+T.testC("pushstring xuxu; gettable R; setmetatable 2", x)
+assert(getmetatable(x) == mt_xuxu)
+
+-- testing luaL_testudata
+-- correct metatable
+local res1, res2, top = T.testC([[testudata -1 xuxu
+ testudata 2 xuxu
+ gettop
+ return 3]], x)
+assert(res1 and res2 and top == 4)
+
+-- wrong metatable
+res1, res2, top = T.testC([[testudata -1 xuxu1
+ testudata 2 xuxu1
+ gettop
+ return 3]], x)
+assert(not res1 and not res2 and top == 4)
+
+-- non-existent type
+res1, res2, top = T.testC([[testudata -1 xuxu2
+ testudata 2 xuxu2
+ gettop
+ return 3]], x)
+assert(not res1 and not res2 and top == 4)
+
+-- userdata has no metatable
+res1, res2, top = T.testC([[testudata -1 xuxu
+ testudata 2 xuxu
+ gettop
+ return 3]], y)
+assert(not res1 and not res2 and top == 4)
+
+-- erase metatables
+do
+ local r = debug.getregistry()
+ assert(r.xuxu == mt_xuxu and r.xuxu1 == d)
+ r.xuxu = nil; r.xuxu1 = nil
+end
+
+print'OK'
+
diff --git a/testes/attrib.lua b/testes/attrib.lua
new file mode 100644
index 0000000000..79a08a4f69
--- /dev/null
+++ b/testes/attrib.lua
@@ -0,0 +1,487 @@
+-- $Id: attrib.lua,v 1.69 2018/03/12 13:51:02 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print "testing require"
+
+assert(require"string" == string)
+assert(require"math" == math)
+assert(require"table" == table)
+assert(require"io" == io)
+assert(require"os" == os)
+assert(require"coroutine" == coroutine)
+
+assert(type(package.path) == "string")
+assert(type(package.cpath) == "string")
+assert(type(package.loaded) == "table")
+assert(type(package.preload) == "table")
+
+assert(type(package.config) == "string")
+print("package config: "..string.gsub(package.config, "\n", "|"))
+
+do
+ -- create a path with 'max' templates,
+ -- each with 1-10 repetitions of '?'
+ local max = _soft and 100 or 2000
+ local t = {}
+ for i = 1,max do t[i] = string.rep("?", i%10 + 1) end
+ t[#t + 1] = ";" -- empty template
+ local path = table.concat(t, ";")
+ -- use that path in a search
+ local s, err = package.searchpath("xuxu", path)
+ -- search fails; check that message has an occurence of
+ -- '??????????' with ? replaced by xuxu and at least 'max' lines
+ assert(not s and
+ string.find(err, string.rep("xuxu", 10)) and
+ #string.gsub(err, "[^\n]", "") >= max)
+ -- path with one very long template
+ local path = string.rep("?", max)
+ local s, err = package.searchpath("xuxu", path)
+ assert(not s and string.find(err, string.rep('xuxu', max)))
+end
+
+do
+ local oldpath = package.path
+ package.path = {}
+ local s, err = pcall(require, "no-such-file")
+ assert(not s and string.find(err, "package.path"))
+ package.path = oldpath
+end
+
+print('+')
+
+
+-- The next tests for 'require' assume some specific directories and
+-- libraries.
+
+if not _port then --[
+
+local dirsep = string.match(package.config, "^([^\n]+)\n")
+
+-- auxiliary directory with C modules and temporary files
+local DIR = "libs" .. dirsep
+
+-- prepend DIR to a name and correct directory separators
+local function D (x)
+ x = string.gsub(x, "/", dirsep)
+ return DIR .. x
+end
+
+-- prepend DIR and pospend proper C lib. extension to a name
+local function DC (x)
+ local ext = (dirsep == '\\') and ".dll" or ".so"
+ return D(x .. ext)
+end
+
+
+local function createfiles (files, preextras, posextras)
+ for n,c in pairs(files) do
+ io.output(D(n))
+ io.write(string.format(preextras, n))
+ io.write(c)
+ io.write(string.format(posextras, n))
+ io.close(io.output())
+ end
+end
+
+function removefiles (files)
+ for n in pairs(files) do
+ os.remove(D(n))
+ end
+end
+
+local files = {
+ ["names.lua"] = "do return {...} end\n",
+ ["err.lua"] = "B = 15; a = a + 1;",
+ ["synerr.lua"] = "B =",
+ ["A.lua"] = "",
+ ["B.lua"] = "assert(...=='B');require 'A'",
+ ["A.lc"] = "",
+ ["A"] = "",
+ ["L"] = "",
+ ["XXxX"] = "",
+ ["C.lua"] = "package.loaded[...] = 25; require'C'",
+}
+
+AA = nil
+local extras = [[
+NAME = '%s'
+REQUIRED = ...
+return AA]]
+
+createfiles(files, "", extras)
+
+-- testing explicit "dir" separator in 'searchpath'
+assert(package.searchpath("C.lua", D"?", "", "") == D"C.lua")
+assert(package.searchpath("C.lua", D"?", ".", ".") == D"C.lua")
+assert(package.searchpath("--x-", D"?", "-", "X") == D"XXxX")
+assert(package.searchpath("---xX", D"?", "---", "XX") == D"XXxX")
+assert(package.searchpath(D"C.lua", "?", dirsep) == D"C.lua")
+assert(package.searchpath(".\\C.lua", D"?", "\\") == D"./C.lua")
+
+local oldpath = package.path
+
+package.path = string.gsub("D/?.lua;D/?.lc;D/?;D/??x?;D/L", "D/", DIR)
+
+local try = function (p, n, r)
+ NAME = nil
+ local rr = require(p)
+ assert(NAME == n)
+ assert(REQUIRED == p)
+ assert(rr == r)
+end
+
+a = require"names"
+assert(a[1] == "names" and a[2] == D"names.lua")
+
+_G.a = nil
+local st, msg = pcall(require, "err")
+assert(not st and string.find(msg, "arithmetic") and B == 15)
+st, msg = pcall(require, "synerr")
+assert(not st and string.find(msg, "error loading module"))
+
+assert(package.searchpath("C", package.path) == D"C.lua")
+assert(require"C" == 25)
+assert(require"C" == 25)
+AA = nil
+try('B', 'B.lua', true)
+assert(package.loaded.B)
+assert(require"B" == true)
+assert(package.loaded.A)
+assert(require"C" == 25)
+package.loaded.A = nil
+try('B', nil, true) -- should not reload package
+try('A', 'A.lua', true)
+package.loaded.A = nil
+os.remove(D'A.lua')
+AA = {}
+try('A', 'A.lc', AA) -- now must find second option
+assert(package.searchpath("A", package.path) == D"A.lc")
+assert(require("A") == AA)
+AA = false
+try('K', 'L', false) -- default option
+try('K', 'L', false) -- default option (should reload it)
+assert(rawget(_G, "_REQUIREDNAME") == nil)
+
+AA = "x"
+try("X", "XXxX", AA)
+
+
+removefiles(files)
+
+
+-- testing require of sub-packages
+
+local _G = _G
+
+package.path = string.gsub("D/?.lua;D/?/init.lua", "D/", DIR)
+
+files = {
+ ["P1/init.lua"] = "AA = 10",
+ ["P1/xuxu.lua"] = "AA = 20",
+}
+
+createfiles(files, "_ENV = {}\n", "\nreturn _ENV\n")
+AA = 0
+
+local m = assert(require"P1")
+assert(AA == 0 and m.AA == 10)
+assert(require"P1" == m)
+assert(require"P1" == m)
+
+assert(package.searchpath("P1.xuxu", package.path) == D"P1/xuxu.lua")
+m.xuxu = assert(require"P1.xuxu")
+assert(AA == 0 and m.xuxu.AA == 20)
+assert(require"P1.xuxu" == m.xuxu)
+assert(require"P1.xuxu" == m.xuxu)
+assert(require"P1" == m and m.AA == 10)
+
+
+removefiles(files)
+
+
+package.path = ""
+assert(not pcall(require, "file_does_not_exist"))
+package.path = "??\0?"
+assert(not pcall(require, "file_does_not_exist1"))
+
+package.path = oldpath
+
+-- check 'require' error message
+local fname = "file_does_not_exist2"
+local m, err = pcall(require, fname)
+for t in string.gmatch(package.path..";"..package.cpath, "[^;]+") do
+ t = string.gsub(t, "?", fname)
+ assert(string.find(err, t, 1, true))
+end
+
+do -- testing 'package.searchers' not being a table
+ local searchers = package.searchers
+ package.searchers = 3
+ local st, msg = pcall(require, 'a')
+ assert(not st and string.find(msg, "must be a table"))
+ package.searchers = searchers
+end
+
+local function import(...)
+ local f = {...}
+ return function (m)
+ for i=1, #f do m[f[i]] = _G[f[i]] end
+ end
+end
+
+-- cannot change environment of a C function
+assert(not pcall(module, 'XUXU'))
+
+
+
+-- testing require of C libraries
+
+
+local p = "" -- On Mac OS X, redefine this to "_"
+
+-- check whether loadlib works in this system
+local st, err, when = package.loadlib(DC"lib1", "*")
+if not st then
+ local f, err, when = package.loadlib("donotexist", p.."xuxu")
+ assert(not f and type(err) == "string" and when == "absent")
+ ;(Message or print)('\n >>> cannot load dynamic library <<<\n')
+ print(err, when)
+else
+ -- tests for loadlib
+ local f = assert(package.loadlib(DC"lib1", p.."onefunction"))
+ local a, b = f(15, 25)
+ assert(a == 25 and b == 15)
+
+ f = assert(package.loadlib(DC"lib1", p.."anotherfunc"))
+ assert(f(10, 20) == "10%20\n")
+
+ -- check error messages
+ local f, err, when = package.loadlib(DC"lib1", p.."xuxu")
+ assert(not f and type(err) == "string" and when == "init")
+ f, err, when = package.loadlib("donotexist", p.."xuxu")
+ assert(not f and type(err) == "string" and when == "open")
+
+ -- symbols from 'lib1' must be visible to other libraries
+ f = assert(package.loadlib(DC"lib11", p.."luaopen_lib11"))
+ assert(f() == "exported")
+
+ -- test C modules with prefixes in names
+ package.cpath = DC"?"
+ local lib2 = require"lib2-v2"
+ -- check correct access to global environment and correct
+ -- parameters
+ assert(_ENV.x == "lib2-v2" and _ENV.y == DC"lib2-v2")
+ assert(lib2.id("x") == "x")
+
+ -- test C submodules
+ local fs = require"lib1.sub"
+ assert(_ENV.x == "lib1.sub" and _ENV.y == DC"lib1")
+ assert(fs.id(45) == 45)
+end
+
+_ENV = _G
+
+
+-- testing preload
+
+do
+ local p = package
+ package = {}
+ p.preload.pl = function (...)
+ local _ENV = {...}
+ function xuxu (x) return x+20 end
+ return _ENV
+ end
+
+ local pl = require"pl"
+ assert(require"pl" == pl)
+ assert(pl.xuxu(10) == 30)
+ assert(pl[1] == "pl" and pl[2] == nil)
+
+ package = p
+ assert(type(package.path) == "string")
+end
+
+print('+')
+
+end --]
+
+print("testing assignments, logical operators, and constructors")
+
+local res, res2 = 27
+
+a, b = 1, 2+3
+assert(a==1 and b==5)
+a={}
+function f() return 10, 11, 12 end
+a.x, b, a[1] = 1, 2, f()
+assert(a.x==1 and b==2 and a[1]==10)
+a[f()], b, a[f()+3] = f(), a, 'x'
+assert(a[10] == 10 and b == a and a[13] == 'x')
+
+do
+ local f = function (n) local x = {}; for i=1,n do x[i]=i end;
+ return table.unpack(x) end;
+ local a,b,c
+ a,b = 0, f(1)
+ assert(a == 0 and b == 1)
+ A,b = 0, f(1)
+ assert(A == 0 and b == 1)
+ a,b,c = 0,5,f(4)
+ assert(a==0 and b==5 and c==1)
+ a,b,c = 0,5,f(0)
+ assert(a==0 and b==5 and c==nil)
+end
+
+a, b, c, d = 1 and nil, 1 or nil, (1 and (nil or 1)), 6
+assert(not a and b and c and d==6)
+
+d = 20
+a, b, c, d = f()
+assert(a==10 and b==11 and c==12 and d==nil)
+a,b = f(), 1, 2, 3, f()
+assert(a==10 and b==1)
+
+assert(ab == true)
+assert((10 and 2) == 2)
+assert((10 or 2) == 10)
+assert((10 or assert(nil)) == 10)
+assert(not (nil and assert(nil)))
+assert((nil or "alo") == "alo")
+assert((nil and 10) == nil)
+assert((false and 10) == false)
+assert((true or 10) == true)
+assert((false or 10) == 10)
+assert(false ~= nil)
+assert(nil ~= false)
+assert(not nil == true)
+assert(not not nil == false)
+assert(not not 1 == true)
+assert(not not a == true)
+assert(not not (6 or nil) == true)
+assert(not not (nil and 56) == false)
+assert(not not (nil and true) == false)
+assert(not 10 == false)
+assert(not {} == false)
+assert(not 0.5 == false)
+assert(not "x" == false)
+
+assert({} ~= {})
+print('+')
+
+a = {}
+a[true] = 20
+a[false] = 10
+assert(a[1<2] == 20 and a[1>2] == 10)
+
+function f(a) return a end
+
+local a = {}
+for i=3000,-3000,-1 do a[i + 0.0] = i; end
+a[10e30] = "alo"; a[true] = 10; a[false] = 20
+assert(a[10e30] == 'alo' and a[not 1] == 20 and a[10<20] == 10)
+for i=3000,-3000,-1 do assert(a[i] == i); end
+a[print] = assert
+a[f] = print
+a[a] = a
+assert(a[a][a][a][a][print] == assert)
+a[print](a[a[f]] == a[print])
+assert(not pcall(function () local a = {}; a[nil] = 10 end))
+assert(not pcall(function () local a = {[nil] = 10} end))
+assert(a[nil] == undef)
+a = nil
+
+a = {10,9,8,7,6,5,4,3,2; [-3]='a', [f]=print, a='a', b='ab'}
+a, a.x, a.y = a, a[-3]
+assert(a[1]==10 and a[-3]==a.a and a[f]==print and a.x=='a' and not a.y)
+a[1], f(a)[2], b, c = {['alo']=assert}, 10, a[1], a[f], 6, 10, 23, f(a), 2
+a[1].alo(a[2]==10 and b==10 and c==print)
+
+a.aVeryLongName012345678901234567890123456789012345678901234567890123456789 = 10
+local function foo ()
+ return a.aVeryLongName012345678901234567890123456789012345678901234567890123456789
+end
+assert(foo() == 10 and
+a.aVeryLongName012345678901234567890123456789012345678901234567890123456789 ==
+10)
+
+
+
+-- test of large float/integer indices
+
+-- compute maximum integer where all bits fit in a float
+local maxint = math.maxinteger
+
+-- trim (if needed) to fit in a float
+while maxint ~= (maxint + 0.0) or (maxint - 1) ~= (maxint - 1.0) do
+ maxint = maxint // 2
+end
+
+maxintF = maxint + 0.0 -- float version
+
+assert(maxintF == maxint and math.type(maxintF) == "float" and
+ maxintF >= 2.0^14)
+
+-- floats and integers must index the same places
+a[maxintF] = 10; a[maxintF - 1.0] = 11;
+a[-maxintF] = 12; a[-maxintF + 1.0] = 13;
+
+assert(a[maxint] == 10 and a[maxint - 1] == 11 and
+ a[-maxint] == 12 and a[-maxint + 1] == 13)
+
+a[maxint] = 20
+a[-maxint] = 22
+
+assert(a[maxintF] == 20 and a[maxintF - 1.0] == 11 and
+ a[-maxintF] == 22 and a[-maxintF + 1.0] == 13)
+
+a = nil
+
+
+-- test conflicts in multiple assignment
+do
+ local a,i,j,b
+ a = {'a', 'b'}; i=1; j=2; b=a
+ i, a[i], a, j, a[j], a[i+j] = j, i, i, b, j, i
+ assert(i == 2 and b[1] == 1 and a == 1 and j == b and b[2] == 2 and
+ b[3] == 1)
+ a = {}
+ local function foo () -- assigining to upvalues
+ b, a.x, a = a, 10, 20
+ end
+ foo()
+ assert(a == 20 and b.x == 10)
+end
+
+-- repeat test with upvalues
+do
+ local a,i,j,b
+ a = {'a', 'b'}; i=1; j=2; b=a
+ local function foo ()
+ i, a[i], a, j, a[j], a[i+j] = j, i, i, b, j, i
+ end
+ foo()
+ assert(i == 2 and b[1] == 1 and a == 1 and j == b and b[2] == 2 and
+ b[3] == 1)
+ local t = {}
+ (function (a) t[a], a = 10, 20 end)(1);
+ assert(t[1] == 10)
+end
+
+-- bug in 5.2 beta
+local function foo ()
+ local a
+ return function ()
+ local b
+ a, b = 3, 14 -- local and upvalue have same index
+ return a, b
+ end
+end
+
+local a, b = foo()()
+assert(a == 3 and b == 14)
+
+print('OK')
+
+return res
+
diff --git a/testes/big.lua b/testes/big.lua
new file mode 100644
index 0000000000..ebee1ec0a4
--- /dev/null
+++ b/testes/big.lua
@@ -0,0 +1,82 @@
+-- $Id: big.lua,v 1.35 2018/03/09 14:23:48 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+if _soft then
+ return 'a'
+end
+
+print "testing large tables"
+
+local debug = require"debug"
+
+local lim = 2^18 + 1000
+local prog = { "local y = {0" }
+for i = 1, lim do prog[#prog + 1] = i end
+prog[#prog + 1] = "}\n"
+prog[#prog + 1] = "X = y\n"
+prog[#prog + 1] = ("assert(X[%d] == %d)"):format(lim - 1, lim - 2)
+prog[#prog + 1] = "return 0"
+prog = table.concat(prog, ";")
+
+local env = {string = string, assert = assert}
+local f = assert(load(prog, nil, nil, env))
+
+f()
+assert(env.X[lim] == lim - 1 and env.X[lim + 1] == lim)
+for k in pairs(env) do env[k] = undef end
+
+-- yields during accesses larger than K (in RK)
+setmetatable(env, {
+ __index = function (t, n) coroutine.yield('g'); return _G[n] end,
+ __newindex = function (t, n, v) coroutine.yield('s'); _G[n] = v end,
+})
+
+X = nil
+co = coroutine.wrap(f)
+assert(co() == 's')
+assert(co() == 'g')
+assert(co() == 'g')
+assert(co() == 0)
+
+assert(X[lim] == lim - 1 and X[lim + 1] == lim)
+
+-- errors in accesses larger than K (in RK)
+getmetatable(env).__index = function () end
+getmetatable(env).__newindex = function () end
+local e, m = pcall(f)
+assert(not e and m:find("global 'X'"))
+
+-- errors in metamethods
+getmetatable(env).__newindex = function () error("hi") end
+local e, m = xpcall(f, debug.traceback)
+assert(not e and m:find("'newindex'"))
+
+f, X = nil
+
+coroutine.yield'b'
+
+if 2^32 == 0 then -- (small integers) {
+
+print "testing string length overflow"
+
+local repstrings = 192 -- number of strings to be concatenated
+local ssize = math.ceil(2.0^32 / repstrings) + 1 -- size of each string
+
+assert(repstrings * ssize > 2.0^32) -- it should be larger than maximum size
+
+local longs = string.rep("\0", ssize) -- create one long string
+
+-- create function to concatentate 'repstrings' copies of its argument
+local rep = assert(load(
+ "local a = ...; return " .. string.rep("a", repstrings, "..")))
+
+local a, b = pcall(rep, longs) -- call that function
+
+-- it should fail without creating string (result would be too large)
+assert(not a and string.find(b, "overflow"))
+
+end -- }
+
+print'OK'
+
+return 'a'
diff --git a/testes/bitwise.lua b/testes/bitwise.lua
new file mode 100755
index 0000000000..3e7079d386
--- /dev/null
+++ b/testes/bitwise.lua
@@ -0,0 +1,346 @@
+-- $Id: bitwise.lua,v 1.27 2018/02/21 17:49:39 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print("testing bitwise operations")
+
+require "bwcoercion"
+
+local numbits = string.packsize('j') * 8
+
+assert(~0 == -1)
+
+assert((1 << (numbits - 1)) == math.mininteger)
+
+-- basic tests for bitwise operators;
+-- use variables to avoid constant folding
+local a, b, c, d
+a = 0xFFFFFFFFFFFFFFFF
+assert(a == -1 and a & -1 == a and a & 35 == 35)
+a = 0xF0F0F0F0F0F0F0F0
+assert(a | -1 == -1)
+assert(a ~ a == 0 and a ~ 0 == a and a ~ ~a == -1)
+assert(a >> 4 == ~a)
+a = 0xF0; b = 0xCC; c = 0xAA; d = 0xFD
+assert(a | b ~ c & d == 0xF4)
+
+a = 0xF0.0; b = 0xCC.0; c = "0xAA.0"; d = "0xFD.0"
+assert(a | b ~ c & d == 0xF4)
+
+a = 0xF0000000; b = 0xCC000000;
+c = 0xAA000000; d = 0xFD000000
+assert(a | b ~ c & d == 0xF4000000)
+assert(~~a == a and ~a == -1 ~ a and -d == ~d + 1)
+
+a = a << 32
+b = b << 32
+c = c << 32
+d = d << 32
+assert(a | b ~ c & d == 0xF4000000 << 32)
+assert(~~a == a and ~a == -1 ~ a and -d == ~d + 1)
+
+assert(-1 >> 1 == (1 << (numbits - 1)) - 1 and 1 << 31 == 0x80000000)
+assert(-1 >> (numbits - 1) == 1)
+assert(-1 >> numbits == 0 and
+ -1 >> -numbits == 0 and
+ -1 << numbits == 0 and
+ -1 << -numbits == 0)
+
+assert((2^30 - 1) << 2^30 == 0)
+assert((2^30 - 1) >> 2^30 == 0)
+
+assert(1 >> -3 == 1 << 3 and 1000 >> 5 == 1000 << -5)
+
+
+-- coercion from strings to integers
+assert("0xffffffffffffffff" | 0 == -1)
+assert("0xfffffffffffffffe" & "-1" == -2)
+assert(" \t-0xfffffffffffffffe\n\t" & "-1" == 2)
+assert(" \n -45 \t " >> " -2 " == -45 * 4)
+assert("1234.0" << "5.0" == 1234 * 32)
+assert("0xffff.0" ~ "0xAAAA" == 0x5555)
+assert(~"0x0.000p4" == -1)
+
+assert("7" .. 3 << 1 == 146)
+assert(10 >> 1 .. "9" == 0)
+assert(10 | 1 .. "9" == 27)
+
+do
+ local st, msg = pcall(function () return 4 & "a" end)
+ assert(string.find(msg, "'band'"))
+
+ local st, msg = pcall(function () return ~"a" end)
+ assert(string.find(msg, "'bnot'"))
+end
+
+
+-- out of range number
+assert(not pcall(function () return "0xffffffffffffffff.0" | 0 end))
+
+-- embedded zeros
+assert(not pcall(function () return "0xffffffffffffffff\0" | 0 end))
+
+print'+'
+
+
+package.preload.bit32 = function () --{
+
+-- no built-in 'bit32' library: implement it using bitwise operators
+
+local bit = {}
+
+function bit.bnot (a)
+ return ~a & 0xFFFFFFFF
+end
+
+
+--
+-- in all vararg functions, avoid creating 'arg' table when there are
+-- only 2 (or less) parameters, as 2 parameters is the common case
+--
+
+function bit.band (x, y, z, ...)
+ if not z then
+ return ((x or -1) & (y or -1)) & 0xFFFFFFFF
+ else
+ local arg = {...}
+ local res = x & y & z
+ for i = 1, #arg do res = res & arg[i] end
+ return res & 0xFFFFFFFF
+ end
+end
+
+function bit.bor (x, y, z, ...)
+ if not z then
+ return ((x or 0) | (y or 0)) & 0xFFFFFFFF
+ else
+ local arg = {...}
+ local res = x | y | z
+ for i = 1, #arg do res = res | arg[i] end
+ return res & 0xFFFFFFFF
+ end
+end
+
+function bit.bxor (x, y, z, ...)
+ if not z then
+ return ((x or 0) ~ (y or 0)) & 0xFFFFFFFF
+ else
+ local arg = {...}
+ local res = x ~ y ~ z
+ for i = 1, #arg do res = res ~ arg[i] end
+ return res & 0xFFFFFFFF
+ end
+end
+
+function bit.btest (...)
+ return bit.band(...) ~= 0
+end
+
+function bit.lshift (a, b)
+ return ((a & 0xFFFFFFFF) << b) & 0xFFFFFFFF
+end
+
+function bit.rshift (a, b)
+ return ((a & 0xFFFFFFFF) >> b) & 0xFFFFFFFF
+end
+
+function bit.arshift (a, b)
+ a = a & 0xFFFFFFFF
+ if b <= 0 or (a & 0x80000000) == 0 then
+ return (a >> b) & 0xFFFFFFFF
+ else
+ return ((a >> b) | ~(0xFFFFFFFF >> b)) & 0xFFFFFFFF
+ end
+end
+
+function bit.lrotate (a ,b)
+ b = b & 31
+ a = a & 0xFFFFFFFF
+ a = (a << b) | (a >> (32 - b))
+ return a & 0xFFFFFFFF
+end
+
+function bit.rrotate (a, b)
+ return bit.lrotate(a, -b)
+end
+
+local function checkfield (f, w)
+ w = w or 1
+ assert(f >= 0, "field cannot be negative")
+ assert(w > 0, "width must be positive")
+ assert(f + w <= 32, "trying to access non-existent bits")
+ return f, ~(-1 << w)
+end
+
+function bit.extract (a, f, w)
+ local f, mask = checkfield(f, w)
+ return (a >> f) & mask
+end
+
+function bit.replace (a, v, f, w)
+ local f, mask = checkfield(f, w)
+ v = v & mask
+ a = (a & ~(mask << f)) | (v << f)
+ return a & 0xFFFFFFFF
+end
+
+return bit
+
+end --}
+
+
+print("testing bitwise library")
+
+local bit32 = require'bit32'
+
+assert(bit32.band() == bit32.bnot(0))
+assert(bit32.btest() == true)
+assert(bit32.bor() == 0)
+assert(bit32.bxor() == 0)
+
+assert(bit32.band() == bit32.band(0xffffffff))
+assert(bit32.band(1,2) == 0)
+
+
+-- out-of-range numbers
+assert(bit32.band(-1) == 0xffffffff)
+assert(bit32.band((1 << 33) - 1) == 0xffffffff)
+assert(bit32.band(-(1 << 33) - 1) == 0xffffffff)
+assert(bit32.band((1 << 33) + 1) == 1)
+assert(bit32.band(-(1 << 33) + 1) == 1)
+assert(bit32.band(-(1 << 40)) == 0)
+assert(bit32.band(1 << 40) == 0)
+assert(bit32.band(-(1 << 40) - 2) == 0xfffffffe)
+assert(bit32.band((1 << 40) - 4) == 0xfffffffc)
+
+assert(bit32.lrotate(0, -1) == 0)
+assert(bit32.lrotate(0, 7) == 0)
+assert(bit32.lrotate(0x12345678, 0) == 0x12345678)
+assert(bit32.lrotate(0x12345678, 32) == 0x12345678)
+assert(bit32.lrotate(0x12345678, 4) == 0x23456781)
+assert(bit32.rrotate(0x12345678, -4) == 0x23456781)
+assert(bit32.lrotate(0x12345678, -8) == 0x78123456)
+assert(bit32.rrotate(0x12345678, 8) == 0x78123456)
+assert(bit32.lrotate(0xaaaaaaaa, 2) == 0xaaaaaaaa)
+assert(bit32.lrotate(0xaaaaaaaa, -2) == 0xaaaaaaaa)
+for i = -50, 50 do
+ assert(bit32.lrotate(0x89abcdef, i) == bit32.lrotate(0x89abcdef, i%32))
+end
+
+assert(bit32.lshift(0x12345678, 4) == 0x23456780)
+assert(bit32.lshift(0x12345678, 8) == 0x34567800)
+assert(bit32.lshift(0x12345678, -4) == 0x01234567)
+assert(bit32.lshift(0x12345678, -8) == 0x00123456)
+assert(bit32.lshift(0x12345678, 32) == 0)
+assert(bit32.lshift(0x12345678, -32) == 0)
+assert(bit32.rshift(0x12345678, 4) == 0x01234567)
+assert(bit32.rshift(0x12345678, 8) == 0x00123456)
+assert(bit32.rshift(0x12345678, 32) == 0)
+assert(bit32.rshift(0x12345678, -32) == 0)
+assert(bit32.arshift(0x12345678, 0) == 0x12345678)
+assert(bit32.arshift(0x12345678, 1) == 0x12345678 // 2)
+assert(bit32.arshift(0x12345678, -1) == 0x12345678 * 2)
+assert(bit32.arshift(-1, 1) == 0xffffffff)
+assert(bit32.arshift(-1, 24) == 0xffffffff)
+assert(bit32.arshift(-1, 32) == 0xffffffff)
+assert(bit32.arshift(-1, -1) == bit32.band(-1 * 2, 0xffffffff))
+
+assert(0x12345678 << 4 == 0x123456780)
+assert(0x12345678 << 8 == 0x1234567800)
+assert(0x12345678 << -4 == 0x01234567)
+assert(0x12345678 << -8 == 0x00123456)
+assert(0x12345678 << 32 == 0x1234567800000000)
+assert(0x12345678 << -32 == 0)
+assert(0x12345678 >> 4 == 0x01234567)
+assert(0x12345678 >> 8 == 0x00123456)
+assert(0x12345678 >> 32 == 0)
+assert(0x12345678 >> -32 == 0x1234567800000000)
+
+print("+")
+-- some special cases
+local c = {0, 1, 2, 3, 10, 0x80000000, 0xaaaaaaaa, 0x55555555,
+ 0xffffffff, 0x7fffffff}
+
+for _, b in pairs(c) do
+ assert(bit32.band(b) == b)
+ assert(bit32.band(b, b) == b)
+ assert(bit32.band(b, b, b, b) == b)
+ assert(bit32.btest(b, b) == (b ~= 0))
+ assert(bit32.band(b, b, b) == b)
+ assert(bit32.band(b, b, b, ~b) == 0)
+ assert(bit32.btest(b, b, b) == (b ~= 0))
+ assert(bit32.band(b, bit32.bnot(b)) == 0)
+ assert(bit32.bor(b, bit32.bnot(b)) == bit32.bnot(0))
+ assert(bit32.bor(b) == b)
+ assert(bit32.bor(b, b) == b)
+ assert(bit32.bor(b, b, b) == b)
+ assert(bit32.bor(b, b, 0, ~b) == 0xffffffff)
+ assert(bit32.bxor(b) == b)
+ assert(bit32.bxor(b, b) == 0)
+ assert(bit32.bxor(b, b, b) == b)
+ assert(bit32.bxor(b, b, b, b) == 0)
+ assert(bit32.bxor(b, 0) == b)
+ assert(bit32.bnot(b) ~= b)
+ assert(bit32.bnot(bit32.bnot(b)) == b)
+ assert(bit32.bnot(b) == (1 << 32) - 1 - b)
+ assert(bit32.lrotate(b, 32) == b)
+ assert(bit32.rrotate(b, 32) == b)
+ assert(bit32.lshift(bit32.lshift(b, -4), 4) == bit32.band(b, bit32.bnot(0xf)))
+ assert(bit32.rshift(bit32.rshift(b, 4), -4) == bit32.band(b, bit32.bnot(0xf)))
+end
+
+-- for this test, use at most 24 bits (mantissa of a single float)
+c = {0, 1, 2, 3, 10, 0x800000, 0xaaaaaa, 0x555555, 0xffffff, 0x7fffff}
+for _, b in pairs(c) do
+ for i = -40, 40 do
+ local x = bit32.lshift(b, i)
+ local y = math.floor(math.fmod(b * 2.0^i, 2.0^32))
+ assert(math.fmod(x - y, 2.0^32) == 0)
+ end
+end
+
+assert(not pcall(bit32.band, {}))
+assert(not pcall(bit32.bnot, "a"))
+assert(not pcall(bit32.lshift, 45))
+assert(not pcall(bit32.lshift, 45, print))
+assert(not pcall(bit32.rshift, 45, print))
+
+print("+")
+
+
+-- testing extract/replace
+
+assert(bit32.extract(0x12345678, 0, 4) == 8)
+assert(bit32.extract(0x12345678, 4, 4) == 7)
+assert(bit32.extract(0xa0001111, 28, 4) == 0xa)
+assert(bit32.extract(0xa0001111, 31, 1) == 1)
+assert(bit32.extract(0x50000111, 31, 1) == 0)
+assert(bit32.extract(0xf2345679, 0, 32) == 0xf2345679)
+
+assert(not pcall(bit32.extract, 0, -1))
+assert(not pcall(bit32.extract, 0, 32))
+assert(not pcall(bit32.extract, 0, 0, 33))
+assert(not pcall(bit32.extract, 0, 31, 2))
+
+assert(bit32.replace(0x12345678, 5, 28, 4) == 0x52345678)
+assert(bit32.replace(0x12345678, 0x87654321, 0, 32) == 0x87654321)
+assert(bit32.replace(0, 1, 2) == 2^2)
+assert(bit32.replace(0, -1, 4) == 2^4)
+assert(bit32.replace(-1, 0, 31) == (1 << 31) - 1)
+assert(bit32.replace(-1, 0, 1, 2) == (1 << 32) - 7)
+
+
+-- testing conversion of floats
+
+assert(bit32.bor(3.0) == 3)
+assert(bit32.bor(-4.0) == 0xfffffffc)
+
+-- large floats and large-enough integers?
+if 2.0^50 < 2.0^50 + 1.0 and 2.0^50 < (-1 >> 1) then
+ assert(bit32.bor(2.0^32 - 5.0) == 0xfffffffb)
+ assert(bit32.bor(-2.0^32 - 6.0) == 0xfffffffa)
+ assert(bit32.bor(2.0^48 - 5.0) == 0xfffffffb)
+ assert(bit32.bor(-2.0^48 - 6.0) == 0xfffffffa)
+end
+
+print'OK'
+
diff --git a/testes/bwcoercion.lua b/testes/bwcoercion.lua
new file mode 100644
index 0000000000..cd735ab0b6
--- /dev/null
+++ b/testes/bwcoercion.lua
@@ -0,0 +1,78 @@
+local tonumber, tointeger = tonumber, math.tointeger
+local type, getmetatable, rawget, error = type, getmetatable, rawget, error
+local strsub = string.sub
+
+local print = print
+
+_ENV = nil
+
+-- Try to convert a value to an integer, without assuming any coercion.
+local function toint (x)
+ x = tonumber(x) -- handle numerical strings
+ if not x then
+ return false -- not coercible to a number
+ end
+ return tointeger(x)
+end
+
+
+-- If operation fails, maybe second operand has a metamethod that should
+-- have been called if not for this string metamethod, so try to
+-- call it.
+local function trymt (x, y, mtname)
+ if type(y) ~= "string" then -- avoid recalling original metamethod
+ local mt = getmetatable(y)
+ local mm = mt and rawget(mt, mtname)
+ if mm then
+ return mm(x, y)
+ end
+ end
+ -- if any test fails, there is no other metamethod to be called
+ error("attempt to '" .. strsub(mtname, 3) ..
+ "' a " .. type(x) .. " with a " .. type(y), 4)
+end
+
+
+local function checkargs (x, y, mtname)
+ local xi = toint(x)
+ local yi = toint(y)
+ if xi and yi then
+ return xi, yi
+ else
+ return trymt(x, y, mtname), nil
+ end
+end
+
+
+local smt = getmetatable("")
+
+smt.__band = function (x, y)
+ local x, y = checkargs(x, y, "__band")
+ return y and x & y or x
+end
+
+smt.__bor = function (x, y)
+ local x, y = checkargs(x, y, "__bor")
+ return y and x | y or x
+end
+
+smt.__bxor = function (x, y)
+ local x, y = checkargs(x, y, "__bxor")
+ return y and x ~ y or x
+end
+
+smt.__shl = function (x, y)
+ local x, y = checkargs(x, y, "__shl")
+ return y and x << y or x
+end
+
+smt.__shr = function (x, y)
+ local x, y = checkargs(x, y, "__shr")
+ return y and x >> y or x
+end
+
+smt.__bnot = function (x)
+ local x, y = checkargs(x, x, "__bnot")
+ return y and ~x or x
+end
+
diff --git a/testes/calls.lua b/testes/calls.lua
new file mode 100644
index 0000000000..95d9d6d6ed
--- /dev/null
+++ b/testes/calls.lua
@@ -0,0 +1,435 @@
+-- $Id: calls.lua,v 1.66 2018/02/09 16:35:21 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print("testing functions and calls")
+
+local debug = require "debug"
+
+-- get the opportunity to test 'type' too ;)
+
+assert(type(1<2) == 'boolean')
+assert(type(true) == 'boolean' and type(false) == 'boolean')
+assert(type(nil) == 'nil'
+ and type(-3) == 'number'
+ and type'x' == 'string'
+ and type{} == 'table'
+ and type(type) == 'function')
+
+assert(type(assert) == type(print))
+function f (x) return a:x (x) end
+assert(type(f) == 'function')
+assert(not pcall(type))
+
+
+do -- test error in 'print' too...
+ local tostring = _ENV.tostring
+
+ _ENV.tostring = nil
+ local st, msg = pcall(print, 1)
+ assert(st == false and string.find(msg, "attempt to call a nil value"))
+
+ _ENV.tostring = function () return {} end
+ local st, msg = pcall(print, 1)
+ assert(st == false and string.find(msg, "must return a string"))
+
+ _ENV.tostring = tostring
+end
+
+
+-- testing local-function recursion
+fact = false
+do
+ local res = 1
+ local function fact (n)
+ if n==0 then return res
+ else return n*fact(n-1)
+ end
+ end
+ assert(fact(5) == 120)
+end
+assert(fact == false)
+
+-- testing declarations
+a = {i = 10}
+self = 20
+function a:x (x) return x+self.i end
+function a.y (x) return x+self end
+
+assert(a:x(1)+10 == a.y(1))
+
+a.t = {i=-100}
+a["t"].x = function (self, a,b) return self.i+a+b end
+
+assert(a.t:x(2,3) == -95)
+
+do
+ local a = {x=0}
+ function a:add (x) self.x, a.y = self.x+x, 20; return self end
+ assert(a:add(10):add(20):add(30).x == 60 and a.y == 20)
+end
+
+local a = {b={c={}}}
+
+function a.b.c.f1 (x) return x+1 end
+function a.b.c:f2 (x,y) self[x] = y end
+assert(a.b.c.f1(4) == 5)
+a.b.c:f2('k', 12); assert(a.b.c.k == 12)
+
+print('+')
+
+t = nil -- 'declare' t
+function f(a,b,c) local d = 'a'; t={a,b,c,d} end
+
+f( -- this line change must be valid
+ 1,2)
+assert(t[1] == 1 and t[2] == 2 and t[3] == nil and t[4] == 'a')
+f(1,2, -- this one too
+ 3,4)
+assert(t[1] == 1 and t[2] == 2 and t[3] == 3 and t[4] == 'a')
+
+function fat(x)
+ if x <= 1 then return 1
+ else return x*load("return fat(" .. x-1 .. ")", "")()
+ end
+end
+
+assert(load "load 'assert(fat(6)==720)' () ")()
+a = load('return fat(5), 3')
+a,b = a()
+assert(a == 120 and b == 3)
+print('+')
+
+function err_on_n (n)
+ if n==0 then error(); exit(1);
+ else err_on_n (n-1); exit(1);
+ end
+end
+
+do
+ function dummy (n)
+ if n > 0 then
+ assert(not pcall(err_on_n, n))
+ dummy(n-1)
+ end
+ end
+end
+
+dummy(10)
+
+function deep (n)
+ if n>0 then deep(n-1) end
+end
+deep(10)
+deep(180)
+
+-- testing tail calls
+function deep (n) if n>0 then return deep(n-1) else return 101 end end
+assert(deep(30000) == 101)
+a = {}
+function a:deep (n) if n>0 then return self:deep(n-1) else return 101 end end
+assert(a:deep(30000) == 101)
+
+do -- tail calls x varargs
+ local function foo (x, ...) local a = {...}; return x, a[1], a[2] end
+
+ local function foo1 (x) return foo(10, x, x + 1) end
+
+ local a, b, c = foo1(-2)
+ assert(a == 10 and b == -2 and c == -1)
+
+ -- tail calls x metamethods
+ local t = setmetatable({}, {__call = foo})
+ local function foo2 (x) return t(10, x) end
+ a, b, c = foo2(100)
+ assert(a == t and b == 10 and c == 100)
+
+ a, b = (function () return foo() end)()
+ assert(a == nil and b == nil)
+
+ local X, Y, A
+ local function foo (x, y, ...) X = x; Y = y; A = {...} end
+ local function foo1 (...) return foo(...) end
+
+ local a, b, c = foo1()
+ assert(X == nil and Y == nil and #A == 0)
+
+ a, b, c = foo1(10)
+ assert(X == 10 and Y == nil and #A == 0)
+
+ a, b, c = foo1(10, 20)
+ assert(X == 10 and Y == 20 and #A == 0)
+
+ a, b, c = foo1(10, 20, 30)
+ assert(X == 10 and Y == 20 and #A == 1 and A[1] == 30)
+end
+
+print('+')
+
+
+a = nil
+(function (x) a=x end)(23)
+assert(a == 23 and (function (x) return x*2 end)(20) == 40)
+
+
+-- testing closures
+
+-- fixed-point operator
+Z = function (le)
+ local function a (f)
+ return le(function (x) return f(f)(x) end)
+ end
+ return a(a)
+ end
+
+
+-- non-recursive factorial
+
+F = function (f)
+ return function (n)
+ if n == 0 then return 1
+ else return n*f(n-1) end
+ end
+ end
+
+fat = Z(F)
+
+assert(fat(0) == 1 and fat(4) == 24 and Z(F)(5)==5*Z(F)(4))
+
+local function g (z)
+ local function f (a,b,c,d)
+ return function (x,y) return a+b+c+d+a+x+y+z end
+ end
+ return f(z,z+1,z+2,z+3)
+end
+
+f = g(10)
+assert(f(9, 16) == 10+11+12+13+10+9+16+10)
+
+Z, F, f = nil
+print('+')
+
+-- testing multiple returns
+
+function unlpack (t, i)
+ i = i or 1
+ if (i <= #t) then
+ return t[i], unlpack(t, i+1)
+ end
+end
+
+function equaltab (t1, t2)
+ assert(#t1 == #t2)
+ for i = 1, #t1 do
+ assert(t1[i] == t2[i])
+ end
+end
+
+local pack = function (...) return (table.pack(...)) end
+
+function f() return 1,2,30,4 end
+function ret2 (a,b) return a,b end
+
+local a,b,c,d = unlpack{1,2,3}
+assert(a==1 and b==2 and c==3 and d==nil)
+a = {1,2,3,4,false,10,'alo',false,assert}
+equaltab(pack(unlpack(a)), a)
+equaltab(pack(unlpack(a), -1), {1,-1})
+a,b,c,d = ret2(f()), ret2(f())
+assert(a==1 and b==1 and c==2 and d==nil)
+a,b,c,d = unlpack(pack(ret2(f()), ret2(f())))
+assert(a==1 and b==1 and c==2 and d==nil)
+a,b,c,d = unlpack(pack(ret2(f()), (ret2(f()))))
+assert(a==1 and b==1 and c==nil and d==nil)
+
+a = ret2{ unlpack{1,2,3}, unlpack{3,2,1}, unlpack{"a", "b"}}
+assert(a[1] == 1 and a[2] == 3 and a[3] == "a" and a[4] == "b")
+
+
+-- testing calls with 'incorrect' arguments
+rawget({}, "x", 1)
+rawset({}, "x", 1, 2)
+assert(math.sin(1,2) == math.sin(1))
+table.sort({10,9,8,4,19,23,0,0}, function (a,b) return a 10 or a[i]() ~= x
+assert(i == 11 and a[1]() == 1 and a[3]() == 3 and i == 4)
+
+
+-- testing closures created in 'then' and 'else' parts of 'if's
+a = {}
+for i = 1, 10 do
+ if i % 3 == 0 then
+ local y = 0
+ a[i] = function (x) local t = y; y = x; return t end
+ elseif i % 3 == 1 then
+ goto L1
+ error'not here'
+ ::L1::
+ local y = 1
+ a[i] = function (x) local t = y; y = x; return t end
+ elseif i % 3 == 2 then
+ local t
+ goto l4
+ ::l4a:: a[i] = t; goto l4b
+ error("should never be here!")
+ ::l4::
+ local y = 2
+ t = function (x) local t = y; y = x; return t end
+ goto l4a
+ error("should never be here!")
+ ::l4b::
+ end
+end
+
+for i = 1, 10 do
+ assert(a[i](i * 10) == i % 3 and a[i]() == i * 10)
+end
+
+print'+'
+
+
+-- test for correctly closing upvalues in tail calls of vararg functions
+local function t ()
+ local function c(a,b) assert(a=="test" and b=="OK") end
+ local function v(f, ...) c("test", f() ~= 1 and "FAILED" or "OK") end
+ local x = 1
+ return v(function() return x end)
+end
+t()
+
+
+-- test for debug manipulation of upvalues
+local debug = require'debug'
+
+do
+ local a , b, c = 3, 5, 7
+ foo1 = function () return a+b end;
+ foo2 = function () return b+a end;
+ do
+ local a = 10
+ foo3 = function () return a+b end;
+ end
+end
+
+assert(debug.upvalueid(foo1, 1))
+assert(debug.upvalueid(foo1, 2))
+assert(not pcall(debug.upvalueid, foo1, 3))
+assert(debug.upvalueid(foo1, 1) == debug.upvalueid(foo2, 2))
+assert(debug.upvalueid(foo1, 2) == debug.upvalueid(foo2, 1))
+assert(debug.upvalueid(foo3, 1))
+assert(debug.upvalueid(foo1, 1) ~= debug.upvalueid(foo3, 1))
+assert(debug.upvalueid(foo1, 2) == debug.upvalueid(foo3, 2))
+
+assert(debug.upvalueid(string.gmatch("x", "x"), 1) ~= nil)
+
+assert(foo1() == 3 + 5 and foo2() == 5 + 3)
+debug.upvaluejoin(foo1, 2, foo2, 2)
+assert(foo1() == 3 + 3 and foo2() == 5 + 3)
+assert(foo3() == 10 + 5)
+debug.upvaluejoin(foo3, 2, foo2, 1)
+assert(foo3() == 10 + 5)
+debug.upvaluejoin(foo3, 2, foo2, 2)
+assert(foo3() == 10 + 3)
+
+assert(not pcall(debug.upvaluejoin, foo1, 3, foo2, 1))
+assert(not pcall(debug.upvaluejoin, foo1, 1, foo2, 3))
+assert(not pcall(debug.upvaluejoin, foo1, 0, foo2, 1))
+assert(not pcall(debug.upvaluejoin, print, 1, foo2, 1))
+assert(not pcall(debug.upvaluejoin, {}, 1, foo2, 1))
+assert(not pcall(debug.upvaluejoin, foo1, 1, print, 1))
+
+print'OK'
diff --git a/testes/code.lua b/testes/code.lua
new file mode 100644
index 0000000000..e39c62adc6
--- /dev/null
+++ b/testes/code.lua
@@ -0,0 +1,347 @@
+-- $Id: code.lua,v 1.55 2018/03/12 14:19:36 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+if T==nil then
+ (Message or print)('\n >>> testC not active: skipping opcode tests <<<\n')
+ return
+end
+print "testing code generation and optimizations"
+
+
+-- this code gave an error for the code checker
+do
+ local function f (a)
+ for k,v,w in a do end
+ end
+end
+
+
+-- testing reuse in constant table
+local function checkKlist (func, list)
+ local k = T.listk(func)
+ assert(#k == #list)
+ for i = 1, #k do
+ assert(k[i] == list[i] and math.type(k[i]) == math.type(list[i]))
+ end
+end
+
+local function foo ()
+ local a
+ a = 3;
+ a = 0; a = 0.0; a = -7 + 7
+ a = 3.78/4; a = 3.78/4
+ a = -3.78/4; a = 3.78/4; a = -3.78/4
+ a = -3.79/4; a = 0.0; a = -0;
+ a = 3; a = 3.0; a = 3; a = 3.0
+end
+
+checkKlist(foo, {3.78/4, -3.78/4, -3.79/4})
+
+
+-- testing opcodes
+
+function check (f, ...)
+ local arg = {...}
+ local c = T.listcode(f)
+ for i=1, #arg do
+ local opcode = string.match(c[i], "%u%w+")
+ -- print(arg[i], opcode)
+ assert(arg[i] == opcode)
+ end
+ assert(c[#arg+2] == undef)
+end
+
+
+function checkequal (a, b)
+ a = T.listcode(a)
+ b = T.listcode(b)
+ for i = 1, #a do
+ a[i] = string.gsub(a[i], '%b()', '') -- remove line number
+ b[i] = string.gsub(b[i], '%b()', '') -- remove line number
+ assert(a[i] == b[i])
+ end
+end
+
+
+-- some basic instructions
+check(function ()
+ (function () end){f()}
+end, 'CLOSURE', 'NEWTABLE', 'GETTABUP', 'CALL', 'SETLIST', 'CALL', 'RETURN')
+
+
+-- sequence of LOADNILs
+check(function ()
+ local a,b,c
+ local d; local e;
+ local f,g,h;
+ d = nil; d=nil; b=nil; a=nil; c=nil;
+end, 'LOADNIL', 'RETURN0')
+
+check(function ()
+ local a,b,c,d = 1,1,1,1
+ d=nil;c=nil;b=nil;a=nil
+end, 'LOADI', 'LOADI', 'LOADI', 'LOADI', 'LOADNIL', 'RETURN0')
+
+do
+ local a,b,c,d = 1,1,1,1
+ d=nil;c=nil;b=nil;a=nil
+ assert(a == nil and b == nil and c == nil and d == nil)
+end
+
+
+-- single return
+check (function (a,b,c) return a end, 'RETURN1')
+
+
+-- infinite loops
+check(function () while true do local a = -1 end end,
+'LOADI', 'JMP', 'RETURN0')
+
+check(function () while 1 do local a = -1 end end,
+'LOADI', 'JMP', 'RETURN0')
+
+check(function () repeat local x = 1 until true end,
+'LOADI', 'RETURN0')
+
+
+-- concat optimization
+check(function (a,b,c,d) return a..b..c..d end,
+ 'MOVE', 'MOVE', 'MOVE', 'MOVE', 'CONCAT', 'RETURN1')
+
+-- not
+check(function () return not not nil end, 'LOADBOOL', 'RETURN1')
+check(function () return not not false end, 'LOADBOOL', 'RETURN1')
+check(function () return not not true end, 'LOADBOOL', 'RETURN1')
+check(function () return not not 1 end, 'LOADBOOL', 'RETURN1')
+
+-- direct access to locals
+check(function ()
+ local a,b,c,d
+ a = b*a
+ c.x, a[b] = -((a + d/b - a[b]) ^ a.x), b
+end,
+ 'LOADNIL',
+ 'MUL',
+ 'DIV', 'ADD', 'GETTABLE', 'SUB', 'GETFIELD', 'POW',
+ 'UNM', 'SETTABLE', 'SETFIELD', 'RETURN0')
+
+
+-- direct access to constants
+check(function ()
+ local a,b
+ a.x = 3.2
+ a.x = b
+ a[b] = 'x'
+end,
+ 'LOADNIL', 'SETFIELD', 'SETFIELD', 'SETTABLE', 'RETURN0')
+
+-- "get/set table" with numeric indices
+check(function (a)
+ a[1] = a[100]
+ a[255] = a[256]
+ a[256] = 5
+end,
+ 'GETI', 'SETI',
+ 'LOADI', 'GETTABLE', 'SETI',
+ 'LOADI', 'SETTABLE', 'RETURN0')
+
+check(function ()
+ local a,b
+ a = a - a
+ b = a/a
+ b = 5-4
+end,
+ 'LOADNIL', 'SUB', 'DIV', 'LOADI', 'RETURN0')
+
+check(function ()
+ local a,b
+ a[true] = false
+end,
+ 'LOADNIL', 'LOADBOOL', 'SETTABLE', 'RETURN0')
+
+
+-- equalities
+check(function (a) if a == 1 then return 2 end end,
+ 'EQI', 'JMP', 'LOADI', 'RETURN1')
+
+check(function (a) if -4.0 == a then return 2 end end,
+ 'EQI', 'JMP', 'LOADI', 'RETURN1')
+
+check(function (a) if a == "hi" then return 2 end end,
+ 'EQK', 'JMP', 'LOADI', 'RETURN1')
+
+check(function (a) if a == 10000 then return 2 end end,
+ 'EQK', 'JMP', 'LOADI', 'RETURN1') -- number too large
+
+check(function (a) if -10000 == a then return 2 end end,
+ 'EQK', 'JMP', 'LOADI', 'RETURN1') -- number too large
+
+-- comparisons
+
+check(function (a) if -10 <= a then return 2 end end,
+ 'GEI', 'JMP', 'LOADI', 'RETURN1')
+
+check(function (a) if 128.0 > a then return 2 end end,
+ 'LTI', 'JMP', 'LOADI', 'RETURN1')
+
+check(function (a) if -127.0 < a then return 2 end end,
+ 'GTI', 'JMP', 'LOADI', 'RETURN1')
+
+check(function (a) if 10 < a then return 2 end end,
+ 'GTI', 'JMP', 'LOADI', 'RETURN1')
+
+check(function (a) if 129 < a then return 2 end end,
+ 'LOADI', 'LT', 'JMP', 'LOADI', 'RETURN1')
+
+check(function (a) if a >= 23.0 then return 2 end end,
+ 'GEI', 'JMP', 'LOADI', 'RETURN1')
+
+check(function (a) if a >= 23.1 then return 2 end end,
+ 'LOADK', 'LE', 'JMP', 'LOADI', 'RETURN1')
+
+check(function (a) if a > 2300.0 then return 2 end end,
+ 'LOADF', 'LT', 'JMP', 'LOADI', 'RETURN1')
+
+
+-- constant folding
+local function checkK (func, val)
+ check(func, 'LOADK', 'RETURN1')
+ local k = T.listk(func)
+ assert(#k == 1 and k[1] == val and math.type(k[1]) == math.type(val))
+ assert(func() == val)
+end
+
+local function checkI (func, val)
+ check(func, 'LOADI', 'RETURN1')
+ assert(#T.listk(func) == 0)
+ assert(func() == val)
+end
+
+local function checkF (func, val)
+ check(func, 'LOADF', 'RETURN1')
+ assert(#T.listk(func) == 0)
+ assert(func() == val)
+end
+
+checkF(function () return 0.0 end, 0.0)
+checkI(function () return 0 end, 0)
+checkI(function () return -0//1 end, 0)
+checkK(function () return 3^-1 end, 1/3)
+checkK(function () return (1 + 1)^(50 + 50) end, 2^100)
+checkK(function () return (-2)^(31 - 2) end, -0x20000000 + 0.0)
+checkF(function () return (-3^0 + 5) // 3.0 end, 1.0)
+checkI(function () return -3 % 5 end, 2)
+checkF(function () return -((2.0^8 + -(-1)) % 8)/2 * 4 - 3 end, -5.0)
+checkF(function () return -((2^8 + -(-1)) % 8)//2 * 4 - 3 end, -7.0)
+checkI(function () return 0xF0.0 | 0xCC.0 ~ 0xAA & 0xFD end, 0xF4)
+checkI(function () return ~(~0xFF0 | 0xFF0) end, 0)
+checkI(function () return ~~-1024.0 end, -1024)
+checkI(function () return ((100 << 6) << -4) >> 2 end, 100)
+
+-- borders around MAXARG_sBx ((((1 << 17) - 1) >> 1) == 65535)
+local sbx = ((1 << "17") - 1) >> 1 -- avoid folding
+checkI(function () return 65535 end, sbx)
+checkI(function () return -65535 end, -sbx)
+checkI(function () return 65536 end, sbx + 1)
+checkK(function () return 65537 end, sbx + 2)
+checkK(function () return -65536 end, -(sbx + 1))
+
+checkF(function () return 65535.0 end, sbx + 0.0)
+checkF(function () return -65535.0 end, -sbx + 0.0)
+checkF(function () return 65536.0 end, (sbx + 1.0))
+checkK(function () return 65537.0 end, (sbx + 2.0))
+checkK(function () return -65536.0 end, -(sbx + 1.0))
+
+
+-- immediate operands
+check(function (x) return x + 1 end, 'ADDI', 'RETURN1')
+check(function (x) return 128 + x end, 'ADDI', 'RETURN1')
+check(function (x) return x * -127 end, 'MULI', 'RETURN1')
+check(function (x) return 20 * x end, 'MULI', 'RETURN1')
+check(function (x) return x ^ -2 end, 'POWI', 'RETURN1')
+check(function (x) return x / 40 end, 'DIVI', 'RETURN1')
+check(function (x) return x // 1 end, 'IDIVI', 'RETURN1')
+check(function (x) return x % (100 - 10) end, 'MODI', 'RETURN1')
+check(function (x) return 1 << x end, 'SHLI', 'RETURN1')
+check(function (x) return x << 2 end, 'SHRI', 'RETURN1')
+check(function (x) return x >> 2 end, 'SHRI', 'RETURN1')
+check(function (x) return x & 1 end, 'BANDK', 'RETURN1')
+check(function (x) return 10 | x end, 'BORK', 'RETURN1')
+check(function (x) return -10 ~ x end, 'BXORK', 'RETURN1')
+
+-- no foldings (and immediate operands)
+check(function () return -0.0 end, 'LOADF', 'UNM', 'RETURN1')
+check(function () return 3/0 end, 'LOADI', 'DIVI', 'RETURN1')
+check(function () return 0%0 end, 'LOADI', 'MODI', 'RETURN1')
+check(function () return -4//0 end, 'LOADI', 'IDIVI', 'RETURN1')
+check(function (x) return x >> 2.0 end, 'LOADF', 'SHR', 'RETURN1')
+check(function (x) return x & 2.0 end, 'LOADF', 'BAND', 'RETURN1')
+
+-- basic 'for' loops
+check(function () for i = -10, 10.5 do end end,
+'LOADI', 'LOADK', 'LOADI', 'FORPREP1', 'FORLOOP1', 'RETURN0')
+check(function () for i = 0xfffffff, 10.0, 1 do end end,
+'LOADK', 'LOADF', 'LOADI', 'FORPREP1', 'FORLOOP1', 'RETURN0')
+
+-- bug in constant folding for 5.1
+check(function () return -nil end, 'LOADNIL', 'UNM', 'RETURN1')
+
+
+check(function ()
+ local a,b,c
+ b[c], a = c, b
+ b[a], a = c, b
+ a, b = c, a
+ a = a
+end,
+ 'LOADNIL',
+ 'MOVE', 'MOVE', 'SETTABLE',
+ 'MOVE', 'MOVE', 'MOVE', 'SETTABLE',
+ 'MOVE', 'MOVE', 'MOVE',
+ -- no code for a = a
+ 'RETURN0')
+
+
+-- x == nil , x ~= nil
+-- checkequal(function (b) if (a==nil) then a=1 end; if a~=nil then a=1 end end,
+-- function () if (a==9) then a=1 end; if a~=9 then a=1 end end)
+
+-- check(function () if a==nil then a='a' end end,
+-- 'GETTABUP', 'EQ', 'JMP', 'SETTABUP', 'RETURN')
+
+do -- tests for table access in upvalues
+ local t
+ check(function () t.x = t.y end, 'GETTABUP', 'SETTABUP')
+ check(function (a) t[a()] = t[a()] end,
+ 'MOVE', 'CALL', 'GETUPVAL', 'MOVE', 'CALL',
+ 'GETUPVAL', 'GETTABLE', 'SETTABLE')
+end
+
+-- de morgan
+checkequal(function () local a; if not (a or b) then b=a end end,
+ function () local a; if (not a and not b) then b=a end end)
+
+checkequal(function (l) local a; return 0 <= a and a <= l end,
+ function (l) local a; return not (not(a >= 0) or not(a <= l)) end)
+
+
+-- if-goto optimizations
+check(function (a, b, c, d, e)
+ if a == b then goto l1;
+ elseif a == c then goto l2;
+ elseif a == d then goto l2;
+ else if a == e then goto l3;
+ else goto l3
+ end
+ end
+ ::l1:: ::l2:: ::l3:: ::l4::
+end, 'EQ', 'JMP', 'EQ', 'JMP', 'EQ', 'JMP', 'EQ', 'JMP', 'JMP',
+'CLOSE', 'CLOSE', 'CLOSE', 'CLOSE', 'RETURN0')
+
+checkequal(
+function (a) while a < 10 do a = a + 1 end end,
+function (a) while true do if not(a < 10) then break end; a = a + 1; end end
+)
+
+print 'OK'
+
diff --git a/testes/constructs.lua b/testes/constructs.lua
new file mode 100644
index 0000000000..7796c46f72
--- /dev/null
+++ b/testes/constructs.lua
@@ -0,0 +1,302 @@
+-- $Id: constructs.lua,v 1.43 2018/02/21 17:41:07 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+;;print "testing syntax";;
+
+local debug = require "debug"
+
+
+local function checkload (s, msg)
+ assert(string.find(select(2, load(s)), msg))
+end
+
+-- testing semicollons
+do ;;; end
+; do ; a = 3; assert(a == 3) end;
+;
+
+
+-- invalid operations should not raise errors when not executed
+if false then a = 3 // 0; a = 0 % 0 end
+
+
+-- testing priorities
+
+assert(2^3^2 == 2^(3^2));
+assert(2^3*4 == (2^3)*4);
+assert(2.0^-2 == 1/4 and -2^- -2 == - - -4);
+assert(not nil and 2 and not(2>3 or 3<2));
+assert(-3-1-5 == 0+0-9);
+assert(-2^2 == -4 and (-2)^2 == 4 and 2*2-3-1 == 0);
+assert(-3%5 == 2 and -3+5 == 2)
+assert(2*1+3/3 == 3 and 1+2 .. 3*1 == "33");
+assert(not(2+1 > 3*1) and "a".."b" > "a");
+
+assert(0xF0 | 0xCC ~ 0xAA & 0xFD == 0xF4)
+assert(0xFD & 0xAA ~ 0xCC | 0xF0 == 0xF4)
+assert(0xF0 & 0x0F + 1 == 0x10)
+
+assert(3^4//2^3//5 == 2)
+
+assert(-3+4*5//2^3^2//9+4%10/3 == (-3)+(((4*5)//(2^(3^2)))//9)+((4%10)/3))
+
+assert(not ((true or false) and nil))
+assert( true or false and nil)
+
+-- old bug
+assert((((1 or false) and true) or false) == true)
+assert((((nil and true) or false) and true) == false)
+
+local a,b = 1,nil;
+assert(-(1 or 2) == -1 and (1 and 2)+(-1.25 or -4) == 0.75);
+x = ((b or a)+1 == 2 and (10 or a)+1 == 11); assert(x);
+x = (((2<3) or 1) == true and (2<3 and 4) == 4); assert(x);
+
+x,y=1,2;
+assert((x>y) and x or y == 2);
+x,y=2,1;
+assert((x>y) and x or y == 2);
+
+assert(1234567890 == tonumber('1234567890') and 1234567890+1 == 1234567891)
+
+
+-- silly loops
+repeat until 1; repeat until true;
+while false do end; while nil do end;
+
+do -- test old bug (first name could not be an `upvalue')
+ local a; function f(x) x={a=1}; x={x=1}; x={G=1} end
+end
+
+function f (i)
+ if type(i) ~= 'number' then return i,'jojo'; end;
+ if i > 0 then return i, f(i-1); end;
+end
+
+x = {f(3), f(5), f(10);};
+assert(x[1] == 3 and x[2] == 5 and x[3] == 10 and x[4] == 9 and x[12] == 1);
+assert(x[nil] == nil)
+x = {f'alo', f'xixi', nil};
+assert(x[1] == 'alo' and x[2] == 'xixi' and x[3] == nil);
+x = {f'alo'..'xixi'};
+assert(x[1] == 'aloxixi')
+x = {f{}}
+assert(x[2] == 'jojo' and type(x[1]) == 'table')
+
+
+local f = function (i)
+ if i < 10 then return 'a';
+ elseif i < 20 then return 'b';
+ elseif i < 30 then return 'c';
+ end;
+end
+
+assert(f(3) == 'a' and f(12) == 'b' and f(26) == 'c' and f(100) == nil)
+
+for i=1,1000 do break; end;
+n=100;
+i=3;
+t = {};
+a=nil
+while not a do
+ a=0; for i=1,n do for i=i,1,-1 do a=a+1; t[i]=1; end; end;
+end
+assert(a == n*(n+1)/2 and i==3);
+assert(t[1] and t[n] and not t[0] and not t[n+1])
+
+function f(b)
+ local x = 1;
+ repeat
+ local a;
+ if b==1 then local b=1; x=10; break
+ elseif b==2 then x=20; break;
+ elseif b==3 then x=30;
+ else local a,b,c,d=math.sin(1); x=x+1;
+ end
+ until x>=12;
+ return x;
+end;
+
+assert(f(1) == 10 and f(2) == 20 and f(3) == 30 and f(4)==12)
+
+
+local f = function (i)
+ if i < 10 then return 'a'
+ elseif i < 20 then return 'b'
+ elseif i < 30 then return 'c'
+ else return 8
+ end
+end
+
+assert(f(3) == 'a' and f(12) == 'b' and f(26) == 'c' and f(100) == 8)
+
+local a, b = nil, 23
+x = {f(100)*2+3 or a, a or b+2}
+assert(x[1] == 19 and x[2] == 25)
+x = {f=2+3 or a, a = b+2}
+assert(x.f == 5 and x.a == 25)
+
+a={y=1}
+x = {a.y}
+assert(x[1] == 1)
+
+function f(i)
+ while 1 do
+ if i>0 then i=i-1;
+ else return; end;
+ end;
+end;
+
+function g(i)
+ while 1 do
+ if i>0 then i=i-1
+ else return end
+ end
+end
+
+f(10); g(10);
+
+do
+ function f () return 1,2,3; end
+ local a, b, c = f();
+ assert(a==1 and b==2 and c==3)
+ a, b, c = (f());
+ assert(a==1 and b==nil and c==nil)
+end
+
+local a,b = 3 and f();
+assert(a==1 and b==nil)
+
+function g() f(); return; end;
+assert(g() == nil)
+function g() return nil or f() end
+a,b = g()
+assert(a==1 and b==nil)
+
+print'+';
+
+
+f = [[
+return function ( a , b , c , d , e )
+ local x = a >= b or c or ( d and e ) or nil
+ return x
+end , { a = 1 , b = 2 >= 1 , } or { 1 };
+]]
+f = string.gsub(f, "%s+", "\n"); -- force a SETLINE between opcodes
+f,a = load(f)();
+assert(a.a == 1 and a.b)
+
+function g (a,b,c,d,e)
+ if not (a>=b or c or d and e or nil) then return 0; else return 1; end;
+end
+
+function h (a,b,c,d,e)
+ while (a>=b or c or (d and e) or nil) do return 1; end;
+ return 0;
+end;
+
+assert(f(2,1) == true and g(2,1) == 1 and h(2,1) == 1)
+assert(f(1,2,'a') == 'a' and g(1,2,'a') == 1 and h(1,2,'a') == 1)
+assert(f(1,2,'a')
+~= -- force SETLINE before nil
+nil, "")
+assert(f(1,2,'a') == 'a' and g(1,2,'a') == 1 and h(1,2,'a') == 1)
+assert(f(1,2,nil,1,'x') == 'x' and g(1,2,nil,1,'x') == 1 and
+ h(1,2,nil,1,'x') == 1)
+assert(f(1,2,nil,nil,'x') == nil and g(1,2,nil,nil,'x') == 0 and
+ h(1,2,nil,nil,'x') == 0)
+assert(f(1,2,nil,1,nil) == nil and g(1,2,nil,1,nil) == 0 and
+ h(1,2,nil,1,nil) == 0)
+
+assert(1 and 2<3 == true and 2<3 and 'a'<'b' == true)
+x = 2<3 and not 3; assert(x==false)
+x = 2<1 or (2>1 and 'a'); assert(x=='a')
+
+
+do
+ local a; if nil then a=1; else a=2; end; -- this nil comes as PUSHNIL 2
+ assert(a==2)
+end
+
+function F(a)
+ assert(debug.getinfo(1, "n").name == 'F')
+ return a,2,3
+end
+
+a,b = F(1)~=nil; assert(a == true and b == nil);
+a,b = F(nil)==nil; assert(a == true and b == nil)
+
+----------------------------------------------------------------
+------------------------------------------------------------------
+
+-- sometimes will be 0, sometimes will not...
+_ENV.GLOB1 = math.floor(os.time()) % 2
+
+-- basic expressions with their respective values
+local basiccases = {
+ {"nil", nil},
+ {"false", false},
+ {"true", true},
+ {"10", 10},
+ {"(0==_ENV.GLOB1)", 0 == _ENV.GLOB1},
+}
+
+print('testing short-circuit optimizations (' .. _ENV.GLOB1 .. ')')
+
+
+-- operators with their respective values
+local binops = {
+ {" and ", function (a,b) if not a then return a else return b end end},
+ {" or ", function (a,b) if a then return a else return b end end},
+}
+
+local cases = {}
+
+-- creates all combinations of '(cases[i] op cases[n-i])' plus
+-- 'not(cases[i] op cases[n-i])' (syntax + value)
+local function createcases (n)
+ local res = {}
+ for i = 1, n - 1 do
+ for _, v1 in ipairs(cases[i]) do
+ for _, v2 in ipairs(cases[n - i]) do
+ for _, op in ipairs(binops) do
+ local t = {
+ "(" .. v1[1] .. op[1] .. v2[1] .. ")",
+ op[2](v1[2], v2[2])
+ }
+ res[#res + 1] = t
+ res[#res + 1] = {"not" .. t[1], not t[2]}
+ end
+ end
+ end
+ end
+ return res
+end
+
+-- do not do too many combinations for soft tests
+local level = _soft and 3 or 4
+
+cases[1] = basiccases
+for i = 2, level do cases[i] = createcases(i) end
+print("+")
+
+local prog = [[if %s then IX = true end; return %s]]
+
+local i = 0
+for n = 1, level do
+ for _, v in pairs(cases[n]) do
+ local s = v[1]
+ local p = load(string.format(prog, s, s), "")
+ IX = false
+ assert(p() == v[2] and IX == not not v[2])
+ i = i + 1
+ if i % 60000 == 0 then print('+') end
+ end
+end
+------------------------------------------------------------------
+
+-- testing some syntax errors (chosen through 'gcov')
+checkload("for x do", "expected")
+checkload("x:call", "expected")
+
+print'OK'
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
new file mode 100644
index 0000000000..220873200d
--- /dev/null
+++ b/testes/coroutine.lua
@@ -0,0 +1,918 @@
+-- $Id: coroutine.lua,v 1.48 2018/03/12 14:19:36 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print "testing coroutines"
+
+local debug = require'debug'
+
+local f
+
+local main, ismain = coroutine.running()
+assert(type(main) == "thread" and ismain)
+assert(not coroutine.resume(main))
+assert(not coroutine.isyieldable())
+assert(not pcall(coroutine.yield))
+
+
+-- trivial errors
+assert(not pcall(coroutine.resume, 0))
+assert(not pcall(coroutine.status, 0))
+
+
+-- tests for multiple yield/resume arguments
+
+local function eqtab (t1, t2)
+ assert(#t1 == #t2)
+ for i = 1, #t1 do
+ local v = t1[i]
+ assert(t2[i] == v)
+ end
+end
+
+_G.x = nil -- declare x
+function foo (a, ...)
+ local x, y = coroutine.running()
+ assert(x == f and y == false)
+ -- next call should not corrupt coroutine (but must fail,
+ -- as it attempts to resume the running coroutine)
+ assert(coroutine.resume(f) == false)
+ assert(coroutine.status(f) == "running")
+ local arg = {...}
+ assert(coroutine.isyieldable())
+ for i=1,#arg do
+ _G.x = {coroutine.yield(table.unpack(arg[i]))}
+ end
+ return table.unpack(a)
+end
+
+f = coroutine.create(foo)
+assert(type(f) == "thread" and coroutine.status(f) == "suspended")
+assert(string.find(tostring(f), "thread"))
+local s,a,b,c,d
+s,a,b,c,d = coroutine.resume(f, {1,2,3}, {}, {1}, {'a', 'b', 'c'})
+assert(s and a == nil and coroutine.status(f) == "suspended")
+s,a,b,c,d = coroutine.resume(f)
+eqtab(_G.x, {})
+assert(s and a == 1 and b == nil)
+s,a,b,c,d = coroutine.resume(f, 1, 2, 3)
+eqtab(_G.x, {1, 2, 3})
+assert(s and a == 'a' and b == 'b' and c == 'c' and d == nil)
+s,a,b,c,d = coroutine.resume(f, "xuxu")
+eqtab(_G.x, {"xuxu"})
+assert(s and a == 1 and b == 2 and c == 3 and d == nil)
+assert(coroutine.status(f) == "dead")
+s, a = coroutine.resume(f, "xuxu")
+assert(not s and string.find(a, "dead") and coroutine.status(f) == "dead")
+
+
+-- yields in tail calls
+local function foo (i) return coroutine.yield(i) end
+f = coroutine.wrap(function ()
+ for i=1,10 do
+ assert(foo(i) == _G.x)
+ end
+ return 'a'
+end)
+for i=1,10 do _G.x = i; assert(f(i) == i) end
+_G.x = 'xuxu'; assert(f('xuxu') == 'a')
+
+-- recursive
+function pf (n, i)
+ coroutine.yield(n)
+ pf(n*i, i+1)
+end
+
+f = coroutine.wrap(pf)
+local s=1
+for i=1,10 do
+ assert(f(1, 1) == s)
+ s = s*i
+end
+
+-- sieve
+function gen (n)
+ return coroutine.wrap(function ()
+ for i=2,n do coroutine.yield(i) end
+ end)
+end
+
+
+function filter (p, g)
+ return coroutine.wrap(function ()
+ while 1 do
+ local n = g()
+ if n == nil then return end
+ if math.fmod(n, p) ~= 0 then coroutine.yield(n) end
+ end
+ end)
+end
+
+local x = gen(100)
+local a = {}
+while 1 do
+ local n = x()
+ if n == nil then break end
+ table.insert(a, n)
+ x = filter(n, x)
+end
+
+assert(#a == 25 and a[#a] == 97)
+x, a = nil
+
+-- yielding across C boundaries
+
+co = coroutine.wrap(function()
+ assert(not pcall(table.sort,{1,2,3}, coroutine.yield))
+ assert(coroutine.isyieldable())
+ coroutine.yield(20)
+ return 30
+ end)
+
+assert(co() == 20)
+assert(co() == 30)
+
+
+local f = function (s, i) return coroutine.yield(i) end
+
+local f1 = coroutine.wrap(function ()
+ return xpcall(pcall, function (...) return ... end,
+ function ()
+ local s = 0
+ for i in f, nil, 1 do pcall(function () s = s + i end) end
+ error({s})
+ end)
+ end)
+
+f1()
+for i = 1, 10 do assert(f1(i) == i) end
+local r1, r2, v = f1(nil)
+assert(r1 and not r2 and v[1] == (10 + 1)*10/2)
+
+
+function f (a, b) a = coroutine.yield(a); error{a + b} end
+function g(x) return x[1]*2 end
+
+co = coroutine.wrap(function ()
+ coroutine.yield(xpcall(f, g, 10, 20))
+ end)
+
+assert(co() == 10)
+r, msg = co(100)
+assert(not r and msg == 240)
+
+
+-- unyieldable C call
+do
+ local function f (c)
+ assert(not coroutine.isyieldable())
+ return c .. c
+ end
+
+ local co = coroutine.wrap(function (c)
+ assert(coroutine.isyieldable())
+ local s = string.gsub("a", ".", f)
+ return s
+ end)
+ assert(co() == "aa")
+end
+
+
+
+do -- testing single trace of coroutines
+ local X
+ local co = coroutine.create(function ()
+ coroutine.yield(10)
+ return 20;
+ end)
+ local trace = {}
+ local function dotrace (event)
+ trace[#trace + 1] = event
+ end
+ debug.sethook(co, dotrace, "clr")
+ repeat until not coroutine.resume(co)
+ local correcttrace = {"call", "line", "call", "return", "line", "return"}
+ assert(#trace == #correcttrace)
+ for k, v in pairs(trace) do
+ assert(v == correcttrace[k])
+ end
+end
+
+-- errors in coroutines
+function foo ()
+ assert(debug.getinfo(1).currentline == debug.getinfo(foo).linedefined + 1)
+ assert(debug.getinfo(2).currentline == debug.getinfo(goo).linedefined)
+ coroutine.yield(3)
+ error(foo)
+end
+
+function goo() foo() end
+x = coroutine.wrap(goo)
+assert(x() == 3)
+local a,b = pcall(x)
+assert(not a and b == foo)
+
+x = coroutine.create(goo)
+a,b = coroutine.resume(x)
+assert(a and b == 3)
+a,b = coroutine.resume(x)
+assert(not a and b == foo and coroutine.status(x) == "dead")
+a,b = coroutine.resume(x)
+assert(not a and string.find(b, "dead") and coroutine.status(x) == "dead")
+
+
+-- co-routines x for loop
+function all (a, n, k)
+ if k == 0 then coroutine.yield(a)
+ else
+ for i=1,n do
+ a[k] = i
+ all(a, n, k-1)
+ end
+ end
+end
+
+local a = 0
+for t in coroutine.wrap(function () all({}, 5, 4) end) do
+ a = a+1
+end
+assert(a == 5^4)
+
+
+-- access to locals of collected corroutines
+local C = {}; setmetatable(C, {__mode = "kv"})
+local x = coroutine.wrap (function ()
+ local a = 10
+ local function f () a = a+10; return a end
+ while true do
+ a = a+1
+ coroutine.yield(f)
+ end
+ end)
+
+C[1] = x;
+
+local f = x()
+assert(f() == 21 and x()() == 32 and x() == f)
+x = nil
+collectgarbage()
+assert(C[1] == undef)
+assert(f() == 43 and f() == 53)
+
+
+-- old bug: attempt to resume itself
+
+function co_func (current_co)
+ assert(coroutine.running() == current_co)
+ assert(coroutine.resume(current_co) == false)
+ coroutine.yield(10, 20)
+ assert(coroutine.resume(current_co) == false)
+ coroutine.yield(23)
+ return 10
+end
+
+local co = coroutine.create(co_func)
+local a,b,c = coroutine.resume(co, co)
+assert(a == true and b == 10 and c == 20)
+a,b = coroutine.resume(co, co)
+assert(a == true and b == 23)
+a,b = coroutine.resume(co, co)
+assert(a == true and b == 10)
+assert(coroutine.resume(co, co) == false)
+assert(coroutine.resume(co, co) == false)
+
+
+-- other old bug when attempting to resume itself
+-- (trigger C-code assertions)
+do
+ local A = coroutine.running()
+ local B = coroutine.create(function() return coroutine.resume(A) end)
+ local st, res = coroutine.resume(B)
+ assert(st == true and res == false)
+
+ A = coroutine.wrap(function() return pcall(A, 1) end)
+ st, res = A()
+ assert(not st and string.find(res, "non%-suspended"))
+end
+
+
+-- attempt to resume 'normal' coroutine
+local co1, co2
+co1 = coroutine.create(function () return co2() end)
+co2 = coroutine.wrap(function ()
+ assert(coroutine.status(co1) == 'normal')
+ assert(not coroutine.resume(co1))
+ coroutine.yield(3)
+ end)
+
+a,b = coroutine.resume(co1)
+assert(a and b == 3)
+assert(coroutine.status(co1) == 'dead')
+
+-- infinite recursion of coroutines
+a = function(a) coroutine.wrap(a)(a) end
+assert(not pcall(a, a))
+a = nil
+
+
+-- access to locals of erroneous coroutines
+local x = coroutine.create (function ()
+ local a = 10
+ _G.f = function () a=a+1; return a end
+ error('x')
+ end)
+
+assert(not coroutine.resume(x))
+-- overwrite previous position of local `a'
+assert(not coroutine.resume(x, 1, 1, 1, 1, 1, 1, 1))
+assert(_G.f() == 11)
+assert(_G.f() == 12)
+
+
+if not T then
+ (Message or print)('\n >>> testC not active: skipping yield/hook tests <<<\n')
+else
+ print "testing yields inside hooks"
+
+ local turn
+
+ function fact (t, x)
+ assert(turn == t)
+ if x == 0 then return 1
+ else return x*fact(t, x-1)
+ end
+ end
+
+ local A, B = 0, 0
+
+ local x = coroutine.create(function ()
+ T.sethook("yield 0", "", 2)
+ A = fact("A", 6)
+ end)
+
+ local y = coroutine.create(function ()
+ T.sethook("yield 0", "", 3)
+ B = fact("B", 7)
+ end)
+
+ while A==0 or B==0 do -- A ~= 0 when 'x' finishes (similar for 'B','y')
+ if A==0 then turn = "A"; assert(T.resume(x)) end
+ if B==0 then turn = "B"; assert(T.resume(y)) end
+ end
+
+ assert(B // A == 7) -- fact(7) // fact(6)
+
+ local line = debug.getinfo(1, "l").currentline + 2 -- get line number
+ local function foo ()
+ local x = 10 --<< this line is 'line'
+ x = x + 10
+ _G.XX = x
+ end
+
+ -- testing yields in line hook
+ local co = coroutine.wrap(function ()
+ T.sethook("setglobal X; yield 0", "l", 0); foo(); return 10 end)
+
+ _G.XX = nil;
+ _G.X = nil; co(); assert(_G.X == line)
+ _G.X = nil; co(); assert(_G.X == line + 1)
+ _G.X = nil; co(); assert(_G.X == line + 2 and _G.XX == nil)
+ _G.X = nil; co(); assert(_G.X == line + 3 and _G.XX == 20)
+ assert(co() == 10)
+
+ -- testing yields in count hook
+ co = coroutine.wrap(function ()
+ T.sethook("yield 0", "", 1); foo(); return 10 end)
+
+ _G.XX = nil;
+ local c = 0
+ repeat c = c + 1; local a = co() until a == 10
+ assert(_G.XX == 20 and c >= 5)
+
+ co = coroutine.wrap(function ()
+ T.sethook("yield 0", "", 2); foo(); return 10 end)
+
+ _G.XX = nil;
+ local c = 0
+ repeat c = c + 1; local a = co() until a == 10
+ assert(_G.XX == 20 and c >= 5)
+ _G.X = nil; _G.XX = nil
+
+ do
+ -- testing debug library on a coroutine suspended inside a hook
+ -- (bug in 5.2/5.3)
+ c = coroutine.create(function (a, ...)
+ T.sethook("yield 0", "l") -- will yield on next two lines
+ assert(a == 10)
+ return ...
+ end)
+
+ assert(coroutine.resume(c, 1, 2, 3)) -- start coroutine
+ local n,v = debug.getlocal(c, 0, 1) -- check its local
+ assert(n == "a" and v == 1)
+ assert(debug.setlocal(c, 0, 1, 10)) -- test 'setlocal'
+ local t = debug.getinfo(c, 0) -- test 'getinfo'
+ assert(t.currentline == t.linedefined + 1)
+ assert(not debug.getinfo(c, 1)) -- no other level
+ assert(coroutine.resume(c)) -- run next line
+ v = {coroutine.resume(c)} -- finish coroutine
+ assert(v[1] == true and v[2] == 2 and v[3] == 3 and v[4] == undef)
+ assert(not coroutine.resume(c))
+ end
+
+ do
+ -- testing debug library on last function in a suspended coroutine
+ -- (bug in 5.2/5.3)
+ local c = coroutine.create(function () T.testC("yield 1", 10, 20) end)
+ local a, b = coroutine.resume(c)
+ assert(a and b == 20)
+ assert(debug.getinfo(c, 0).linedefined == -1)
+ a, b = debug.getlocal(c, 0, 2)
+ assert(b == 10)
+ end
+
+
+ print "testing coroutine API"
+
+ -- reusing a thread
+ assert(T.testC([[
+ newthread # create thread
+ pushvalue 2 # push body
+ pushstring 'a a a' # push argument
+ xmove 0 3 2 # move values to new thread
+ resume -1, 1 # call it first time
+ pushstatus
+ xmove 3 0 0 # move results back to stack
+ setglobal X # result
+ setglobal Y # status
+ pushvalue 2 # push body (to call it again)
+ pushstring 'b b b'
+ xmove 0 3 2
+ resume -1, 1 # call it again
+ pushstatus
+ xmove 3 0 0
+ return 1 # return result
+ ]], function (...) return ... end) == 'b b b')
+
+ assert(X == 'a a a' and Y == 'OK')
+
+
+ -- resuming running coroutine
+ C = coroutine.create(function ()
+ return T.testC([[
+ pushnum 10;
+ pushnum 20;
+ resume -3 2;
+ pushstatus
+ gettop;
+ return 3]], C)
+ end)
+ local a, b, c, d = coroutine.resume(C)
+ assert(a == true and string.find(b, "non%-suspended") and
+ c == "ERRRUN" and d == 4)
+
+ a, b, c, d = T.testC([[
+ rawgeti R 1 # get main thread
+ pushnum 10;
+ pushnum 20;
+ resume -3 2;
+ pushstatus
+ gettop;
+ return 4]])
+ assert(a == coroutine.running() and string.find(b, "non%-suspended") and
+ c == "ERRRUN" and d == 4)
+
+
+ -- using a main thread as a coroutine
+ local state = T.newstate()
+ T.loadlib(state)
+
+ assert(T.doremote(state, [[
+ coroutine = require'coroutine';
+ X = function (x) coroutine.yield(x, 'BB'); return 'CC' end;
+ return 'ok']]))
+
+ t = table.pack(T.testC(state, [[
+ rawgeti R 1 # get main thread
+ pushstring 'XX'
+ getglobal X # get function for body
+ pushstring AA # arg
+ resume 1 1 # 'resume' shadows previous stack!
+ gettop
+ setglobal T # top
+ setglobal B # second yielded value
+ setglobal A # fist yielded value
+ rawgeti R 1 # get main thread
+ pushnum 5 # arg (noise)
+ resume 1 1 # after coroutine ends, previous stack is back
+ pushstatus
+ return *
+ ]]))
+ assert(t.n == 4 and t[2] == 'XX' and t[3] == 'CC' and t[4] == 'OK')
+ assert(T.doremote(state, "return T") == '2')
+ assert(T.doremote(state, "return A") == 'AA')
+ assert(T.doremote(state, "return B") == 'BB')
+
+ T.closestate(state)
+
+ print'+'
+
+end
+
+
+-- leaving a pending coroutine open
+_X = coroutine.wrap(function ()
+ local a = 10
+ local x = function () a = a+1 end
+ coroutine.yield()
+ end)
+
+_X()
+
+
+if not _soft then
+ -- bug (stack overflow)
+ local j = 2^9
+ local lim = 1000000 -- (C stack limit; assume 32-bit machine)
+ local t = {lim - 10, lim - 5, lim - 1, lim, lim + 1}
+ for i = 1, #t do
+ local j = t[i]
+ co = coroutine.create(function()
+ local t = {}
+ for i = 1, j do t[i] = i end
+ return table.unpack(t)
+ end)
+ local r, msg = coroutine.resume(co)
+ assert(not r)
+ end
+ co = nil
+end
+
+
+assert(coroutine.running() == main)
+
+print"+"
+
+
+print"testing yields inside metamethods"
+
+local function val(x)
+ if type(x) == "table" then return x.x else return x end
+end
+
+local mt = {
+ __eq = function(a,b) coroutine.yield(nil, "eq"); return val(a) == val(b) end,
+ __lt = function(a,b) coroutine.yield(nil, "lt"); return val(a) < val(b) end,
+ __le = function(a,b) coroutine.yield(nil, "le"); return a - b <= 0 end,
+ __add = function(a,b) coroutine.yield(nil, "add");
+ return val(a) + val(b) end,
+ __sub = function(a,b) coroutine.yield(nil, "sub"); return val(a) - val(b) end,
+ __mul = function(a,b) coroutine.yield(nil, "mul"); return val(a) * val(b) end,
+ __div = function(a,b) coroutine.yield(nil, "div"); return val(a) / val(b) end,
+ __idiv = function(a,b) coroutine.yield(nil, "idiv");
+ return val(a) // val(b) end,
+ __pow = function(a,b) coroutine.yield(nil, "pow"); return val(a) ^ val(b) end,
+ __mod = function(a,b) coroutine.yield(nil, "mod"); return val(a) % val(b) end,
+ __unm = function(a,b) coroutine.yield(nil, "unm"); return -val(a) end,
+ __bnot = function(a,b) coroutine.yield(nil, "bnot"); return ~val(a) end,
+ __shl = function(a,b) coroutine.yield(nil, "shl");
+ return val(a) << val(b) end,
+ __shr = function(a,b) coroutine.yield(nil, "shr");
+ return val(a) >> val(b) end,
+ __band = function(a,b)
+ coroutine.yield(nil, "band")
+ return val(a) & val(b)
+ end,
+ __bor = function(a,b) coroutine.yield(nil, "bor");
+ return val(a) | val(b) end,
+ __bxor = function(a,b) coroutine.yield(nil, "bxor");
+ return val(a) ~ val(b) end,
+
+ __concat = function(a,b)
+ coroutine.yield(nil, "concat");
+ return val(a) .. val(b)
+ end,
+ __index = function (t,k) coroutine.yield(nil, "idx"); return t.k[k] end,
+ __newindex = function (t,k,v) coroutine.yield(nil, "nidx"); t.k[k] = v end,
+}
+
+
+local function new (x)
+ return setmetatable({x = x, k = {}}, mt)
+end
+
+
+local a = new(10)
+local b = new(12)
+local c = new"hello"
+
+local function run (f, t)
+ local i = 1
+ local c = coroutine.wrap(f)
+ while true do
+ local res, stat = c()
+ if res then assert(t[i] == undef); return res, t end
+ assert(stat == t[i])
+ i = i + 1
+ end
+end
+
+
+assert(run(function () if (a>=b) then return '>=' else return '<' end end,
+ {"le", "sub"}) == "<")
+-- '<=' using '<'
+mt.__le = nil
+assert(run(function () if (a<=b) then return '<=' else return '>' end end,
+ {"lt"}) == "<=")
+assert(run(function () if (a==b) then return '==' else return '~=' end end,
+ {"eq"}) == "~=")
+
+assert(run(function () return a & b + a end, {"add", "band"}) == 2)
+
+assert(run(function () return 1 + a end, {"add"}) == 11)
+assert(run(function () return a - 25 end, {"sub"}) == -15)
+assert(run(function () return 2 * a end, {"mul"}) == 20)
+assert(run(function () return a ^ 2 end, {"pow"}) == 100)
+assert(run(function () return a / 2 end, {"div"}) == 5)
+assert(run(function () return a % 6 end, {"mod"}) == 4)
+assert(run(function () return a // 3 end, {"idiv"}) == 3)
+
+assert(run(function () return a + b end, {"add"}) == 22)
+assert(run(function () return a - b end, {"sub"}) == -2)
+assert(run(function () return a * b end, {"mul"}) == 120)
+assert(run(function () return a ^ b end, {"pow"}) == 10^12)
+assert(run(function () return a / b end, {"div"}) == 10/12)
+assert(run(function () return a % b end, {"mod"}) == 10)
+assert(run(function () return a // b end, {"idiv"}) == 0)
+
+
+assert(run(function () return a % b end, {"mod"}) == 10)
+
+assert(run(function () return ~a & b end, {"bnot", "band"}) == ~10 & 12)
+assert(run(function () return a | b end, {"bor"}) == 10 | 12)
+assert(run(function () return a ~ b end, {"bxor"}) == 10 ~ 12)
+assert(run(function () return a << b end, {"shl"}) == 10 << 12)
+assert(run(function () return a >> b end, {"shr"}) == 10 >> 12)
+
+assert(run(function () return 10 & b end, {"band"}) == 10 & 12)
+assert(run(function () return a | 2 end, {"bor"}) == 10 | 2)
+assert(run(function () return a ~ 2 end, {"bxor"}) == 10 ~ 2)
+
+assert(run(function () return a..b end, {"concat"}) == "1012")
+
+assert(run(function() return a .. b .. c .. a end,
+ {"concat", "concat", "concat"}) == "1012hello10")
+
+assert(run(function() return "a" .. "b" .. a .. "c" .. c .. b .. "x" end,
+ {"concat", "concat", "concat"}) == "ab10chello12x")
+
+
+do -- a few more tests for comparsion operators
+ local mt1 = {
+ __le = function (a,b)
+ coroutine.yield(10)
+ return (val(a) <= val(b))
+ end,
+ __lt = function (a,b)
+ coroutine.yield(10)
+ return val(a) < val(b)
+ end,
+ }
+ local mt2 = { __lt = mt1.__lt } -- no __le
+
+ local function run (f)
+ local co = coroutine.wrap(f)
+ local res
+ repeat
+ res = co()
+ until res ~= 10
+ return res
+ end
+
+ local function test ()
+ local a1 = setmetatable({x=1}, mt1)
+ local a2 = setmetatable({x=2}, mt2)
+ assert(a1 < a2)
+ assert(a1 <= a2)
+ assert(1 < a2)
+ assert(1 <= a2)
+ assert(2 > a1)
+ assert(2 >= a2)
+ return true
+ end
+
+ run(test)
+
+end
+
+assert(run(function ()
+ a.BB = print
+ return a.BB
+ end, {"nidx", "idx"}) == print)
+
+-- getuptable & setuptable
+do local _ENV = _ENV
+ f = function () AAA = BBB + 1; return AAA end
+end
+g = new(10); g.k.BBB = 10;
+debug.setupvalue(f, 1, g)
+assert(run(f, {"idx", "nidx", "idx"}) == 11)
+assert(g.k.AAA == 11)
+
+print"+"
+
+print"testing yields inside 'for' iterators"
+
+local f = function (s, i)
+ if i%2 == 0 then coroutine.yield(nil, "for") end
+ if i < s then return i + 1 end
+ end
+
+assert(run(function ()
+ local s = 0
+ for i in f, 4, 0 do s = s + i end
+ return s
+ end, {"for", "for", "for"}) == 10)
+
+
+
+-- tests for coroutine API
+if T==nil then
+ (Message or print)('\n >>> testC not active: skipping coroutine API tests <<<\n')
+ return
+end
+
+print('testing coroutine API')
+
+local function apico (...)
+ local x = {...}
+ return coroutine.wrap(function ()
+ return T.testC(table.unpack(x))
+ end)
+end
+
+local a = {apico(
+[[
+ pushstring errorcode
+ pcallk 1 0 2;
+ invalid command (should not arrive here)
+]],
+[[return *]],
+"stackmark",
+error
+)()}
+assert(#a == 4 and
+ a[3] == "stackmark" and
+ a[4] == "errorcode" and
+ _G.status == "ERRRUN" and
+ _G.ctx == 2) -- 'ctx' to pcallk
+
+local co = apico(
+ "pushvalue 2; pushnum 10; pcallk 1 2 3; invalid command;",
+ coroutine.yield,
+ "getglobal status; getglobal ctx; pushvalue 2; pushstring a; pcallk 1 0 4; invalid command",
+ "getglobal status; getglobal ctx; return *")
+
+assert(co() == 10)
+assert(co(20, 30) == 'a')
+a = {co()}
+assert(#a == 10 and
+ a[2] == coroutine.yield and
+ a[5] == 20 and a[6] == 30 and
+ a[7] == "YIELD" and a[8] == 3 and
+ a[9] == "YIELD" and a[10] == 4)
+assert(not pcall(co)) -- coroutine is dead now
+
+
+f = T.makeCfunc("pushnum 3; pushnum 5; yield 1;")
+co = coroutine.wrap(function ()
+ assert(f() == 23); assert(f() == 23); return 10
+end)
+assert(co(23,16) == 5)
+assert(co(23,16) == 5)
+assert(co(23,16) == 10)
+
+
+-- testing coroutines with C bodies
+f = T.makeCfunc([[
+ pushnum 102
+ yieldk 1 U2
+ cannot be here!
+]],
+[[ # continuation
+ pushvalue U3 # accessing upvalues inside a continuation
+ pushvalue U4
+ return *
+]], 23, "huu")
+
+x = coroutine.wrap(f)
+assert(x() == 102)
+eqtab({x()}, {23, "huu"})
+
+
+f = T.makeCfunc[[pushstring 'a'; pushnum 102; yield 2; ]]
+
+a, b, c, d = T.testC([[newthread; pushvalue 2; xmove 0 3 1; resume 3 0;
+ pushstatus; xmove 3 0 0; resume 3 0; pushstatus;
+ return 4; ]], f)
+
+assert(a == 'YIELD' and b == 'a' and c == 102 and d == 'OK')
+
+
+-- testing chain of suspendable C calls
+
+local count = 3 -- number of levels
+
+f = T.makeCfunc([[
+ remove 1; # remove argument
+ pushvalue U3; # get selection function
+ call 0 1; # call it (result is 'f' or 'yield')
+ pushstring hello # single argument for selected function
+ pushupvalueindex 2; # index of continuation program
+ callk 1 -1 .; # call selected function
+ errorerror # should never arrive here
+]],
+[[
+ # continuation program
+ pushnum 34 # return value
+ return * # return all results
+]],
+function () -- selection function
+ count = count - 1
+ if count == 0 then return coroutine.yield
+ else return f
+ end
+end
+)
+
+co = coroutine.wrap(function () return f(nil) end)
+assert(co() == "hello") -- argument to 'yield'
+a = {co()}
+-- three '34's (one from each pending C call)
+assert(#a == 3 and a[1] == a[2] and a[2] == a[3] and a[3] == 34)
+
+
+-- testing yields with continuations
+
+co = coroutine.wrap(function (...) return
+ T.testC([[ # initial function
+ yieldk 1 2
+ cannot be here!
+ ]],
+ [[ # 1st continuation
+ yieldk 0 3
+ cannot be here!
+ ]],
+ [[ # 2nd continuation
+ yieldk 0 4
+ cannot be here!
+ ]],
+ [[ # 3th continuation
+ pushvalue 6 # function which is last arg. to 'testC' here
+ pushnum 10; pushnum 20;
+ pcall 2 0 0 # call should throw an error and return to next line
+ pop 1 # remove error message
+ pushvalue 6
+ getglobal status; getglobal ctx
+ pcallk 2 2 5 # call should throw an error and jump to continuation
+ cannot be here!
+ ]],
+ [[ # 4th (and last) continuation
+ return *
+ ]],
+ -- function called by 3th continuation
+ function (a,b) x=a; y=b; error("errmsg") end,
+ ...
+)
+end)
+
+local a = {co(3,4,6)}
+assert(a[1] == 6 and a[2] == undef)
+a = {co()}; assert(a[1] == undef and _G.status == "YIELD" and _G.ctx == 2)
+a = {co()}; assert(a[1] == undef and _G.status == "YIELD" and _G.ctx == 3)
+a = {co(7,8)};
+-- original arguments
+assert(type(a[1]) == 'string' and type(a[2]) == 'string' and
+ type(a[3]) == 'string' and type(a[4]) == 'string' and
+ type(a[5]) == 'string' and type(a[6]) == 'function')
+-- arguments left from fist resume
+assert(a[7] == 3 and a[8] == 4)
+-- arguments to last resume
+assert(a[9] == 7 and a[10] == 8)
+-- error message and nothing more
+assert(a[11]:find("errmsg") and #a == 11)
+-- check arguments to pcallk
+assert(x == "YIELD" and y == 4)
+
+assert(not pcall(co)) -- coroutine should be dead
+
+
+-- bug in nCcalls
+local co = coroutine.wrap(function ()
+ local a = {pcall(pcall,pcall,pcall,pcall,pcall,pcall,pcall,error,"hi")}
+ return pcall(assert, table.unpack(a))
+end)
+
+local a = {co()}
+assert(a[10] == "hi")
+
+print'OK'
diff --git a/testes/db.lua b/testes/db.lua
new file mode 100644
index 0000000000..36d1cdaab6
--- /dev/null
+++ b/testes/db.lua
@@ -0,0 +1,948 @@
+-- $Id: db.lua,v 1.90 2018/04/02 17:55:58 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+-- testing debug library
+
+local debug = require "debug"
+
+local function dostring(s) return assert(load(s))() end
+
+print"testing debug library and debug information"
+
+do
+local a=1
+end
+
+assert(not debug.gethook())
+
+local testline = 19 -- line where 'test' is defined
+function test (s, l, p) -- this must be line 19
+ collectgarbage() -- avoid gc during trace
+ local function f (event, line)
+ assert(event == 'line')
+ local l = table.remove(l, 1)
+ if p then print(l, line) end
+ assert(l == line, "wrong trace!!")
+ end
+ debug.sethook(f,"l"); load(s)(); debug.sethook()
+ assert(#l == 0)
+end
+
+
+do
+ assert(not pcall(debug.getinfo, print, "X")) -- invalid option
+ assert(not debug.getinfo(1000)) -- out of range level
+ assert(not debug.getinfo(-1)) -- out of range level
+ local a = debug.getinfo(print)
+ assert(a.what == "C" and a.short_src == "[C]")
+ a = debug.getinfo(print, "L")
+ assert(a.activelines == nil)
+ local b = debug.getinfo(test, "SfL")
+ assert(b.name == nil and b.what == "Lua" and b.linedefined == testline and
+ b.lastlinedefined == b.linedefined + 10 and
+ b.func == test and not string.find(b.short_src, "%["))
+ assert(b.activelines[b.linedefined + 1] and
+ b.activelines[b.lastlinedefined])
+ assert(not b.activelines[b.linedefined] and
+ not b.activelines[b.lastlinedefined + 1])
+end
+
+
+-- test file and string names truncation
+a = "function f () end"
+local function dostring (s, x) return load(s, x)() end
+dostring(a)
+assert(debug.getinfo(f).short_src == string.format('[string "%s"]', a))
+dostring(a..string.format("; %s\n=1", string.rep('p', 400)))
+assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$'))
+dostring(a..string.format("; %s=1", string.rep('p', 400)))
+assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$'))
+dostring("\n"..a)
+assert(debug.getinfo(f).short_src == '[string "..."]')
+dostring(a, "")
+assert(debug.getinfo(f).short_src == '[string ""]')
+dostring(a, "@xuxu")
+assert(debug.getinfo(f).short_src == "xuxu")
+dostring(a, "@"..string.rep('p', 1000)..'t')
+assert(string.find(debug.getinfo(f).short_src, "^%.%.%.p*t$"))
+dostring(a, "=xuxu")
+assert(debug.getinfo(f).short_src == "xuxu")
+dostring(a, string.format("=%s", string.rep('x', 500)))
+assert(string.find(debug.getinfo(f).short_src, "^x*$"))
+dostring(a, "=")
+assert(debug.getinfo(f).short_src == "")
+a = nil; f = nil;
+
+
+repeat
+ local g = {x = function ()
+ local a = debug.getinfo(2)
+ assert(a.name == 'f' and a.namewhat == 'local')
+ a = debug.getinfo(1)
+ assert(a.name == 'x' and a.namewhat == 'field')
+ return 'xixi'
+ end}
+ local f = function () return 1+1 and (not 1 or g.x()) end
+ assert(f() == 'xixi')
+ g = debug.getinfo(f)
+ assert(g.what == "Lua" and g.func == f and g.namewhat == "" and not g.name)
+
+ function f (x, name) -- local!
+ name = name or 'f'
+ local a = debug.getinfo(1)
+ assert(a.name == name and a.namewhat == 'local')
+ return x
+ end
+
+ -- breaks in different conditions
+ if 3>4 then break end; f()
+ if 3<4 then a=1 else break end; f()
+ while 1 do local x=10; break end; f()
+ local b = 1
+ if 3>4 then return math.sin(1) end; f()
+ a = 3<4; f()
+ a = 3<4 or 1; f()
+ repeat local x=20; if 4>3 then f() else break end; f() until 1
+ g = {}
+ f(g).x = f(2) and f(10)+f(9)
+ assert(g.x == f(19))
+ function g(x) if not x then return 3 end return (x('a', 'x')) end
+ assert(g(f) == 'a')
+until 1
+
+test([[if
+math.sin(1)
+then
+ a=1
+else
+ a=2
+end
+]], {2,3,4,7})
+
+test([[--
+if nil then
+ a=1
+else
+ a=2
+end
+]], {2,5,6})
+
+test([[a=1
+repeat
+ a=a+1
+until a==3
+]], {1,3,4,3,4})
+
+test([[ do
+ return
+end
+]], {2})
+
+test([[local a
+a=1
+while a<=3 do
+ a=a+1
+end
+]], {1,2,3,4,3,4,3,4,3,5})
+
+test([[while math.sin(1) do
+ if math.sin(1)
+ then break
+ end
+end
+a=1]], {1,2,3,6})
+
+test([[for i=1,3 do
+ a=i
+end
+]], {1,2,1,2,1,2,1,3})
+
+test([[for i,v in pairs{'a','b'} do
+ a=tostring(i) .. v
+end
+]], {1,2,1,2,1,3})
+
+test([[for i=1,4 do a=1 end]], {1,1,1,1,1})
+
+
+do -- testing line info/trace with large gaps in source
+
+ local a = {1, 2, 3, 10, 124, 125, 126, 127, 128, 129, 130,
+ 255, 256, 257, 500, 1000}
+ local s = [[
+ local b = {10}
+ a = b[1] X + Y b[1]
+ b = 4
+ ]]
+ for _, i in ipairs(a) do
+ local subs = {X = string.rep("\n", i)}
+ for _, j in ipairs(a) do
+ subs.Y = string.rep("\n", j)
+ local s = string.gsub(s, "[XY]", subs)
+ test(s, {1, 2 + i, 2 + i + j, 2 + i, 2 + i + j, 3 + i + j})
+ end
+ end
+end
+
+print'+'
+
+-- invalid levels in [gs]etlocal
+assert(not pcall(debug.getlocal, 20, 1))
+assert(not pcall(debug.setlocal, -1, 1, 10))
+
+
+-- parameter names
+local function foo (a,b,...) local d, e end
+local co = coroutine.create(foo)
+
+assert(debug.getlocal(foo, 1) == 'a')
+assert(debug.getlocal(foo, 2) == 'b')
+assert(not debug.getlocal(foo, 3))
+assert(debug.getlocal(co, foo, 1) == 'a')
+assert(debug.getlocal(co, foo, 2) == 'b')
+assert(not debug.getlocal(co, foo, 3))
+
+assert(not debug.getlocal(print, 1))
+
+
+local function foo () return (debug.getlocal(1, -1)) end
+assert(not foo(10))
+
+
+-- varargs
+local function foo (a, ...)
+ local t = table.pack(...)
+ for i = 1, t.n do
+ local n, v = debug.getlocal(1, -i)
+ assert(n == "(*vararg)" and v == t[i])
+ end
+ assert(not debug.getlocal(1, -(t.n + 1)))
+ assert(not debug.setlocal(1, -(t.n + 1), 30))
+ if t.n > 0 then
+ (function (x)
+ assert(debug.setlocal(2, -1, x) == "(*vararg)")
+ assert(debug.setlocal(2, -t.n, x) == "(*vararg)")
+ end)(430)
+ assert(... == 430)
+ end
+end
+
+foo()
+foo(print)
+foo(200, 3, 4)
+local a = {}
+for i = 1, (_soft and 100 or 1000) do a[i] = i end
+foo(table.unpack(a))
+a = nil
+
+
+
+do -- test hook presence in debug info
+ assert(not debug.gethook())
+ local count = 0
+ local function f ()
+ assert(debug.getinfo(1).namewhat == "hook")
+ local sndline = string.match(debug.traceback(), "\n(.-)\n")
+ assert(string.find(sndline, "hook"))
+ count = count + 1
+ end
+ debug.sethook(f, "l")
+ local a = 0
+ _ENV.a = a
+ a = 1
+ debug.sethook()
+ assert(count == 4)
+end
+
+
+a = {}; L = nil
+local glob = 1
+local oldglob = glob
+debug.sethook(function (e,l)
+ collectgarbage() -- force GC during a hook
+ local f, m, c = debug.gethook()
+ assert(m == 'crl' and c == 0)
+ if e == "line" then
+ if glob ~= oldglob then
+ L = l-1 -- get the first line where "glob" has changed
+ oldglob = glob
+ end
+ elseif e == "call" then
+ local f = debug.getinfo(2, "f").func
+ a[f] = 1
+ else assert(e == "return")
+ end
+end, "crl")
+
+
+function f(a,b)
+ collectgarbage()
+ local _, x = debug.getlocal(1, 1)
+ local _, y = debug.getlocal(1, 2)
+ assert(x == a and y == b)
+ assert(debug.setlocal(2, 3, "pera") == "AA".."AA")
+ assert(debug.setlocal(2, 4, "ma") == "B")
+ x = debug.getinfo(2)
+ assert(x.func == g and x.what == "Lua" and x.name == 'g' and
+ x.nups == 2 and string.find(x.source, "^@.*db%.lua$"))
+ glob = glob+1
+ assert(debug.getinfo(1, "l").currentline == L+1)
+ assert(debug.getinfo(1, "l").currentline == L+2)
+end
+
+function foo()
+ glob = glob+1
+ assert(debug.getinfo(1, "l").currentline == L+1)
+end; foo() -- set L
+-- check line counting inside strings and empty lines
+
+_ = 'alo\
+alo' .. [[
+
+]]
+--[[
+]]
+assert(debug.getinfo(1, "l").currentline == L+11) -- check count of lines
+
+
+function g (...)
+ local arg = {...}
+ do local a,b,c; a=math.sin(40); end
+ local feijao
+ local AAAA,B = "xuxu", "mamo"
+ f(AAAA,B)
+ assert(AAAA == "pera" and B == "ma")
+ do
+ local B = 13
+ local x,y = debug.getlocal(1,5)
+ assert(x == 'B' and y == 13)
+ end
+end
+
+g()
+
+
+assert(a[f] and a[g] and a[assert] and a[debug.getlocal] and not a[print])
+
+
+-- tests for manipulating non-registered locals (C and Lua temporaries)
+
+local n, v = debug.getlocal(0, 1)
+assert(v == 0 and n == "(*temporary)")
+local n, v = debug.getlocal(0, 2)
+assert(v == 2 and n == "(*temporary)")
+assert(not debug.getlocal(0, 3))
+assert(not debug.getlocal(0, 0))
+
+function f()
+ assert(select(2, debug.getlocal(2,3)) == 1)
+ assert(not debug.getlocal(2,4))
+ debug.setlocal(2, 3, 10)
+ return 20
+end
+
+function g(a,b) return (a+1) + f() end
+
+assert(g(0,0) == 30)
+
+
+debug.sethook(nil);
+assert(debug.gethook() == nil)
+
+
+-- minimal tests for setuservalue/getuservalue
+do
+ assert(debug.setuservalue(io.stdin, 10) == nil)
+ local a, b = debug.getuservalue(io.stdin, 10)
+ assert(a == nil and not b)
+end
+
+-- testing iteraction between multiple values x hooks
+do
+ local function f(...) return 3, ... end
+ local count = 0
+ local a = {}
+ for i = 1, 100 do a[i] = i end
+ debug.sethook(function () count = count + 1 end, "", 1)
+ local t = {table.unpack(a)}
+ assert(#t == 100)
+ t = {table.unpack(a, 1, 3)}
+ assert(#t == 3)
+ t = {f(table.unpack(a, 1, 30))}
+ assert(#t == 31)
+end
+
+
+-- testing access to function arguments
+
+local function collectlocals (level)
+ local tab = {}
+ for i = 1, math.huge do
+ local n, v = debug.getlocal(level + 1, i)
+ if not (n and string.find(n, "^[a-zA-Z0-9_]+$")) then
+ break -- consider only real variables
+ end
+ tab[n] = v
+ end
+ return tab
+end
+
+
+X = nil
+a = {}
+function a:f (a, b, ...) local arg = {...}; local c = 13 end
+debug.sethook(function (e)
+ assert(e == "call")
+ dostring("XX = 12") -- test dostring inside hooks
+ -- testing errors inside hooks
+ assert(not pcall(load("a='joao'+1")))
+ debug.sethook(function (e, l)
+ assert(debug.getinfo(2, "l").currentline == l)
+ local f,m,c = debug.gethook()
+ assert(e == "line")
+ assert(m == 'l' and c == 0)
+ debug.sethook(nil) -- hook is called only once
+ assert(not X) -- check that
+ X = collectlocals(2)
+ end, "l")
+end, "c")
+
+a:f(1,2,3,4,5)
+assert(X.self == a and X.a == 1 and X.b == 2 and X.c == nil)
+assert(XX == 12)
+assert(debug.gethook() == nil)
+
+
+-- testing access to local variables in return hook (bug in 5.2)
+do
+ local X = false
+
+ local function foo (a, b, ...)
+ do local x,y,z end
+ local c, d = 10, 20
+ return
+ end
+
+ local function aux ()
+ if debug.getinfo(2).name == "foo" then
+ X = true -- to signal that it found 'foo'
+ local tab = {a = 100, b = 200, c = 10, d = 20}
+ for n, v in pairs(collectlocals(2)) do
+ assert(tab[n] == v)
+ tab[n] = undef
+ end
+ assert(next(tab) == nil) -- 'tab' must be empty
+ end
+ end
+
+ debug.sethook(aux, "r"); foo(100, 200); debug.sethook()
+ assert(X)
+
+end
+
+
+local function eqseq (t1, t2)
+ assert(#t1 == #t2)
+ for i = 1, #t1 do
+ assert(t1[i] == t2[i])
+ end
+end
+
+
+do print("testing inspection of parameters/returned values")
+ local on = false
+ local inp, out
+
+ local function hook (event)
+ if not on then return end
+ local ar = debug.getinfo(2, "ruS")
+ local t = {}
+ for i = ar.ftransfer, ar.ftransfer + ar.ntransfer - 1 do
+ local _, v = debug.getlocal(2, i)
+ t[#t + 1] = v
+ end
+ if event == "return" then
+ out = t
+ else
+ inp = t
+ end
+ end
+
+ debug.sethook(hook, "cr")
+
+ on = true; math.sin(3); on = false
+ eqseq(inp, {3}); eqseq(out, {math.sin(3)})
+
+ on = true; select(2, 10, 20, 30, 40); on = false
+ eqseq(inp, {2, 10, 20, 30, 40}); eqseq(out, {20, 30, 40})
+
+ local function foo (a, ...) return ... end
+ local function foo1 () on = not on; return foo(20, 10, 0) end
+ foo1(); on = false
+ eqseq(inp, {20}); eqseq(out, {10, 0})
+
+ debug.sethook()
+end
+
+
+
+-- testing upvalue access
+local function getupvalues (f)
+ local t = {}
+ local i = 1
+ while true do
+ local name, value = debug.getupvalue(f, i)
+ if not name then break end
+ assert(not t[name])
+ t[name] = value
+ i = i + 1
+ end
+ return t
+end
+
+local a,b,c = 1,2,3
+local function foo1 (a) b = a; return c end
+local function foo2 (x) a = x; return c+b end
+assert(not debug.getupvalue(foo1, 3))
+assert(not debug.getupvalue(foo1, 0))
+assert(not debug.setupvalue(foo1, 3, "xuxu"))
+local t = getupvalues(foo1)
+assert(t.a == nil and t.b == 2 and t.c == 3)
+t = getupvalues(foo2)
+assert(t.a == 1 and t.b == 2 and t.c == 3)
+assert(debug.setupvalue(foo1, 1, "xuxu") == "b")
+assert(({debug.getupvalue(foo2, 3)})[2] == "xuxu")
+-- upvalues of C functions are allways "called" "" (the empty string)
+assert(debug.getupvalue(string.gmatch("x", "x"), 1) == "")
+
+
+-- testing count hooks
+local a=0
+debug.sethook(function (e) a=a+1 end, "", 1)
+a=0; for i=1,1000 do end; assert(1000 < a and a < 1012)
+debug.sethook(function (e) a=a+1 end, "", 4)
+a=0; for i=1,1000 do end; assert(250 < a and a < 255)
+local f,m,c = debug.gethook()
+assert(m == "" and c == 4)
+debug.sethook(function (e) a=a+1 end, "", 4000)
+a=0; for i=1,1000 do end; assert(a == 0)
+
+do
+ debug.sethook(print, "", 2^24 - 1) -- count upperbound
+ local f,m,c = debug.gethook()
+ assert(({debug.gethook()})[3] == 2^24 - 1)
+end
+
+debug.sethook()
+
+
+-- tests for tail calls
+local function f (x)
+ if x then
+ assert(debug.getinfo(1, "S").what == "Lua")
+ assert(debug.getinfo(1, "t").istailcall == true)
+ local tail = debug.getinfo(2)
+ assert(tail.func == g1 and tail.istailcall == true)
+ assert(debug.getinfo(3, "S").what == "main")
+ print"+"
+ end
+end
+
+function g(x) return f(x) end
+
+function g1(x) g(x) end
+
+local function h (x) local f=g1; return f(x) end
+
+h(true)
+
+local b = {}
+debug.sethook(function (e) table.insert(b, e) end, "cr")
+h(false)
+debug.sethook()
+local res = {"return", -- first return (from sethook)
+ "call", "tail call", "call", "tail call",
+ "return", "return",
+ "call", -- last call (to sethook)
+}
+for i = 1, #res do assert(res[i] == table.remove(b, 1)) end
+
+b = 0
+debug.sethook(function (e)
+ if e == "tail call" then
+ b = b + 1
+ assert(debug.getinfo(2, "t").istailcall == true)
+ else
+ assert(debug.getinfo(2, "t").istailcall == false)
+ end
+ end, "c")
+h(false)
+debug.sethook()
+assert(b == 2) -- two tail calls
+
+lim = _soft and 3000 or 30000
+local function foo (x)
+ if x==0 then
+ assert(debug.getinfo(2).what == "main")
+ local info = debug.getinfo(1)
+ assert(info.istailcall == true and info.func == foo)
+ else return foo(x-1)
+ end
+end
+
+foo(lim)
+
+
+print"+"
+
+
+-- testing local function information
+co = load[[
+ local A = function ()
+ return x
+ end
+ return
+]]
+
+local a = 0
+-- 'A' should be visible to debugger only after its complete definition
+debug.sethook(function (e, l)
+ if l == 3 then a = a + 1; assert(debug.getlocal(2, 1) == "(*temporary)")
+ elseif l == 4 then a = a + 1; assert(debug.getlocal(2, 1) == "A")
+ end
+end, "l")
+co() -- run local function definition
+debug.sethook() -- turn off hook
+assert(a == 2) -- ensure all two lines where hooked
+
+-- testing traceback
+
+assert(debug.traceback(print) == print)
+assert(debug.traceback(print, 4) == print)
+assert(string.find(debug.traceback("hi", 4), "^hi\n"))
+assert(string.find(debug.traceback("hi"), "^hi\n"))
+assert(not string.find(debug.traceback("hi"), "'debug.traceback'"))
+assert(string.find(debug.traceback("hi", 0), "'debug.traceback'"))
+assert(string.find(debug.traceback(), "^stack traceback:\n"))
+
+do -- C-function names in traceback
+ local st, msg = (function () return pcall end)()(debug.traceback)
+ assert(st == true and string.find(msg, "pcall"))
+end
+
+
+-- testing nparams, nups e isvararg
+local t = debug.getinfo(print, "u")
+assert(t.isvararg == true and t.nparams == 0 and t.nups == 0)
+
+t = debug.getinfo(function (a,b,c) end, "u")
+assert(t.isvararg == false and t.nparams == 3 and t.nups == 0)
+
+t = debug.getinfo(function (a,b,...) return t[a] end, "u")
+assert(t.isvararg == true and t.nparams == 2 and t.nups == 1)
+
+t = debug.getinfo(1) -- main
+assert(t.isvararg == true and t.nparams == 0 and t.nups == 1 and
+ debug.getupvalue(t.func, 1) == "_ENV")
+
+
+
+
+-- testing debugging of coroutines
+
+local function checktraceback (co, p, level)
+ local tb = debug.traceback(co, nil, level)
+ local i = 0
+ for l in string.gmatch(tb, "[^\n]+\n?") do
+ assert(i == 0 or string.find(l, p[i]))
+ i = i+1
+ end
+ assert(p[i] == undef)
+end
+
+
+local function f (n)
+ if n > 0 then f(n-1)
+ else coroutine.yield() end
+end
+
+local co = coroutine.create(f)
+coroutine.resume(co, 3)
+checktraceback(co, {"yield", "db.lua", "db.lua", "db.lua", "db.lua"})
+checktraceback(co, {"db.lua", "db.lua", "db.lua", "db.lua"}, 1)
+checktraceback(co, {"db.lua", "db.lua", "db.lua"}, 2)
+checktraceback(co, {"db.lua"}, 4)
+checktraceback(co, {}, 40)
+
+
+co = coroutine.create(function (x)
+ local a = 1
+ coroutine.yield(debug.getinfo(1, "l"))
+ coroutine.yield(debug.getinfo(1, "l").currentline)
+ return a
+ end)
+
+local tr = {}
+local foo = function (e, l) if l then table.insert(tr, l) end end
+debug.sethook(co, foo, "lcr")
+
+local _, l = coroutine.resume(co, 10)
+local x = debug.getinfo(co, 1, "lfLS")
+assert(x.currentline == l.currentline and x.activelines[x.currentline])
+assert(type(x.func) == "function")
+for i=x.linedefined + 1, x.lastlinedefined do
+ assert(x.activelines[i])
+ x.activelines[i] = undef
+end
+assert(next(x.activelines) == nil) -- no 'extra' elements
+assert(not debug.getinfo(co, 2))
+local a,b = debug.getlocal(co, 1, 1)
+assert(a == "x" and b == 10)
+a,b = debug.getlocal(co, 1, 2)
+assert(a == "a" and b == 1)
+debug.setlocal(co, 1, 2, "hi")
+assert(debug.gethook(co) == foo)
+assert(#tr == 2 and
+ tr[1] == l.currentline-1 and tr[2] == l.currentline)
+
+a,b,c = pcall(coroutine.resume, co)
+assert(a and b and c == l.currentline+1)
+checktraceback(co, {"yield", "in function <"})
+
+a,b = coroutine.resume(co)
+assert(a and b == "hi")
+assert(#tr == 4 and tr[4] == l.currentline+2)
+assert(debug.gethook(co) == foo)
+assert(not debug.gethook())
+checktraceback(co, {})
+
+
+-- check get/setlocal in coroutines
+co = coroutine.create(function (x)
+ local a, b = coroutine.yield(x)
+ assert(a == 100 and b == nil)
+ return x
+end)
+a, b = coroutine.resume(co, 10)
+assert(a and b == 10)
+a, b = debug.getlocal(co, 1, 1)
+assert(a == "x" and b == 10)
+assert(not debug.getlocal(co, 1, 5))
+assert(debug.setlocal(co, 1, 1, 30) == "x")
+assert(not debug.setlocal(co, 1, 5, 40))
+a, b = coroutine.resume(co, 100)
+assert(a and b == 30)
+
+
+-- check traceback of suspended (or dead with error) coroutines
+
+function f(i) if i==0 then error(i) else coroutine.yield(); f(i-1) end end
+
+co = coroutine.create(function (x) f(x) end)
+a, b = coroutine.resume(co, 3)
+t = {"'coroutine.yield'", "'f'", "in function <"}
+while coroutine.status(co) == "suspended" do
+ checktraceback(co, t)
+ a, b = coroutine.resume(co)
+ table.insert(t, 2, "'f'") -- one more recursive call to 'f'
+end
+t[1] = "'error'"
+checktraceback(co, t)
+
+
+-- test acessing line numbers of a coroutine from a resume inside
+-- a C function (this is a known bug in Lua 5.0)
+
+local function g(x)
+ coroutine.yield(x)
+end
+
+local function f (i)
+ debug.sethook(function () end, "l")
+ for j=1,1000 do
+ g(i+j)
+ end
+end
+
+local co = coroutine.wrap(f)
+co(10)
+pcall(co)
+pcall(co)
+
+
+assert(type(debug.getregistry()) == "table")
+
+
+-- test tagmethod information
+local a = {}
+local function f (t)
+ local info = debug.getinfo(1);
+ assert(info.namewhat == "metamethod")
+ a.op = info.name
+ return info.name
+end
+setmetatable(a, {
+ __index = f; __add = f; __div = f; __mod = f; __concat = f; __pow = f;
+ __mul = f; __idiv = f; __unm = f; __len = f; __sub = f;
+ __shl = f; __shr = f; __bor = f; __bxor = f;
+ __eq = f; __le = f; __lt = f; __unm = f; __len = f; __band = f;
+ __bnot = f;
+})
+
+local b = setmetatable({}, getmetatable(a))
+
+assert(a[3] == "index" and a^3 == "pow" and a..a == "concat")
+assert(a/3 == "div" and 3%a == "mod")
+assert(a+3 == "add" and 3-a == "sub" and a*3 == "mul" and
+ -a == "unm" and #a == "len" and a&3 == "band")
+assert(a|3 == "bor" and 3~a == "bxor" and a<<3 == "shift" and
+ a>>1 == "shift")
+assert (a==b and a.op == "eq")
+assert (a>=b and a.op == "order")
+assert (a>b and a.op == "order")
+assert(~a == "bnot")
+
+do -- testing for-iterator name
+ local function f()
+ assert(debug.getinfo(1).name == "for iterator")
+ end
+
+ for i in f do end
+end
+
+
+do -- testing debug info for finalizers
+ local name = nil
+
+ -- create a piece of garbage with a finalizer
+ setmetatable({}, {__gc = function ()
+ local t = debug.getinfo(2) -- get callee information
+ assert(t.namewhat == "metamethod")
+ name = t.name
+ end})
+
+ -- repeat until previous finalizer runs (setting 'name')
+ repeat local a = {} until name
+ assert(name == "__gc")
+end
+
+
+do
+ print("testing traceback sizes")
+
+ local function countlines (s)
+ return select(2, string.gsub(s, "\n", ""))
+ end
+
+ local function deep (lvl, n)
+ if lvl == 0 then
+ return (debug.traceback("message", n))
+ else
+ return (deep(lvl-1, n))
+ end
+ end
+
+ local function checkdeep (total, start)
+ local s = deep(total, start)
+ local rest = string.match(s, "^message\nstack traceback:\n(.*)$")
+ local cl = countlines(rest)
+ -- at most 10 lines in first part, 11 in second, plus '...'
+ assert(cl <= 10 + 11 + 1)
+ local brk = string.find(rest, "%.%.%.")
+ if brk then -- does message have '...'?
+ local rest1 = string.sub(rest, 1, brk)
+ local rest2 = string.sub(rest, brk, #rest)
+ assert(countlines(rest1) == 10 and countlines(rest2) == 11)
+ else
+ assert(cl == total - start + 2)
+ end
+ end
+
+ for d = 1, 51, 10 do
+ for l = 1, d do
+ -- use coroutines to ensure complete control of the stack
+ coroutine.wrap(checkdeep)(d, l)
+ end
+ end
+
+end
+
+
+print("testing debug functions on chunk without debug info")
+prog = [[-- program to be loaded without debug information
+local debug = require'debug'
+local a = 12 -- a local variable
+
+local n, v = debug.getlocal(1, 1)
+assert(n == "(*temporary)" and v == debug) -- unkown name but known value
+n, v = debug.getlocal(1, 2)
+assert(n == "(*temporary)" and v == 12) -- unkown name but known value
+
+-- a function with an upvalue
+local f = function () local x; return a end
+n, v = debug.getupvalue(f, 1)
+assert(n == "(*no name)" and v == 12)
+assert(debug.setupvalue(f, 1, 13) == "(*no name)")
+assert(a == 13)
+
+local t = debug.getinfo(f)
+assert(t.name == nil and t.linedefined > 0 and
+ t.lastlinedefined == t.linedefined and
+ t.short_src == "?")
+assert(debug.getinfo(1).currentline == -1)
+
+t = debug.getinfo(f, "L").activelines
+assert(next(t) == nil) -- active lines are empty
+
+-- dump/load a function without debug info
+f = load(string.dump(f))
+
+t = debug.getinfo(f)
+assert(t.name == nil and t.linedefined > 0 and
+ t.lastlinedefined == t.linedefined and
+ t.short_src == "?")
+assert(debug.getinfo(1).currentline == -1)
+
+return a
+]]
+
+
+-- load 'prog' without debug info
+local f = assert(load(string.dump(load(prog), true)))
+
+assert(f() == 13)
+
+do -- tests for 'source' in binary dumps
+ local prog = [[
+ return function (x)
+ return function (y)
+ return x + y
+ end
+ end
+ ]]
+ local name = string.rep("x", 1000)
+ local p = assert(load(prog, name))
+ -- load 'p' as a binary chunk with debug information
+ local c = string.dump(p)
+ assert(#c > 1000 and #c < 2000) -- no repetition of 'source' in dump
+ local f = assert(load(c))
+ local g = f()
+ local h = g(3)
+ assert(h(5) == 8)
+ assert(debug.getinfo(f).source == name and -- all functions have 'source'
+ debug.getinfo(g).source == name and
+ debug.getinfo(h).source == name)
+ -- again, without debug info
+ local c = string.dump(p, true)
+ assert(#c < 500) -- no 'source' in dump
+ local f = assert(load(c))
+ local g = f()
+ local h = g(30)
+ assert(h(50) == 80)
+ assert(debug.getinfo(f).source == '=?' and -- no function has 'source'
+ debug.getinfo(g).source == '=?' and
+ debug.getinfo(h).source == '=?')
+end
+
+print"OK"
+
diff --git a/testes/errors.lua b/testes/errors.lua
new file mode 100644
index 0000000000..63a7b740cd
--- /dev/null
+++ b/testes/errors.lua
@@ -0,0 +1,554 @@
+-- $Id: errors.lua,v 1.97 2017/11/28 15:31:56 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print("testing errors")
+
+local debug = require"debug"
+
+-- avoid problems with 'strict' module (which may generate other error messages)
+local mt = getmetatable(_G) or {}
+local oldmm = mt.__index
+mt.__index = nil
+
+local function checkerr (msg, f, ...)
+ local st, err = pcall(f, ...)
+ assert(not st and string.find(err, msg))
+end
+
+
+local function doit (s)
+ local f, msg = load(s)
+ if f == nil then return msg end
+ local cond, msg = pcall(f)
+ return (not cond) and msg
+end
+
+
+local function checkmessage (prog, msg)
+ local m = doit(prog)
+ assert(string.find(m, msg, 1, true))
+end
+
+local function checksyntax (prog, extra, token, line)
+ local msg = doit(prog)
+ if not string.find(token, "^<%a") and not string.find(token, "^char%(")
+ then token = "'"..token.."'" end
+ token = string.gsub(token, "(%p)", "%%%1")
+ local pt = string.format([[^%%[string ".*"%%]:%d: .- near %s$]],
+ line, token)
+ assert(string.find(msg, pt))
+ assert(string.find(msg, msg, 1, true))
+end
+
+
+-- test error message with no extra info
+assert(doit("error('hi', 0)") == 'hi')
+
+-- test error message with no info
+assert(doit("error()") == nil)
+
+
+-- test common errors/errors that crashed in the past
+assert(doit("table.unpack({}, 1, n=2^30)"))
+assert(doit("a=math.sin()"))
+assert(not doit("tostring(1)") and doit("tostring()"))
+assert(doit"tonumber()")
+assert(doit"repeat until 1; a")
+assert(doit"return;;")
+assert(doit"assert(false)")
+assert(doit"assert(nil)")
+assert(doit("function a (... , ...) end"))
+assert(doit("function a (, ...) end"))
+assert(doit("local t={}; t = t[#t] + 1"))
+
+checksyntax([[
+ local a = {4
+
+]], "'}' expected (to close '{' at line 1)", "", 3)
+
+
+if not T then
+ (Message or print)
+ ('\n >>> testC not active: skipping memory message test <<<\n')
+else
+ print "testing memory error message"
+ local a = {}
+ for i = 1, 10000 do a[i] = true end -- preallocate array
+ collectgarbage()
+ T.totalmem(T.totalmem() + 10000)
+ -- force a memory error (by a small margin)
+ local st, msg = pcall(function()
+ for i = 1, 100000 do a[i] = tostring(i) end
+ end)
+ T.totalmem(0)
+ assert(not st and msg == "not enough" .. " memory")
+end
+
+
+-- tests for better error messages
+
+checkmessage("a = {} + 1", "arithmetic")
+checkmessage("a = {} | 1", "bitwise operation")
+checkmessage("a = {} < 1", "attempt to compare")
+checkmessage("a = {} <= 1", "attempt to compare")
+
+checkmessage("a=1; bbbb=2; a=math.sin(3)+bbbb(3)", "global 'bbbb'")
+checkmessage("a={}; do local a=1 end a:bbbb(3)", "method 'bbbb'")
+checkmessage("local a={}; a.bbbb(3)", "field 'bbbb'")
+assert(not string.find(doit"a={13}; local bbbb=1; a[bbbb](3)", "'bbbb'"))
+checkmessage("a={13}; local bbbb=1; a[bbbb](3)", "number")
+checkmessage("a=(1)..{}", "a table value")
+
+checkmessage("a = #print", "length of a function value")
+checkmessage("a = #3", "length of a number value")
+
+aaa = nil
+checkmessage("aaa.bbb:ddd(9)", "global 'aaa'")
+checkmessage("local aaa={bbb=1}; aaa.bbb:ddd(9)", "field 'bbb'")
+checkmessage("local aaa={bbb={}}; aaa.bbb:ddd(9)", "method 'ddd'")
+checkmessage("local a,b,c; (function () a = b+1.1 end)()", "upvalue 'b'")
+assert(not doit"local aaa={bbb={ddd=next}}; aaa.bbb:ddd(nil)")
+
+-- upvalues being indexed do not go to the stack
+checkmessage("local a,b,cc; (function () a = cc[1] end)()", "upvalue 'cc'")
+checkmessage("local a,b,cc; (function () a.x = 1 end)()", "upvalue 'a'")
+
+checkmessage("local _ENV = {x={}}; a = a + 1", "global 'a'")
+
+checkmessage("b=1; local aaa={}; x=aaa+b", "local 'aaa'")
+checkmessage("aaa={}; x=3.3/aaa", "global 'aaa'")
+checkmessage("aaa=2; b=nil;x=aaa*b", "global 'b'")
+checkmessage("aaa={}; x=-aaa", "global 'aaa'")
+
+-- short circuit
+checkmessage("a=1; local a,bbbb=2,3; a = math.sin(1) and bbbb(3)",
+ "local 'bbbb'")
+checkmessage("a=1; local a,bbbb=2,3; a = bbbb(1) or a(3)", "local 'bbbb'")
+checkmessage("local a,b,c,f = 1,1,1; f((a and b) or c)", "local 'f'")
+checkmessage("local a,b,c = 1,1,1; ((a and b) or c)()", "call a number value")
+assert(not string.find(doit"aaa={}; x=(aaa or aaa)+(aaa and aaa)", "'aaa'"))
+assert(not string.find(doit"aaa={}; (aaa or aaa)()", "'aaa'"))
+
+checkmessage("print(print < 10)", "function with number")
+checkmessage("print(print < print)", "two function values")
+checkmessage("print('10' < 10)", "string with number")
+checkmessage("print(10 < '23')", "number with string")
+
+-- float->integer conversions
+checkmessage("local a = 2.0^100; x = a << 2", "local a")
+checkmessage("local a = 1 >> 2.0^100", "has no integer representation")
+checkmessage("local a = 10.1 << 2.0^100", "has no integer representation")
+checkmessage("local a = 2.0^100 & 1", "has no integer representation")
+checkmessage("local a = 2.0^100 & 1e100", "has no integer representation")
+checkmessage("local a = 2.0 | 1e40", "has no integer representation")
+checkmessage("local a = 2e100 ~ 1", "has no integer representation")
+checkmessage("string.sub('a', 2.0^100)", "has no integer representation")
+checkmessage("string.rep('a', 3.3)", "has no integer representation")
+checkmessage("return 6e40 & 7", "has no integer representation")
+checkmessage("return 34 << 7e30", "has no integer representation")
+checkmessage("return ~-3e40", "has no integer representation")
+checkmessage("return ~-3.009", "has no integer representation")
+checkmessage("return 3.009 & 1", "has no integer representation")
+checkmessage("return 34 >> {}", "table value")
+checkmessage("a = 24 // 0", "divide by zero")
+checkmessage("a = 1 % 0", "'n%0'")
+
+
+-- passing light userdata instead of full userdata
+_G.D = debug
+checkmessage([[
+ -- create light udata
+ local x = D.upvalueid(function () return debug end, 1)
+ D.setuservalue(x, {})
+]], "light userdata")
+_G.D = nil
+
+do -- named objects (field '__name')
+ checkmessage("math.sin(io.input())", "(number expected, got FILE*)")
+ _G.XX = setmetatable({}, {__name = "My Type"})
+ assert(string.find(tostring(XX), "^My Type"))
+ checkmessage("io.input(XX)", "(FILE* expected, got My Type)")
+ checkmessage("return XX + 1", "on a My Type value")
+ checkmessage("return ~io.stdin", "on a FILE* value")
+ checkmessage("return XX < XX", "two My Type values")
+ checkmessage("return {} < XX", "table with My Type")
+ checkmessage("return XX < io.stdin", "My Type with FILE*")
+ _G.XX = nil
+end
+
+-- global functions
+checkmessage("(io.write or print){}", "io.write")
+checkmessage("(collectgarbage or print){}", "collectgarbage")
+
+-- errors in functions without debug info
+do
+ local f = function (a) return a + 1 end
+ f = assert(load(string.dump(f, true)))
+ assert(f(3) == 4)
+ checkerr("^%?:%-1:", f, {})
+
+ -- code with a move to a local var ('OP_MOV A B' with A3+1,
+ {d = x and aaa[x or y]}}
+]], "global 'aaa'")
+
+checkmessage([[
+local x,y = {},1
+if math.sin(1) == 0 then return 3 end -- return
+x.a()]], "field 'a'")
+
+checkmessage([[
+prefix = nil
+insert = nil
+while 1 do
+ local a
+ if nil then break end
+ insert(prefix, a)
+end]], "global 'insert'")
+
+checkmessage([[ -- tail call
+ return math.sin("a")
+]], "'sin'")
+
+checkmessage([[collectgarbage("nooption")]], "invalid option")
+
+checkmessage([[x = print .. "a"]], "concatenate")
+checkmessage([[x = "a" .. false]], "concatenate")
+checkmessage([[x = {} .. 2]], "concatenate")
+
+checkmessage("getmetatable(io.stdin).__gc()", "no value")
+
+checkmessage([[
+local Var
+local function main()
+ NoSuchName (function() Var=0 end)
+end
+main()
+]], "global 'NoSuchName'")
+print'+'
+
+a = {}; setmetatable(a, {__index = string})
+checkmessage("a:sub()", "bad self")
+checkmessage("string.sub('a', {})", "#2")
+checkmessage("('a'):sub{}", "#1")
+
+checkmessage("table.sort({1,2,3}, table.sort)", "'table.sort'")
+checkmessage("string.gsub('s', 's', setmetatable)", "'setmetatable'")
+
+-- tests for errors in coroutines
+
+local function f (n)
+ local c = coroutine.create(f)
+ local a,b = coroutine.resume(c)
+ return b
+end
+assert(string.find(f(), "C stack overflow"))
+
+checkmessage("coroutine.yield()", "outside a coroutine")
+
+f = coroutine.wrap(function () table.sort({1,2,3}, coroutine.yield) end)
+checkerr("yield across", f)
+
+
+-- testing size of 'source' info; size of buffer for that info is
+-- LUA_IDSIZE, declared as 60 in luaconf. Get one position for '\0'.
+idsize = 60 - 1
+local function checksize (source)
+ -- syntax error
+ local _, msg = load("x", source)
+ msg = string.match(msg, "^([^:]*):") -- get source (1st part before ':')
+ assert(msg:len() <= idsize)
+end
+
+for i = 60 - 10, 60 + 10 do -- check border cases around 60
+ checksize("@" .. string.rep("x", i)) -- file names
+ checksize(string.rep("x", i - 10)) -- string sources
+ checksize("=" .. string.rep("x", i)) -- exact sources
+end
+
+
+-- testing line error
+
+local function lineerror (s, l)
+ local err,msg = pcall(load(s))
+ local line = string.match(msg, ":(%d+):")
+ assert((line and line+0) == l)
+end
+
+lineerror("local a\n for i=1,'a' do \n print(i) \n end", 2)
+lineerror("\n local a \n for k,v in 3 \n do \n print(k) \n end", 3)
+lineerror("\n\n for k,v in \n 3 \n do \n print(k) \n end", 4)
+lineerror("function a.x.y ()\na=a+1\nend", 1)
+
+lineerror("a = \na\n+\n{}", 3)
+lineerror("a = \n3\n+\n(\n4\n/\nprint)", 6)
+lineerror("a = \nprint\n+\n(\n4\n/\n7)", 3)
+
+lineerror("a\n=\n-\n\nprint\n;", 3)
+
+lineerror([[
+a
+(
+23)
+]], 1)
+
+lineerror([[
+local a = {x = 13}
+a
+.
+x
+(
+23
+)
+]], 2)
+
+lineerror([[
+local a = {x = 13}
+a
+.
+x
+(
+23 + a
+)
+]], 6)
+
+local p = [[
+ function g() f() end
+ function f(x) error('a', X) end
+g()
+]]
+X=3;lineerror((p), 3)
+X=0;lineerror((p), nil)
+X=1;lineerror((p), 2)
+X=2;lineerror((p), 1)
+
+
+if not _soft then
+ -- several tests that exaust the Lua stack
+ collectgarbage()
+ print"testing stack overflow"
+ C = 0
+ local l = debug.getinfo(1, "l").currentline; function y () C=C+1; y() end
+
+ local function checkstackmessage (m)
+ return (string.find(m, "stack overflow"))
+ end
+ -- repeated stack overflows (to check stack recovery)
+ assert(checkstackmessage(doit('y()')))
+ print('+')
+ assert(checkstackmessage(doit('y()')))
+ print('+')
+ assert(checkstackmessage(doit('y()')))
+ print('+')
+
+
+ -- error lines in stack overflow
+ C = 0
+ local l1
+ local function g(x)
+ l1 = debug.getinfo(x, "l").currentline; y()
+ end
+ local _, stackmsg = xpcall(g, debug.traceback, 1)
+ print('+')
+ local stack = {}
+ for line in string.gmatch(stackmsg, "[^\n]*") do
+ local curr = string.match(line, ":(%d+):")
+ if curr then table.insert(stack, tonumber(curr)) end
+ end
+ local i=1
+ while stack[i] ~= l1 do
+ assert(stack[i] == l)
+ i = i+1
+ end
+ assert(i > 15)
+
+
+ -- error in error handling
+ local res, msg = xpcall(error, error)
+ assert(not res and type(msg) == 'string')
+ print('+')
+
+ local function f (x)
+ if x==0 then error('a\n')
+ else
+ local aux = function () return f(x-1) end
+ local a,b = xpcall(aux, aux)
+ return a,b
+ end
+ end
+ f(3)
+
+ local function loop (x,y,z) return 1 + loop(x, y, z) end
+
+ local res, msg = xpcall(loop, function (m)
+ assert(string.find(m, "stack overflow"))
+ checkerr("error handling", loop)
+ assert(math.sin(0) == 0)
+ return 15
+ end)
+ assert(msg == 15)
+
+ local f = function ()
+ for i = 999900, 1000000, 1 do table.unpack({}, 1, i) end
+ end
+ checkerr("too many results", f)
+
+end
+
+
+do
+ -- non string messages
+ local t = {}
+ local res, msg = pcall(function () error(t) end)
+ assert(not res and msg == t)
+
+ res, msg = pcall(function () error(nil) end)
+ assert(not res and msg == nil)
+
+ local function f() error{msg='x'} end
+ res, msg = xpcall(f, function (r) return {msg=r.msg..'y'} end)
+ assert(msg.msg == 'xy')
+
+ -- 'assert' with extra arguments
+ res, msg = pcall(assert, false, "X", t)
+ assert(not res and msg == "X")
+
+ -- 'assert' with no message
+ res, msg = pcall(function () assert(false) end)
+ local line = string.match(msg, "%w+%.lua:(%d+): assertion failed!$")
+ assert(tonumber(line) == debug.getinfo(1, "l").currentline - 2)
+
+ -- 'assert' with non-string messages
+ res, msg = pcall(assert, false, t)
+ assert(not res and msg == t)
+
+ res, msg = pcall(assert, nil, nil)
+ assert(not res and msg == nil)
+
+ -- 'assert' without arguments
+ res, msg = pcall(assert)
+ assert(not res and string.find(msg, "value expected"))
+end
+
+-- xpcall with arguments
+a, b, c = xpcall(string.find, error, "alo", "al")
+assert(a and b == 1 and c == 2)
+a, b, c = xpcall(string.find, function (x) return {} end, true, "al")
+assert(not a and type(b) == "table" and c == nil)
+
+
+print("testing tokens in error messages")
+checksyntax("syntax error", "", "error", 1)
+checksyntax("1.000", "", "1.000", 1)
+checksyntax("[[a]]", "", "[[a]]", 1)
+checksyntax("'aa'", "", "'aa'", 1)
+checksyntax("while << do end", "", "<<", 1)
+checksyntax("for >> do end", "", ">>", 1)
+
+-- test invalid non-printable char in a chunk
+checksyntax("a\1a = 1", "", "<\\1>", 1)
+
+-- test 255 as first char in a chunk
+checksyntax("\255a = 1", "", "<\\255>", 1)
+
+doit('I = load("a=9+"); a=3')
+assert(a==3 and I == nil)
+print('+')
+
+lim = 1000
+if _soft then lim = 100 end
+for i=1,lim do
+ doit('a = ')
+ doit('a = 4+nil')
+end
+
+
+-- testing syntax limits
+
+local function testrep (init, rep, close, repc)
+ local s = init .. string.rep(rep, 100) .. close .. string.rep(repc, 100)
+ assert(load(s)) -- 100 levels is OK
+ s = init .. string.rep(rep, 10000)
+ local res, msg = load(s) -- 10000 levels not ok
+ assert(not res and (string.find(msg, "too many registers") or
+ string.find(msg, "stack overflow")))
+end
+
+testrep("local a; a", ",a", "= 1", ",1") -- multiple assignment
+testrep("local a; a=", "{", "0", "}")
+testrep("local a; a=", "(", "2", ")")
+testrep("local a; ", "a(", "2", ")")
+testrep("", "do ", "", " end")
+testrep("", "while a do ", "", " end")
+testrep("local a; ", "if a then else ", "", " end")
+testrep("", "function foo () ", "", " end")
+testrep("local a; a=", "a..", "a", "")
+testrep("local a; a=", "a^", "a", "")
+
+checkmessage("a = f(x" .. string.rep(",x", 260) .. ")", "too many registers")
+
+
+-- testing other limits
+
+-- upvalues
+local lim = 127
+local s = "local function fooA ()\n local "
+for j = 1,lim do
+ s = s.."a"..j..", "
+end
+s = s.."b,c\n"
+s = s.."local function fooB ()\n local "
+for j = 1,lim do
+ s = s.."b"..j..", "
+end
+s = s.."b\n"
+s = s.."function fooC () return b+c"
+local c = 1+2
+for j = 1,lim do
+ s = s.."+a"..j.."+b"..j
+ c = c + 2
+end
+s = s.."\nend end end"
+local a,b = load(s)
+assert(c > 255 and string.find(b, "too many upvalues") and
+ string.find(b, "line 5"))
+
+-- local variables
+s = "\nfunction foo ()\n local "
+for j = 1,300 do
+ s = s.."a"..j..", "
+end
+s = s.."b\n"
+local a,b = load(s)
+assert(string.find(b, "line 2") and string.find(b, "too many local variables"))
+
+mt.__index = oldmm
+
+print('OK')
diff --git a/testes/events.lua b/testes/events.lua
new file mode 100644
index 0000000000..cf064d3d6b
--- /dev/null
+++ b/testes/events.lua
@@ -0,0 +1,476 @@
+-- $Id: events.lua,v 1.52 2018/03/12 13:51:02 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print('testing metatables')
+
+local debug = require'debug'
+
+X = 20; B = 30
+
+_ENV = setmetatable({}, {__index=_G})
+
+collectgarbage()
+
+X = X+10
+assert(X == 30 and _G.X == 20)
+B = false
+assert(B == false)
+_ENV["B"] = undef
+assert(B == 30)
+
+assert(getmetatable{} == nil)
+assert(getmetatable(4) == nil)
+assert(getmetatable(nil) == nil)
+a={name = "NAME"}; setmetatable(a, {__metatable = "xuxu",
+ __tostring=function(x) return x.name end})
+assert(getmetatable(a) == "xuxu")
+assert(tostring(a) == "NAME")
+-- cannot change a protected metatable
+assert(pcall(setmetatable, a, {}) == false)
+a.name = "gororoba"
+assert(tostring(a) == "gororoba")
+
+local a, t = {10,20,30; x="10", y="20"}, {}
+assert(setmetatable(a,t) == a)
+assert(getmetatable(a) == t)
+assert(setmetatable(a,nil) == a)
+assert(getmetatable(a) == nil)
+assert(setmetatable(a,t) == a)
+
+
+function f (t, i, e)
+ assert(not e)
+ local p = rawget(t, "parent")
+ return (p and p[i]+3), "dummy return"
+end
+
+t.__index = f
+
+a.parent = {z=25, x=12, [4] = 24}
+assert(a[1] == 10 and a.z == 28 and a[4] == 27 and a.x == "10")
+
+collectgarbage()
+
+a = setmetatable({}, t)
+function f(t, i, v) rawset(t, i, v-3) end
+setmetatable(t, t) -- causes a bug in 5.1 !
+t.__newindex = f
+a[1] = 30; a.x = "101"; a[5] = 200
+assert(a[1] == 27 and a.x == 98 and a[5] == 197)
+
+do -- bug in Lua 5.3.2
+ local mt = {}
+ mt.__newindex = mt
+ local t = setmetatable({}, mt)
+ t[1] = 10 -- will segfault on some machines
+ assert(mt[1] == 10)
+end
+
+
+local c = {}
+a = setmetatable({}, t)
+t.__newindex = c
+t.__index = c
+a[1] = 10; a[2] = 20; a[3] = 90;
+for i = 4, 20 do a[i] = i * 10 end
+assert(a[1] == 10 and a[2] == 20 and a[3] == 90)
+for i = 4, 20 do assert(a[i] == i * 10) end
+assert(next(a) == nil)
+
+
+do
+ local a;
+ a = setmetatable({}, {__index = setmetatable({},
+ {__index = setmetatable({},
+ {__index = function (_,n) return a[n-3]+4, "lixo" end})})})
+ a[0] = 20
+ for i=0,10 do
+ assert(a[i*3] == 20 + i*4)
+ end
+end
+
+
+do -- newindex
+ local foi
+ local a = {}
+ for i=1,10 do a[i] = 0; a['a'..i] = 0; end
+ setmetatable(a, {__newindex = function (t,k,v) foi=true; rawset(t,k,v) end})
+ foi = false; a[1]=0; assert(not foi)
+ foi = false; a['a1']=0; assert(not foi)
+ foi = false; a['a11']=0; assert(foi)
+ foi = false; a[11]=0; assert(foi)
+ foi = false; a[1]=undef; assert(not foi)
+ a[1] = undef
+ foi = false; a[1]=nil; assert(foi)
+end
+
+
+setmetatable(t, nil)
+function f (t, ...) return t, {...} end
+t.__call = f
+
+do
+ local x,y = a(table.unpack{'a', 1})
+ assert(x==a and y[1]=='a' and y[2]==1 and y[3]==undef)
+ x,y = a()
+ assert(x==a and y[1]==undef)
+end
+
+
+local b = setmetatable({}, t)
+setmetatable(b,t)
+
+function f(op)
+ return function (...) cap = {[0] = op, ...} ; return (...) end
+end
+t.__add = f("add")
+t.__sub = f("sub")
+t.__mul = f("mul")
+t.__div = f("div")
+t.__idiv = f("idiv")
+t.__mod = f("mod")
+t.__unm = f("unm")
+t.__pow = f("pow")
+t.__len = f("len")
+t.__band = f("band")
+t.__bor = f("bor")
+t.__bxor = f("bxor")
+t.__shl = f("shl")
+t.__shr = f("shr")
+t.__bnot = f("bnot")
+
+-- Some tests are done inside small anonymous functions to ensure
+-- that constants go to constant table even in debug compilation,
+-- when the constant table is very small.
+assert(b+5 == b)
+assert(cap[0] == "add" and cap[1] == b and cap[2] == 5 and cap[3]==undef)
+assert(b+'5' == b)
+assert(cap[0] == "add" and cap[1] == b and cap[2] == '5' and cap[3]==undef)
+assert(5+b == 5)
+assert(cap[0] == "add" and cap[1] == 5 and cap[2] == b and cap[3]==undef)
+assert('5'+b == '5')
+assert(cap[0] == "add" and cap[1] == '5' and cap[2] == b and cap[3]==undef)
+b=b-3; assert(getmetatable(b) == t)
+assert(cap[0] == "sub" and cap[1] == b and cap[2] == 3 and cap[3]==undef)
+assert(5-a == 5)
+assert(cap[0] == "sub" and cap[1] == 5 and cap[2] == a and cap[3]==undef)
+assert('5'-a == '5')
+assert(cap[0] == "sub" and cap[1] == '5' and cap[2] == a and cap[3]==undef)
+assert(a*a == a)
+assert(cap[0] == "mul" and cap[1] == a and cap[2] == a and cap[3]==undef)
+assert(a/0 == a)
+assert(cap[0] == "div" and cap[1] == a and cap[2] == 0 and cap[3]==undef)
+assert(a%2 == a)
+assert(cap[0] == "mod" and cap[1] == a and cap[2] == 2 and cap[3]==undef)
+assert(a // (1/0) == a)
+assert(cap[0] == "idiv" and cap[1] == a and cap[2] == 1/0 and cap[3]==undef)
+;(function () assert(a & "hi" == a) end)()
+assert(cap[0] == "band" and cap[1] == a and cap[2] == "hi" and cap[3]==undef)
+;(function () assert(10 & a == 10) end)()
+assert(cap[0] == "band" and cap[1] == 10 and cap[2] == a and cap[3]==undef)
+;(function () assert(a | 10 == a) end)()
+assert(cap[0] == "bor" and cap[1] == a and cap[2] == 10 and cap[3]==undef)
+assert(a | "hi" == a)
+assert(cap[0] == "bor" and cap[1] == a and cap[2] == "hi" and cap[3]==undef)
+assert("hi" ~ a == "hi")
+assert(cap[0] == "bxor" and cap[1] == "hi" and cap[2] == a and cap[3]==undef)
+;(function () assert(10 ~ a == 10) end)()
+assert(cap[0] == "bxor" and cap[1] == 10 and cap[2] == a and cap[3]==undef)
+assert(-a == a)
+assert(cap[0] == "unm" and cap[1] == a)
+assert(a^4 == a)
+assert(cap[0] == "pow" and cap[1] == a and cap[2] == 4 and cap[3]==undef)
+assert(a^'4' == a)
+assert(cap[0] == "pow" and cap[1] == a and cap[2] == '4' and cap[3]==undef)
+assert(4^a == 4)
+assert(cap[0] == "pow" and cap[1] == 4 and cap[2] == a and cap[3]==undef)
+assert('4'^a == '4')
+assert(cap[0] == "pow" and cap[1] == '4' and cap[2] == a and cap[3]==undef)
+assert(#a == a)
+assert(cap[0] == "len" and cap[1] == a)
+assert(~a == a)
+assert(cap[0] == "bnot" and cap[1] == a)
+assert(a << 3 == a)
+assert(cap[0] == "shl" and cap[1] == a and cap[2] == 3)
+assert(1.5 >> a == 1.5)
+assert(cap[0] == "shr" and cap[1] == 1.5 and cap[2] == a)
+
+
+-- test for rawlen
+t = setmetatable({1,2,3}, {__len = function () return 10 end})
+assert(#t == 10 and rawlen(t) == 3)
+assert(rawlen"abc" == 3)
+assert(not pcall(rawlen, io.stdin))
+assert(not pcall(rawlen, 34))
+assert(not pcall(rawlen))
+
+-- rawlen for long strings
+assert(rawlen(string.rep('a', 1000)) == 1000)
+
+
+t = {}
+t.__lt = function (a,b,c)
+ collectgarbage()
+ assert(c == nil)
+ if type(a) == 'table' then a = a.x end
+ if type(b) == 'table' then b = b.x end
+ return aOp(1)) and not(Op(1)>Op(2)) and (Op(2)>Op(1)))
+ assert(not(Op('a')>Op('a')) and not(Op('a')>Op('b')) and (Op('b')>Op('a')))
+ assert((Op(1)>=Op(1)) and not(Op(1)>=Op(2)) and (Op(2)>=Op(1)))
+ assert((1 >= Op(1)) and not(1 >= Op(2)) and (Op(2) >= 1))
+ assert((Op('a')>=Op('a')) and not(Op('a')>=Op('b')) and (Op('b')>=Op('a')))
+ assert(('a' >= Op('a')) and not(Op('a') >= 'b') and (Op('b') >= Op('a')))
+end
+
+test()
+
+t.__le = function (a,b,c)
+ assert(c == nil)
+ if type(a) == 'table' then a = a.x end
+ if type(b) == 'table' then b = b.x end
+ return a<=b, "dummy"
+end
+
+test() -- retest comparisons, now using both `lt' and `le'
+
+
+-- test `partial order'
+
+local function rawSet(x)
+ local y = {}
+ for _,k in pairs(x) do y[k] = 1 end
+ return y
+end
+
+local function Set(x)
+ return setmetatable(rawSet(x), t)
+end
+
+t.__lt = function (a,b)
+ for k in pairs(a) do
+ if not b[k] then return false end
+ b[k] = undef
+ end
+ return next(b) ~= nil
+end
+
+t.__le = nil
+
+assert(Set{1,2,3} < Set{1,2,3,4})
+assert(not(Set{1,2,3,4} < Set{1,2,3,4}))
+assert((Set{1,2,3,4} <= Set{1,2,3,4}))
+assert((Set{1,2,3,4} >= Set{1,2,3,4}))
+assert((Set{1,3} <= Set{3,5})) -- wrong!! model needs a `le' method ;-)
+
+t.__le = function (a,b)
+ for k in pairs(a) do
+ if not b[k] then return false end
+ end
+ return true
+end
+
+assert(not (Set{1,3} <= Set{3,5})) -- now its OK!
+assert(not(Set{1,3} <= Set{3,5}))
+assert(not(Set{1,3} >= Set{3,5}))
+
+t.__eq = function (a,b)
+ for k in pairs(a) do
+ if not b[k] then return false end
+ b[k] = undef
+ end
+ return next(b) == nil
+end
+
+local s = Set{1,3,5}
+assert(s == Set{3,5,1})
+assert(not rawequal(s, Set{3,5,1}))
+assert(rawequal(s, s))
+assert(Set{1,3,5,1} == rawSet{3,5,1})
+assert(rawSet{1,3,5,1} == Set{3,5,1})
+assert(Set{1,3,5} ~= Set{3,5,1,6})
+
+-- '__eq' is not used for table accesses
+t[Set{1,3,5}] = 1
+assert(t[Set{1,3,5}] == undef)
+
+
+if not T then
+ (Message or print)('\n >>> testC not active: skipping tests for \z
+userdata <<<\n')
+else
+ local u1 = T.newuserdata(0, 1)
+ local u2 = T.newuserdata(0, 1)
+ local u3 = T.newuserdata(0, 1)
+ assert(u1 ~= u2 and u1 ~= u3)
+ debug.setuservalue(u1, 1);
+ debug.setuservalue(u2, 2);
+ debug.setuservalue(u3, 1);
+ debug.setmetatable(u1, {__eq = function (a, b)
+ return debug.getuservalue(a) == debug.getuservalue(b)
+ end})
+ debug.setmetatable(u2, {__eq = function (a, b)
+ return true
+ end})
+ assert(u1 == u3 and u3 == u1 and u1 ~= u2)
+ assert(u2 == u1 and u2 == u3 and u3 == u2)
+ assert(u2 ~= {}) -- different types cannot be equal
+
+ local mirror = {}
+ debug.setmetatable(u3, {__index = mirror, __newindex = mirror})
+ for i = 1, 10 do u3[i] = i end
+ for i = 1, 10 do assert(u3[i] == i) end
+end
+
+
+t.__concat = function (a,b,c)
+ assert(c == nil)
+ if type(a) == 'table' then a = a.val end
+ if type(b) == 'table' then b = b.val end
+ if A then return a..b
+ else
+ return setmetatable({val=a..b}, t)
+ end
+end
+
+c = {val="c"}; setmetatable(c, t)
+d = {val="d"}; setmetatable(d, t)
+
+A = true
+assert(c..d == 'cd')
+assert(0 .."a".."b"..c..d.."e".."f"..(5+3).."g" == "0abcdef8g")
+
+A = false
+assert((c..d..c..d).val == 'cdcd')
+x = c..d
+assert(getmetatable(x) == t and x.val == 'cd')
+x = 0 .."a".."b"..c..d.."e".."f".."g"
+assert(x.val == "0abcdefg")
+
+
+-- concat metamethod x numbers (bug in 5.1.1)
+c = {}
+local x
+setmetatable(c, {__concat = function (a,b)
+ assert(type(a) == "number" and b == c or type(b) == "number" and a == c)
+ return c
+end})
+assert(c..5 == c and 5 .. c == c)
+assert(4 .. c .. 5 == c and 4 .. 5 .. 6 .. 7 .. c == c)
+
+
+-- test comparison compatibilities
+local t1, t2, c, d
+t1 = {}; c = {}; setmetatable(c, t1)
+d = {}
+t1.__eq = function () return true end
+t1.__lt = function () return true end
+setmetatable(d, t1)
+assert(c == d and c < d and not(d <= c))
+t2 = {}
+t2.__eq = t1.__eq
+t2.__lt = t1.__lt
+setmetatable(d, t2)
+assert(c == d and c < d and not(d <= c))
+
+
+
+-- test for several levels of calls
+local i
+local tt = {
+ __call = function (t, ...)
+ i = i+1
+ if t.f then return t.f(...)
+ else return {...}
+ end
+ end
+}
+
+local a = setmetatable({}, tt)
+local b = setmetatable({f=a}, tt)
+local c = setmetatable({f=b}, tt)
+
+i = 0
+x = c(3,4,5)
+assert(i == 3 and x[1] == 3 and x[3] == 5)
+
+
+assert(_G.X == 20)
+
+print'+'
+
+local _g = _G
+_ENV = setmetatable({}, {__index=function (_,k) return _g[k] end})
+
+
+a = {}
+rawset(a, "x", 1, 2, 3)
+assert(a.x == 1 and rawget(a, "x", 3) == 1)
+
+print '+'
+
+-- testing metatables for basic types
+mt = {__index = function (a,b) return a+b end,
+ __len = function (x) return math.floor(x) end}
+debug.setmetatable(10, mt)
+assert(getmetatable(-2) == mt)
+assert((10)[3] == 13)
+assert((10)["3"] == 13)
+assert(#3.45 == 3)
+debug.setmetatable(23, nil)
+assert(getmetatable(-2) == nil)
+
+debug.setmetatable(true, mt)
+assert(getmetatable(false) == mt)
+mt.__index = function (a,b) return a or b end
+assert((true)[false] == true)
+assert((false)[false] == false)
+debug.setmetatable(false, nil)
+assert(getmetatable(true) == nil)
+
+debug.setmetatable(nil, mt)
+assert(getmetatable(nil) == mt)
+mt.__add = function (a,b) return (a or 1) + (b or 2) end
+assert(10 + nil == 12)
+assert(nil + 23 == 24)
+assert(nil + nil == 3)
+debug.setmetatable(nil, nil)
+assert(getmetatable(nil) == nil)
+
+debug.setmetatable(nil, {})
+
+
+-- loops in delegation
+a = {}; setmetatable(a, a); a.__index = a; a.__newindex = a
+assert(not pcall(function (a,b) return a[b] end, a, 10))
+assert(not pcall(function (a,b,c) a[b] = c end, a, 10, true))
+
+-- bug in 5.1
+T, K, V = nil
+grandparent = {}
+grandparent.__newindex = function(t,k,v) T=t; K=k; V=v end
+
+parent = {}
+parent.__newindex = parent
+setmetatable(parent, grandparent)
+
+child = setmetatable({}, parent)
+child.foo = 10 --> CRASH (on some machines)
+assert(T == parent and K == "foo" and V == 10)
+
+print 'OK'
+
+return 12
+
+
diff --git a/testes/files.lua b/testes/files.lua
new file mode 100644
index 0000000000..b2c7c202f1
--- /dev/null
+++ b/testes/files.lua
@@ -0,0 +1,832 @@
+-- $Id: files.lua,v 1.101 2018/03/12 13:51:02 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+local debug = require "debug"
+
+local maxint = math.maxinteger
+
+assert(type(os.getenv"PATH") == "string")
+
+assert(io.input(io.stdin) == io.stdin)
+assert(not pcall(io.input, "non-existent-file"))
+assert(io.output(io.stdout) == io.stdout)
+
+
+local function testerr (msg, f, ...)
+ local stat, err = pcall(f, ...)
+ return (not stat and string.find(err, msg, 1, true))
+end
+
+
+local function checkerr (msg, f, ...)
+ assert(testerr(msg, f, ...))
+end
+
+
+-- cannot close standard files
+assert(not io.close(io.stdin) and
+ not io.stdout:close() and
+ not io.stderr:close())
+
+-- cannot call close method without an argument (new in 5.3.5)
+checkerr("got no value", io.stdin.close)
+
+
+assert(type(io.input()) == "userdata" and io.type(io.output()) == "file")
+assert(type(io.stdin) == "userdata" and io.type(io.stderr) == "file")
+assert(not io.type(8))
+local a = {}; setmetatable(a, {})
+assert(not io.type(a))
+
+assert(getmetatable(io.input()).__name == "FILE*")
+
+local a,b,c = io.open('xuxu_nao_existe')
+assert(not a and type(b) == "string" and type(c) == "number")
+
+a,b,c = io.open('/a/b/c/d', 'w')
+assert(not a and type(b) == "string" and type(c) == "number")
+
+local file = os.tmpname()
+local f, msg = io.open(file, "w")
+if not f then
+ (Message or print)("'os.tmpname' file cannot be open; skipping file tests")
+
+else --{ most tests here need tmpname
+f:close()
+
+print('testing i/o')
+
+local otherfile = os.tmpname()
+
+checkerr("invalid mode", io.open, file, "rw")
+checkerr("invalid mode", io.open, file, "rb+")
+checkerr("invalid mode", io.open, file, "r+bk")
+checkerr("invalid mode", io.open, file, "")
+checkerr("invalid mode", io.open, file, "+")
+checkerr("invalid mode", io.open, file, "b")
+assert(io.open(file, "r+b")):close()
+assert(io.open(file, "r+")):close()
+assert(io.open(file, "rb")):close()
+
+assert(os.setlocale('C', 'all'))
+
+io.input(io.stdin); io.output(io.stdout);
+
+os.remove(file)
+assert(not loadfile(file))
+checkerr("", dofile, file)
+assert(not io.open(file))
+io.output(file)
+assert(io.output() ~= io.stdout)
+
+if not _port then -- invalid seek
+ local status, msg, code = io.stdin:seek("set", 1000)
+ assert(not status and type(msg) == "string" and type(code) == "number")
+end
+
+assert(io.output():seek() == 0)
+assert(io.write("alo alo"):seek() == string.len("alo alo"))
+assert(io.output():seek("cur", -3) == string.len("alo alo")-3)
+assert(io.write("joao"))
+assert(io.output():seek("end") == string.len("alo joao"))
+
+assert(io.output():seek("set") == 0)
+
+assert(io.write('"lo"', "{a}\n", "second line\n", "third line \n"))
+assert(io.write('fourth_line'))
+io.output(io.stdout)
+collectgarbage() -- file should be closed by GC
+assert(io.input() == io.stdin and rawequal(io.output(), io.stdout))
+print('+')
+
+-- test GC for files
+collectgarbage()
+for i=1,120 do
+ for i=1,5 do
+ io.input(file)
+ assert(io.open(file, 'r'))
+ io.lines(file)
+ end
+ collectgarbage()
+end
+
+io.input():close()
+io.close()
+
+assert(os.rename(file, otherfile))
+assert(not os.rename(file, otherfile))
+
+io.output(io.open(otherfile, "ab"))
+assert(io.write("\n\n\t\t ", 3450, "\n"));
+io.close()
+
+-- test writing/reading numbers
+f = assert(io.open(file, "w"))
+f:write(maxint, '\n')
+f:write(string.format("0X%x\n", maxint))
+f:write("0xABCp-3", '\n')
+f:write(0, '\n')
+f:write(-maxint, '\n')
+f:write(string.format("0x%X\n", -maxint))
+f:write("-0xABCp-3", '\n')
+assert(f:close())
+f = assert(io.open(file, "r"))
+assert(f:read("n") == maxint)
+assert(f:read("n") == maxint)
+assert(f:read("n") == 0xABCp-3)
+assert(f:read("n") == 0)
+assert(f:read("*n") == -maxint) -- test old format (with '*')
+assert(f:read("n") == -maxint)
+assert(f:read("*n") == -0xABCp-3) -- test old format (with '*')
+assert(f:close())
+assert(os.remove(file))
+
+
+-- testing multiple arguments to io.read
+do
+ local f = assert(io.open(file, "w"))
+ f:write[[
+a line
+another line
+1234
+3.45
+one
+two
+three
+]]
+ local l1, l2, l3, l4, n1, n2, c, dummy
+ assert(f:close())
+ f = assert(io.open(file, "r"))
+ l1, l2, n1, n2, dummy = f:read("l", "L", "n", "n")
+ assert(l1 == "a line" and l2 == "another line\n" and
+ n1 == 1234 and n2 == 3.45 and dummy == nil)
+ assert(f:close())
+ f = assert(io.open(file, "r"))
+ l1, l2, n1, n2, c, l3, l4, dummy = f:read(7, "l", "n", "n", 1, "l", "l")
+ assert(l1 == "a line\n" and l2 == "another line" and c == '\n' and
+ n1 == 1234 and n2 == 3.45 and l3 == "one" and l4 == "two"
+ and dummy == nil)
+ assert(f:close())
+ f = assert(io.open(file, "r"))
+ -- second item failing
+ l1, n1, n2, dummy = f:read("l", "n", "n", "l")
+ assert(l1 == "a line" and n1 == nil)
+ assert(f:close())
+ assert(os.remove(file))
+end
+
+
+
+-- test yielding during 'dofile'
+f = assert(io.open(file, "w"))
+f:write[[
+local x, z = coroutine.yield(10)
+local y = coroutine.yield(20)
+return x + y * z
+]]
+assert(f:close())
+f = coroutine.wrap(dofile)
+assert(f(file) == 10)
+print(f(100, 101) == 20)
+assert(f(200) == 100 + 200 * 101)
+assert(os.remove(file))
+
+
+f = assert(io.open(file, "w"))
+-- test number termination
+f:write[[
+-12.3- -0xffff+ .3|5.E-3X +234e+13E 0xDEADBEEFDEADBEEFx
+0x1.13Ap+3e
+]]
+-- very long number
+f:write("1234"); for i = 1, 1000 do f:write("0") end; f:write("\n")
+-- invalid sequences (must read and discard valid prefixes)
+f:write[[
+.e+ 0.e; --; 0xX;
+]]
+assert(f:close())
+f = assert(io.open(file, "r"))
+assert(f:read("n") == -12.3); assert(f:read(1) == "-")
+assert(f:read("n") == -0xffff); assert(f:read(2) == "+ ")
+assert(f:read("n") == 0.3); assert(f:read(1) == "|")
+assert(f:read("n") == 5e-3); assert(f:read(1) == "X")
+assert(f:read("n") == 234e13); assert(f:read(1) == "E")
+assert(f:read("n") == 0Xdeadbeefdeadbeef); assert(f:read(2) == "x\n")
+assert(f:read("n") == 0x1.13aP3); assert(f:read(1) == "e")
+
+do -- attempt to read too long number
+ assert(f:read("n") == nil) -- fails
+ local s = f:read("L") -- read rest of line
+ assert(string.find(s, "^00*\n$")) -- lots of 0's left
+end
+
+assert(not f:read("n")); assert(f:read(2) == "e+")
+assert(not f:read("n")); assert(f:read(1) == ";")
+assert(not f:read("n")); assert(f:read(2) == "-;")
+assert(not f:read("n")); assert(f:read(1) == "X")
+assert(not f:read("n")); assert(f:read(1) == ";")
+assert(not f:read("n")); assert(not f:read(0)) -- end of file
+assert(f:close())
+assert(os.remove(file))
+
+
+-- test line generators
+assert(not pcall(io.lines, "non-existent-file"))
+assert(os.rename(otherfile, file))
+io.output(otherfile)
+local n = 0
+local f = io.lines(file)
+while f() do n = n + 1 end;
+assert(n == 6) -- number of lines in the file
+checkerr("file is already closed", f)
+checkerr("file is already closed", f)
+-- copy from file to otherfile
+n = 0
+for l in io.lines(file) do io.write(l, "\n"); n = n + 1 end
+io.close()
+assert(n == 6)
+-- copy from otherfile back to file
+local f = assert(io.open(otherfile))
+assert(io.type(f) == "file")
+io.output(file)
+assert(not io.output():read())
+n = 0
+for l in f:lines() do io.write(l, "\n"); n = n + 1 end
+assert(tostring(f):sub(1, 5) == "file ")
+assert(f:close()); io.close()
+assert(n == 6)
+checkerr("closed file", io.close, f)
+assert(tostring(f) == "file (closed)")
+assert(io.type(f) == "closed file")
+io.input(file)
+f = io.open(otherfile):lines()
+n = 0
+for l in io.lines() do assert(l == f()); n = n + 1 end
+f = nil; collectgarbage()
+assert(n == 6)
+assert(os.remove(otherfile))
+
+do -- bug in 5.3.1
+ io.output(otherfile)
+ io.write(string.rep("a", 300), "\n")
+ io.close()
+ local t ={}; for i = 1, 250 do t[i] = 1 end
+ t = {io.lines(otherfile, table.unpack(t))()}
+ -- everything ok here
+ assert(#t == 250 and t[1] == 'a' and t[#t] == 'a')
+ t[#t + 1] = 1 -- one too many
+ checkerr("too many arguments", io.lines, otherfile, table.unpack(t))
+ collectgarbage() -- ensure 'otherfile' is closed
+ assert(os.remove(otherfile))
+end
+
+io.input(file)
+do -- test error returns
+ local a,b,c = io.input():write("xuxu")
+ assert(not a and type(b) == "string" and type(c) == "number")
+end
+checkerr("invalid format", io.read, "x")
+assert(io.read(0) == "") -- not eof
+assert(io.read(5, 'l') == '"lo"')
+assert(io.read(0) == "")
+assert(io.read() == "second line")
+local x = io.input():seek()
+assert(io.read() == "third line ")
+assert(io.input():seek("set", x))
+assert(io.read('L') == "third line \n")
+assert(io.read(1) == "")
+assert(io.read(string.len"fourth_line") == "fourth_line")
+assert(io.input():seek("cur", -string.len"fourth_line"))
+assert(io.read() == "fourth_line")
+assert(io.read() == "") -- empty line
+assert(io.read('n') == 3450)
+assert(io.read(1) == '\n')
+assert(io.read(0) == nil) -- end of file
+assert(io.read(1) == nil) -- end of file
+assert(io.read(30000) == nil) -- end of file
+assert(({io.read(1)})[2] == undef)
+assert(io.read() == nil) -- end of file
+assert(({io.read()})[2] == undef)
+assert(io.read('n') == nil) -- end of file
+assert(({io.read('n')})[2] == undef)
+assert(io.read('a') == '') -- end of file (OK for 'a')
+assert(io.read('a') == '') -- end of file (OK for 'a')
+collectgarbage()
+print('+')
+io.close(io.input())
+checkerr(" input file is closed", io.read)
+
+assert(os.remove(file))
+
+local t = '0123456789'
+for i=1,10 do t = t..t; end
+assert(string.len(t) == 10*2^10)
+
+io.output(file)
+io.write("alo"):write("\n")
+io.close()
+checkerr(" output file is closed", io.write)
+local f = io.open(file, "a+b")
+io.output(f)
+collectgarbage()
+
+assert(io.write(' ' .. t .. ' '))
+assert(io.write(';', 'end of file\n'))
+f:flush(); io.flush()
+f:close()
+print('+')
+
+io.input(file)
+assert(io.read() == "alo")
+assert(io.read(1) == ' ')
+assert(io.read(string.len(t)) == t)
+assert(io.read(1) == ' ')
+assert(io.read(0))
+assert(io.read('a') == ';end of file\n')
+assert(io.read(0) == nil)
+assert(io.close(io.input()))
+
+
+-- test errors in read/write
+do
+ local function ismsg (m)
+ -- error message is not a code number
+ return (type(m) == "string" and tonumber(m) == nil)
+ end
+
+ -- read
+ local f = io.open(file, "w")
+ local r, m, c = f:read()
+ assert(not r and ismsg(m) and type(c) == "number")
+ assert(f:close())
+ -- write
+ f = io.open(file, "r")
+ r, m, c = f:write("whatever")
+ assert(not r and ismsg(m) and type(c) == "number")
+ assert(f:close())
+ -- lines
+ f = io.open(file, "w")
+ r, m = pcall(f:lines())
+ assert(r == false and ismsg(m))
+ assert(f:close())
+end
+
+assert(os.remove(file))
+
+-- test for L format
+io.output(file); io.write"\n\nline\nother":close()
+io.input(file)
+assert(io.read"L" == "\n")
+assert(io.read"L" == "\n")
+assert(io.read"L" == "line\n")
+assert(io.read"L" == "other")
+assert(io.read"L" == nil)
+io.input():close()
+
+local f = assert(io.open(file))
+local s = ""
+for l in f:lines("L") do s = s .. l end
+assert(s == "\n\nline\nother")
+f:close()
+
+io.input(file)
+s = ""
+for l in io.lines(nil, "L") do s = s .. l end
+assert(s == "\n\nline\nother")
+io.input():close()
+
+s = ""
+for l in io.lines(file, "L") do s = s .. l end
+assert(s == "\n\nline\nother")
+
+s = ""
+for l in io.lines(file, "l") do s = s .. l end
+assert(s == "lineother")
+
+io.output(file); io.write"a = 10 + 34\na = 2*a\na = -a\n":close()
+local t = {}
+assert(load(io.lines(file, "L"), nil, nil, t))()
+assert(t.a == -((10 + 34) * 2))
+
+
+-- test for multipe arguments in 'lines'
+io.output(file); io.write"0123456789\n":close()
+for a,b in io.lines(file, 1, 1) do
+ if a == "\n" then assert(b == nil)
+ else assert(tonumber(a) == tonumber(b) - 1)
+ end
+end
+
+for a,b,c in io.lines(file, 1, 2, "a") do
+ assert(a == "0" and b == "12" and c == "3456789\n")
+end
+
+for a,b,c in io.lines(file, "a", 0, 1) do
+ if a == "" then break end
+ assert(a == "0123456789\n" and b == nil and c == nil)
+end
+collectgarbage() -- to close file in previous iteration
+
+io.output(file); io.write"00\n10\n20\n30\n40\n":close()
+for a, b in io.lines(file, "n", "n") do
+ if a == 40 then assert(b == nil)
+ else assert(a == b - 10)
+ end
+end
+
+
+-- test load x lines
+io.output(file);
+io.write[[
+local y
+= X
+X =
+X *
+2 +
+X;
+X =
+X
+- y;
+]]:close()
+_G.X = 1
+assert(not load(io.lines(file)))
+collectgarbage() -- to close file in previous iteration
+load(io.lines(file, "L"))()
+assert(_G.X == 2)
+load(io.lines(file, 1))()
+assert(_G.X == 4)
+load(io.lines(file, 3))()
+assert(_G.X == 8)
+
+print('+')
+
+local x1 = "string\n\n\\com \"\"''coisas [[estranhas]] ]]'"
+io.output(file)
+assert(io.write(string.format("x2 = %q\n-- comment without ending EOS", x1)))
+io.close()
+assert(loadfile(file))()
+assert(x1 == x2)
+print('+')
+assert(os.remove(file))
+assert(not os.remove(file))
+assert(not os.remove(otherfile))
+
+-- testing loadfile
+local function testloadfile (s, expres)
+ io.output(file)
+ if s then io.write(s) end
+ io.close()
+ local res = assert(loadfile(file))()
+ assert(os.remove(file))
+ assert(res == expres)
+end
+
+-- loading empty file
+testloadfile(nil, nil)
+
+-- loading file with initial comment without end of line
+testloadfile("# a non-ending comment", nil)
+
+
+-- checking Unicode BOM in files
+testloadfile("\xEF\xBB\xBF# some comment\nreturn 234", 234)
+testloadfile("\xEF\xBB\xBFreturn 239", 239)
+testloadfile("\xEF\xBB\xBF", nil) -- empty file with a BOM
+
+
+-- checking line numbers in files with initial comments
+testloadfile("# a comment\nreturn require'debug'.getinfo(1).currentline", 2)
+
+
+-- loading binary file
+io.output(io.open(file, "wb"))
+assert(io.write(string.dump(function () return 10, '\0alo\255', 'hi' end)))
+io.close()
+a, b, c = assert(loadfile(file))()
+assert(a == 10 and b == "\0alo\255" and c == "hi")
+assert(os.remove(file))
+
+-- bug in 5.2.1
+do
+ io.output(io.open(file, "wb"))
+ -- save function with no upvalues
+ assert(io.write(string.dump(function () return 1 end)))
+ io.close()
+ f = assert(loadfile(file, "b", {}))
+ assert(type(f) == "function" and f() == 1)
+ assert(os.remove(file))
+end
+
+-- loading binary file with initial comment
+io.output(io.open(file, "wb"))
+assert(io.write("#this is a comment for a binary file\0\n",
+ string.dump(function () return 20, '\0\0\0' end)))
+io.close()
+a, b, c = assert(loadfile(file))()
+assert(a == 20 and b == "\0\0\0" and c == nil)
+assert(os.remove(file))
+
+
+-- 'loadfile' with 'env'
+do
+ local f = io.open(file, 'w')
+ f:write[[
+ if (...) then a = 15; return b, c, d
+ else return _ENV
+ end
+ ]]
+ f:close()
+ local t = {b = 12, c = "xuxu", d = print}
+ local f = assert(loadfile(file, 't', t))
+ local b, c, d = f(1)
+ assert(t.a == 15 and b == 12 and c == t.c and d == print)
+ assert(f() == t)
+ f = assert(loadfile(file, 't', nil))
+ assert(f() == nil)
+ f = assert(loadfile(file))
+ assert(f() == _G)
+ assert(os.remove(file))
+end
+
+
+-- 'loadfile' x modes
+do
+ io.open(file, 'w'):write("return 10"):close()
+ local s, m = loadfile(file, 'b')
+ assert(not s and string.find(m, "a text chunk"))
+ io.open(file, 'w'):write("\27 return 10"):close()
+ local s, m = loadfile(file, 't')
+ assert(not s and string.find(m, "a binary chunk"))
+ assert(os.remove(file))
+end
+
+
+io.output(file)
+assert(io.write("qualquer coisa\n"))
+assert(io.write("mais qualquer coisa"))
+io.close()
+assert(io.output(assert(io.open(otherfile, 'wb')))
+ :write("outra coisa\0\1\3\0\0\0\0\255\0")
+ :close())
+
+local filehandle = assert(io.open(file, 'r+'))
+local otherfilehandle = assert(io.open(otherfile, 'rb'))
+assert(filehandle ~= otherfilehandle)
+assert(type(filehandle) == "userdata")
+assert(filehandle:read('l') == "qualquer coisa")
+io.input(otherfilehandle)
+assert(io.read(string.len"outra coisa") == "outra coisa")
+assert(filehandle:read('l') == "mais qualquer coisa")
+filehandle:close();
+assert(type(filehandle) == "userdata")
+io.input(otherfilehandle)
+assert(io.read(4) == "\0\1\3\0")
+assert(io.read(3) == "\0\0\0")
+assert(io.read(0) == "") -- 255 is not eof
+assert(io.read(1) == "\255")
+assert(io.read('a') == "\0")
+assert(not io.read(0))
+assert(otherfilehandle == io.input())
+otherfilehandle:close()
+assert(os.remove(file))
+assert(os.remove(otherfile))
+collectgarbage()
+
+io.output(file)
+ :write[[
+ 123.4 -56e-2 not a number
+second line
+third line
+
+and the rest of the file
+]]
+ :close()
+io.input(file)
+local _,a,b,c,d,e,h,__ = io.read(1, 'n', 'n', 'l', 'l', 'l', 'a', 10)
+assert(io.close(io.input()))
+assert(_ == ' ' and __ == nil)
+assert(type(a) == 'number' and a==123.4 and b==-56e-2)
+assert(d=='second line' and e=='third line')
+assert(h==[[
+
+and the rest of the file
+]])
+assert(os.remove(file))
+collectgarbage()
+
+-- testing buffers
+do
+ local f = assert(io.open(file, "w"))
+ local fr = assert(io.open(file, "r"))
+ assert(f:setvbuf("full", 2000))
+ f:write("x")
+ assert(fr:read("all") == "") -- full buffer; output not written yet
+ f:close()
+ fr:seek("set")
+ assert(fr:read("all") == "x") -- `close' flushes it
+ f = assert(io.open(file), "w")
+ assert(f:setvbuf("no"))
+ f:write("x")
+ fr:seek("set")
+ assert(fr:read("all") == "x") -- no buffer; output is ready
+ f:close()
+ f = assert(io.open(file, "a"))
+ assert(f:setvbuf("line"))
+ f:write("x")
+ fr:seek("set", 1)
+ assert(fr:read("all") == "") -- line buffer; no output without `\n'
+ f:write("a\n"):seek("set", 1)
+ assert(fr:read("all") == "xa\n") -- now we have a whole line
+ f:close(); fr:close()
+ assert(os.remove(file))
+end
+
+
+if not _soft then
+ print("testing large files (> BUFSIZ)")
+ io.output(file)
+ for i=1,5001 do io.write('0123456789123') end
+ io.write('\n12346'):close()
+ io.input(file)
+ local x = io.read('a')
+ io.input():seek('set', 0)
+ local y = io.read(30001)..io.read(1005)..io.read(0)..
+ io.read(1)..io.read(100003)
+ assert(x == y and string.len(x) == 5001*13 + 6)
+ io.input():seek('set', 0)
+ y = io.read() -- huge line
+ assert(x == y..'\n'..io.read())
+ assert(io.read() == nil)
+ io.close(io.input())
+ assert(os.remove(file))
+ x = nil; y = nil
+end
+
+if not _port then
+ local progname
+ do -- get name of running executable
+ local arg = arg or ARG
+ local i = 0
+ while arg[i] do i = i - 1 end
+ progname = '"' .. arg[i + 1] .. '"'
+ end
+ print("testing popen/pclose and execute")
+ local tests = {
+ -- command, what, code
+ {"ls > /dev/null", "ok"},
+ {"not-to-be-found-command", "exit"},
+ {"exit 3", "exit", 3},
+ {"exit 129", "exit", 129},
+ {"kill -s HUP $$", "signal", 1},
+ {"kill -s KILL $$", "signal", 9},
+ {"sh -c 'kill -s HUP $$'", "exit"},
+ {progname .. ' -e " "', "ok"},
+ {progname .. ' -e "os.exit(0, true)"', "ok"},
+ {progname .. ' -e "os.exit(20, true)"', "exit", 20},
+ }
+ print("\n(some error messages are expected now)")
+ for _, v in ipairs(tests) do
+ local x, y, z = io.popen(v[1]):close()
+ local x1, y1, z1 = os.execute(v[1])
+ assert(x == x1 and y == y1 and z == z1)
+ if v[2] == "ok" then
+ assert(x and y == 'exit' and z == 0)
+ else
+ assert(not x and y == v[2]) -- correct status and 'what'
+ -- correct code if known (but always different from 0)
+ assert((v[3] == nil and z > 0) or v[3] == z)
+ end
+ end
+end
+
+
+-- testing tmpfile
+f = io.tmpfile()
+assert(io.type(f) == "file")
+f:write("alo")
+f:seek("set")
+assert(f:read"a" == "alo")
+
+end --}
+
+print'+'
+
+print("testing date/time")
+
+assert(os.date("") == "")
+assert(os.date("!") == "")
+assert(os.date("\0\0") == "\0\0")
+assert(os.date("!\0\0") == "\0\0")
+local x = string.rep("a", 10000)
+assert(os.date(x) == x)
+local t = os.time()
+D = os.date("*t", t)
+assert(os.date(string.rep("%d", 1000), t) ==
+ string.rep(os.date("%d", t), 1000))
+assert(os.date(string.rep("%", 200)) == string.rep("%", 100))
+
+local t = os.time()
+D = os.date("*t", t)
+load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and
+ D.hour==%H and D.min==%M and D.sec==%S and
+ D.wday==%w+1 and D.yday==%j and type(D.isdst) == 'boolean')]], t))()
+
+checkerr("invalid conversion specifier", os.date, "%")
+checkerr("invalid conversion specifier", os.date, "%9")
+checkerr("invalid conversion specifier", os.date, "%")
+checkerr("invalid conversion specifier", os.date, "%O")
+checkerr("invalid conversion specifier", os.date, "%E")
+checkerr("invalid conversion specifier", os.date, "%Ea")
+
+checkerr("not an integer", os.time, {year=1000, month=1, day=1, hour='x'})
+checkerr("not an integer", os.time, {year=1000, month=1, day=1, hour=1.5})
+
+checkerr("missing", os.time, {hour = 12}) -- missing date
+
+if not _port then
+ -- test Posix-specific modifiers
+ assert(type(os.date("%Ex")) == 'string')
+ assert(type(os.date("%Oy")) == 'string')
+
+
+ -- test out-of-range dates (at least for Unix)
+ if maxint >= 2^62 then -- cannot do these tests in Small Lua
+ -- no arith overflows
+ checkerr("out-of-bound", os.time, {year = -maxint, month = 1, day = 1})
+ if string.packsize("i") == 4 then -- 4-byte ints
+ if testerr("out-of-bound", os.date, "%Y", 2^40) then
+ -- time_t has 4 bytes and therefore cannot represent year 4000
+ print(" 4-byte time_t")
+ checkerr("cannot be represented", os.time, {year=4000, month=1, day=1})
+ else
+ -- time_t has 8 bytes; an int year cannot represent a huge time
+ print(" 8-byte time_t")
+ checkerr("cannot be represented", os.date, "%Y", 2^60)
+ -- it should have no problems with year 4000
+ assert(tonumber(os.time{year=4000, month=1, day=1}))
+ end
+ else -- 8-byte ints
+ -- assume time_t has 8 bytes too
+ print(" 8-byte time_t")
+ assert(tonumber(os.date("%Y", 2^60)))
+ -- but still cannot represent a huge year
+ checkerr("cannot be represented", os.time, {year=2^60, month=1, day=1})
+ end
+ end
+end
+
+
+D = os.date("!*t", t)
+load(os.date([[!assert(D.year==%Y and D.month==%m and D.day==%d and
+ D.hour==%H and D.min==%M and D.sec==%S and
+ D.wday==%w+1 and D.yday==%j and type(D.isdst) == 'boolean')]], t))()
+
+do
+ local D = os.date("*t")
+ local t = os.time(D)
+ assert(type(D.isdst) == 'boolean')
+ D.isdst = nil
+ local t1 = os.time(D)
+ assert(t == t1) -- if isdst is absent uses correct default
+end
+
+t = os.time(D)
+D.year = D.year-1;
+local t1 = os.time(D)
+-- allow for leap years
+assert(math.abs(os.difftime(t,t1)/(24*3600) - 365) < 2)
+
+-- should not take more than 1 second to execute these two lines
+t = os.time()
+t1 = os.time(os.date("*t"))
+local diff = os.difftime(t1,t)
+assert(0 <= diff and diff <= 1)
+diff = os.difftime(t,t1)
+assert(-1 <= diff and diff <= 0)
+
+local t1 = os.time{year=2000, month=10, day=1, hour=23, min=12}
+local t2 = os.time{year=2000, month=10, day=1, hour=23, min=10, sec=19}
+assert(os.difftime(t1,t2) == 60*2-19)
+
+-- since 5.3.3, 'os.time' normalizes table fields
+t1 = {year = 2005, month = 1, day = 1, hour = 1, min = 0, sec = -3602}
+os.time(t1)
+assert(t1.day == 31 and t1.month == 12 and t1.year == 2004 and
+ t1.hour == 23 and t1.min == 59 and t1.sec == 58 and
+ t1.yday == 366)
+
+io.output(io.stdout)
+local t = os.date('%d %m %Y %H %M %S')
+local d, m, a, h, min, s = string.match(t,
+ "(%d+) (%d+) (%d+) (%d+) (%d+) (%d+)")
+d = tonumber(d)
+m = tonumber(m)
+a = tonumber(a)
+h = tonumber(h)
+min = tonumber(min)
+s = tonumber(s)
+io.write(string.format('test done on %2.2d/%2.2d/%d', d, m, a))
+io.write(string.format(', at %2.2d:%2.2d:%2.2d\n', h, min, s))
+io.write(string.format('%s\n', _VERSION))
+
+
diff --git a/testes/gc.lua b/testes/gc.lua
new file mode 100644
index 0000000000..9647cd542f
--- /dev/null
+++ b/testes/gc.lua
@@ -0,0 +1,661 @@
+-- $Id: gc.lua,v 1.82 2018/03/12 14:19:36 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print('testing garbage collection')
+
+local debug = require"debug"
+
+assert(collectgarbage("isrunning"))
+
+collectgarbage()
+
+local oldmode = collectgarbage("incremental")
+
+
+local function gcinfo ()
+ return collectgarbage"count" * 1024
+end
+
+
+-- test weird parameters to 'collectgarbage'
+do
+ -- save original parameters
+ local a = collectgarbage("setpause", 200)
+ local b = collectgarbage("setstepmul", 200)
+ local t = {0, 2, 10, 90, 500, 5000, 30000, 0x7ffffffe}
+ for i = 1, #t do
+ local p = t[i]
+ for j = 1, #t do
+ local m = t[j]
+ collectgarbage("setpause", p)
+ collectgarbage("setstepmul", m)
+ collectgarbage("step", 0)
+ collectgarbage("step", 10000)
+ end
+ end
+ -- restore original parameters
+ collectgarbage("setpause", a)
+ collectgarbage("setstepmul", b)
+ collectgarbage()
+end
+
+
+_G["while"] = 234
+
+
+--
+-- tests for GC activation when creating different kinds of objects
+--
+local function GC1 ()
+ local u
+ local b -- (above 'u' it in the stack)
+ local finish = false
+ u = setmetatable({}, {__gc = function () finish = true end})
+ b = {34}
+ repeat u = {} until finish
+ assert(b[1] == 34) -- 'u' was collected, but 'b' was not
+
+ finish = false; local i = 1
+ u = setmetatable({}, {__gc = function () finish = true end})
+ repeat i = i + 1; u = tostring(i) .. tostring(i) until finish
+ assert(b[1] == 34) -- 'u' was collected, but 'b' was not
+
+ finish = false
+ u = setmetatable({}, {__gc = function () finish = true end})
+ repeat local i; u = function () return i end until finish
+ assert(b[1] == 34) -- 'u' was collected, but 'b' was not
+end
+
+local function GC2 ()
+ local u
+ local finish = false
+ u = {setmetatable({}, {__gc = function () finish = true end})}
+ local b = {34}
+ repeat u = {{}} until finish
+ assert(b[1] == 34) -- 'u' was collected, but 'b' was not
+
+ finish = false; local i = 1
+ u = {setmetatable({}, {__gc = function () finish = true end})}
+ repeat i = i + 1; u = {tostring(i) .. tostring(i)} until finish
+ assert(b[1] == 34) -- 'u' was collected, but 'b' was not
+
+ finish = false
+ u = {setmetatable({}, {__gc = function () finish = true end})}
+ repeat local i; u = {function () return i end} until finish
+ assert(b[1] == 34) -- 'u' was collected, but 'b' was not
+end
+
+local function GC() GC1(); GC2() end
+
+
+do
+ print("creating many objects")
+
+ local contCreate = 0
+
+ local limit = 5000
+
+ while contCreate <= limit do
+ local a = {}; a = nil
+ contCreate = contCreate+1
+ end
+
+ local a = "a"
+
+ contCreate = 0
+ while contCreate <= limit do
+ a = contCreate .. "b";
+ a = string.gsub(a, '(%d%d*)', string.upper)
+ a = "a"
+ contCreate = contCreate+1
+ end
+
+
+ contCreate = 0
+
+ a = {}
+
+ function a:test ()
+ while contCreate <= limit do
+ load(string.format("function temp(a) return 'a%d' end", contCreate), "")()
+ assert(temp() == string.format('a%d', contCreate))
+ contCreate = contCreate+1
+ end
+ end
+
+ a:test()
+
+end
+
+
+-- collection of functions without locals, globals, etc.
+do local f = function () end end
+
+
+print("functions with errors")
+prog = [[
+do
+ a = 10;
+ function foo(x,y)
+ a = sin(a+0.456-0.23e-12);
+ return function (z) return sin(%x+z) end
+ end
+ local x = function (w) a=a+w; end
+end
+]]
+do
+ local step = 1
+ if _soft then step = 13 end
+ for i=1, string.len(prog), step do
+ for j=i, string.len(prog), step do
+ pcall(load(string.sub(prog, i, j), ""))
+ end
+ end
+end
+
+foo = nil
+print('long strings')
+x = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
+assert(string.len(x)==80)
+s = ''
+n = 0
+k = math.min(300, (math.maxinteger // 80) // 2)
+while n < k do s = s..x; n=n+1; j=tostring(n) end
+assert(string.len(s) == k*80)
+s = string.sub(s, 1, 10000)
+s, i = string.gsub(s, '(%d%d%d%d)', '')
+assert(i==10000 // 4)
+s = nil
+x = nil
+
+assert(_G["while"] == 234)
+
+
+--
+-- test the "size" of basic GC steps (whatever they mean...)
+--
+do
+print("steps")
+
+ print("steps (2)")
+
+ local function dosteps (siz)
+ collectgarbage()
+ local a = {}
+ for i=1,100 do a[i] = {{}}; local b = {} end
+ local x = gcinfo()
+ local i = 0
+ repeat -- do steps until it completes a collection cycle
+ i = i+1
+ until collectgarbage("step", siz)
+ assert(gcinfo() < x)
+ return i -- number of steps
+ end
+
+ collectgarbage"stop"
+
+ if not _port then
+ assert(dosteps(10) < dosteps(2))
+ end
+
+ -- collector should do a full collection with so many steps
+ assert(dosteps(20000) == 1)
+ assert(collectgarbage("step", 20000) == true)
+ assert(collectgarbage("step", 20000) == true)
+
+ assert(not collectgarbage("isrunning"))
+ collectgarbage"restart"
+ assert(collectgarbage("isrunning"))
+
+end
+
+
+if not _port then
+ -- test the pace of the collector
+ collectgarbage(); collectgarbage()
+ local x = gcinfo()
+ collectgarbage"stop"
+ repeat
+ local a = {}
+ until gcinfo() > 3 * x
+ collectgarbage"restart"
+ assert(collectgarbage("isrunning"))
+ repeat
+ local a = {}
+ until gcinfo() <= x * 2
+end
+
+
+print("clearing tables")
+lim = 15
+a = {}
+-- fill a with `collectable' indices
+for i=1,lim do a[{}] = i end
+b = {}
+for k,v in pairs(a) do b[k]=v end
+-- remove all indices and collect them
+for n in pairs(b) do
+ a[n] = undef
+ assert(type(n) == 'table' and next(n) == nil)
+ collectgarbage()
+end
+b = nil
+collectgarbage()
+for n in pairs(a) do error'cannot be here' end
+for i=1,lim do a[i] = i end
+for i=1,lim do assert(a[i] == i) end
+
+
+print('weak tables')
+a = {}; setmetatable(a, {__mode = 'k'});
+-- fill a with some `collectable' indices
+for i=1,lim do a[{}] = i end
+-- and some non-collectable ones
+for i=1,lim do a[i] = i end
+for i=1,lim do local s=string.rep('@', i); a[s] = s..'#' end
+collectgarbage()
+local i = 0
+for k,v in pairs(a) do assert(k==v or k..'#'==v); i=i+1 end
+assert(i == 2*lim)
+
+a = {}; setmetatable(a, {__mode = 'v'});
+a[1] = string.rep('b', 21)
+collectgarbage()
+assert(a[1]) -- strings are *values*
+a[1] = undef
+-- fill a with some `collectable' values (in both parts of the table)
+for i=1,lim do a[i] = {} end
+for i=1,lim do a[i..'x'] = {} end
+-- and some non-collectable ones
+for i=1,lim do local t={}; a[t]=t end
+for i=1,lim do a[i+lim]=i..'x' end
+collectgarbage()
+local i = 0
+for k,v in pairs(a) do assert(k==v or k-lim..'x' == v); i=i+1 end
+assert(i == 2*lim)
+
+a = {}; setmetatable(a, {__mode = 'kv'});
+local x, y, z = {}, {}, {}
+-- keep only some items
+a[1], a[2], a[3] = x, y, z
+a[string.rep('$', 11)] = string.rep('$', 11)
+-- fill a with some `collectable' values
+for i=4,lim do a[i] = {} end
+for i=1,lim do a[{}] = i end
+for i=1,lim do local t={}; a[t]=t end
+collectgarbage()
+assert(next(a) ~= nil)
+local i = 0
+for k,v in pairs(a) do
+ assert((k == 1 and v == x) or
+ (k == 2 and v == y) or
+ (k == 3 and v == z) or k==v);
+ i = i+1
+end
+assert(i == 4)
+x,y,z=nil
+collectgarbage()
+assert(next(a) == string.rep('$', 11))
+
+
+-- 'bug' in 5.1
+a = {}
+local t = {x = 10}
+local C = setmetatable({key = t}, {__mode = 'v'})
+local C1 = setmetatable({[t] = 1}, {__mode = 'k'})
+a.x = t -- this should not prevent 't' from being removed from
+ -- weak table 'C' by the time 'a' is finalized
+
+setmetatable(a, {__gc = function (u)
+ assert(C.key == nil)
+ assert(type(next(C1)) == 'table')
+ end})
+
+a, t = nil
+collectgarbage()
+collectgarbage()
+assert(next(C) == nil and next(C1) == nil)
+C, C1 = nil
+
+
+-- ephemerons
+local mt = {__mode = 'k'}
+a = {{10},{20},{30},{40}}; setmetatable(a, mt)
+x = nil
+for i = 1, 100 do local n = {}; a[n] = {k = {x}}; x = n end
+GC()
+local n = x
+local i = 0
+while n do n = a[n].k[1]; i = i + 1 end
+assert(i == 100)
+x = nil
+GC()
+for i = 1, 4 do assert(a[i][1] == i * 10); a[i] = undef end
+assert(next(a) == nil)
+
+local K = {}
+a[K] = {}
+for i=1,10 do a[K][i] = {}; a[a[K][i]] = setmetatable({}, mt) end
+x = nil
+local k = 1
+for j = 1,100 do
+ local n = {}; local nk = k%10 + 1
+ a[a[K][nk]][n] = {x, k = k}; x = n; k = nk
+end
+GC()
+local n = x
+local i = 0
+while n do local t = a[a[K][k]][n]; n = t[1]; k = t.k; i = i + 1 end
+assert(i == 100)
+K = nil
+GC()
+-- assert(next(a) == nil)
+
+
+-- testing errors during GC
+do
+collectgarbage("stop") -- stop collection
+local u = {}
+local s = {}; setmetatable(s, {__mode = 'k'})
+setmetatable(u, {__gc = function (o)
+ local i = s[o]
+ s[i] = true
+ assert(not s[i - 1]) -- check proper finalization order
+ if i == 8 then error("here") end -- error during GC
+end})
+
+for i = 6, 10 do
+ local n = setmetatable({}, getmetatable(u))
+ s[n] = i
+end
+
+assert(not pcall(collectgarbage))
+for i = 8, 10 do assert(s[i]) end
+
+for i = 1, 5 do
+ local n = setmetatable({}, getmetatable(u))
+ s[n] = i
+end
+
+collectgarbage()
+for i = 1, 10 do assert(s[i]) end
+
+getmetatable(u).__gc = false
+
+
+-- __gc errors with non-string messages
+setmetatable({}, {__gc = function () error{} end})
+local a, b = pcall(collectgarbage)
+assert(not a and type(b) == "string" and string.find(b, "error in __gc"))
+
+end
+print '+'
+
+
+-- testing userdata
+if T==nil then
+ (Message or print)('\n >>> testC not active: skipping userdata GC tests <<<\n')
+
+else
+
+ local function newproxy(u)
+ return debug.setmetatable(T.newuserdata(0), debug.getmetatable(u))
+ end
+
+ collectgarbage("stop") -- stop collection
+ local u = newproxy(nil)
+ debug.setmetatable(u, {__gc = true})
+ local s = 0
+ local a = {[u] = 0}; setmetatable(a, {__mode = 'vk'})
+ for i=1,10 do a[newproxy(u)] = i end
+ for k in pairs(a) do assert(getmetatable(k) == getmetatable(u)) end
+ local a1 = {}; for k,v in pairs(a) do a1[k] = v end
+ for k,v in pairs(a1) do a[v] = k end
+ for i =1,10 do assert(a[i]) end
+ getmetatable(u).a = a1
+ getmetatable(u).u = u
+ do
+ local u = u
+ getmetatable(u).__gc = function (o)
+ assert(a[o] == 10-s)
+ assert(a[10-s] == undef) -- udata already removed from weak table
+ assert(getmetatable(o) == getmetatable(u))
+ assert(getmetatable(o).a[o] == 10-s)
+ s=s+1
+ end
+ end
+ a1, u = nil
+ assert(next(a) ~= nil)
+ collectgarbage()
+ assert(s==11)
+ collectgarbage()
+ assert(next(a) == nil) -- finalized keys are removed in two cycles
+end
+
+
+-- __gc x weak tables
+local u = setmetatable({}, {__gc = true})
+-- __gc metamethod should be collected before running
+setmetatable(getmetatable(u), {__mode = "v"})
+getmetatable(u).__gc = function (o) os.exit(1) end -- cannot happen
+u = nil
+collectgarbage()
+
+local u = setmetatable({}, {__gc = true})
+local m = getmetatable(u)
+m.x = {[{0}] = 1; [0] = {1}}; setmetatable(m.x, {__mode = "kv"});
+m.__gc = function (o)
+ assert(next(getmetatable(o).x) == nil)
+ m = 10
+end
+u, m = nil
+collectgarbage()
+assert(m==10)
+
+do -- tests for string keys in weak tables
+ collectgarbage(); collectgarbage()
+ local m = collectgarbage("count") -- current memory
+ local a = setmetatable({}, {__mode = "kv"})
+ a[string.rep("a", 2^22)] = 25 -- long string key -> number value
+ a[string.rep("b", 2^22)] = {} -- long string key -> colectable value
+ a[{}] = 14 -- colectable key
+ assert(collectgarbage("count") > m + 2^13) -- 2^13 == 2 * 2^22 in KB
+ collectgarbage()
+ assert(collectgarbage("count") >= m + 2^12 and
+ collectgarbage("count") < m + 2^13) -- one key was collected
+ local k, v = next(a) -- string key with number value preserved
+ assert(k == string.rep("a", 2^22) and v == 25)
+ assert(next(a, k) == nil) -- everything else cleared
+ assert(a[string.rep("b", 2^22)] == undef)
+ a[k] = undef -- erase this last entry
+ k = nil
+ collectgarbage()
+ assert(next(a) == nil)
+ -- make sure will not try to compare with dead key
+ assert(a[string.rep("b", 100)] == undef)
+ assert(collectgarbage("count") <= m + 1) -- eveything collected
+end
+
+
+-- errors during collection
+u = setmetatable({}, {__gc = function () error "!!!" end})
+u = nil
+assert(not pcall(collectgarbage))
+
+
+if not _soft then
+ print("long list")
+ local a = {}
+ for i = 1,200000 do
+ a = {next = a}
+ end
+ a = nil
+ collectgarbage()
+end
+
+-- create many threads with self-references and open upvalues
+print("self-referenced threads")
+local thread_id = 0
+local threads = {}
+
+local function fn (thread)
+ local x = {}
+ threads[thread_id] = function()
+ thread = x
+ end
+ coroutine.yield()
+end
+
+while thread_id < 1000 do
+ local thread = coroutine.create(fn)
+ coroutine.resume(thread, thread)
+ thread_id = thread_id + 1
+end
+
+
+-- Create a closure (function inside 'f') with an upvalue ('param') that
+-- points (through a table) to the closure itself and to the thread
+-- ('co' and the initial value of 'param') where closure is running.
+-- Then, assert that table (and therefore everything else) will be
+-- collected.
+do
+ local collected = false -- to detect collection
+ collectgarbage(); collectgarbage("stop")
+ do
+ local function f (param)
+ ;(function ()
+ assert(type(f) == 'function' and type(param) == 'thread')
+ param = {param, f}
+ setmetatable(param, {__gc = function () collected = true end})
+ coroutine.yield(100)
+ end)()
+ end
+ local co = coroutine.create(f)
+ assert(coroutine.resume(co, co))
+ end
+ -- Now, thread and closure are not reacheable any more.
+ collectgarbage()
+ assert(collected)
+ collectgarbage("restart")
+end
+
+
+do
+ collectgarbage()
+ collectgarbage"stop"
+ collectgarbage("step", 0) -- steps should not unblock the collector
+ local x = gcinfo()
+ repeat
+ for i=1,1000 do _ENV.a = {} end -- no collection during the loop
+ until gcinfo() > 2 * x
+ collectgarbage"restart"
+end
+
+
+if T then -- tests for weird cases collecting upvalues
+
+ local function foo ()
+ local a = {x = 20}
+ coroutine.yield(function () return a.x end) -- will run collector
+ assert(a.x == 20) -- 'a' is 'ok'
+ a = {x = 30} -- create a new object
+ assert(T.gccolor(a) == "white") -- of course it is new...
+ coroutine.yield(100) -- 'a' is still local to this thread
+ end
+
+ local t = setmetatable({}, {__mode = "kv"})
+ collectgarbage(); collectgarbage('stop')
+ -- create coroutine in a weak table, so it will never be marked
+ t.co = coroutine.wrap(foo)
+ local f = t.co() -- create function to access local 'a'
+ T.gcstate("atomic") -- ensure all objects are traversed
+ assert(T.gcstate() == "atomic")
+ assert(t.co() == 100) -- resume coroutine, creating new table for 'a'
+ assert(T.gccolor(t.co) == "white") -- thread was not traversed
+ T.gcstate("pause") -- collect thread, but should mark 'a' before that
+ assert(t.co == nil and f() == 30) -- ensure correct access to 'a'
+
+ collectgarbage("restart")
+
+ -- test barrier in sweep phase (backing userdata to gray)
+ local u = T.newuserdata(0, 1) -- create a userdata
+ collectgarbage()
+ collectgarbage"stop"
+ local a = {} -- avoid 'u' as first element in 'allgc'
+ T.gcstate"atomic"
+ T.gcstate"sweepallgc"
+ local x = {}
+ assert(T.gccolor(u) == "black") -- userdata is "old" (black)
+ assert(T.gccolor(x) == "white") -- table is "new" (white)
+ debug.setuservalue(u, x) -- trigger barrier
+ assert(T.gccolor(u) == "gray") -- userdata changed back to gray
+ collectgarbage"restart"
+
+ print"+"
+end
+
+
+if T then
+ local debug = require "debug"
+ collectgarbage("stop")
+ local x = T.newuserdata(0)
+ local y = T.newuserdata(0)
+ debug.setmetatable(y, {__gc = true}) -- bless the new udata before...
+ debug.setmetatable(x, {__gc = true}) -- ...the old one
+ assert(T.gccolor(y) == "white")
+ T.checkmemory()
+ collectgarbage("restart")
+end
+
+
+if T then
+ print("emergency collections")
+ collectgarbage()
+ collectgarbage()
+ T.totalmem(T.totalmem() + 200)
+ for i=1,200 do local a = {} end
+ T.totalmem(0)
+ collectgarbage()
+ local t = T.totalmem("table")
+ local a = {{}, {}, {}} -- create 4 new tables
+ assert(T.totalmem("table") == t + 4)
+ t = T.totalmem("function")
+ a = function () end -- create 1 new closure
+ assert(T.totalmem("function") == t + 1)
+ t = T.totalmem("thread")
+ a = coroutine.create(function () end) -- create 1 new coroutine
+ assert(T.totalmem("thread") == t + 1)
+end
+
+-- create an object to be collected when state is closed
+do
+ local setmetatable,assert,type,print,getmetatable =
+ setmetatable,assert,type,print,getmetatable
+ local tt = {}
+ tt.__gc = function (o)
+ assert(getmetatable(o) == tt)
+ -- create new objects during GC
+ local a = 'xuxu'..(10+3)..'joao', {}
+ ___Glob = o -- ressurect object!
+ setmetatable({}, tt) -- creates a new one with same metatable
+ print(">>> closing state " .. "<<<\n")
+ end
+ local u = setmetatable({}, tt)
+ ___Glob = {u} -- avoid object being collected before program end
+end
+
+-- create several objects to raise errors when collected while closing state
+do
+ local mt = {__gc = function (o) return o + 1 end}
+ for i = 1,10 do
+ -- create object and preserve it until the end
+ table.insert(___Glob, setmetatable({}, mt))
+ end
+end
+
+-- just to make sure
+assert(collectgarbage'isrunning')
+
+collectgarbage(oldmode)
+
+print('OK')
diff --git a/testes/goto.lua b/testes/goto.lua
new file mode 100644
index 0000000000..d22601f934
--- /dev/null
+++ b/testes/goto.lua
@@ -0,0 +1,256 @@
+-- $Id: goto.lua,v 1.15 2017/11/30 13:31:07 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+collectgarbage()
+
+local function errmsg (code, m)
+ local st, msg = load(code)
+ assert(not st and string.find(msg, m))
+end
+
+-- cannot see label inside block
+errmsg([[ goto l1; do ::l1:: end ]], "label 'l1'")
+errmsg([[ do ::l1:: end goto l1; ]], "label 'l1'")
+
+-- repeated label
+errmsg([[ ::l1:: ::l1:: ]], "label 'l1'")
+
+
+-- undefined label
+errmsg([[ goto l1; local aa ::l1:: ::l2:: print(3) ]], "local 'aa'")
+
+-- jumping over variable definition
+errmsg([[
+do local bb, cc; goto l1; end
+local aa
+::l1:: print(3)
+]], "local 'aa'")
+
+-- jumping into a block
+errmsg([[ do ::l1:: end goto l1 ]], "label 'l1'")
+errmsg([[ goto l1 do ::l1:: end ]], "label 'l1'")
+
+-- cannot continue a repeat-until with variables
+errmsg([[
+ repeat
+ if x then goto cont end
+ local xuxu = 10
+ ::cont::
+ until xuxu < x
+]], "local 'xuxu'")
+
+-- simple gotos
+local x
+do
+ local y = 12
+ goto l1
+ ::l2:: x = x + 1; goto l3
+ ::l1:: x = y; goto l2
+end
+::l3:: ::l3_1:: assert(x == 13)
+
+
+-- long labels
+do
+ local prog = [[
+ do
+ local a = 1
+ goto l%sa; a = a + 1
+ ::l%sa:: a = a + 10
+ goto l%sb; a = a + 2
+ ::l%sb:: a = a + 20
+ return a
+ end
+ ]]
+ local label = string.rep("0123456789", 40)
+ prog = string.format(prog, label, label, label, label)
+ assert(assert(load(prog))() == 31)
+end
+
+-- goto to correct label when nested
+do goto l3; ::l3:: end -- does not loop jumping to previous label 'l3'
+
+-- ok to jump over local dec. to end of block
+do
+ goto l1
+ local a = 23
+ x = a
+ ::l1::;
+end
+
+while true do
+ goto l4
+ goto l1 -- ok to jump over local dec. to end of block
+ goto l1 -- multiple uses of same label
+ local x = 45
+ ::l1:: ;;;
+end
+::l4:: assert(x == 13)
+
+if print then
+ goto l1 -- ok to jump over local dec. to end of block
+ error("should not be here")
+ goto l2 -- ok to jump over local dec. to end of block
+ local x
+ ::l1:: ; ::l2:: ;;
+else end
+
+-- to repeat a label in a different function is OK
+local function foo ()
+ local a = {}
+ goto l3
+ ::l1:: a[#a + 1] = 1; goto l2;
+ ::l2:: a[#a + 1] = 2; goto l5;
+ ::l3::
+ ::l3a:: a[#a + 1] = 3; goto l1;
+ ::l4:: a[#a + 1] = 4; goto l6;
+ ::l5:: a[#a + 1] = 5; goto l4;
+ ::l6:: assert(a[1] == 3 and a[2] == 1 and a[3] == 2 and
+ a[4] == 5 and a[5] == 4)
+ if not a[6] then a[6] = true; goto l3a end -- do it twice
+end
+
+::l6:: foo()
+
+
+do -- bug in 5.2 -> 5.3.2
+ local x
+ ::L1::
+ local y -- cannot join this SETNIL with previous one
+ assert(y == nil)
+ y = true
+ if x == nil then
+ x = 1
+ goto L1
+ else
+ x = x + 1
+ end
+ assert(x == 2 and y == true)
+end
+
+-- bug in 5.3
+do
+ local first = true
+ local a = false
+ if true then
+ goto LBL
+ ::loop::
+ a = true
+ ::LBL::
+ if first then
+ first = false
+ goto loop
+ end
+ end
+ assert(a)
+end
+
+do -- compiling infinite loops
+ goto escape -- do not run the infinite loops
+ ::a:: goto a
+ ::b:: goto c
+ ::c:: goto b
+end
+::escape::
+--------------------------------------------------------------------------------
+-- testing closing of upvalues
+
+local debug = require 'debug'
+
+local function foo ()
+ local t = {}
+ do
+ local i = 1
+ local a, b, c, d
+ t[1] = function () return a, b, c, d end
+ ::l1::
+ local b
+ do
+ local c
+ t[#t + 1] = function () return a, b, c, d end -- t[2], t[4], t[6]
+ if i > 2 then goto l2 end
+ do
+ local d
+ t[#t + 1] = function () return a, b, c, d end -- t[3], t[5]
+ i = i + 1
+ local a
+ goto l1
+ end
+ end
+ end
+ ::l2:: return t
+end
+
+local a = foo()
+assert(#a == 6)
+
+-- all functions share same 'a'
+for i = 2, 6 do
+ assert(debug.upvalueid(a[1], 1) == debug.upvalueid(a[i], 1))
+end
+
+-- 'b' and 'c' are shared among some of them
+for i = 2, 6 do
+ -- only a[1] uses external 'b'/'b'
+ assert(debug.upvalueid(a[1], 2) ~= debug.upvalueid(a[i], 2))
+ assert(debug.upvalueid(a[1], 3) ~= debug.upvalueid(a[i], 3))
+end
+
+for i = 3, 5, 2 do
+ -- inner functions share 'b'/'c' with previous ones
+ assert(debug.upvalueid(a[i], 2) == debug.upvalueid(a[i - 1], 2))
+ assert(debug.upvalueid(a[i], 3) == debug.upvalueid(a[i - 1], 3))
+ -- but not with next ones
+ assert(debug.upvalueid(a[i], 2) ~= debug.upvalueid(a[i + 1], 2))
+ assert(debug.upvalueid(a[i], 3) ~= debug.upvalueid(a[i + 1], 3))
+end
+
+-- only external 'd' is shared
+for i = 2, 6, 2 do
+ assert(debug.upvalueid(a[1], 4) == debug.upvalueid(a[i], 4))
+end
+
+-- internal 'd's are all different
+for i = 3, 5, 2 do
+ for j = 1, 6 do
+ assert((debug.upvalueid(a[i], 4) == debug.upvalueid(a[j], 4))
+ == (i == j))
+ end
+end
+
+--------------------------------------------------------------------------------
+-- testing if x goto optimizations
+
+local function testG (a)
+ if a == 1 then
+ goto l1
+ error("should never be here!")
+ elseif a == 2 then goto l2
+ elseif a == 3 then goto l3
+ elseif a == 4 then
+ goto l1 -- go to inside the block
+ error("should never be here!")
+ ::l1:: a = a + 1 -- must go to 'if' end
+ else
+ goto l4
+ ::l4a:: a = a * 2; goto l4b
+ error("should never be here!")
+ ::l4:: goto l4a
+ error("should never be here!")
+ ::l4b::
+ end
+ do return a end
+ ::l2:: do return "2" end
+ ::l3:: do return "3" end
+ ::l1:: return "1"
+end
+
+assert(testG(1) == "1")
+assert(testG(2) == "2")
+assert(testG(3) == "3")
+assert(testG(4) == 5)
+assert(testG(5) == 10)
+--------------------------------------------------------------------------------
+
+
+print'OK'
diff --git a/testes/libs/lib1.c b/testes/libs/lib1.c
new file mode 100644
index 0000000000..56b6ef419c
--- /dev/null
+++ b/testes/libs/lib1.c
@@ -0,0 +1,44 @@
+#include "lua.h"
+#include "lauxlib.h"
+
+static int id (lua_State *L) {
+ return lua_gettop(L);
+}
+
+
+static const struct luaL_Reg funcs[] = {
+ {"id", id},
+ {NULL, NULL}
+};
+
+
+/* function used by lib11.c */
+LUAMOD_API int lib1_export (lua_State *L) {
+ lua_pushstring(L, "exported");
+ return 1;
+}
+
+
+LUAMOD_API int onefunction (lua_State *L) {
+ luaL_checkversion(L);
+ lua_settop(L, 2);
+ lua_pushvalue(L, 1);
+ return 2;
+}
+
+
+LUAMOD_API int anotherfunc (lua_State *L) {
+ luaL_checkversion(L);
+ lua_pushfstring(L, "%d%%%d\n", (int)lua_tointeger(L, 1),
+ (int)lua_tointeger(L, 2));
+ return 1;
+}
+
+
+LUAMOD_API int luaopen_lib1_sub (lua_State *L) {
+ lua_setglobal(L, "y"); /* 2nd arg: extra value (file name) */
+ lua_setglobal(L, "x"); /* 1st arg: module name */
+ luaL_newlib(L, funcs);
+ return 1;
+}
+
diff --git a/testes/libs/lib11.c b/testes/libs/lib11.c
new file mode 100644
index 0000000000..377d0c484f
--- /dev/null
+++ b/testes/libs/lib11.c
@@ -0,0 +1,10 @@
+#include "lua.h"
+
+/* function from lib1.c */
+int lib1_export (lua_State *L);
+
+LUAMOD_API int luaopen_lib11 (lua_State *L) {
+ return lib1_export(L);
+}
+
+
diff --git a/testes/libs/lib2.c b/testes/libs/lib2.c
new file mode 100644
index 0000000000..bc9651eea5
--- /dev/null
+++ b/testes/libs/lib2.c
@@ -0,0 +1,23 @@
+#include "lua.h"
+#include "lauxlib.h"
+
+static int id (lua_State *L) {
+ return lua_gettop(L);
+}
+
+
+static const struct luaL_Reg funcs[] = {
+ {"id", id},
+ {NULL, NULL}
+};
+
+
+LUAMOD_API int luaopen_lib2 (lua_State *L) {
+ lua_settop(L, 2);
+ lua_setglobal(L, "y"); /* y gets 2nd parameter */
+ lua_setglobal(L, "x"); /* x gets 1st parameter */
+ luaL_newlib(L, funcs);
+ return 1;
+}
+
+
diff --git a/testes/libs/lib21.c b/testes/libs/lib21.c
new file mode 100644
index 0000000000..a39b683d8c
--- /dev/null
+++ b/testes/libs/lib21.c
@@ -0,0 +1,10 @@
+#include "lua.h"
+
+
+int luaopen_lib2 (lua_State *L);
+
+LUAMOD_API int luaopen_lib21 (lua_State *L) {
+ return luaopen_lib2(L);
+}
+
+
diff --git a/testes/libs/makefile b/testes/libs/makefile
new file mode 100644
index 0000000000..9925fb005b
--- /dev/null
+++ b/testes/libs/makefile
@@ -0,0 +1,26 @@
+# change this variable to point to the directory with Lua headers
+# of the version being tested
+LUA_DIR = ../../
+
+CC = gcc
+
+# compilation should generate Dynamic-Link Libraries
+CFLAGS = -Wall -std=gnu99 -O2 -I$(LUA_DIR) -fPIC -shared
+
+# libraries used by the tests
+all: lib1.so lib11.so lib2.so lib21.so lib2-v2.so
+
+lib1.so: lib1.c
+ $(CC) $(CFLAGS) -o lib1.so lib1.c
+
+lib11.so: lib11.c
+ $(CC) $(CFLAGS) -o lib11.so lib11.c
+
+lib2.so: lib2.c
+ $(CC) $(CFLAGS) -o lib2.so lib2.c
+
+lib21.so: lib21.c
+ $(CC) $(CFLAGS) -o lib21.so lib21.c
+
+lib2-v2.so: lib2.so
+ mv lib2.so ./lib2-v2.so
diff --git a/testes/literals.lua b/testes/literals.lua
new file mode 100644
index 0000000000..3922b3f502
--- /dev/null
+++ b/testes/literals.lua
@@ -0,0 +1,302 @@
+-- $Id: literals.lua,v 1.36 2016/11/07 13:11:28 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print('testing scanner')
+
+local debug = require "debug"
+
+
+local function dostring (x) return assert(load(x), "")() end
+
+dostring("x \v\f = \t\r 'a\0a' \v\f\f")
+assert(x == 'a\0a' and string.len(x) == 3)
+
+-- escape sequences
+assert('\n\"\'\\' == [[
+
+"'\]])
+
+assert(string.find("\a\b\f\n\r\t\v", "^%c%c%c%c%c%c%c$"))
+
+-- assume ASCII just for tests:
+assert("\09912" == 'c12')
+assert("\99ab" == 'cab')
+assert("\099" == '\99')
+assert("\099\n" == 'c\10')
+assert('\0\0\0alo' == '\0' .. '\0\0' .. 'alo')
+
+assert(010 .. 020 .. -030 == "1020-30")
+
+-- hexadecimal escapes
+assert("\x00\x05\x10\x1f\x3C\xfF\xe8" == "\0\5\16\31\60\255\232")
+
+local function lexstring (x, y, n)
+ local f = assert(load('return ' .. x ..
+ ', require"debug".getinfo(1).currentline', ''))
+ local s, l = f()
+ assert(s == y and l == n)
+end
+
+lexstring("'abc\\z \n efg'", "abcefg", 2)
+lexstring("'abc\\z \n\n\n'", "abc", 4)
+lexstring("'\\z \n\t\f\v\n'", "", 3)
+lexstring("[[\nalo\nalo\n\n]]", "alo\nalo\n\n", 5)
+lexstring("[[\nalo\ralo\n\n]]", "alo\nalo\n\n", 5)
+lexstring("[[\nalo\ralo\r\n]]", "alo\nalo\n", 4)
+lexstring("[[\ralo\n\ralo\r\n]]", "alo\nalo\n", 4)
+lexstring("[[alo]\n]alo]]", "alo]\n]alo", 2)
+
+assert("abc\z
+ def\z
+ ghi\z
+ " == 'abcdefghi')
+
+
+-- UTF-8 sequences
+assert("\u{0}\u{00000000}\x00\0" == string.char(0, 0, 0, 0))
+
+-- limits for 1-byte sequences
+assert("\u{0}\u{7F}" == "\x00\z\x7F")
+
+-- limits for 2-byte sequences
+assert("\u{80}\u{7FF}" == "\xC2\x80\z\xDF\xBF")
+
+-- limits for 3-byte sequences
+assert("\u{800}\u{FFFF}" == "\xE0\xA0\x80\z\xEF\xBF\xBF")
+
+-- limits for 4-byte sequences
+assert("\u{10000}\u{10FFFF}" == "\xF0\x90\x80\x80\z\xF4\x8F\xBF\xBF")
+
+
+-- Error in escape sequences
+local function lexerror (s, err)
+ local st, msg = load('return ' .. s, '')
+ if err ~= '' then err = err .. "'" end
+ assert(not st and string.find(msg, "near .-" .. err))
+end
+
+lexerror([["abc\x"]], [[\x"]])
+lexerror([["abc\x]], [[\x]])
+lexerror([["\x]], [[\x]])
+lexerror([["\x5"]], [[\x5"]])
+lexerror([["\x5]], [[\x5]])
+lexerror([["\xr"]], [[\xr]])
+lexerror([["\xr]], [[\xr]])
+lexerror([["\x.]], [[\x.]])
+lexerror([["\x8%"]], [[\x8%%]])
+lexerror([["\xAG]], [[\xAG]])
+lexerror([["\g"]], [[\g]])
+lexerror([["\g]], [[\g]])
+lexerror([["\."]], [[\%.]])
+
+lexerror([["\999"]], [[\999"]])
+lexerror([["xyz\300"]], [[\300"]])
+lexerror([[" \256"]], [[\256"]])
+
+-- errors in UTF-8 sequences
+lexerror([["abc\u{110000}"]], [[abc\u{110000]]) -- too large
+lexerror([["abc\u11r"]], [[abc\u1]]) -- missing '{'
+lexerror([["abc\u"]], [[abc\u"]]) -- missing '{'
+lexerror([["abc\u{11r"]], [[abc\u{11r]]) -- missing '}'
+lexerror([["abc\u{11"]], [[abc\u{11"]]) -- missing '}'
+lexerror([["abc\u{11]], [[abc\u{11]]) -- missing '}'
+lexerror([["abc\u{r"]], [[abc\u{r]]) -- no digits
+
+-- unfinished strings
+lexerror("[=[alo]]", "")
+lexerror("[=[alo]=", "")
+lexerror("[=[alo]", "")
+lexerror("'alo", "")
+lexerror("'alo \\z \n\n", "")
+lexerror("'alo \\z", "")
+lexerror([['alo \98]], "")
+
+-- valid characters in variable names
+for i = 0, 255 do
+ local s = string.char(i)
+ assert(not string.find(s, "[a-zA-Z_]") == not load(s .. "=1", ""))
+ assert(not string.find(s, "[a-zA-Z_0-9]") ==
+ not load("a" .. s .. "1 = 1", ""))
+end
+
+
+-- long variable names
+
+var1 = string.rep('a', 15000) .. '1'
+var2 = string.rep('a', 15000) .. '2'
+prog = string.format([[
+ %s = 5
+ %s = %s + 1
+ return function () return %s - %s end
+]], var1, var2, var1, var1, var2)
+local f = dostring(prog)
+assert(_G[var1] == 5 and _G[var2] == 6 and f() == -1)
+var1, var2, f = nil
+print('+')
+
+-- escapes --
+assert("\n\t" == [[
+
+ ]])
+assert([[
+
+ $debug]] == "\n $debug")
+assert([[ [ ]] ~= [[ ] ]])
+-- long strings --
+b = "001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789"
+assert(string.len(b) == 960)
+prog = [=[
+print('+')
+
+a1 = [["this is a 'string' with several 'quotes'"]]
+a2 = "'quotes'"
+
+assert(string.find(a1, a2) == 34)
+print('+')
+
+a1 = [==[temp = [[an arbitrary value]]; ]==]
+assert(load(a1))()
+assert(temp == 'an arbitrary value')
+-- long strings --
+b = "001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789"
+assert(string.len(b) == 960)
+print('+')
+
+a = [[00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+00123456789012345678901234567890123456789123456789012345678901234567890123456789
+]]
+assert(string.len(a) == 1863)
+assert(string.sub(a, 1, 40) == string.sub(b, 1, 40))
+x = 1
+]=]
+
+print('+')
+x = nil
+dostring(prog)
+assert(x)
+
+prog = nil
+a = nil
+b = nil
+
+
+-- testing line ends
+prog = [[
+a = 1 -- a comment
+b = 2
+
+
+x = [=[
+hi
+]=]
+y = "\
+hello\r\n\
+"
+return require"debug".getinfo(1).currentline
+]]
+
+for _, n in pairs{"\n", "\r", "\n\r", "\r\n"} do
+ local prog, nn = string.gsub(prog, "\n", n)
+ assert(dostring(prog) == nn)
+ assert(_G.x == "hi\n" and _G.y == "\nhello\r\n\n")
+end
+
+
+-- testing comments and strings with long brackets
+a = [==[]=]==]
+assert(a == "]=")
+
+a = [==[[===[[=[]]=][====[]]===]===]==]
+assert(a == "[===[[=[]]=][====[]]===]===")
+
+a = [====[[===[[=[]]=][====[]]===]===]====]
+assert(a == "[===[[=[]]=][====[]]===]===")
+
+a = [=[]]]]]]]]]=]
+assert(a == "]]]]]]]]")
+
+
+--[===[
+x y z [==[ blu foo
+]==
+]
+]=]==]
+error error]=]===]
+
+-- generate all strings of four of these chars
+local x = {"=", "[", "]", "\n"}
+local len = 4
+local function gen (c, n)
+ if n==0 then coroutine.yield(c)
+ else
+ for _, a in pairs(x) do
+ gen(c..a, n-1)
+ end
+ end
+end
+
+for s in coroutine.wrap(function () gen("", len) end) do
+ assert(s == load("return [====[\n"..s.."]====]", "")())
+end
+
+
+-- testing decimal point locale
+if os.setlocale("pt_BR") or os.setlocale("ptb") then
+ assert(tonumber("3,4") == 3.4 and tonumber"3.4" == 3.4)
+ assert(tonumber(" -.4 ") == -0.4)
+ assert(tonumber(" +0x.41 ") == 0X0.41)
+ assert(not load("a = (3,4)"))
+ assert(assert(load("return 3.4"))() == 3.4)
+ assert(assert(load("return .4,3"))() == .4)
+ assert(assert(load("return 4."))() == 4.)
+ assert(assert(load("return 4.+.5"))() == 4.5)
+
+ assert(" 0x.1 " + " 0x,1" + "-0X.1\t" == 0x0.1)
+
+ assert(tonumber"inf" == nil and tonumber"NAN" == nil)
+
+ assert(assert(load(string.format("return %q", 4.51)))() == 4.51)
+
+ local a,b = load("return 4.5.")
+ assert(string.find(b, "'4%.5%.'"))
+
+ assert(os.setlocale("C"))
+else
+ (Message or print)(
+ '\n >>> pt_BR locale not available: skipping decimal point tests <<<\n')
+end
+
+
+-- testing %q x line ends
+local s = "a string with \r and \n and \r\n and \n\r"
+local c = string.format("return %q", s)
+assert(assert(load(c))() == s)
+
+-- testing errors
+assert(not load"a = 'non-ending string")
+assert(not load"a = 'non-ending string\n'")
+assert(not load"a = '\\345'")
+assert(not load"a = [=x]")
+
+print('OK')
diff --git a/testes/locals.lua b/testes/locals.lua
new file mode 100644
index 0000000000..f0780a031b
--- /dev/null
+++ b/testes/locals.lua
@@ -0,0 +1,181 @@
+-- $Id: locals.lua,v 1.41 2018/06/19 12:25:39 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print('testing local variables and environments')
+
+local debug = require"debug"
+
+
+-- bug in 5.1:
+
+local function f(x) x = nil; return x end
+assert(f(10) == nil)
+
+local function f() local x; return x end
+assert(f(10) == nil)
+
+local function f(x) x = nil; local y; return x, y end
+assert(f(10) == nil and select(2, f(20)) == nil)
+
+do
+ local i = 10
+ do local i = 100; assert(i==100) end
+ do local i = 1000; assert(i==1000) end
+ assert(i == 10)
+ if i ~= 10 then
+ local i = 20
+ else
+ local i = 30
+ assert(i == 30)
+ end
+end
+
+
+
+f = nil
+
+local f
+x = 1
+
+a = nil
+load('local a = {}')()
+assert(a == nil)
+
+function f (a)
+ local _1, _2, _3, _4, _5
+ local _6, _7, _8, _9, _10
+ local x = 3
+ local b = a
+ local c,d = a,b
+ if (d == b) then
+ local x = 'q'
+ x = b
+ assert(x == 2)
+ else
+ assert(nil)
+ end
+ assert(x == 3)
+ local f = 10
+end
+
+local b=10
+local a; repeat local b; a,b=1,2; assert(a+1==b); until a+b==3
+
+
+assert(x == 1)
+
+f(2)
+assert(type(f) == 'function')
+
+
+local function getenv (f)
+ local a,b = debug.getupvalue(f, 1)
+ assert(a == '_ENV')
+ return b
+end
+
+-- test for global table of loaded chunks
+assert(getenv(load"a=3") == _G)
+local c = {}; local f = load("a = 3", nil, nil, c)
+assert(getenv(f) == c)
+assert(c.a == nil)
+f()
+assert(c.a == 3)
+
+-- old test for limits for special instructions (now just a generic test)
+do
+ local i = 2
+ local p = 4 -- p == 2^i
+ repeat
+ for j=-3,3 do
+ assert(load(string.format([[local a=%s;
+ a=a+%s;
+ assert(a ==2^%s)]], j, p-j, i), '')) ()
+ assert(load(string.format([[local a=%s;
+ a=a-%s;
+ assert(a==-2^%s)]], -j, p-j, i), '')) ()
+ assert(load(string.format([[local a,b=0,%s;
+ a=b-%s;
+ assert(a==-2^%s)]], -j, p-j, i), '')) ()
+ end
+ p = 2 * p; i = i + 1
+ until p <= 0
+end
+
+print'+'
+
+
+if rawget(_G, "T") then
+ -- testing clearing of dead elements from tables
+ collectgarbage("stop") -- stop GC
+ local a = {[{}] = 4, [3] = 0, alo = 1,
+ a1234567890123456789012345678901234567890 = 10}
+
+ local t = T.querytab(a)
+
+ for k,_ in pairs(a) do a[k] = undef end
+ collectgarbage() -- restore GC and collect dead fiels in `a'
+ for i=0,t-1 do
+ local k = querytab(a, i)
+ assert(k == nil or type(k) == 'number' or k == 'alo')
+ end
+
+ -- testing allocation errors during table insertions
+ local a = {}
+ local function additems ()
+ a.x = true; a.y = true; a.z = true
+ a[1] = true
+ a[2] = true
+ end
+ for i = 1, math.huge do
+ T.alloccount(i)
+ local st, msg = pcall(additems)
+ T.alloccount()
+ local count = 0
+ for k, v in pairs(a) do
+ assert(a[k] == v)
+ count = count + 1
+ end
+ if st then assert(count == 5); break end
+ end
+end
+
+
+-- testing lexical environments
+
+assert(_ENV == _G)
+
+do
+local dummy
+local _ENV = (function (...) return ... end)(_G, dummy) -- {
+
+do local _ENV = {assert=assert}; assert(true) end
+mt = {_G = _G}
+local foo,x
+A = false -- "declare" A
+do local _ENV = mt
+ function foo (x)
+ A = x
+ do local _ENV = _G; A = 1000 end
+ return function (x) return A .. x end
+ end
+end
+assert(getenv(foo) == mt)
+x = foo('hi'); assert(mt.A == 'hi' and A == 1000)
+assert(x('*') == mt.A .. '*')
+
+do local _ENV = {assert=assert, A=10};
+ do local _ENV = {assert=assert, A=20};
+ assert(A==20);x=A
+ end
+ assert(A==10 and x==20)
+end
+assert(x==20)
+
+
+print('OK')
+
+return 5,f
+
+end -- }
+
diff --git a/testes/main.lua b/testes/main.lua
new file mode 100644
index 0000000000..582b39c02e
--- /dev/null
+++ b/testes/main.lua
@@ -0,0 +1,381 @@
+# testing special comment on first line
+-- $Id: main.lua,v 1.69 2018/06/19 12:23:50 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+-- most (all?) tests here assume a reasonable "Unix-like" shell
+if _port then return end
+
+-- use only "double quotes" inside shell scripts (better change to
+-- run on Windows)
+
+
+print ("testing stand-alone interpreter")
+
+assert(os.execute()) -- machine has a system command
+
+local arg = arg or ARG
+
+local prog = os.tmpname()
+local otherprog = os.tmpname()
+local out = os.tmpname()
+
+local progname
+do
+ local i = 0
+ while arg[i] do i=i-1 end
+ progname = arg[i+1]
+end
+print("progname: "..progname)
+
+local prepfile = function (s, p)
+ p = p or prog
+ io.output(p)
+ io.write(s)
+ assert(io.close())
+end
+
+local function getoutput ()
+ io.input(out)
+ local t = io.read("a")
+ io.input():close()
+ assert(os.remove(out))
+ return t
+end
+
+local function checkprogout (s)
+ local t = getoutput()
+ for line in string.gmatch(s, ".-\n") do
+ assert(string.find(t, line, 1, true))
+ end
+end
+
+local function checkout (s)
+ local t = getoutput()
+ if s ~= t then print(string.format("'%s' - '%s'\n", s, t)) end
+ assert(s == t)
+ return t
+end
+
+
+local function RUN (p, ...)
+ p = string.gsub(p, "lua", '"'..progname..'"', 1)
+ local s = string.format(p, ...)
+ assert(os.execute(s))
+end
+
+local function NoRun (msg, p, ...)
+ p = string.gsub(p, "lua", '"'..progname..'"', 1)
+ local s = string.format(p, ...)
+ s = string.format("%s 2> %s", s, out) -- will send error to 'out'
+ assert(not os.execute(s))
+ assert(string.find(getoutput(), msg, 1, true)) -- check error message
+end
+
+RUN('lua -v')
+
+print(string.format("(temporary program file used in these tests: %s)", prog))
+
+-- running stdin as a file
+prepfile""
+RUN('lua - < %s > %s', prog, out)
+checkout("")
+
+prepfile[[
+ print(
+1, a
+)
+]]
+RUN('lua - < %s > %s', prog, out)
+checkout("1\tnil\n")
+
+RUN('echo "print(10)\nprint(2)\n" | lua > %s', out)
+checkout("10\n2\n")
+
+
+-- test option '-'
+RUN('echo "print(arg[1])" | lua - -h > %s', out)
+checkout("-h\n")
+
+-- test environment variables used by Lua
+
+prepfile("print(package.path)")
+
+-- test LUA_PATH
+RUN('env LUA_INIT= LUA_PATH=x lua %s > %s', prog, out)
+checkout("x\n")
+
+-- test LUA_PATH_version
+RUN('env LUA_INIT= LUA_PATH_5_4=y LUA_PATH=x lua %s > %s', prog, out)
+checkout("y\n")
+
+-- test LUA_CPATH
+prepfile("print(package.cpath)")
+RUN('env LUA_INIT= LUA_CPATH=xuxu lua %s > %s', prog, out)
+checkout("xuxu\n")
+
+-- test LUA_CPATH_version
+RUN('env LUA_INIT= LUA_CPATH_5_4=yacc LUA_CPATH=x lua %s > %s', prog, out)
+checkout("yacc\n")
+
+-- test LUA_INIT (and its access to 'arg' table)
+prepfile("print(X)")
+RUN('env LUA_INIT="X=tonumber(arg[1])" lua %s 3.2 > %s', prog, out)
+checkout("3.2\n")
+
+-- test LUA_INIT_version
+prepfile("print(X)")
+RUN('env LUA_INIT_5_4="X=10" LUA_INIT="X=3" lua %s > %s', prog, out)
+checkout("10\n")
+
+-- test LUA_INIT for files
+prepfile("x = x or 10; print(x); x = x + 1")
+RUN('env LUA_INIT="@%s" lua %s > %s', prog, prog, out)
+checkout("10\n11\n")
+
+-- test errors in LUA_INIT
+NoRun('LUA_INIT:1: msg', 'env LUA_INIT="error(\'msg\')" lua')
+
+-- test option '-E'
+local defaultpath, defaultCpath
+
+do
+ prepfile("print(package.path, package.cpath)")
+ RUN('env LUA_INIT="error(10)" LUA_PATH=xxx LUA_CPATH=xxx lua -E %s > %s',
+ prog, out)
+ local out = getoutput()
+ defaultpath = string.match(out, "^(.-)\t")
+ defaultCpath = string.match(out, "\t(.-)$")
+end
+
+-- paths did not changed
+assert(not string.find(defaultpath, "xxx") and
+ string.find(defaultpath, "lua") and
+ not string.find(defaultCpath, "xxx") and
+ string.find(defaultCpath, "lua"))
+
+
+-- test replacement of ';;' to default path
+local function convert (p)
+ prepfile("print(package.path)")
+ RUN('env LUA_PATH="%s" lua %s > %s', p, prog, out)
+ local expected = getoutput()
+ expected = string.sub(expected, 1, -2) -- cut final end of line
+ assert(string.gsub(p, ";;", ";"..defaultpath..";") == expected)
+end
+
+convert(";")
+convert(";;")
+convert(";;;")
+convert(";;;;")
+convert(";;;;;")
+convert(";;a;;;bc")
+
+
+-- test -l over multiple libraries
+prepfile("print(1); a=2; return {x=15}")
+prepfile(("print(a); print(_G['%s'].x)"):format(prog), otherprog)
+RUN('env LUA_PATH="?;;" lua -l %s -l%s -lstring -l io %s > %s', prog, otherprog, otherprog, out)
+checkout("1\n2\n15\n2\n15\n")
+
+-- test 'arg' table
+local a = [[
+ assert(#arg == 3 and arg[1] == 'a' and
+ arg[2] == 'b' and arg[3] == 'c')
+ assert(arg[-1] == '--' and arg[-2] == "-e " and arg[-3] == '%s')
+ assert(arg[4] == undef and arg[-4] == undef)
+ local a, b, c = ...
+ assert(... == 'a' and a == 'a' and b == 'b' and c == 'c')
+]]
+a = string.format(a, progname)
+prepfile(a)
+RUN('lua "-e " -- %s a b c', prog) -- "-e " runs an empty command
+
+-- test 'arg' availability in libraries
+prepfile"assert(arg)"
+prepfile("assert(arg)", otherprog)
+RUN('env LUA_PATH="?;;" lua -l%s - < %s', prog, otherprog)
+
+-- test messing up the 'arg' table
+RUN('echo "print(...)" | lua -e "arg[1] = 100" - > %s', out)
+checkout("100\n")
+NoRun("'arg' is not a table", 'echo "" | lua -e "arg = 1" -')
+
+-- test error in 'print'
+RUN('echo 10 | lua -e "print=nil" -i > /dev/null 2> %s', out)
+assert(string.find(getoutput(), "error calling 'print'"))
+
+-- test 'debug.debug'
+RUN('echo "io.stderr:write(1000)\ncont" | lua -e "require\'debug\'.debug()" 2> %s', out)
+checkout("lua_debug> 1000lua_debug> ")
+
+-- test many arguments
+prepfile[[print(({...})[30])]]
+RUN('lua %s %s > %s', prog, string.rep(" a", 30), out)
+checkout("a\n")
+
+RUN([[lua "-eprint(1)" -ea=3 -e "print(a)" > %s]], out)
+checkout("1\n3\n")
+
+-- test iteractive mode
+prepfile[[
+(6*2-6) -- ===
+a =
+10
+print(a)
+a]]
+RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
+checkprogout("6\n10\n10\n\n")
+
+prepfile("a = [[b\nc\nd\ne]]\n=a")
+RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
+checkprogout("b\nc\nd\ne\n\n")
+
+prompt = "alo"
+prepfile[[ --
+a = 2
+]]
+RUN([[lua "-e_PROMPT='%s'" -i < %s > %s]], prompt, prog, out)
+local t = getoutput()
+assert(string.find(t, prompt .. ".*" .. prompt .. ".*" .. prompt))
+
+-- test for error objects
+prepfile[[
+debug = require "debug"
+m = {x=0}
+setmetatable(m, {__tostring = function(x)
+ return tostring(debug.getinfo(4).currentline + x.x)
+end})
+error(m)
+]]
+NoRun(progname .. ": 6\n", [[lua %s]], prog)
+
+prepfile("error{}")
+NoRun("error object is a table value", [[lua %s]], prog)
+
+
+-- chunk broken in many lines
+s = [=[ --
+function f ( x )
+ local a = [[
+xuxu
+]]
+ local b = "\
+xuxu\n"
+ if x == 11 then return 1 + 12 , 2 + 20 end --[[ test multiple returns ]]
+ return x + 1
+ --\\
+end
+return( f( 100 ) )
+assert( a == b )
+do return f( 11 ) end ]=]
+s = string.gsub(s, ' ', '\n\n') -- change all spaces for newlines
+prepfile(s)
+RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
+checkprogout("101\n13\t22\n\n")
+
+prepfile[[#comment in 1st line without \n at the end]]
+RUN('lua %s', prog)
+
+prepfile[[#test line number when file starts with comment line
+debug = require"debug"
+print(debug.getinfo(1).currentline)
+]]
+RUN('lua %s > %s', prog, out)
+checkprogout('3')
+
+-- close Lua with an open file
+prepfile(string.format([[io.output(%q); io.write('alo')]], out))
+RUN('lua %s', prog)
+checkout('alo')
+
+-- bug in 5.2 beta (extra \0 after version line)
+RUN([[lua -v -e"print'hello'" > %s]], out)
+t = getoutput()
+assert(string.find(t, "PUC%-Rio\nhello"))
+
+
+-- testing os.exit
+prepfile("os.exit(nil, true)")
+RUN('lua %s', prog)
+prepfile("os.exit(0, true)")
+RUN('lua %s', prog)
+prepfile("os.exit(true, true)")
+RUN('lua %s', prog)
+prepfile("os.exit(1, true)")
+NoRun("", "lua %s", prog) -- no message
+prepfile("os.exit(false, true)")
+NoRun("", "lua %s", prog) -- no message
+
+-- remove temporary files
+assert(os.remove(prog))
+assert(os.remove(otherprog))
+assert(not os.remove(out))
+
+-- invalid options
+NoRun("unrecognized option '-h'", "lua -h")
+NoRun("unrecognized option '---'", "lua ---")
+NoRun("unrecognized option '-Ex'", "lua -Ex")
+NoRun("unrecognized option '-vv'", "lua -vv")
+NoRun("unrecognized option '-iv'", "lua -iv")
+NoRun("'-e' needs argument", "lua -e")
+NoRun("syntax error", "lua -e a")
+NoRun("'-l' needs argument", "lua -l")
+
+
+if T then -- auxiliary library?
+ print("testing 'not enough memory' to create a state")
+ NoRun("not enough memory", "env MEMLIMIT=100 lua")
+end
+print('+')
+
+print('testing Ctrl C')
+do
+ -- interrupt a script
+ local function kill (pid)
+ return os.execute(string.format('kill -INT %s 2> /dev/null', pid))
+ end
+
+ -- function to run a script in background, returning its output file
+ -- descriptor and its pid
+ local function runback (luaprg)
+ -- shell script to run 'luaprg' in background and echo its pid
+ local shellprg = string.format('%s -e "%s" & echo $!', progname, luaprg)
+ local f = io.popen(shellprg, "r") -- run shell script
+ local pid = f:read() -- get pid for Lua script
+ print("(if test fails now, it may leave a Lua script running in \z
+ background, pid " .. pid .. ")")
+ return f, pid
+ end
+
+ -- Lua script that runs protected infinite loop and then prints '42'
+ local f, pid = runback[[
+ pcall(function () print(12); while true do end end); print(42)]]
+ -- wait until script is inside 'pcall'
+ assert(f:read() == "12")
+ kill(pid) -- send INT signal to Lua script
+ -- check that 'pcall' captured the exception and script continued running
+ assert(f:read() == "42") -- expected output
+ assert(f:close())
+ print("done")
+
+ -- Lua script in a long unbreakable search
+ local f, pid = runback[[
+ print(15); string.find(string.rep('a', 100000), '.*b')]]
+ -- wait (so script can reach the loop)
+ assert(f:read() == "15")
+ assert(os.execute("sleep 1"))
+ -- must send at least two INT signals to stop this Lua script
+ local n = 100
+ for i = 0, 100 do -- keep sending signals
+ if not kill(pid) then -- until it fails
+ n = i -- number of non-failed kills
+ break
+ end
+ end
+ assert(f:close())
+ assert(n >= 2)
+ print(string.format("done (with %d kills)", n))
+
+end
+
+print("OK")
diff --git a/testes/math.lua b/testes/math.lua
new file mode 100644
index 0000000000..66998460d0
--- /dev/null
+++ b/testes/math.lua
@@ -0,0 +1,931 @@
+-- $Id: math.lua,v 1.86 2018/05/09 14:55:52 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print("testing numbers and math lib")
+
+local minint = math.mininteger
+local maxint = math.maxinteger
+
+local intbits = math.floor(math.log(maxint, 2) + 0.5) + 1
+assert((1 << intbits) == 0)
+
+assert(minint == 1 << (intbits - 1))
+assert(maxint == minint - 1)
+
+-- number of bits in the mantissa of a floating-point number
+local floatbits = 24
+do
+ local p = 2.0^floatbits
+ while p < p + 1.0 do
+ p = p * 2.0
+ floatbits = floatbits + 1
+ end
+end
+
+local function isNaN (x)
+ return (x ~= x)
+end
+
+assert(isNaN(0/0))
+assert(not isNaN(1/0))
+
+
+do
+ local x = 2.0^floatbits
+ assert(x > x - 1.0 and x == x + 1.0)
+
+ print(string.format("%d-bit integers, %d-bit (mantissa) floats",
+ intbits, floatbits))
+end
+
+assert(math.type(0) == "integer" and math.type(0.0) == "float"
+ and math.type("10") == nil)
+
+
+local function checkerror (msg, f, ...)
+ local s, err = pcall(f, ...)
+ assert(not s and string.find(err, msg))
+end
+
+local msgf2i = "number.* has no integer representation"
+
+-- float equality
+function eq (a,b,limit)
+ if not limit then
+ if floatbits >= 50 then limit = 1E-11
+ else limit = 1E-5
+ end
+ end
+ -- a == b needed for +inf/-inf
+ return a == b or math.abs(a-b) <= limit
+end
+
+
+-- equality with types
+function eqT (a,b)
+ return a == b and math.type(a) == math.type(b)
+end
+
+
+-- basic float notation
+assert(0e12 == 0 and .0 == 0 and 0. == 0 and .2e2 == 20 and 2.E-1 == 0.2)
+
+do
+ local a,b,c = "2", " 3e0 ", " 10 "
+ assert(a+b == 5 and -b == -3 and b+"2" == 5 and "10"-c == 0)
+ assert(type(a) == 'string' and type(b) == 'string' and type(c) == 'string')
+ assert(a == "2" and b == " 3e0 " and c == " 10 " and -c == -" 10 ")
+ assert(c%a == 0 and a^b == 08)
+ a = 0
+ assert(a == -a and 0 == -0)
+end
+
+do
+ local x = -1
+ local mz = 0/x -- minus zero
+ t = {[0] = 10, 20, 30, 40, 50}
+ assert(t[mz] == t[0] and t[-0] == t[0])
+end
+
+do -- tests for 'modf'
+ local a,b = math.modf(3.5)
+ assert(a == 3.0 and b == 0.5)
+ a,b = math.modf(-2.5)
+ assert(a == -2.0 and b == -0.5)
+ a,b = math.modf(-3e23)
+ assert(a == -3e23 and b == 0.0)
+ a,b = math.modf(3e35)
+ assert(a == 3e35 and b == 0.0)
+ a,b = math.modf(-1/0) -- -inf
+ assert(a == -1/0 and b == 0.0)
+ a,b = math.modf(1/0) -- inf
+ assert(a == 1/0 and b == 0.0)
+ a,b = math.modf(0/0) -- NaN
+ assert(isNaN(a) and isNaN(b))
+ a,b = math.modf(3) -- integer argument
+ assert(eqT(a, 3) and eqT(b, 0.0))
+ a,b = math.modf(minint)
+ assert(eqT(a, minint) and eqT(b, 0.0))
+end
+
+assert(math.huge > 10e30)
+assert(-math.huge < -10e30)
+
+
+-- integer arithmetic
+assert(minint < minint + 1)
+assert(maxint - 1 < maxint)
+assert(0 - minint == minint)
+assert(minint * minint == 0)
+assert(maxint * maxint * maxint == maxint)
+
+
+-- testing floor division and conversions
+
+for _, i in pairs{-16, -15, -3, -2, -1, 0, 1, 2, 3, 15} do
+ for _, j in pairs{-16, -15, -3, -2, -1, 1, 2, 3, 15} do
+ for _, ti in pairs{0, 0.0} do -- try 'i' as integer and as float
+ for _, tj in pairs{0, 0.0} do -- try 'j' as integer and as float
+ local x = i + ti
+ local y = j + tj
+ assert(i//j == math.floor(i/j))
+ end
+ end
+ end
+end
+
+assert(1//0.0 == 1/0)
+assert(-1 // 0.0 == -1/0)
+assert(eqT(3.5 // 1.5, 2.0))
+assert(eqT(3.5 // -1.5, -3.0))
+
+assert(maxint // maxint == 1)
+assert(maxint // 1 == maxint)
+assert((maxint - 1) // maxint == 0)
+assert(maxint // (maxint - 1) == 1)
+assert(minint // minint == 1)
+assert(minint // minint == 1)
+assert((minint + 1) // minint == 0)
+assert(minint // (minint + 1) == 1)
+assert(minint // 1 == minint)
+
+assert(minint // -1 == -minint)
+assert(minint // -2 == 2^(intbits - 2))
+assert(maxint // -1 == -maxint)
+
+
+-- negative exponents
+do
+ assert(2^-3 == 1 / 2^3)
+ assert(eq((-3)^-3, 1 / (-3)^3))
+ for i = -3, 3 do -- variables avoid constant folding
+ for j = -3, 3 do
+ -- domain errors (0^(-n)) are not portable
+ if not _port or i ~= 0 or j > 0 then
+ assert(eq(i^j, 1 / i^(-j)))
+ end
+ end
+ end
+end
+
+-- comparison between floats and integers (border cases)
+if floatbits < intbits then
+ assert(2.0^floatbits == (1 << floatbits))
+ assert(2.0^floatbits - 1.0 == (1 << floatbits) - 1.0)
+ assert(2.0^floatbits - 1.0 ~= (1 << floatbits))
+ -- float is rounded, int is not
+ assert(2.0^floatbits + 1.0 ~= (1 << floatbits) + 1)
+else -- floats can express all integers with full accuracy
+ assert(maxint == maxint + 0.0)
+ assert(maxint - 1 == maxint - 1.0)
+ assert(minint + 1 == minint + 1.0)
+ assert(maxint ~= maxint - 1.0)
+end
+assert(maxint + 0.0 == 2.0^(intbits - 1) - 1.0)
+assert(minint + 0.0 == minint)
+assert(minint + 0.0 == -2.0^(intbits - 1))
+
+
+-- order between floats and integers
+assert(1 < 1.1); assert(not (1 < 0.9))
+assert(1 <= 1.1); assert(not (1 <= 0.9))
+assert(-1 < -0.9); assert(not (-1 < -1.1))
+assert(1 <= 1.1); assert(not (-1 <= -1.1))
+assert(-1 < -0.9); assert(not (-1 < -1.1))
+assert(-1 <= -0.9); assert(not (-1 <= -1.1))
+assert(minint <= minint + 0.0)
+assert(minint + 0.0 <= minint)
+assert(not (minint < minint + 0.0))
+assert(not (minint + 0.0 < minint))
+assert(maxint < minint * -1.0)
+assert(maxint <= minint * -1.0)
+
+do
+ local fmaxi1 = 2^(intbits - 1)
+ assert(maxint < fmaxi1)
+ assert(maxint <= fmaxi1)
+ assert(not (fmaxi1 <= maxint))
+ assert(minint <= -2^(intbits - 1))
+ assert(-2^(intbits - 1) <= minint)
+end
+
+if floatbits < intbits then
+ print("testing order (floats cannot represent all integers)")
+ local fmax = 2^floatbits
+ local ifmax = fmax | 0
+ assert(fmax < ifmax + 1)
+ assert(fmax - 1 < ifmax)
+ assert(-(fmax - 1) > -ifmax)
+ assert(not (fmax <= ifmax - 1))
+ assert(-fmax > -(ifmax + 1))
+ assert(not (-fmax >= -(ifmax - 1)))
+
+ assert(fmax/2 - 0.5 < ifmax//2)
+ assert(-(fmax/2 - 0.5) > -ifmax//2)
+
+ assert(maxint < 2^intbits)
+ assert(minint > -2^intbits)
+ assert(maxint <= 2^intbits)
+ assert(minint >= -2^intbits)
+else
+ print("testing order (floats can represent all integers)")
+ assert(maxint < maxint + 1.0)
+ assert(maxint < maxint + 0.5)
+ assert(maxint - 1.0 < maxint)
+ assert(maxint - 0.5 < maxint)
+ assert(not (maxint + 0.0 < maxint))
+ assert(maxint + 0.0 <= maxint)
+ assert(not (maxint < maxint + 0.0))
+ assert(maxint + 0.0 <= maxint)
+ assert(maxint <= maxint + 0.0)
+ assert(not (maxint + 1.0 <= maxint))
+ assert(not (maxint + 0.5 <= maxint))
+ assert(not (maxint <= maxint - 1.0))
+ assert(not (maxint <= maxint - 0.5))
+
+ assert(minint < minint + 1.0)
+ assert(minint < minint + 0.5)
+ assert(minint <= minint + 0.5)
+ assert(minint - 1.0 < minint)
+ assert(minint - 1.0 <= minint)
+ assert(not (minint + 0.0 < minint))
+ assert(not (minint + 0.5 < minint))
+ assert(not (minint < minint + 0.0))
+ assert(minint + 0.0 <= minint)
+ assert(minint <= minint + 0.0)
+ assert(not (minint + 1.0 <= minint))
+ assert(not (minint + 0.5 <= minint))
+ assert(not (minint <= minint - 1.0))
+end
+
+do
+ local NaN = 0/0
+ assert(not (NaN < 0))
+ assert(not (NaN > minint))
+ assert(not (NaN <= -9))
+ assert(not (NaN <= maxint))
+ assert(not (NaN < maxint))
+ assert(not (minint <= NaN))
+ assert(not (minint < NaN))
+ assert(not (4 <= NaN))
+ assert(not (4 < NaN))
+end
+
+
+-- avoiding errors at compile time
+local function checkcompt (msg, code)
+ checkerror(msg, assert(load(code)))
+end
+checkcompt("divide by zero", "return 2 // 0")
+checkcompt(msgf2i, "return 2.3 >> 0")
+checkcompt(msgf2i, ("return 2.0^%d & 1"):format(intbits - 1))
+checkcompt("field 'huge'", "return math.huge << 1")
+checkcompt(msgf2i, ("return 1 | 2.0^%d"):format(intbits - 1))
+checkcompt(msgf2i, "return 2.3 ~ 0.0")
+
+
+-- testing overflow errors when converting from float to integer (runtime)
+local function f2i (x) return x | x end
+checkerror(msgf2i, f2i, math.huge) -- +inf
+checkerror(msgf2i, f2i, -math.huge) -- -inf
+checkerror(msgf2i, f2i, 0/0) -- NaN
+
+if floatbits < intbits then
+ -- conversion tests when float cannot represent all integers
+ assert(maxint + 1.0 == maxint + 0.0)
+ assert(minint - 1.0 == minint + 0.0)
+ checkerror(msgf2i, f2i, maxint + 0.0)
+ assert(f2i(2.0^(intbits - 2)) == 1 << (intbits - 2))
+ assert(f2i(-2.0^(intbits - 2)) == -(1 << (intbits - 2)))
+ assert((2.0^(floatbits - 1) + 1.0) // 1 == (1 << (floatbits - 1)) + 1)
+ -- maximum integer representable as a float
+ local mf = maxint - (1 << (floatbits - intbits)) + 1
+ assert(f2i(mf + 0.0) == mf) -- OK up to here
+ mf = mf + 1
+ assert(f2i(mf + 0.0) ~= mf) -- no more representable
+else
+ -- conversion tests when float can represent all integers
+ assert(maxint + 1.0 > maxint)
+ assert(minint - 1.0 < minint)
+ assert(f2i(maxint + 0.0) == maxint)
+ checkerror("no integer rep", f2i, maxint + 1.0)
+ checkerror("no integer rep", f2i, minint - 1.0)
+end
+
+-- 'minint' should be representable as a float no matter the precision
+assert(f2i(minint + 0.0) == minint)
+
+
+-- testing numeric strings
+
+assert("2" + 1 == 3)
+assert("2 " + 1 == 3)
+assert(" -2 " + 1 == -1)
+assert(" -0xa " + 1 == -9)
+
+
+-- Literal integer Overflows (new behavior in 5.3.3)
+do
+ -- no overflows
+ assert(eqT(tonumber(tostring(maxint)), maxint))
+ assert(eqT(tonumber(tostring(minint)), minint))
+
+ -- add 1 to last digit as a string (it cannot be 9...)
+ local function incd (n)
+ local s = string.format("%d", n)
+ s = string.gsub(s, "%d$", function (d)
+ assert(d ~= '9')
+ return string.char(string.byte(d) + 1)
+ end)
+ return s
+ end
+
+ -- 'tonumber' with overflow by 1
+ assert(eqT(tonumber(incd(maxint)), maxint + 1.0))
+ assert(eqT(tonumber(incd(minint)), minint - 1.0))
+
+ -- large numbers
+ assert(eqT(tonumber("1"..string.rep("0", 30)), 1e30))
+ assert(eqT(tonumber("-1"..string.rep("0", 30)), -1e30))
+
+ -- hexa format still wraps around
+ assert(eqT(tonumber("0x1"..string.rep("0", 30)), 0))
+
+ -- lexer in the limits
+ assert(minint == load("return " .. minint)())
+ assert(eqT(maxint, load("return " .. maxint)()))
+
+ assert(eqT(10000000000000000000000.0, 10000000000000000000000))
+ assert(eqT(-10000000000000000000000.0, -10000000000000000000000))
+end
+
+
+-- testing 'tonumber'
+
+-- 'tonumber' with numbers
+assert(tonumber(3.4) == 3.4)
+assert(eqT(tonumber(3), 3))
+assert(eqT(tonumber(maxint), maxint) and eqT(tonumber(minint), minint))
+assert(tonumber(1/0) == 1/0)
+
+-- 'tonumber' with strings
+assert(tonumber("0") == 0)
+assert(tonumber("") == nil)
+assert(tonumber(" ") == nil)
+assert(tonumber("-") == nil)
+assert(tonumber(" -0x ") == nil)
+assert(tonumber{} == nil)
+assert(tonumber'+0.01' == 1/100 and tonumber'+.01' == 0.01 and
+ tonumber'.01' == 0.01 and tonumber'-1.' == -1 and
+ tonumber'+1.' == 1)
+assert(tonumber'+ 0.01' == nil and tonumber'+.e1' == nil and
+ tonumber'1e' == nil and tonumber'1.0e+' == nil and
+ tonumber'.' == nil)
+assert(tonumber('-012') == -010-2)
+assert(tonumber('-1.2e2') == - - -120)
+
+assert(tonumber("0xffffffffffff") == (1 << (4*12)) - 1)
+assert(tonumber("0x"..string.rep("f", (intbits//4))) == -1)
+assert(tonumber("-0x"..string.rep("f", (intbits//4))) == 1)
+
+-- testing 'tonumber' with base
+assert(tonumber(' 001010 ', 2) == 10)
+assert(tonumber(' 001010 ', 10) == 001010)
+assert(tonumber(' -1010 ', 2) == -10)
+assert(tonumber('10', 36) == 36)
+assert(tonumber(' -10 ', 36) == -36)
+assert(tonumber(' +1Z ', 36) == 36 + 35)
+assert(tonumber(' -1z ', 36) == -36 + -35)
+assert(tonumber('-fFfa', 16) == -(10+(16*(15+(16*(15+(16*15)))))))
+assert(tonumber(string.rep('1', (intbits - 2)), 2) + 1 == 2^(intbits - 2))
+assert(tonumber('ffffFFFF', 16)+1 == (1 << 32))
+assert(tonumber('0ffffFFFF', 16)+1 == (1 << 32))
+assert(tonumber('-0ffffffFFFF', 16) - 1 == -(1 << 40))
+for i = 2,36 do
+ local i2 = i * i
+ local i10 = i2 * i2 * i2 * i2 * i2 -- i^10
+ assert(tonumber('\t10000000000\t', i) == i10)
+end
+
+if not _soft then
+ -- tests with very long numerals
+ assert(tonumber("0x"..string.rep("f", 13)..".0") == 2.0^(4*13) - 1)
+ assert(tonumber("0x"..string.rep("f", 150)..".0") == 2.0^(4*150) - 1)
+ assert(tonumber("0x"..string.rep("f", 300)..".0") == 2.0^(4*300) - 1)
+ assert(tonumber("0x"..string.rep("f", 500)..".0") == 2.0^(4*500) - 1)
+ assert(tonumber('0x3.' .. string.rep('0', 1000)) == 3)
+ assert(tonumber('0x' .. string.rep('0', 1000) .. 'a') == 10)
+ assert(tonumber('0x0.' .. string.rep('0', 13).."1") == 2.0^(-4*14))
+ assert(tonumber('0x0.' .. string.rep('0', 150).."1") == 2.0^(-4*151))
+ assert(tonumber('0x0.' .. string.rep('0', 300).."1") == 2.0^(-4*301))
+ assert(tonumber('0x0.' .. string.rep('0', 500).."1") == 2.0^(-4*501))
+
+ assert(tonumber('0xe03' .. string.rep('0', 1000) .. 'p-4000') == 3587.0)
+ assert(tonumber('0x.' .. string.rep('0', 1000) .. '74p4004') == 0x7.4)
+end
+
+-- testing 'tonumber' for invalid formats
+
+local function f (...)
+ if select('#', ...) == 1 then
+ return (...)
+ else
+ return "***"
+ end
+end
+
+assert(f(tonumber('fFfa', 15)) == nil)
+assert(f(tonumber('099', 8)) == nil)
+assert(f(tonumber('1\0', 2)) == nil)
+assert(f(tonumber('', 8)) == nil)
+assert(f(tonumber(' ', 9)) == nil)
+assert(f(tonumber(' ', 9)) == nil)
+assert(f(tonumber('0xf', 10)) == nil)
+
+assert(f(tonumber('inf')) == nil)
+assert(f(tonumber(' INF ')) == nil)
+assert(f(tonumber('Nan')) == nil)
+assert(f(tonumber('nan')) == nil)
+
+assert(f(tonumber(' ')) == nil)
+assert(f(tonumber('')) == nil)
+assert(f(tonumber('1 a')) == nil)
+assert(f(tonumber('1 a', 2)) == nil)
+assert(f(tonumber('1\0')) == nil)
+assert(f(tonumber('1 \0')) == nil)
+assert(f(tonumber('1\0 ')) == nil)
+assert(f(tonumber('e1')) == nil)
+assert(f(tonumber('e 1')) == nil)
+assert(f(tonumber(' 3.4.5 ')) == nil)
+
+
+-- testing 'tonumber' for invalid hexadecimal formats
+
+assert(tonumber('0x') == nil)
+assert(tonumber('x') == nil)
+assert(tonumber('x3') == nil)
+assert(tonumber('0x3.3.3') == nil) -- two decimal points
+assert(tonumber('00x2') == nil)
+assert(tonumber('0x 2') == nil)
+assert(tonumber('0 x2') == nil)
+assert(tonumber('23x') == nil)
+assert(tonumber('- 0xaa') == nil)
+assert(tonumber('-0xaaP ') == nil) -- no exponent
+assert(tonumber('0x0.51p') == nil)
+assert(tonumber('0x5p+-2') == nil)
+
+
+-- testing hexadecimal numerals
+
+assert(0x10 == 16 and 0xfff == 2^12 - 1 and 0XFB == 251)
+assert(0x0p12 == 0 and 0x.0p-3 == 0)
+assert(0xFFFFFFFF == (1 << 32) - 1)
+assert(tonumber('+0x2') == 2)
+assert(tonumber('-0xaA') == -170)
+assert(tonumber('-0xffFFFfff') == -(1 << 32) + 1)
+
+-- possible confusion with decimal exponent
+assert(0E+1 == 0 and 0xE+1 == 15 and 0xe-1 == 13)
+
+
+-- floating hexas
+
+assert(tonumber(' 0x2.5 ') == 0x25/16)
+assert(tonumber(' -0x2.5 ') == -0x25/16)
+assert(tonumber(' +0x0.51p+8 ') == 0x51)
+assert(0x.FfffFFFF == 1 - '0x.00000001')
+assert('0xA.a' + 0 == 10 + 10/16)
+assert(0xa.aP4 == 0XAA)
+assert(0x4P-2 == 1)
+assert(0x1.1 == '0x1.' + '+0x.1')
+assert(0Xabcdef.0 == 0x.ABCDEFp+24)
+
+
+assert(1.1 == 1.+.1)
+assert(100.0 == 1E2 and .01 == 1e-2)
+assert(1111111111 - 1111111110 == 1000.00e-03)
+assert(1.1 == '1.'+'.1')
+assert(tonumber'1111111111' - tonumber'1111111110' ==
+ tonumber" +0.001e+3 \n\t")
+
+assert(0.1e-30 > 0.9E-31 and 0.9E30 < 0.1e31)
+
+assert(0.123456 > 0.123455)
+
+assert(tonumber('+1.23E18') == 1.23*10.0^18)
+
+-- testing order operators
+assert(not(1<1) and (1<2) and not(2<1))
+assert(not('a'<'a') and ('a'<'b') and not('b'<'a'))
+assert((1<=1) and (1<=2) and not(2<=1))
+assert(('a'<='a') and ('a'<='b') and not('b'<='a'))
+assert(not(1>1) and not(1>2) and (2>1))
+assert(not('a'>'a') and not('a'>'b') and ('b'>'a'))
+assert((1>=1) and not(1>=2) and (2>=1))
+assert(('a'>='a') and not('a'>='b') and ('b'>='a'))
+assert(1.3 < 1.4 and 1.3 <= 1.4 and not (1.3 < 1.3) and 1.3 <= 1.3)
+
+-- testing mod operator
+assert(eqT(-4 % 3, 2))
+assert(eqT(4 % -3, -2))
+assert(eqT(-4.0 % 3, 2.0))
+assert(eqT(4 % -3.0, -2.0))
+assert(math.pi - math.pi % 1 == 3)
+assert(math.pi - math.pi % 0.001 == 3.141)
+
+assert(eqT(minint % minint, 0))
+assert(eqT(maxint % maxint, 0))
+assert((minint + 1) % minint == minint + 1)
+assert((maxint - 1) % maxint == maxint - 1)
+assert(minint % maxint == maxint - 1)
+
+assert(minint % -1 == 0)
+assert(minint % -2 == 0)
+assert(maxint % -2 == -1)
+
+-- non-portable tests because Windows C library cannot compute
+-- fmod(1, huge) correctly
+if not _port then
+ local function anan (x) assert(isNaN(x)) end -- assert Not a Number
+ anan(0.0 % 0)
+ anan(1.3 % 0)
+ anan(math.huge % 1)
+ anan(math.huge % 1e30)
+ anan(-math.huge % 1e30)
+ anan(-math.huge % -1e30)
+ assert(1 % math.huge == 1)
+ assert(1e30 % math.huge == 1e30)
+ assert(1e30 % -math.huge == -math.huge)
+ assert(-1 % math.huge == math.huge)
+ assert(-1 % -math.huge == -1)
+end
+
+
+-- testing unsigned comparisons
+assert(math.ult(3, 4))
+assert(not math.ult(4, 4))
+assert(math.ult(-2, -1))
+assert(math.ult(2, -1))
+assert(not math.ult(-2, -2))
+assert(math.ult(maxint, minint))
+assert(not math.ult(minint, maxint))
+
+
+assert(eq(math.sin(-9.8)^2 + math.cos(-9.8)^2, 1))
+assert(eq(math.tan(math.pi/4), 1))
+assert(eq(math.sin(math.pi/2), 1) and eq(math.cos(math.pi/2), 0))
+assert(eq(math.atan(1), math.pi/4) and eq(math.acos(0), math.pi/2) and
+ eq(math.asin(1), math.pi/2))
+assert(eq(math.deg(math.pi/2), 90) and eq(math.rad(90), math.pi/2))
+assert(math.abs(-10.43) == 10.43)
+assert(eqT(math.abs(minint), minint))
+assert(eqT(math.abs(maxint), maxint))
+assert(eqT(math.abs(-maxint), maxint))
+assert(eq(math.atan(1,0), math.pi/2))
+assert(math.fmod(10,3) == 1)
+assert(eq(math.sqrt(10)^2, 10))
+assert(eq(math.log(2, 10), math.log(2)/math.log(10)))
+assert(eq(math.log(2, 2), 1))
+assert(eq(math.log(9, 3), 2))
+assert(eq(math.exp(0), 1))
+assert(eq(math.sin(10), math.sin(10%(2*math.pi))))
+
+
+assert(tonumber(' 1.3e-2 ') == 1.3e-2)
+assert(tonumber(' -1.00000000000001 ') == -1.00000000000001)
+
+-- testing constant limits
+-- 2^23 = 8388608
+assert(8388609 + -8388609 == 0)
+assert(8388608 + -8388608 == 0)
+assert(8388607 + -8388607 == 0)
+
+
+
+do -- testing floor & ceil
+ assert(eqT(math.floor(3.4), 3))
+ assert(eqT(math.ceil(3.4), 4))
+ assert(eqT(math.floor(-3.4), -4))
+ assert(eqT(math.ceil(-3.4), -3))
+ assert(eqT(math.floor(maxint), maxint))
+ assert(eqT(math.ceil(maxint), maxint))
+ assert(eqT(math.floor(minint), minint))
+ assert(eqT(math.floor(minint + 0.0), minint))
+ assert(eqT(math.ceil(minint), minint))
+ assert(eqT(math.ceil(minint + 0.0), minint))
+ assert(math.floor(1e50) == 1e50)
+ assert(math.ceil(1e50) == 1e50)
+ assert(math.floor(-1e50) == -1e50)
+ assert(math.ceil(-1e50) == -1e50)
+ for _, p in pairs{31,32,63,64} do
+ assert(math.floor(2^p) == 2^p)
+ assert(math.floor(2^p + 0.5) == 2^p)
+ assert(math.ceil(2^p) == 2^p)
+ assert(math.ceil(2^p - 0.5) == 2^p)
+ end
+ checkerror("number expected", math.floor, {})
+ checkerror("number expected", math.ceil, print)
+ assert(eqT(math.tointeger(minint), minint))
+ assert(eqT(math.tointeger(minint .. ""), minint))
+ assert(eqT(math.tointeger(maxint), maxint))
+ assert(eqT(math.tointeger(maxint .. ""), maxint))
+ assert(eqT(math.tointeger(minint + 0.0), minint))
+ assert(math.tointeger(0.0 - minint) == nil)
+ assert(math.tointeger(math.pi) == nil)
+ assert(math.tointeger(-math.pi) == nil)
+ assert(math.floor(math.huge) == math.huge)
+ assert(math.ceil(math.huge) == math.huge)
+ assert(math.tointeger(math.huge) == nil)
+ assert(math.floor(-math.huge) == -math.huge)
+ assert(math.ceil(-math.huge) == -math.huge)
+ assert(math.tointeger(-math.huge) == nil)
+ assert(math.tointeger("34.0") == 34)
+ assert(math.tointeger("34.3") == nil)
+ assert(math.tointeger({}) == nil)
+ assert(math.tointeger(0/0) == nil) -- NaN
+end
+
+
+-- testing fmod for integers
+for i = -6, 6 do
+ for j = -6, 6 do
+ if j ~= 0 then
+ local mi = math.fmod(i, j)
+ local mf = math.fmod(i + 0.0, j)
+ assert(mi == mf)
+ assert(math.type(mi) == 'integer' and math.type(mf) == 'float')
+ if (i >= 0 and j >= 0) or (i <= 0 and j <= 0) or mi == 0 then
+ assert(eqT(mi, i % j))
+ end
+ end
+ end
+end
+assert(eqT(math.fmod(minint, minint), 0))
+assert(eqT(math.fmod(maxint, maxint), 0))
+assert(eqT(math.fmod(minint + 1, minint), minint + 1))
+assert(eqT(math.fmod(maxint - 1, maxint), maxint - 1))
+
+checkerror("zero", math.fmod, 3, 0)
+
+
+do -- testing max/min
+ checkerror("value expected", math.max)
+ checkerror("value expected", math.min)
+ assert(eqT(math.max(3), 3))
+ assert(eqT(math.max(3, 5, 9, 1), 9))
+ assert(math.max(maxint, 10e60) == 10e60)
+ assert(eqT(math.max(minint, minint + 1), minint + 1))
+ assert(eqT(math.min(3), 3))
+ assert(eqT(math.min(3, 5, 9, 1), 1))
+ assert(math.min(3.2, 5.9, -9.2, 1.1) == -9.2)
+ assert(math.min(1.9, 1.7, 1.72) == 1.7)
+ assert(math.min(-10e60, minint) == -10e60)
+ assert(eqT(math.min(maxint, maxint - 1), maxint - 1))
+ assert(eqT(math.min(maxint - 2, maxint, maxint - 1), maxint - 2))
+end
+-- testing implicit convertions
+
+local a,b = '10', '20'
+assert(a*b == 200 and a+b == 30 and a-b == -10 and a/b == 0.5 and -b == -20)
+assert(a == '10' and b == '20')
+
+
+do
+ print("testing -0 and NaN")
+ local mz, z = -0.0, 0.0
+ assert(mz == z)
+ assert(1/mz < 0 and 0 < 1/z)
+ local a = {[mz] = 1}
+ assert(a[z] == 1 and a[mz] == 1)
+ a[z] = 2
+ assert(a[z] == 2 and a[mz] == 2)
+ local inf = math.huge * 2 + 1
+ mz, z = -1/inf, 1/inf
+ assert(mz == z)
+ assert(1/mz < 0 and 0 < 1/z)
+ local NaN = inf - inf
+ assert(NaN ~= NaN)
+ assert(not (NaN < NaN))
+ assert(not (NaN <= NaN))
+ assert(not (NaN > NaN))
+ assert(not (NaN >= NaN))
+ assert(not (0 < NaN) and not (NaN < 0))
+ local NaN1 = 0/0
+ assert(NaN ~= NaN1 and not (NaN <= NaN1) and not (NaN1 <= NaN))
+ local a = {}
+ assert(not pcall(rawset, a, NaN, 1))
+ assert(a[NaN] == undef)
+ a[1] = 1
+ assert(not pcall(rawset, a, NaN, 1))
+ assert(a[NaN] == undef)
+ -- strings with same binary representation as 0.0 (might create problems
+ -- for constant manipulation in the pre-compiler)
+ local a1, a2, a3, a4, a5 = 0, 0, "\0\0\0\0\0\0\0\0", 0, "\0\0\0\0\0\0\0\0"
+ assert(a1 == a2 and a2 == a4 and a1 ~= a3)
+ assert(a3 == a5)
+end
+
+
+print("testing 'math.random'")
+
+local random, max, min = math.random, math.max, math.min
+
+local function testnear (val, ref, tol)
+ return (math.abs(val - ref) < ref * tol)
+end
+
+
+-- low-level!! For the current implementation of random in Lua,
+-- the first call after seed 1007 should return 0x7a7040a5a323c9d6
+do
+ -- all computations assume at most 32-bit integers
+ local h = 0x7a7040a5 -- higher half
+ local l = 0xa323c9d6 -- lower half
+
+ math.randomseed(1007)
+ -- get the low 'intbits' of the 64-bit expected result
+ local res = (h << 32 | l) & ~(~0 << intbits)
+ assert(random(0) == res)
+
+ math.randomseed(1007, 0)
+ -- using lower bits to generate random floats; (the '% 2^32' converts
+ -- 32-bit integers to floats as unsigned)
+ local res
+ if floatbits <= 32 then
+ -- get all bits from the lower half
+ res = (l & ~(~0 << floatbits)) % 2^32
+ else
+ -- get 32 bits from the lower half and the rest from the higher half
+ res = ((h & ~(~0 << (floatbits - 32))) % 2^32) * 2^32 + (l % 2^32)
+ end
+ assert(random() * 2^floatbits == res)
+end
+
+math.randomseed(0, os.time())
+
+do -- test random for floats
+ local randbits = math.min(floatbits, 64) -- at most 64 random bits
+ local mult = 2^randbits -- to make random float into an integral
+ local counts = {} -- counts for bits
+ for i = 1, randbits do counts[i] = 0 end
+ local up = -math.huge
+ local low = math.huge
+ local rounds = 100 * randbits -- 100 times for each bit
+ local totalrounds = 0
+ ::doagain:: -- will repeat test until we get good statistics
+ for i = 0, rounds do
+ local t = random()
+ assert(0 <= t and t < 1)
+ up = max(up, t)
+ low = min(low, t)
+ assert(t * mult % 1 == 0) -- no extra bits
+ local bit = i % randbits -- bit to be tested
+ if (t * 2^bit) % 1 >= 0.5 then -- is bit set?
+ counts[bit + 1] = counts[bit + 1] + 1 -- increment its count
+ end
+ end
+ totalrounds = totalrounds + rounds
+ if not (eq(up, 1, 0.001) and eq(low, 0, 0.001)) then
+ goto doagain
+ end
+ -- all bit counts should be near 50%
+ local expected = (totalrounds / randbits / 2)
+ for i = 1, randbits do
+ if not testnear(counts[i], expected, 0.10) then
+ goto doagain
+ end
+ end
+ print(string.format("float random range in %d calls: [%f, %f]",
+ totalrounds, low, up))
+end
+
+
+do -- test random for full integers
+ local up = 0
+ local low = 0
+ local counts = {} -- counts for bits
+ for i = 1, intbits do counts[i] = 0 end
+ local rounds = 100 * intbits -- 100 times for each bit
+ local totalrounds = 0
+ ::doagain:: -- will repeat test until we get good statistics
+ for i = 0, rounds do
+ local t = random(0)
+ up = max(up, t)
+ low = min(low, t)
+ local bit = i % intbits -- bit to be tested
+ -- increment its count if it is set
+ counts[bit + 1] = counts[bit + 1] + ((t >> bit) & 1)
+ end
+ totalrounds = totalrounds + rounds
+ local lim = maxint >> 10
+ if not (maxint - up < lim and low - minint < lim) then
+ goto doagain
+ end
+ -- all bit counts should be near 50%
+ local expected = (totalrounds / intbits / 2)
+ for i = 1, intbits do
+ if not testnear(counts[i], expected, 0.10) then
+ goto doagain
+ end
+ end
+ print(string.format(
+ "integer random range in %d calls: [minint + %.0fppm, maxint - %.0fppm]",
+ totalrounds, (minint - low) / minint * 1e6,
+ (maxint - up) / maxint * 1e6))
+end
+
+do
+ -- test distribution for a dice
+ local count = {0, 0, 0, 0, 0, 0}
+ local rep = 200
+ local totalrep = 0
+ ::doagain::
+ for i = 1, rep * 6 do
+ local r = random(6)
+ count[r] = count[r] + 1
+ end
+ totalrep = totalrep + rep
+ for i = 1, 6 do
+ if not testnear(count[i], totalrep, 0.05) then
+ goto doagain
+ end
+ end
+end
+
+do
+ local function aux (x1, x2) -- test random for small intervals
+ local mark = {}; local count = 0 -- to check that all values appeared
+ while true do
+ local t = random(x1, x2)
+ assert(x1 <= t and t <= x2)
+ if not mark[t] then -- new value
+ mark[t] = true
+ count = count + 1
+ if count == x2 - x1 + 1 then -- all values appeared; OK
+ goto ok
+ end
+ end
+ end
+ ::ok::
+ end
+
+ aux(-10,0)
+ aux(1, 6)
+ aux(1, 2)
+ aux(1, 32)
+ aux(-10, 10)
+ aux(-10,-10) -- unit set
+ aux(minint, minint) -- unit set
+ aux(maxint, maxint) -- unit set
+ aux(minint, minint + 9)
+ aux(maxint - 3, maxint)
+end
+
+do
+ local function aux(p1, p2) -- test random for large intervals
+ local max = minint
+ local min = maxint
+ local n = 100
+ local mark = {}; local count = 0 -- to count how many different values
+ ::doagain::
+ for _ = 1, n do
+ local t = random(p1, p2)
+ if not mark[t] then -- new value
+ assert(p1 <= t and t <= p2)
+ max = math.max(max, t)
+ min = math.min(min, t)
+ mark[t] = true
+ count = count + 1
+ end
+ end
+ -- at least 80% of values are different
+ if not (count >= n * 0.8) then
+ goto doagain
+ end
+ -- min and max not too far from formal min and max
+ local diff = (p2 - p1) >> 4
+ if not (min < p1 + diff and max > p2 - diff) then
+ goto doagain
+ end
+ end
+ aux(0, maxint)
+ aux(1, maxint)
+ aux(minint, -1)
+ aux(minint // 2, maxint // 2)
+ aux(minint, maxint)
+ aux(minint + 1, maxint)
+ aux(minint, maxint - 1)
+ aux(0, 1 << (intbits - 5))
+end
+
+
+assert(not pcall(random, 1, 2, 3)) -- too many arguments
+
+-- empty interval
+assert(not pcall(random, minint + 1, minint))
+assert(not pcall(random, maxint, maxint - 1))
+assert(not pcall(random, maxint, minint))
+
+
+
+print('OK')
diff --git a/testes/nextvar.lua b/testes/nextvar.lua
new file mode 100644
index 0000000000..3ac3acd94e
--- /dev/null
+++ b/testes/nextvar.lua
@@ -0,0 +1,669 @@
+-- $Id: nextvar.lua,v 1.85 2018/06/19 12:24:19 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print('testing tables, next, and for')
+
+local function checkerror (msg, f, ...)
+ local s, err = pcall(f, ...)
+ assert(not s and string.find(err, msg))
+end
+
+
+local a = {}
+
+-- make sure table has lots of space in hash part
+for i=1,100 do a[i.."+"] = true end
+for i=1,100 do a[i.."+"] = undef end
+-- fill hash part with numeric indices testing size operator
+for i=1,100 do
+ a[i] = true
+ assert(#a == i)
+end
+
+-- testing ipairs
+local x = 0
+for k,v in ipairs{10,20,30;x=12} do
+ x = x + 1
+ assert(k == x and v == x * 10)
+end
+
+for _ in ipairs{x=12, y=24} do assert(nil) end
+
+-- test for 'false' x ipair
+x = false
+local i = 0
+for k,v in ipairs{true,false,true,false} do
+ i = i + 1
+ x = not x
+ assert(x == v)
+end
+assert(i == 4)
+
+-- iterator function is always the same
+assert(type(ipairs{}) == 'function' and ipairs{} == ipairs{})
+
+
+if not T then
+ (Message or print)
+ ('\n >>> testC not active: skipping tests for table sizes <<<\n')
+else --[
+-- testing table sizes
+
+local function log2 (x) return math.log(x, 2) end
+
+local function mp2 (n) -- minimum power of 2 >= n
+ local mp = 2^math.ceil(log2(n))
+ assert(n == 0 or (mp/2 < n and n <= mp))
+ return mp
+end
+
+local function fb (n)
+ local r, nn = T.int2fb(n)
+ assert(r < 256)
+ return nn
+end
+
+-- test fb function
+for a = 1, 10000 do -- all numbers up to 10^4
+ local n = fb(a)
+ assert(a <= n and n <= a*1.125)
+end
+local a = 1024 -- plus a few up to 2 ^30
+local lim = 2^30
+while a < lim do
+ local n = fb(a)
+ assert(a <= n and n <= a*1.125)
+ a = math.ceil(a*1.3)
+end
+
+
+local function check (t, na, nh)
+ local a, h = T.querytab(t)
+ if a ~= na or h ~= nh then
+ print(na, nh, a, h)
+ assert(nil)
+ end
+end
+
+
+-- testing C library sizes
+do
+ local s = 0
+ for _ in pairs(math) do s = s + 1 end
+ check(math, 0, mp2(s))
+end
+
+
+-- testing constructor sizes
+local lim = 40
+local s = 'return {'
+for i=1,lim do
+ s = s..i..','
+ local s = s
+ for k=0,lim do
+ local t = load(s..'}', '')()
+ assert(#t == i)
+ check(t, fb(i), mp2(k))
+ s = string.format('%sa%d=%d,', s, k, k)
+ end
+end
+
+
+-- tests with unknown number of elements
+local a = {}
+for i=1,lim do a[i] = i end -- build auxiliary table
+for k=0,lim do
+ local a = {table.unpack(a,1,k)}
+ assert(#a == k)
+ check(a, k, 0)
+ a = {1,2,3,table.unpack(a,1,k)}
+ check(a, k+3, 0)
+ assert(#a == k + 3)
+end
+
+
+-- testing tables dynamically built
+local lim = 130
+local a = {}; a[2] = 1; check(a, 0, 1)
+a = {}; a[0] = 1; check(a, 0, 1); a[2] = 1; check(a, 0, 2)
+a = {}; a[0] = 1; a[1] = 1; check(a, 1, 1)
+a = {}
+for i = 1,lim do
+ a[i] = 1
+ assert(#a == i)
+ check(a, mp2(i), 0)
+end
+
+a = {}
+for i = 1,lim do
+ a['a'..i] = 1
+ assert(#a == 0)
+ check(a, 0, mp2(i))
+end
+
+a = {}
+for i=1,16 do a[i] = i end
+check(a, 16, 0)
+do
+ for i=1,11 do a[i] = undef end
+ for i=30,50 do a[i] = true; a[i] = undef end -- force a rehash (?)
+ check(a, 0, 8) -- 5 elements in the table
+ a[10] = 1
+ for i=30,50 do a[i] = true; a[i] = undef end -- force a rehash (?)
+ check(a, 0, 8) -- only 6 elements in the table
+ for i=1,14 do a[i] = true; a[i] = undef end
+ for i=18,50 do a[i] = true; a[i] = undef end -- force a rehash (?)
+ check(a, 0, 4) -- only 2 elements ([15] and [16])
+end
+
+-- reverse filling
+for i=1,lim do
+ local a = {}
+ for i=i,1,-1 do a[i] = i end -- fill in reverse
+ check(a, mp2(i), 0)
+end
+
+-- size tests for vararg
+lim = 35
+function foo (n, ...)
+ local arg = {...}
+ check(arg, n, 0)
+ assert(select('#', ...) == n)
+ arg[n+1] = true
+ check(arg, mp2(n+1), 0)
+ arg.x = true
+ check(arg, mp2(n+1), 1)
+end
+local a = {}
+for i=1,lim do a[i] = true; foo(i, table.unpack(a)) end
+
+
+-- Table length with limit smaller than maximum value at array
+local a = {}
+for i = 1,64 do a[i] = true end -- make its array size 64
+for i = 1,64 do a[i] = nil end -- erase all elements
+assert(T.querytab(a) == 64) -- array part has 64 elements
+a[32] = true; a[48] = true; -- binary search will find these ones
+a[51] = true -- binary search will miss this one
+assert(#a == 48) -- this will set the limit
+assert(select(4, T.querytab(a)) == 48) -- this is the limit now
+a[50] = true -- this will set a new limit
+assert(select(4, T.querytab(a)) == 50) -- this is the limit now
+-- but the size is larger (and still inside the array part)
+assert(#a == 51)
+
+end --]
+
+
+-- test size operation on tables with nils
+assert(#{} == 0)
+assert(#{nil} == 0)
+assert(#{nil, nil} == 0)
+assert(#{nil, nil, nil} == 0)
+assert(#{nil, nil, nil, nil} == 0)
+assert(#{1, 2, 3, nil, nil} == 3)
+print'+'
+
+
+local nofind = {}
+
+a,b,c = 1,2,3
+a,b,c = nil
+
+
+-- next uses always the same iteraction function
+assert(next{} == next{})
+
+local function find (name)
+ local n,v
+ while 1 do
+ n,v = next(_G, n)
+ if not n then return nofind end
+ assert(_G[n] ~= undef)
+ if n == name then return v end
+ end
+end
+
+local function find1 (name)
+ for n,v in pairs(_G) do
+ if n==name then return v end
+ end
+ return nil -- not found
+end
+
+
+assert(print==find("print") and print == find1("print"))
+assert(_G["print"]==find("print"))
+assert(assert==find1("assert"))
+assert(nofind==find("return"))
+assert(not find1("return"))
+_G["ret" .. "urn"] = undef
+assert(nofind==find("return"))
+_G["xxx"] = 1
+assert(xxx==find("xxx"))
+
+-- invalid key to 'next'
+checkerror("invalid key", next, {10,20}, 3)
+
+-- both 'pairs' and 'ipairs' need an argument
+checkerror("bad argument", pairs)
+checkerror("bad argument", ipairs)
+
+print('+')
+
+a = {}
+for i=0,10000 do
+ if math.fmod(i,10) ~= 0 then
+ a['x'..i] = i
+ end
+end
+
+n = {n=0}
+for i,v in pairs(a) do
+ n.n = n.n+1
+ assert(i and v and a[i] == v)
+end
+assert(n.n == 9000)
+a = nil
+
+do -- clear global table
+ local a = {}
+ for n,v in pairs(_G) do a[n]=v end
+ for n,v in pairs(a) do
+ if not package.loaded[n] and type(v) ~= "function" and
+ not string.find(n, "^[%u_]") then
+ _G[n] = undef
+ end
+ collectgarbage()
+ end
+end
+
+
+--
+
+local function checknext (a)
+ local b = {}
+ do local k,v = next(a); while k do b[k] = v; k,v = next(a,k) end end
+ for k,v in pairs(b) do assert(a[k] == v) end
+ for k,v in pairs(a) do assert(b[k] == v) end
+end
+
+checknext{1,x=1,y=2,z=3}
+checknext{1,2,x=1,y=2,z=3}
+checknext{1,2,3,x=1,y=2,z=3}
+checknext{1,2,3,4,x=1,y=2,z=3}
+checknext{1,2,3,4,5,x=1,y=2,z=3}
+
+assert(#{} == 0)
+assert(#{[-1] = 2} == 0)
+for i=0,40 do
+ local a = {}
+ for j=1,i do a[j]=j end
+ assert(#a == i)
+end
+
+-- 'maxn' is now deprecated, but it is easily defined in Lua
+function table.maxn (t)
+ local max = 0
+ for k in pairs(t) do
+ max = (type(k) == 'number') and math.max(max, k) or max
+ end
+ return max
+end
+
+assert(table.maxn{} == 0)
+assert(table.maxn{["1000"] = true} == 0)
+assert(table.maxn{["1000"] = true, [24.5] = 3} == 24.5)
+assert(table.maxn{[1000] = true} == 1000)
+assert(table.maxn{[10] = true, [100*math.pi] = print} == 100*math.pi)
+
+table.maxn = nil
+
+-- int overflow
+a = {}
+for i=0,50 do a[2^i] = true end
+assert(a[#a])
+
+print('+')
+
+
+do -- testing 'next' with all kinds of keys
+ local a = {
+ [1] = 1, -- integer
+ [1.1] = 2, -- float
+ ['x'] = 3, -- short string
+ [string.rep('x', 1000)] = 4, -- long string
+ [print] = 5, -- C function
+ [checkerror] = 6, -- Lua function
+ [coroutine.running()] = 7, -- thread
+ [true] = 8, -- boolean
+ [io.stdin] = 9, -- userdata
+ [{}] = 10, -- table
+ }
+ local b = {}; for i = 1, 10 do b[i] = true end
+ for k, v in pairs(a) do
+ assert(b[v]); b[v] = undef
+ end
+ assert(next(b) == nil) -- 'b' now is empty
+end
+
+
+-- erasing values
+local t = {[{1}] = 1, [{2}] = 2, [string.rep("x ", 4)] = 3,
+ [100.3] = 4, [4] = 5}
+
+local n = 0
+for k, v in pairs( t ) do
+ n = n+1
+ assert(t[k] == v)
+ t[k] = undef
+ collectgarbage()
+ assert(t[k] == undef)
+end
+assert(n == 5)
+
+
+local function test (a)
+ assert(not pcall(table.insert, a, 2, 20));
+ table.insert(a, 10); table.insert(a, 2, 20);
+ table.insert(a, 1, -1); table.insert(a, 40);
+ table.insert(a, #a+1, 50)
+ table.insert(a, 2, -2)
+ assert(a[2] ~= undef)
+ assert(a["2"] == undef)
+ assert(not pcall(table.insert, a, 0, 20));
+ assert(not pcall(table.insert, a, #a + 2, 20));
+ assert(table.remove(a,1) == -1)
+ assert(table.remove(a,1) == -2)
+ assert(table.remove(a,1) == 10)
+ assert(table.remove(a,1) == 20)
+ assert(table.remove(a,1) == 40)
+ assert(table.remove(a,1) == 50)
+ assert(table.remove(a,1) == nil)
+ assert(table.remove(a) == nil)
+ assert(table.remove(a, #a) == nil)
+end
+
+a = {n=0, [-7] = "ban"}
+test(a)
+assert(a.n == 0 and a[-7] == "ban")
+
+a = {[-7] = "ban"};
+test(a)
+assert(a.n == nil and #a == 0 and a[-7] == "ban")
+
+a = {[-1] = "ban"}
+test(a)
+assert(#a == 0 and table.remove(a) == nil and a[-1] == "ban")
+
+a = {[0] = "ban"}
+assert(#a == 0 and table.remove(a) == "ban" and a[0] == undef)
+
+table.insert(a, 1, 10); table.insert(a, 1, 20); table.insert(a, 1, -1)
+assert(table.remove(a) == 10)
+assert(table.remove(a) == 20)
+assert(table.remove(a) == -1)
+assert(table.remove(a) == nil)
+
+a = {'c', 'd'}
+table.insert(a, 3, 'a')
+table.insert(a, 'b')
+assert(table.remove(a, 1) == 'c')
+assert(table.remove(a, 1) == 'd')
+assert(table.remove(a, 1) == 'a')
+assert(table.remove(a, 1) == 'b')
+assert(table.remove(a, 1) == nil)
+assert(#a == 0 and a.n == nil)
+
+a = {10,20,30,40}
+assert(table.remove(a, #a + 1) == nil)
+assert(not pcall(table.remove, a, 0))
+assert(a[#a] == 40)
+assert(table.remove(a, #a) == 40)
+assert(a[#a] == 30)
+assert(table.remove(a, 2) == 20)
+assert(a[#a] == 30 and #a == 2)
+
+do -- testing table library with metamethods
+ local function test (proxy, t)
+ for i = 1, 10 do
+ table.insert(proxy, 1, i)
+ end
+ assert(#proxy == 10 and #t == 10 and proxy[1] ~= undef)
+ for i = 1, 10 do
+ assert(t[i] == 11 - i)
+ end
+ table.sort(proxy)
+ for i = 1, 10 do
+ assert(t[i] == i and proxy[i] == i)
+ end
+ assert(table.concat(proxy, ",") == "1,2,3,4,5,6,7,8,9,10")
+ for i = 1, 8 do
+ assert(table.remove(proxy, 1) == i)
+ end
+ assert(#proxy == 2 and #t == 2)
+ local a, b, c = table.unpack(proxy)
+ assert(a == 9 and b == 10 and c == nil)
+ end
+
+ -- all virtual
+ local t = {}
+ local proxy = setmetatable({}, {
+ __len = function () return #t end,
+ __index = t,
+ __newindex = t,
+ })
+ test(proxy, t)
+
+ -- only __newindex
+ local count = 0
+ t = setmetatable({}, {
+ __newindex = function (t,k,v) count = count + 1; rawset(t,k,v) end})
+ test(t, t)
+ assert(count == 10) -- after first 10, all other sets are not new
+
+ -- no __newindex
+ t = setmetatable({}, {
+ __index = function (_,k) return k + 1 end,
+ __len = function (_) return 5 end})
+ assert(table.concat(t, ";") == "2;3;4;5;6")
+
+end
+
+
+if not T then
+ (Message or print)
+ ('\n >>> testC not active: skipping tests for table library on non-tables <<<\n')
+else --[
+ local debug = require'debug'
+ local tab = {10, 20, 30}
+ local mt = {}
+ local u = T.newuserdata(0)
+ checkerror("table expected", table.insert, u, 40)
+ checkerror("table expected", table.remove, u)
+ debug.setmetatable(u, mt)
+ checkerror("table expected", table.insert, u, 40)
+ checkerror("table expected", table.remove, u)
+ mt.__index = tab
+ checkerror("table expected", table.insert, u, 40)
+ checkerror("table expected", table.remove, u)
+ mt.__newindex = tab
+ checkerror("table expected", table.insert, u, 40)
+ checkerror("table expected", table.remove, u)
+ mt.__len = function () return #tab end
+ table.insert(u, 40)
+ assert(#u == 4 and #tab == 4 and u[4] == 40 and tab[4] == 40)
+ assert(table.remove(u) == 40)
+ table.insert(u, 1, 50)
+ assert(#u == 4 and #tab == 4 and u[4] == 30 and tab[1] == 50)
+
+ mt.__newindex = nil
+ mt.__len = nil
+ local tab2 = {}
+ local u2 = T.newuserdata(0)
+ debug.setmetatable(u2, {__newindex = function (_, k, v) tab2[k] = v end})
+ table.move(u, 1, 4, 1, u2)
+ assert(#tab2 == 4 and tab2[1] == tab[1] and tab2[4] == tab[4])
+
+end -- ]
+
+print('+')
+
+a = {}
+for i=1,1000 do
+ a[i] = i; a[i - 1] = undef
+end
+assert(next(a,nil) == 1000 and next(a,1000) == nil)
+
+assert(next({}) == nil)
+assert(next({}, nil) == nil)
+
+for a,b in pairs{} do error"not here" end
+for i=1,0 do error'not here' end
+for i=0,1,-1 do error'not here' end
+a = nil; for i=1,1 do assert(not a); a=1 end; assert(a)
+a = nil; for i=1,1,-1 do assert(not a); a=1 end; assert(a)
+
+do
+ print("testing floats in numeric for")
+ local a
+ -- integer count
+ a = 0; for i=1, 1, 1 do a=a+1 end; assert(a==1)
+ a = 0; for i=10000, 1e4, -1 do a=a+1 end; assert(a==1)
+ a = 0; for i=1, 0.99999, 1 do a=a+1 end; assert(a==0)
+ a = 0; for i=9999, 1e4, -1 do a=a+1 end; assert(a==0)
+ a = 0; for i=1, 0.99999, -1 do a=a+1 end; assert(a==1)
+
+ -- float count
+ a = 0; for i=0, 0.999999999, 0.1 do a=a+1 end; assert(a==10)
+ a = 0; for i=1.0, 1, 1 do a=a+1 end; assert(a==1)
+ a = 0; for i=-1.5, -1.5, 1 do a=a+1 end; assert(a==1)
+ a = 0; for i=1e6, 1e6, -1 do a=a+1 end; assert(a==1)
+ a = 0; for i=1.0, 0.99999, 1 do a=a+1 end; assert(a==0)
+ a = 0; for i=99999, 1e5, -1.0 do a=a+1 end; assert(a==0)
+ a = 0; for i=1.0, 0.99999, -1 do a=a+1 end; assert(a==1)
+end
+
+-- conversion
+a = 0; for i="10","1","-2" do a=a+1 end; assert(a==5)
+
+do -- checking types
+ local c
+ local function checkfloat (i)
+ assert(math.type(i) == "float")
+ c = c + 1
+ end
+
+ c = 0; for i = 1.0, 10 do checkfloat(i) end
+ assert(c == 10)
+
+ c = 0; for i = -1, -10, -1.0 do checkfloat(i) end
+ assert(c == 10)
+
+ local function checkint (i)
+ assert(math.type(i) == "integer")
+ c = c + 1
+ end
+
+ local m = math.maxinteger
+ c = 0; for i = m, m - 10, -1 do checkint(i) end
+ assert(c == 11)
+
+ c = 0; for i = 1, 10.9 do checkint(i) end
+ assert(c == 10)
+
+ c = 0; for i = 10, 0.001, -1 do checkint(i) end
+ assert(c == 10)
+
+ c = 0; for i = 1, "10.8" do checkint(i) end
+ assert(c == 10)
+
+ c = 0; for i = 9, "3.4", -1 do checkint(i) end
+ assert(c == 6)
+
+ c = 0; for i = 0, " -3.4 ", -1 do checkint(i) end
+ assert(c == 4)
+
+ c = 0; for i = 100, "96.3", -2 do checkint(i) end
+ assert(c == 2)
+
+ c = 0; for i = 1, math.huge do if i > 10 then break end; checkint(i) end
+ assert(c == 10)
+
+ c = 0; for i = -1, -math.huge, -1 do
+ if i < -10 then break end; checkint(i)
+ end
+ assert(c == 10)
+
+
+ for i = math.mininteger, -10e100 do assert(false) end
+ for i = math.maxinteger, 10e100, -1 do assert(false) end
+
+end
+
+collectgarbage()
+
+
+-- testing generic 'for'
+
+local function f (n, p)
+ local t = {}; for i=1,p do t[i] = i*10 end
+ return function (_,n)
+ if n > 0 then
+ n = n-1
+ return n, table.unpack(t)
+ end
+ end, nil, n
+end
+
+local x = 0
+for n,a,b,c,d in f(5,3) do
+ x = x+1
+ assert(a == 10 and b == 20 and c == 30 and d == nil)
+end
+assert(x == 5)
+
+
+
+-- testing __pairs and __ipairs metamethod
+a = {}
+do
+ local x,y,z = pairs(a)
+ assert(type(x) == 'function' and y == a and z == nil)
+end
+
+local function foo (e,i)
+ assert(e == a)
+ if i <= 10 then return i+1, i+2 end
+end
+
+local function foo1 (e,i)
+ i = i + 1
+ assert(e == a)
+ if i <= e.n then return i,a[i] end
+end
+
+setmetatable(a, {__pairs = function (x) return foo, x, 0 end})
+
+local i = 0
+for k,v in pairs(a) do
+ i = i + 1
+ assert(k == i and v == k+1)
+end
+
+a.n = 5
+a[3] = 30
+
+-- testing ipairs with metamethods
+a = {n=10}
+setmetatable(a, { __index = function (t,k)
+ if k <= t.n then return k * 10 end
+ end})
+i = 0
+for k,v in ipairs(a) do
+ i = i + 1
+ assert(k == i and v == i * 10)
+end
+assert(i == a.n)
+
+print"OK"
diff --git a/testes/pm.lua b/testes/pm.lua
new file mode 100644
index 0000000000..e517c8b668
--- /dev/null
+++ b/testes/pm.lua
@@ -0,0 +1,374 @@
+-- $Id: pm.lua,v 1.50 2018/03/12 14:19:36 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print('testing pattern matching')
+
+local function checkerror (msg, f, ...)
+ local s, err = pcall(f, ...)
+ assert(not s and string.find(err, msg))
+end
+
+
+function f(s, p)
+ local i,e = string.find(s, p)
+ if i then return string.sub(s, i, e) end
+end
+
+a,b = string.find('', '') -- empty patterns are tricky
+assert(a == 1 and b == 0);
+a,b = string.find('alo', '')
+assert(a == 1 and b == 0)
+a,b = string.find('a\0o a\0o a\0o', 'a', 1) -- first position
+assert(a == 1 and b == 1)
+a,b = string.find('a\0o a\0o a\0o', 'a\0o', 2) -- starts in the midle
+assert(a == 5 and b == 7)
+a,b = string.find('a\0o a\0o a\0o', 'a\0o', 9) -- starts in the midle
+assert(a == 9 and b == 11)
+a,b = string.find('a\0a\0a\0a\0\0ab', '\0ab', 2); -- finds at the end
+assert(a == 9 and b == 11);
+a,b = string.find('a\0a\0a\0a\0\0ab', 'b') -- last position
+assert(a == 11 and b == 11)
+assert(string.find('a\0a\0a\0a\0\0ab', 'b\0') == nil) -- check ending
+assert(string.find('', '\0') == nil)
+assert(string.find('alo123alo', '12') == 4)
+assert(string.find('alo123alo', '^12') == nil)
+
+assert(string.match("aaab", ".*b") == "aaab")
+assert(string.match("aaa", ".*a") == "aaa")
+assert(string.match("b", ".*b") == "b")
+
+assert(string.match("aaab", ".+b") == "aaab")
+assert(string.match("aaa", ".+a") == "aaa")
+assert(not string.match("b", ".+b"))
+
+assert(string.match("aaab", ".?b") == "ab")
+assert(string.match("aaa", ".?a") == "aa")
+assert(string.match("b", ".?b") == "b")
+
+assert(f('aloALO', '%l*') == 'alo')
+assert(f('aLo_ALO', '%a*') == 'aLo')
+
+assert(f(" \n\r*&\n\r xuxu \n\n", "%g%g%g+") == "xuxu")
+
+assert(f('aaab', 'a*') == 'aaa');
+assert(f('aaa', '^.*$') == 'aaa');
+assert(f('aaa', 'b*') == '');
+assert(f('aaa', 'ab*a') == 'aa')
+assert(f('aba', 'ab*a') == 'aba')
+assert(f('aaab', 'a+') == 'aaa')
+assert(f('aaa', '^.+$') == 'aaa')
+assert(f('aaa', 'b+') == nil)
+assert(f('aaa', 'ab+a') == nil)
+assert(f('aba', 'ab+a') == 'aba')
+assert(f('a$a', '.$') == 'a')
+assert(f('a$a', '.%$') == 'a$')
+assert(f('a$a', '.$.') == 'a$a')
+assert(f('a$a', '$$') == nil)
+assert(f('a$b', 'a$') == nil)
+assert(f('a$a', '$') == '')
+assert(f('', 'b*') == '')
+assert(f('aaa', 'bb*') == nil)
+assert(f('aaab', 'a-') == '')
+assert(f('aaa', '^.-$') == 'aaa')
+assert(f('aabaaabaaabaaaba', 'b.*b') == 'baaabaaabaaab')
+assert(f('aabaaabaaabaaaba', 'b.-b') == 'baaab')
+assert(f('alo xo', '.o$') == 'xo')
+assert(f(' \n isto assim', '%S%S*') == 'isto')
+assert(f(' \n isto assim', '%S*$') == 'assim')
+assert(f(' \n isto assim', '[a-z]*$') == 'assim')
+assert(f('um caracter ? extra', '[^%sa-z]') == '?')
+assert(f('', 'a?') == '')
+assert(f('', '?') == '')
+assert(f('bl', '?b?l?') == 'bl')
+assert(f(' bl', '?b?l?') == '')
+assert(f('aa', '^aa?a?a') == 'aa')
+assert(f(']]]b', '[^]]') == '')
+assert(f("0alo alo", "%x*") == "0a")
+assert(f("alo alo", "%C+") == "alo alo")
+print('+')
+
+
+function f1(s, p)
+ p = string.gsub(p, "%%([0-9])", function (s)
+ return "%" .. (tonumber(s)+1)
+ end)
+ p = string.gsub(p, "^(^?)", "%1()", 1)
+ p = string.gsub(p, "($?)$", "()%1", 1)
+ local t = {string.match(s, p)}
+ return string.sub(s, t[1], t[#t] - 1)
+end
+
+assert(f1('alo alx 123 b\0o b\0o', '(..*) %1') == "b\0o b\0o")
+assert(f1('axz123= 4= 4 34', '(.+)=(.*)=%2 %1') == '3= 4= 4 3')
+assert(f1('=======', '^(=*)=%1$') == '=======')
+assert(string.match('==========', '^([=]*)=%1$') == nil)
+
+local function range (i, j)
+ if i <= j then
+ return i, range(i+1, j)
+ end
+end
+
+local abc = string.char(range(0, 127)) .. string.char(range(128, 255));
+
+assert(string.len(abc) == 256)
+
+function strset (p)
+ local res = {s=''}
+ string.gsub(abc, p, function (c) res.s = res.s .. c end)
+ return res.s
+end;
+
+assert(string.len(strset('[\200-\210]')) == 11)
+
+assert(strset('[a-z]') == "abcdefghijklmnopqrstuvwxyz")
+assert(strset('[a-z%d]') == strset('[%da-uu-z]'))
+assert(strset('[a-]') == "-a")
+assert(strset('[^%W]') == strset('[%w]'))
+assert(strset('[]%%]') == '%]')
+assert(strset('[a%-z]') == '-az')
+assert(strset('[%^%[%-a%]%-b]') == '-[]^ab')
+assert(strset('%Z') == strset('[\1-\255]'))
+assert(strset('.') == strset('[\1-\255%z]'))
+print('+');
+
+assert(string.match("alo xyzK", "(%w+)K") == "xyz")
+assert(string.match("254 K", "(%d*)K") == "")
+assert(string.match("alo ", "(%w*)$") == "")
+assert(string.match("alo ", "(%w+)$") == nil)
+assert(string.find("(lo)", "%(") == 1)
+local a, b, c, d, e = string.match("lo alo", "^(((.).).* (%w*))$")
+assert(a == 'lo alo' and b == 'l' and c == '' and d == 'alo' and e == nil)
+a, b, c, d = string.match('0123456789', '(.+(.?)())')
+assert(a == '0123456789' and b == '' and c == 11 and d == nil)
+print('+')
+
+assert(string.gsub('lo lo', '', 'x') == 'xlo xlo')
+assert(string.gsub('alo lo ', ' +$', '') == 'alo lo') -- trim
+assert(string.gsub(' alo alo ', '^%s*(.-)%s*$', '%1') == 'alo alo') -- double trim
+assert(string.gsub('alo alo \n 123\n ', '%s+', ' ') == 'alo alo 123 ')
+t = "ab d"
+a, b = string.gsub(t, '(.)', '%1@')
+assert('@'..a == string.gsub(t, '', '@') and b == 5)
+a, b = string.gsub('abd', '(.)', '%0@', 2)
+assert(a == 'a@b@d' and b == 2)
+assert(string.gsub('alo alo', '()[al]', '%1') == '12o 56o')
+assert(string.gsub("abc=xyz", "(%w*)(%p)(%w+)", "%3%2%1-%0") ==
+ "xyz=abc-abc=xyz")
+assert(string.gsub("abc", "%w", "%1%0") == "aabbcc")
+assert(string.gsub("abc", "%w+", "%0%1") == "abcabc")
+assert(string.gsub('', '$', '\0') == '\0')
+assert(string.gsub('', '^', 'r') == 'r')
+assert(string.gsub('', '$', 'r') == 'r')
+print('+')
+
+
+do -- new (5.3.3) semantics for empty matches
+ assert(string.gsub("a b cd", " *", "-") == "-a-b-c-d-")
+
+ local res = ""
+ local sub = "a \nbc\t\td"
+ local i = 1
+ for p, e in string.gmatch(sub, "()%s*()") do
+ res = res .. string.sub(sub, i, p - 1) .. "-"
+ i = e
+ end
+ assert(res == "-a-b-c-d-")
+end
+
+
+assert(string.gsub("um (dois) tres (quatro)", "(%(%w+%))", string.upper) ==
+ "um (DOIS) tres (QUATRO)")
+
+do
+ local function setglobal (n,v) rawset(_G, n, v) end
+ string.gsub("a=roberto,roberto=a", "(%w+)=(%w%w*)", setglobal)
+ assert(_G.a=="roberto" and _G.roberto=="a")
+end
+
+function f(a,b) return string.gsub(a,'.',b) end
+assert(string.gsub("trocar tudo em |teste|b| |beleza|al|", "|([^|]*)|([^|]*)|", f) ==
+ "trocar tudo em bbbbb alalalalalal")
+
+local function dostring (s) return load(s, "")() or "" end
+assert(string.gsub("alo $a='x'$ novamente $return a$",
+ "$([^$]*)%$",
+ dostring) == "alo novamente x")
+
+x = string.gsub("$x=string.gsub('alo', '.', string.upper)$ assim vai para $return x$",
+ "$([^$]*)%$", dostring)
+assert(x == ' assim vai para ALO')
+
+t = {}
+s = 'a alo jose joao'
+r = string.gsub(s, '()(%w+)()', function (a,w,b)
+ assert(string.len(w) == b-a);
+ t[a] = b-a;
+ end)
+assert(s == r and t[1] == 1 and t[3] == 3 and t[7] == 4 and t[13] == 4)
+
+
+function isbalanced (s)
+ return string.find(string.gsub(s, "%b()", ""), "[()]") == nil
+end
+
+assert(isbalanced("(9 ((8))(\0) 7) \0\0 a b ()(c)() a"))
+assert(not isbalanced("(9 ((8) 7) a b (\0 c) a"))
+assert(string.gsub("alo 'oi' alo", "%b''", '"') == 'alo " alo')
+
+
+local t = {"apple", "orange", "lime"; n=0}
+assert(string.gsub("x and x and x", "x", function () t.n=t.n+1; return t[t.n] end)
+ == "apple and orange and lime")
+
+t = {n=0}
+string.gsub("first second word", "%w%w*", function (w) t.n=t.n+1; t[t.n] = w end)
+assert(t[1] == "first" and t[2] == "second" and t[3] == "word" and t.n == 3)
+
+t = {n=0}
+assert(string.gsub("first second word", "%w+",
+ function (w) t.n=t.n+1; t[t.n] = w end, 2) == "first second word")
+assert(t[1] == "first" and t[2] == "second" and t[3] == undef)
+
+checkerror("invalid replacement value %(a table%)",
+ string.gsub, "alo", ".", {a = {}})
+checkerror("invalid capture index %%2", string.gsub, "alo", ".", "%2")
+checkerror("invalid capture index %%0", string.gsub, "alo", "(%0)", "a")
+checkerror("invalid capture index %%1", string.gsub, "alo", "(%1)", "a")
+checkerror("invalid use of '%%'", string.gsub, "alo", ".", "%x")
+
+-- bug since 2.5 (C-stack overflow)
+do
+ local function f (size)
+ local s = string.rep("a", size)
+ local p = string.rep(".?", size)
+ return pcall(string.match, s, p)
+ end
+ local r, m = f(80)
+ assert(r and #m == 80)
+ r, m = f(200000)
+ assert(not r and string.find(m, "too complex"))
+end
+
+if not _soft then
+ print("big strings")
+ local a = string.rep('a', 300000)
+ assert(string.find(a, '^a*.?$'))
+ assert(not string.find(a, '^a*.?b$'))
+ assert(string.find(a, '^a-.?$'))
+
+ -- bug in 5.1.2
+ a = string.rep('a', 10000) .. string.rep('b', 10000)
+ assert(not pcall(string.gsub, a, 'b'))
+end
+
+-- recursive nest of gsubs
+function rev (s)
+ return string.gsub(s, "(.)(.+)", function (c,s1) return rev(s1)..c end)
+end
+
+local x = "abcdef"
+assert(rev(rev(x)) == x)
+
+
+-- gsub with tables
+assert(string.gsub("alo alo", ".", {}) == "alo alo")
+assert(string.gsub("alo alo", "(.)", {a="AA", l=""}) == "AAo AAo")
+assert(string.gsub("alo alo", "(.).", {a="AA", l="K"}) == "AAo AAo")
+assert(string.gsub("alo alo", "((.)(.?))", {al="AA", o=false}) == "AAo AAo")
+
+assert(string.gsub("alo alo", "().", {'x','yy','zzz'}) == "xyyzzz alo")
+
+t = {}; setmetatable(t, {__index = function (t,s) return string.upper(s) end})
+assert(string.gsub("a alo b hi", "%w%w+", t) == "a ALO b HI")
+
+
+-- tests for gmatch
+local a = 0
+for i in string.gmatch('abcde', '()') do assert(i == a+1); a=i end
+assert(a==6)
+
+t = {n=0}
+for w in string.gmatch("first second word", "%w+") do
+ t.n=t.n+1; t[t.n] = w
+end
+assert(t[1] == "first" and t[2] == "second" and t[3] == "word")
+
+t = {3, 6, 9}
+for i in string.gmatch ("xuxx uu ppar r", "()(.)%2") do
+ assert(i == table.remove(t, 1))
+end
+assert(#t == 0)
+
+t = {}
+for i,j in string.gmatch("13 14 10 = 11, 15= 16, 22=23", "(%d+)%s*=%s*(%d+)") do
+ t[tonumber(i)] = tonumber(j)
+end
+a = 0
+for k,v in pairs(t) do assert(k+1 == v+0); a=a+1 end
+assert(a == 3)
+
+
+-- tests for `%f' (`frontiers')
+
+assert(string.gsub("aaa aa a aaa a", "%f[%w]a", "x") == "xaa xa x xaa x")
+assert(string.gsub("[[]] [][] [[[[", "%f[[].", "x") == "x[]] x]x] x[[[")
+assert(string.gsub("01abc45de3", "%f[%d]", ".") == ".01abc.45de.3")
+assert(string.gsub("01abc45 de3x", "%f[%D]%w", ".") == "01.bc45 de3.")
+assert(string.gsub("function", "%f[\1-\255]%w", ".") == ".unction")
+assert(string.gsub("function", "%f[^\1-\255]", ".") == "function.")
+
+assert(string.find("a", "%f[a]") == 1)
+assert(string.find("a", "%f[^%z]") == 1)
+assert(string.find("a", "%f[^%l]") == 2)
+assert(string.find("aba", "%f[a%z]") == 3)
+assert(string.find("aba", "%f[%z]") == 4)
+assert(not string.find("aba", "%f[%l%z]"))
+assert(not string.find("aba", "%f[^%l%z]"))
+
+local i, e = string.find(" alo aalo allo", "%f[%S].-%f[%s].-%f[%S]")
+assert(i == 2 and e == 5)
+local k = string.match(" alo aalo allo", "%f[%S](.-%f[%s].-%f[%S])")
+assert(k == 'alo ')
+
+local a = {1, 5, 9, 14, 17,}
+for k in string.gmatch("alo alo th02 is 1hat", "()%f[%w%d]") do
+ assert(table.remove(a, 1) == k)
+end
+assert(#a == 0)
+
+
+-- malformed patterns
+local function malform (p, m)
+ m = m or "malformed"
+ local r, msg = pcall(string.find, "a", p)
+ assert(not r and string.find(msg, m))
+end
+
+malform("(.", "unfinished capture")
+malform(".)", "invalid pattern capture")
+malform("[a")
+malform("[]")
+malform("[^]")
+malform("[a%]")
+malform("[a%")
+malform("%b")
+malform("%ba")
+malform("%")
+malform("%f", "missing")
+
+-- \0 in patterns
+assert(string.match("ab\0\1\2c", "[\0-\2]+") == "\0\1\2")
+assert(string.match("ab\0\1\2c", "[\0-\0]+") == "\0")
+assert(string.find("b$a", "$\0?") == 2)
+assert(string.find("abc\0efg", "%\0") == 4)
+assert(string.match("abc\0efg\0\1e\1g", "%b\0\1") == "\0efg\0\1e\1")
+assert(string.match("abc\0\0\0", "%\0+") == "\0\0\0")
+assert(string.match("abc\0\0\0", "%\0%\0?") == "\0\0")
+
+-- magic char after \0
+assert(string.find("abc\0\0","\0.") == 4)
+assert(string.find("abcx\0\0abc\0abc","x\0\0abc\0a.") == 4)
+
+print('OK')
+
diff --git a/testes/sort.lua b/testes/sort.lua
new file mode 100644
index 0000000000..6eb9b70643
--- /dev/null
+++ b/testes/sort.lua
@@ -0,0 +1,310 @@
+-- $Id: sort.lua,v 1.39 2018/03/12 13:51:02 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print "testing (parts of) table library"
+
+print "testing unpack"
+
+local unpack = table.unpack
+
+local maxI = math.maxinteger
+local minI = math.mininteger
+
+
+local function checkerror (msg, f, ...)
+ local s, err = pcall(f, ...)
+ assert(not s and string.find(err, msg))
+end
+
+
+checkerror("wrong number of arguments", table.insert, {}, 2, 3, 4)
+
+local x,y,z,a,n
+a = {}; lim = _soft and 200 or 2000
+for i=1, lim do a[i]=i end
+assert(select(lim, unpack(a)) == lim and select('#', unpack(a)) == lim)
+x = unpack(a)
+assert(x == 1)
+x = {unpack(a)}
+assert(#x == lim and x[1] == 1 and x[lim] == lim)
+x = {unpack(a, lim-2)}
+assert(#x == 3 and x[1] == lim-2 and x[3] == lim)
+x = {unpack(a, 10, 6)}
+assert(next(x) == nil) -- no elements
+x = {unpack(a, 11, 10)}
+assert(next(x) == nil) -- no elements
+x,y = unpack(a, 10, 10)
+assert(x == 10 and y == nil)
+x,y,z = unpack(a, 10, 11)
+assert(x == 10 and y == 11 and z == nil)
+a,x = unpack{1}
+assert(a==1 and x==nil)
+a,x = unpack({1,2}, 1, 1)
+assert(a==1 and x==nil)
+
+do
+ local maxi = (1 << 31) - 1 -- maximum value for an int (usually)
+ local mini = -(1 << 31) -- minimum value for an int (usually)
+ checkerror("too many results", unpack, {}, 0, maxi)
+ checkerror("too many results", unpack, {}, 1, maxi)
+ checkerror("too many results", unpack, {}, 0, maxI)
+ checkerror("too many results", unpack, {}, 1, maxI)
+ checkerror("too many results", unpack, {}, mini, maxi)
+ checkerror("too many results", unpack, {}, -maxi, maxi)
+ checkerror("too many results", unpack, {}, minI, maxI)
+ unpack({}, maxi, 0)
+ unpack({}, maxi, 1)
+ unpack({}, maxI, minI)
+ pcall(unpack, {}, 1, maxi + 1)
+ local a, b = unpack({[maxi] = 20}, maxi, maxi)
+ assert(a == 20 and b == nil)
+ a, b = unpack({[maxi] = 20}, maxi - 1, maxi)
+ assert(a == nil and b == 20)
+ local t = {[maxI - 1] = 12, [maxI] = 23}
+ a, b = unpack(t, maxI - 1, maxI); assert(a == 12 and b == 23)
+ a, b = unpack(t, maxI, maxI); assert(a == 23 and b == nil)
+ a, b = unpack(t, maxI, maxI - 1); assert(a == nil and b == nil)
+ t = {[minI] = 12.3, [minI + 1] = 23.5}
+ a, b = unpack(t, minI, minI + 1); assert(a == 12.3 and b == 23.5)
+ a, b = unpack(t, minI, minI); assert(a == 12.3 and b == nil)
+ a, b = unpack(t, minI + 1, minI); assert(a == nil and b == nil)
+end
+
+do -- length is not an integer
+ local t = setmetatable({}, {__len = function () return 'abc' end})
+ assert(#t == 'abc')
+ checkerror("object length is not an integer", table.insert, t, 1)
+end
+
+print "testing pack"
+
+a = table.pack()
+assert(a[1] == undef and a.n == 0)
+
+a = table.pack(table)
+assert(a[1] == table and a.n == 1)
+
+a = table.pack(nil, nil, nil, nil)
+assert(a[1] == nil and a.n == 4)
+
+
+-- testing move
+do
+
+ checkerror("table expected", table.move, 1, 2, 3, 4)
+
+ local function eqT (a, b)
+ for k, v in pairs(a) do assert(b[k] == v) end
+ for k, v in pairs(b) do assert(a[k] == v) end
+ end
+
+ local a = table.move({10,20,30}, 1, 3, 2) -- move forward
+ eqT(a, {10,10,20,30})
+
+ -- move forward with overlap of 1
+ a = table.move({10, 20, 30}, 1, 3, 3)
+ eqT(a, {10, 20, 10, 20, 30})
+
+ -- moving to the same table (not being explicit about it)
+ a = {10, 20, 30, 40}
+ table.move(a, 1, 4, 2, a)
+ eqT(a, {10, 10, 20, 30, 40})
+
+ a = table.move({10,20,30}, 2, 3, 1) -- move backward
+ eqT(a, {20,30,30})
+
+ a = {} -- move to new table
+ assert(table.move({10,20,30}, 1, 3, 1, a) == a)
+ eqT(a, {10,20,30})
+
+ a = {}
+ assert(table.move({10,20,30}, 1, 0, 3, a) == a) -- empty move (no move)
+ eqT(a, {})
+
+ a = table.move({10,20,30}, 1, 10, 1) -- move to the same place
+ eqT(a, {10,20,30})
+
+ -- moving on the fringes
+ a = table.move({[maxI - 2] = 1, [maxI - 1] = 2, [maxI] = 3},
+ maxI - 2, maxI, -10, {})
+ eqT(a, {[-10] = 1, [-9] = 2, [-8] = 3})
+
+ a = table.move({[minI] = 1, [minI + 1] = 2, [minI + 2] = 3},
+ minI, minI + 2, -10, {})
+ eqT(a, {[-10] = 1, [-9] = 2, [-8] = 3})
+
+ a = table.move({45}, 1, 1, maxI)
+ eqT(a, {45, [maxI] = 45})
+
+ a = table.move({[maxI] = 100}, maxI, maxI, minI)
+ eqT(a, {[minI] = 100, [maxI] = 100})
+
+ a = table.move({[minI] = 100}, minI, minI, maxI)
+ eqT(a, {[minI] = 100, [maxI] = 100})
+
+ a = setmetatable({}, {
+ __index = function (_,k) return k * 10 end,
+ __newindex = error})
+ local b = table.move(a, 1, 10, 3, {})
+ eqT(a, {})
+ eqT(b, {nil,nil,10,20,30,40,50,60,70,80,90,100})
+
+ b = setmetatable({""}, {
+ __index = error,
+ __newindex = function (t,k,v)
+ t[1] = string.format("%s(%d,%d)", t[1], k, v)
+ end})
+ table.move(a, 10, 13, 3, b)
+ assert(b[1] == "(3,100)(4,110)(5,120)(6,130)")
+ local stat, msg = pcall(table.move, b, 10, 13, 3, b)
+ assert(not stat and msg == b)
+end
+
+do
+ -- for very long moves, just check initial accesses and interrupt
+ -- move with an error
+ local function checkmove (f, e, t, x, y)
+ local pos1, pos2
+ local a = setmetatable({}, {
+ __index = function (_,k) pos1 = k end,
+ __newindex = function (_,k) pos2 = k; error() end, })
+ local st, msg = pcall(table.move, a, f, e, t)
+ assert(not st and not msg and pos1 == x and pos2 == y)
+ end
+ checkmove(1, maxI, 0, 1, 0)
+ checkmove(0, maxI - 1, 1, maxI - 1, maxI)
+ checkmove(minI, -2, -5, -2, maxI - 6)
+ checkmove(minI + 1, -1, -2, -1, maxI - 3)
+ checkmove(minI, -2, 0, minI, 0) -- non overlapping
+ checkmove(minI + 1, -1, 1, minI + 1, 1) -- non overlapping
+end
+
+checkerror("too many", table.move, {}, 0, maxI, 1)
+checkerror("too many", table.move, {}, -1, maxI - 1, 1)
+checkerror("too many", table.move, {}, minI, -1, 1)
+checkerror("too many", table.move, {}, minI, maxI, 1)
+checkerror("wrap around", table.move, {}, 1, maxI, 2)
+checkerror("wrap around", table.move, {}, 1, 2, maxI)
+checkerror("wrap around", table.move, {}, minI, -2, 2)
+
+
+print"testing sort"
+
+
+-- strange lengths
+local a = setmetatable({}, {__len = function () return -1 end})
+assert(#a == -1)
+table.sort(a, error) -- should not compare anything
+a = setmetatable({}, {__len = function () return maxI end})
+checkerror("too big", table.sort, a)
+
+-- test checks for invalid order functions
+local function check (t)
+ local function f(a, b) assert(a and b); return true end
+ checkerror("invalid order function", table.sort, t, f)
+end
+
+check{1,2,3,4}
+check{1,2,3,4,5}
+check{1,2,3,4,5,6}
+
+
+function check (a, f)
+ f = f or function (x,y) return x 'alo\0alo\0')
+assert('alo' < 'alo\0')
+assert('alo\0' > 'alo')
+assert('\0' < '\1')
+assert('\0\0' < '\0\1')
+assert('\1\0a\0a' <= '\1\0a\0a')
+assert(not ('\1\0a\0b' <= '\1\0a\0a'))
+assert('\0\0\0' < '\0\0\0\0')
+assert(not('\0\0\0\0' < '\0\0\0'))
+assert('\0\0\0' <= '\0\0\0\0')
+assert(not('\0\0\0\0' <= '\0\0\0'))
+assert('\0\0\0' <= '\0\0\0')
+assert('\0\0\0' >= '\0\0\0')
+assert(not ('\0\0b' < '\0\0a\0'))
+
+-- testing string.sub
+assert(string.sub("123456789",2,4) == "234")
+assert(string.sub("123456789",7) == "789")
+assert(string.sub("123456789",7,6) == "")
+assert(string.sub("123456789",7,7) == "7")
+assert(string.sub("123456789",0,0) == "")
+assert(string.sub("123456789",-10,10) == "123456789")
+assert(string.sub("123456789",1,9) == "123456789")
+assert(string.sub("123456789",-10,-20) == "")
+assert(string.sub("123456789",-1) == "9")
+assert(string.sub("123456789",-4) == "6789")
+assert(string.sub("123456789",-6, -4) == "456")
+assert(string.sub("123456789", mini, -4) == "123456")
+assert(string.sub("123456789", mini, maxi) == "123456789")
+assert(string.sub("123456789", mini, mini) == "")
+assert(string.sub("\000123456789",3,5) == "234")
+assert(("\000123456789"):sub(8) == "789")
+
+-- testing string.find
+assert(string.find("123456789", "345") == 3)
+a,b = string.find("123456789", "345")
+assert(string.sub("123456789", a, b) == "345")
+assert(string.find("1234567890123456789", "345", 3) == 3)
+assert(string.find("1234567890123456789", "345", 4) == 13)
+assert(string.find("1234567890123456789", "346", 4) == nil)
+assert(string.find("1234567890123456789", ".45", -9) == 13)
+assert(string.find("abcdefg", "\0", 5, 1) == nil)
+assert(string.find("", "") == 1)
+assert(string.find("", "", 1) == 1)
+assert(not string.find("", "", 2))
+assert(string.find('', 'aaa', 1) == nil)
+assert(('alo(.)alo'):find('(.)', 1, 1) == 4)
+
+assert(string.len("") == 0)
+assert(string.len("\0\0\0") == 3)
+assert(string.len("1234567890") == 10)
+
+assert(#"" == 0)
+assert(#"\0\0\0" == 3)
+assert(#"1234567890" == 10)
+
+-- testing string.byte/string.char
+assert(string.byte("a") == 97)
+assert(string.byte("\xe4") > 127)
+assert(string.byte(string.char(255)) == 255)
+assert(string.byte(string.char(0)) == 0)
+assert(string.byte("\0") == 0)
+assert(string.byte("\0\0alo\0x", -1) == string.byte('x'))
+assert(string.byte("ba", 2) == 97)
+assert(string.byte("\n\n", 2, -1) == 10)
+assert(string.byte("\n\n", 2, 2) == 10)
+assert(string.byte("") == nil)
+assert(string.byte("hi", -3) == nil)
+assert(string.byte("hi", 3) == nil)
+assert(string.byte("hi", 9, 10) == nil)
+assert(string.byte("hi", 2, 1) == nil)
+assert(string.char() == "")
+assert(string.char(0, 255, 0) == "\0\255\0")
+assert(string.char(0, string.byte("\xe4"), 0) == "\0\xe4\0")
+assert(string.char(string.byte("\xe4l\0u", 1, -1)) == "\xe4l\0u")
+assert(string.char(string.byte("\xe4l\0u", 1, 0)) == "")
+assert(string.char(string.byte("\xe4l\0u", -10, 100)) == "\xe4l\0u")
+
+assert(string.upper("ab\0c") == "AB\0C")
+assert(string.lower("\0ABCc%$") == "\0abcc%$")
+assert(string.rep('teste', 0) == '')
+assert(string.rep('ts\00t', 2) == 'ts\0tts\000t')
+assert(string.rep('', 10) == '')
+
+if string.packsize("i") == 4 then
+ -- result length would be 2^31 (int overflow)
+ checkerror("too large", string.rep, 'aa', (1 << 30))
+ checkerror("too large", string.rep, 'a', (1 << 30), ',')
+end
+
+-- repetitions with separator
+assert(string.rep('teste', 0, 'xuxu') == '')
+assert(string.rep('teste', 1, 'xuxu') == 'teste')
+assert(string.rep('\1\0\1', 2, '\0\0') == '\1\0\1\0\0\1\0\1')
+assert(string.rep('', 10, '.') == string.rep('.', 9))
+assert(not pcall(string.rep, "aa", maxi // 2 + 10))
+assert(not pcall(string.rep, "", maxi // 2 + 10, "aa"))
+
+assert(string.reverse"" == "")
+assert(string.reverse"\0\1\2\3" == "\3\2\1\0")
+assert(string.reverse"\0001234" == "4321\0")
+
+for i=0,30 do assert(string.len(string.rep('a', i)) == i) end
+
+assert(type(tostring(nil)) == 'string')
+assert(type(tostring(12)) == 'string')
+assert(string.find(tostring{}, 'table:'))
+assert(string.find(tostring(print), 'function:'))
+assert(#tostring('\0') == 1)
+assert(tostring(true) == "true")
+assert(tostring(false) == "false")
+assert(tostring(-1203) == "-1203")
+assert(tostring(1203.125) == "1203.125")
+assert(tostring(-0.5) == "-0.5")
+assert(tostring(-32767) == "-32767")
+if math.tointeger(2147483647) then -- no overflow? (32 bits)
+ assert(tostring(-2147483647) == "-2147483647")
+end
+if math.tointeger(4611686018427387904) then -- no overflow? (64 bits)
+ assert(tostring(4611686018427387904) == "4611686018427387904")
+ assert(tostring(-4611686018427387904) == "-4611686018427387904")
+end
+
+if tostring(0.0) == "0.0" then -- "standard" coercion float->string
+ assert('' .. 12 == '12' and 12.0 .. '' == '12.0')
+ assert(tostring(-1203 + 0.0) == "-1203.0")
+else -- compatible coercion
+ assert(tostring(0.0) == "0")
+ assert('' .. 12 == '12' and 12.0 .. '' == '12')
+ assert(tostring(-1203 + 0.0) == "-1203")
+end
+
+
+x = '"lo"\n\\'
+assert(string.format('%q%s', x, x) == '"\\"lo\\"\\\n\\\\""lo"\n\\')
+assert(string.format('%q', "\0") == [["\0"]])
+assert(load(string.format('return %q', x))() == x)
+x = "\0\1\0023\5\0009"
+assert(load(string.format('return %q', x))() == x)
+assert(string.format("\0%c\0%c%x\0", string.byte("\xe4"), string.byte("b"), 140) ==
+ "\0\xe4\0b8c\0")
+assert(string.format('') == "")
+assert(string.format("%c",34)..string.format("%c",48)..string.format("%c",90)..string.format("%c",100) ==
+ string.format("%c%c%c%c", 34, 48, 90, 100))
+assert(string.format("%s\0 is not \0%s", 'not be', 'be') == 'not be\0 is not \0be')
+assert(string.format("%%%d %010d", 10, 23) == "%10 0000000023")
+assert(tonumber(string.format("%f", 10.3)) == 10.3)
+x = string.format('"%-50s"', 'a')
+assert(#x == 52)
+assert(string.sub(x, 1, 4) == '"a ')
+
+assert(string.format("-%.20s.20s", string.rep("%", 2000)) ==
+ "-"..string.rep("%", 20)..".20s")
+assert(string.format('"-%20s.20s"', string.rep("%", 2000)) ==
+ string.format("%q", "-"..string.rep("%", 2000)..".20s"))
+
+do
+ local function checkQ (v)
+ local s = string.format("%q", v)
+ local nv = load("return " .. s)()
+ assert(v == nv and math.type(v) == math.type(nv))
+ end
+ checkQ("\0\0\1\255\u{234}")
+ checkQ(math.maxinteger)
+ checkQ(math.mininteger)
+ checkQ(math.pi)
+ checkQ(0.1)
+ checkQ(true)
+ checkQ(nil)
+ checkQ(false)
+ checkQ(math.huge)
+ checkQ(-math.huge)
+ assert(string.format("%q", 0/0) == "(0/0)") -- NaN
+ checkerror("no literal", string.format, "%q", {})
+end
+
+assert(string.format("\0%s\0", "\0\0\1") == "\0\0\0\1\0")
+checkerror("contains zeros", string.format, "%10s", "\0")
+
+-- format x tostring
+assert(string.format("%s %s", nil, true) == "nil true")
+assert(string.format("%s %.4s", false, true) == "false true")
+assert(string.format("%.3s %.3s", false, true) == "fal tru")
+local m = setmetatable({}, {__tostring = function () return "hello" end,
+ __name = "hi"})
+assert(string.format("%s %.10s", m, m) == "hello hello")
+getmetatable(m).__tostring = nil -- will use '__name' from now on
+assert(string.format("%.4s", m) == "hi: ")
+
+getmetatable(m).__tostring = function () return {} end
+checkerror("'__tostring' must return a string", tostring, m)
+
+
+assert(string.format("%x", 0.0) == "0")
+assert(string.format("%02x", 0.0) == "00")
+assert(string.format("%08X", 0xFFFFFFFF) == "FFFFFFFF")
+assert(string.format("%+08d", 31501) == "+0031501")
+assert(string.format("%+08d", -30927) == "-0030927")
+
+
+do -- longest number that can be formatted
+ local i = 1
+ local j = 10000
+ while i + 1 < j do -- binary search for maximum finite float
+ local m = (i + j) // 2
+ if 10^m < math.huge then i = m else j = m end
+ end
+ assert(10^i < math.huge and 10^j == math.huge)
+ local s = string.format('%.99f', -(10^i))
+ assert(string.len(s) >= i + 101)
+ assert(tonumber(s) == -(10^i))
+end
+
+
+-- testing large numbers for format
+do -- assume at least 32 bits
+ local max, min = 0x7fffffff, -0x80000000 -- "large" for 32 bits
+ assert(string.sub(string.format("%8x", -1), -8) == "ffffffff")
+ assert(string.format("%x", max) == "7fffffff")
+ assert(string.sub(string.format("%x", min), -8) == "80000000")
+ assert(string.format("%d", max) == "2147483647")
+ assert(string.format("%d", min) == "-2147483648")
+ assert(string.format("%u", 0xffffffff) == "4294967295")
+ assert(string.format("%o", 0xABCD) == "125715")
+
+ max, min = 0x7fffffffffffffff, -0x8000000000000000
+ if max > 2.0^53 then -- only for 64 bits
+ assert(string.format("%x", (2^52 | 0) - 1) == "fffffffffffff")
+ assert(string.format("0x%8X", 0x8f000003) == "0x8F000003")
+ assert(string.format("%d", 2^53) == "9007199254740992")
+ assert(string.format("%i", -2^53) == "-9007199254740992")
+ assert(string.format("%x", max) == "7fffffffffffffff")
+ assert(string.format("%x", min) == "8000000000000000")
+ assert(string.format("%d", max) == "9223372036854775807")
+ assert(string.format("%d", min) == "-9223372036854775808")
+ assert(string.format("%u", ~(-1 << 64)) == "18446744073709551615")
+ assert(tostring(1234567890123) == '1234567890123')
+ end
+end
+
+
+do print("testing 'format %a %A'")
+ local function matchhexa (n)
+ local s = string.format("%a", n)
+ -- result matches ISO C requirements
+ assert(string.find(s, "^%-?0x[1-9a-f]%.?[0-9a-f]*p[-+]?%d+$"))
+ assert(tonumber(s) == n) -- and has full precision
+ s = string.format("%A", n)
+ assert(string.find(s, "^%-?0X[1-9A-F]%.?[0-9A-F]*P[-+]?%d+$"))
+ assert(tonumber(s) == n)
+ end
+ for _, n in ipairs{0.1, -0.1, 1/3, -1/3, 1e30, -1e30,
+ -45/247, 1, -1, 2, -2, 3e-20, -3e-20} do
+ matchhexa(n)
+ end
+
+ assert(string.find(string.format("%A", 0.0), "^0X0%.?0?P%+?0$"))
+ assert(string.find(string.format("%a", -0.0), "^%-0x0%.?0?p%+?0$"))
+
+ if not _port then -- test inf, -inf, NaN, and -0.0
+ assert(string.find(string.format("%a", 1/0), "^inf"))
+ assert(string.find(string.format("%A", -1/0), "^%-INF"))
+ assert(string.find(string.format("%a", 0/0), "^%-?nan"))
+ assert(string.find(string.format("%a", -0.0), "^%-0x0"))
+ end
+
+ if not pcall(string.format, "%.3a", 0) then
+ (Message or print)("\n >>> modifiers for format '%a' not available <<<\n")
+ else
+ assert(string.find(string.format("%+.2A", 12), "^%+0X%x%.%x0P%+?%d$"))
+ assert(string.find(string.format("%.4A", -12), "^%-0X%x%.%x000P%+?%d$"))
+ end
+end
+
+
+-- errors in format
+
+local function check (fmt, msg)
+ checkerror(msg, string.format, fmt, 10)
+end
+
+local aux = string.rep('0', 600)
+check("%100.3d", "too long")
+check("%1"..aux..".3d", "too long")
+check("%1.100d", "too long")
+check("%10.1"..aux.."004d", "too long")
+check("%t", "invalid option")
+check("%"..aux.."d", "repeated flags")
+check("%d %d", "no value")
+
+
+assert(load("return 1\n--comment without ending EOL")() == 1)
+
+
+checkerror("table expected", table.concat, 3)
+assert(table.concat{} == "")
+assert(table.concat({}, 'x') == "")
+assert(table.concat({'\0', '\0\1', '\0\1\2'}, '.\0.') == "\0.\0.\0\1.\0.\0\1\2")
+local a = {}; for i=1,300 do a[i] = "xuxu" end
+assert(table.concat(a, "123").."123" == string.rep("xuxu123", 300))
+assert(table.concat(a, "b", 20, 20) == "xuxu")
+assert(table.concat(a, "", 20, 21) == "xuxuxuxu")
+assert(table.concat(a, "x", 22, 21) == "")
+assert(table.concat(a, "3", 299) == "xuxu3xuxu")
+assert(table.concat({}, "x", maxi, maxi - 1) == "")
+assert(table.concat({}, "x", mini + 1, mini) == "")
+assert(table.concat({}, "x", maxi, mini) == "")
+assert(table.concat({[maxi] = "alo"}, "x", maxi, maxi) == "alo")
+assert(table.concat({[maxi] = "alo", [maxi - 1] = "y"}, "-", maxi - 1, maxi)
+ == "y-alo")
+
+assert(not pcall(table.concat, {"a", "b", {}}))
+
+a = {"a","b","c"}
+assert(table.concat(a, ",", 1, 0) == "")
+assert(table.concat(a, ",", 1, 1) == "a")
+assert(table.concat(a, ",", 1, 2) == "a,b")
+assert(table.concat(a, ",", 2) == "b,c")
+assert(table.concat(a, ",", 3) == "c")
+assert(table.concat(a, ",", 4) == "")
+
+if not _port then
+
+ local locales = { "ptb", "pt_BR.iso88591", "ISO-8859-1" }
+ local function trylocale (w)
+ for i = 1, #locales do
+ if os.setlocale(locales[i], w) then
+ print(string.format("'%s' locale set to '%s'", w, locales[i]))
+ return locales[i]
+ end
+ end
+ print(string.format("'%s' locale not found", w))
+ return false
+ end
+
+ if trylocale("collate") then
+ assert("alo" < "lo" and "lo" < "amo")
+ end
+
+ if trylocale("ctype") then
+ assert(string.gsub("", "%a", "x") == "xxxxx")
+ assert(string.gsub("", "%l", "x") == "xx")
+ assert(string.gsub("", "%u", "x") == "xx")
+ assert(string.upper"{xuxu}o" == "{XUXU}O")
+ end
+
+ os.setlocale("C")
+ assert(os.setlocale() == 'C')
+ assert(os.setlocale(nil, "numeric") == 'C')
+
+end
+
+
+-- bug in Lua 5.3.2
+-- 'gmatch' iterator does not work across coroutines
+do
+ local f = string.gmatch("1 2 3 4 5", "%d+")
+ assert(f() == "1")
+ co = coroutine.wrap(f)
+ assert(co() == "2")
+end
+
+print('OK')
+
diff --git a/testes/tpack.lua b/testes/tpack.lua
new file mode 100644
index 0000000000..0e639cc5b4
--- /dev/null
+++ b/testes/tpack.lua
@@ -0,0 +1,324 @@
+-- $Id: tpack.lua,v 1.14 2018/06/04 14:26:32 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+local pack = string.pack
+local packsize = string.packsize
+local unpack = string.unpack
+
+print "testing pack/unpack"
+
+-- maximum size for integers
+local NB = 16
+
+local sizeshort = packsize("h")
+local sizeint = packsize("i")
+local sizelong = packsize("l")
+local sizesize_t = packsize("T")
+local sizeLI = packsize("j")
+local sizefloat = packsize("f")
+local sizedouble = packsize("d")
+local sizenumber = packsize("n")
+local little = (pack("i2", 1) == "\1\0")
+local align = packsize("!xXi16")
+
+assert(1 <= sizeshort and sizeshort <= sizeint and sizeint <= sizelong and
+ sizefloat <= sizedouble)
+
+print("platform:")
+print(string.format(
+ "\tshort %d, int %d, long %d, size_t %d, float %d, double %d,\n\z
+ \tlua Integer %d, lua Number %d",
+ sizeshort, sizeint, sizelong, sizesize_t, sizefloat, sizedouble,
+ sizeLI, sizenumber))
+print("\t" .. (little and "little" or "big") .. " endian")
+print("\talignment: " .. align)
+
+
+-- check errors in arguments
+function checkerror (msg, f, ...)
+ local status, err = pcall(f, ...)
+ -- print(status, err, msg)
+ assert(not status and string.find(err, msg))
+end
+
+-- minimum behavior for integer formats
+assert(unpack("B", pack("B", 0xff)) == 0xff)
+assert(unpack("b", pack("b", 0x7f)) == 0x7f)
+assert(unpack("b", pack("b", -0x80)) == -0x80)
+
+assert(unpack("H", pack("H", 0xffff)) == 0xffff)
+assert(unpack("h", pack("h", 0x7fff)) == 0x7fff)
+assert(unpack("h", pack("h", -0x8000)) == -0x8000)
+
+assert(unpack("L", pack("L", 0xffffffff)) == 0xffffffff)
+assert(unpack("l", pack("l", 0x7fffffff)) == 0x7fffffff)
+assert(unpack("l", pack("l", -0x80000000)) == -0x80000000)
+
+
+for i = 1, NB do
+ -- small numbers with signal extension ("\xFF...")
+ local s = string.rep("\xff", i)
+ assert(pack("i" .. i, -1) == s)
+ assert(packsize("i" .. i) == #s)
+ assert(unpack("i" .. i, s) == -1)
+
+ -- small unsigned number ("\0...\xAA")
+ s = "\xAA" .. string.rep("\0", i - 1)
+ assert(pack("I" .. i, 0xAA) == s:reverse())
+ assert(unpack(">I" .. i, s:reverse()) == 0xAA)
+end
+
+do
+ local lnum = 0x13121110090807060504030201
+ local s = pack("i" .. i, ("\xFF"):rep(i - sizeLI) .. s:reverse()) == -lnum)
+ assert(unpack("i" .. i, "\1" .. ("\x00"):rep(i - 1))
+ end
+end
+
+for i = 1, sizeLI do
+ local lstr = "\1\2\3\4\5\6\7\8\9\10\11\12\13"
+ local lnum = 0x13121110090807060504030201
+ local n = lnum & (~(-1 << (i * 8)))
+ local s = string.sub(lstr, 1, i)
+ assert(pack("i" .. i, n) == s:reverse())
+ assert(unpack(">i" .. i, s:reverse()) == n)
+end
+
+-- sign extension
+do
+ local u = 0xf0
+ for i = 1, sizeLI - 1 do
+ assert(unpack("I"..i, "\xf0"..("\xff"):rep(i - 1)) == u)
+ u = u * 256 + 0xff
+ end
+end
+
+-- mixed endianness
+do
+ assert(pack(">i2 i2", "\10\0\0\20")
+ assert(a == 10 and b == 20)
+ assert(pack("=i4", 2001) == pack("i4", 2001))
+end
+
+print("testing invalid formats")
+
+checkerror("out of limits", pack, "i0", 0)
+checkerror("out of limits", pack, "i" .. NB + 1, 0)
+checkerror("out of limits", pack, "!" .. NB + 1, 0)
+checkerror("%(17%) out of limits %[1,16%]", pack, "Xi" .. NB + 1)
+checkerror("invalid format option 'r'", pack, "i3r", 0)
+checkerror("16%-byte integer", unpack, "i16", string.rep('\3', 16))
+checkerror("not power of 2", pack, "!4i3", 0);
+checkerror("missing size", pack, "c", "")
+checkerror("variable%-length format", packsize, "s")
+checkerror("variable%-length format", packsize, "z")
+
+-- overflow in option size (error will be in digit after limit)
+checkerror("invalid format", packsize, "c1" .. string.rep("0", 40))
+
+if packsize("i") == 4 then
+ -- result would be 2^31 (2^3 repetitions of 2^28 strings)
+ local s = string.rep("c268435456", 2^3)
+ checkerror("too large", packsize, s)
+ -- one less is OK
+ s = string.rep("c268435456", 2^3 - 1) .. "c268435455"
+ assert(packsize(s) == 0x7fffffff)
+end
+
+-- overflow in packing
+for i = 1, sizeLI - 1 do
+ local umax = (1 << (i * 8)) - 1
+ local max = umax >> 1
+ local min = ~max
+ checkerror("overflow", pack, "I" .. i, umax + 1)
+
+ checkerror("overflow", pack, ">i" .. i, umax)
+ checkerror("overflow", pack, ">i" .. i, max + 1)
+ checkerror("overflow", pack, "i" .. i, pack(">i" .. i, max)) == max)
+ assert(unpack("I" .. i, pack(">I" .. i, umax)) == umax)
+end
+
+-- Lua integer size
+assert(unpack(">j", pack(">j", math.maxinteger)) == math.maxinteger)
+assert(unpack("f", 24))
+end
+
+print "testing pack/unpack of floating-point numbers"
+
+for _, n in ipairs{0, -1.1, 1.9, 1/0, -1/0, 1e20, -1e20, 0.1, 2000.7} do
+ assert(unpack("n", pack("n", n)) == n)
+ assert(unpack("n", pack(">n", n)) == n)
+ assert(pack("f", n):reverse())
+ assert(pack(">d", n) == pack("f", pack(">f", n)) == n)
+ assert(unpack("d", pack(">d", n)) == n)
+end
+
+print "testing pack/unpack of strings"
+do
+ local s = string.rep("abc", 1000)
+ assert(pack("zB", s, 247) == s .. "\0\xF7")
+ local s1, b = unpack("zB", s .. "\0\xF9")
+ assert(b == 249 and s1 == s)
+ s1 = pack("s", s)
+ assert(unpack("s", s1) == s)
+
+ checkerror("does not fit", pack, "s1", s)
+
+ checkerror("contains zeros", pack, "z", "alo\0");
+
+ checkerror("unfinished string", unpack, "zc10000000", "alo")
+
+ for i = 2, NB do
+ local s1 = pack("s" .. i, s)
+ assert(unpack("s" .. i, s1) == s and #s1 == #s + i)
+ end
+end
+
+do
+ local x = pack("s", "alo")
+ checkerror("too short", unpack, "s", x:sub(1, -2))
+ checkerror("too short", unpack, "c5", "abcd")
+ checkerror("out of limits", pack, "s100", "alo")
+end
+
+do
+ assert(pack("c0", "") == "")
+ assert(packsize("c0") == 0)
+ assert(unpack("c0", "") == "")
+ assert(pack("!4 c6", "abcdef") == "abcdef")
+ assert(pack("c3", "123") == "123")
+ assert(pack("c0", "") == "")
+ assert(pack("c8", "123456") == "123456\0\0")
+ assert(pack("c88", "") == string.rep("\0", 88))
+ assert(pack("c188", "ab") == "ab" .. string.rep("\0", 188 - 2))
+ local a, b, c = unpack("!4 z c3", "abcdefghi\0xyz")
+ assert(a == "abcdefghi" and b == "xyz" and c == 14)
+ checkerror("longer than", pack, "c3", "1234")
+end
+
+
+-- testing multiple types and sequence
+do
+ local x = pack("!8 b Xh i4 i8 c1 Xi8", -12, 100, 200, "\xEC")
+ assert(#x == packsize(">!8 b Xh i4 i8 c1 Xi8"))
+ assert(x == "\xf4" .. "\0\0\0" ..
+ "\0\0\0\100" ..
+ "\0\0\0\0\0\0\0\xC8" ..
+ "\xEC" .. "\0\0\0\0\0\0\0")
+ local a, b, c, d, pos = unpack(">!8 c1 Xh i4 i8 b Xi8 XI XH", x)
+ assert(a == "\xF4" and b == 100 and c == 200 and d == -20 and (pos - 1) == #x)
+
+ x = pack(">!4 c3 c4 c2 z i4 c5 c2 Xi4",
+ "abc", "abcd", "xz", "hello", 5, "world", "xy")
+ assert(x == "abcabcdxzhello\0\0\0\0\0\5worldxy\0")
+ local a, b, c, d, e, f, g, pos = unpack(">!4 c3 c4 c2 z i4 c5 c2 Xh Xi4", x)
+ assert(a == "abc" and b == "abcd" and c == "xz" and d == "hello" and
+ e == 5 and f == "world" and g == "xy" and (pos - 1) % 4 == 0)
+
+ x = pack(" b b Xd b Xb x", 1, 2, 3)
+ assert(packsize(" b b Xd b Xb x") == 4)
+ assert(x == "\1\2\3\0")
+ a, b, c, pos = unpack("bbXdb", x)
+ assert(a == 1 and b == 2 and c == 3 and pos == #x)
+
+ -- only alignment
+ assert(packsize("!8 xXi8") == 8)
+ local pos = unpack("!8 xXi8", "0123456701234567"); assert(pos == 9)
+ assert(packsize("!8 xXi2") == 2)
+ local pos = unpack("!8 xXi2", "0123456701234567"); assert(pos == 3)
+ assert(packsize("!2 xXi2") == 2)
+ local pos = unpack("!2 xXi2", "0123456701234567"); assert(pos == 3)
+ assert(packsize("!2 xXi8") == 2)
+ local pos = unpack("!2 xXi8", "0123456701234567"); assert(pos == 3)
+ assert(packsize("!16 xXi16") == 16)
+ local pos = unpack("!16 xXi16", "0123456701234567"); assert(pos == 17)
+
+ checkerror("invalid next option", pack, "X")
+ checkerror("invalid next option", unpack, "XXi", "")
+ checkerror("invalid next option", unpack, "X i", "")
+ checkerror("invalid next option", pack, "Xc1")
+end
+
+do -- testing initial position
+ local x = pack("i4i4i4i4", 1, 2, 3, 4)
+ for pos = 1, 16, 4 do
+ local i, p = unpack("i4", x, pos)
+ assert(i == pos//4 + 1 and p == pos + 4)
+ end
+
+ -- with alignment
+ for pos = 0, 12 do -- will always round position to power of 2
+ local i, p = unpack("!4 i4", x, pos + 1)
+ assert(i == (pos + 3)//4 + 1 and p == i*4 + 1)
+ end
+
+ -- negative indices
+ local i, p = unpack("!4 i4", x, -4)
+ assert(i == 4 and p == 17)
+ local i, p = unpack("!4 i4", x, -7)
+ assert(i == 4 and p == 17)
+ local i, p = unpack("!4 i4", x, -#x)
+ assert(i == 1 and p == 5)
+
+ -- limits
+ for i = 1, #x + 1 do
+ assert(unpack("c0", x, i) == "")
+ end
+ checkerror("out of string", unpack, "c0", x, 0)
+ checkerror("out of string", unpack, "c0", x, #x + 2)
+ checkerror("out of string", unpack, "c0", x, -(#x + 1))
+
+end
+
+print "OK"
+
diff --git a/testes/utf8.lua b/testes/utf8.lua
new file mode 100644
index 0000000000..ebc190b75d
--- /dev/null
+++ b/testes/utf8.lua
@@ -0,0 +1,210 @@
+-- $Id: utf8.lua,v 1.12 2016/11/07 13:11:28 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print "testing UTF-8 library"
+
+local utf8 = require'utf8'
+
+
+local function checkerror (msg, f, ...)
+ local s, err = pcall(f, ...)
+ assert(not s and string.find(err, msg))
+end
+
+
+local function len (s)
+ return #string.gsub(s, "[\x80-\xBF]", "")
+end
+
+
+local justone = "^" .. utf8.charpattern .. "$"
+
+-- 't' is the list of codepoints of 's'
+local function checksyntax (s, t)
+ local ts = {"return '"}
+ for i = 1, #t do ts[i + 1] = string.format("\\u{%x}", t[i]) end
+ ts[#t + 2] = "'"
+ ts = table.concat(ts)
+ assert(assert(load(ts))() == s)
+end
+
+assert(utf8.offset("alo", 5) == nil)
+assert(utf8.offset("alo", -4) == nil)
+
+-- 't' is the list of codepoints of 's'
+local function check (s, t)
+ local l = utf8.len(s)
+ assert(#t == l and len(s) == l)
+ assert(utf8.char(table.unpack(t)) == s)
+
+ assert(utf8.offset(s, 0) == 1)
+
+ checksyntax(s, t)
+
+ local t1 = {utf8.codepoint(s, 1, -1)}
+ assert(#t == #t1)
+ for i = 1, #t do assert(t[i] == t1[i]) end
+
+ for i = 1, l do
+ local pi = utf8.offset(s, i) -- position of i-th char
+ local pi1 = utf8.offset(s, 2, pi) -- position of next char
+ assert(string.find(string.sub(s, pi, pi1 - 1), justone))
+ assert(utf8.offset(s, -1, pi1) == pi)
+ assert(utf8.offset(s, i - l - 1) == pi)
+ assert(pi1 - pi == #utf8.char(utf8.codepoint(s, pi)))
+ for j = pi, pi1 - 1 do
+ assert(utf8.offset(s, 0, j) == pi)
+ end
+ for j = pi + 1, pi1 - 1 do
+ assert(not utf8.len(s, j))
+ end
+ assert(utf8.len(s, pi, pi) == 1)
+ assert(utf8.len(s, pi, pi1 - 1) == 1)
+ assert(utf8.len(s, pi) == l - i + 1)
+ assert(utf8.len(s, pi1) == l - i)
+ assert(utf8.len(s, 1, pi) == i)
+ end
+
+ local i = 0
+ for p, c in utf8.codes(s) do
+ i = i + 1
+ assert(c == t[i] and p == utf8.offset(s, i))
+ assert(utf8.codepoint(s, p) == c)
+ end
+ assert(i == #t)
+
+ i = 0
+ for p, c in utf8.codes(s) do
+ i = i + 1
+ assert(c == t[i] and p == utf8.offset(s, i))
+ end
+ assert(i == #t)
+
+ i = 0
+ for c in string.gmatch(s, utf8.charpattern) do
+ i = i + 1
+ assert(c == utf8.char(t[i]))
+ end
+ assert(i == #t)
+
+ for i = 1, l do
+ assert(utf8.offset(s, i) == utf8.offset(s, i - l - 1, #s + 1))
+ end
+
+end
+
+
+do -- error indication in utf8.len
+ local function check (s, p)
+ local a, b = utf8.len(s)
+ assert(not a and b == p)
+ end
+ check("abc\xE3def", 4)
+ check("汉字\x80", #("汉字") + 1)
+ check("\xF4\x9F\xBF", 1)
+ check("\xF4\x9F\xBF\xBF", 1)
+end
+
+-- error in utf8.codes
+checkerror("invalid UTF%-8 code",
+ function ()
+ local s = "ab\xff"
+ for c in utf8.codes(s) do assert(c) end
+ end)
+
+
+-- error in initial position for offset
+checkerror("position out of range", utf8.offset, "abc", 1, 5)
+checkerror("position out of range", utf8.offset, "abc", 1, -4)
+checkerror("position out of range", utf8.offset, "", 1, 2)
+checkerror("position out of range", utf8.offset, "", 1, -1)
+checkerror("continuation byte", utf8.offset, "𦧺", 1, 2)
+checkerror("continuation byte", utf8.offset, "𦧺", 1, 2)
+checkerror("continuation byte", utf8.offset, "\x80", 1)
+
+
+
+local s = "hello World"
+local t = {string.byte(s, 1, -1)}
+for i = 1, utf8.len(s) do assert(t[i] == string.byte(s, i)) end
+check(s, t)
+
+check("汉字/漢字", {27721, 23383, 47, 28450, 23383,})
+
+do
+ local s = "áéí\128"
+ local t = {utf8.codepoint(s,1,#s - 1)}
+ assert(#t == 3 and t[1] == 225 and t[2] == 233 and t[3] == 237)
+ checkerror("invalid UTF%-8 code", utf8.codepoint, s, 1, #s)
+ checkerror("out of range", utf8.codepoint, s, #s + 1)
+ t = {utf8.codepoint(s, 4, 3)}
+ assert(#t == 0)
+ checkerror("out of range", utf8.codepoint, s, -(#s + 1), 1)
+ checkerror("out of range", utf8.codepoint, s, 1, #s + 1)
+end
+
+assert(utf8.char() == "")
+assert(utf8.char(97, 98, 99) == "abc")
+
+assert(utf8.codepoint(utf8.char(0x10FFFF)) == 0x10FFFF)
+
+checkerror("value out of range", utf8.char, 0x10FFFF + 1)
+
+local function invalid (s)
+ checkerror("invalid UTF%-8 code", utf8.codepoint, s)
+ assert(not utf8.len(s))
+end
+
+-- UTF-8 representation for 0x11ffff (value out of valid range)
+invalid("\xF4\x9F\xBF\xBF")
+
+-- overlong sequences
+invalid("\xC0\x80") -- zero
+invalid("\xC1\xBF") -- 0x7F (should be coded in 1 byte)
+invalid("\xE0\x9F\xBF") -- 0x7FF (should be coded in 2 bytes)
+invalid("\xF0\x8F\xBF\xBF") -- 0xFFFF (should be coded in 3 bytes)
+
+
+-- invalid bytes
+invalid("\x80") -- continuation byte
+invalid("\xBF") -- continuation byte
+invalid("\xFE") -- invalid byte
+invalid("\xFF") -- invalid byte
+
+
+-- empty string
+check("", {})
+
+-- minimum and maximum values for each sequence size
+s = "\0 \x7F\z
+ \xC2\x80 \xDF\xBF\z
+ \xE0\xA0\x80 \xEF\xBF\xBF\z
+ \xF0\x90\x80\x80 \xF4\x8F\xBF\xBF"
+s = string.gsub(s, " ", "")
+check(s, {0,0x7F, 0x80,0x7FF, 0x800,0xFFFF, 0x10000,0x10FFFF})
+
+x = "日本語a-4\0éó"
+check(x, {26085, 26412, 35486, 97, 45, 52, 0, 233, 243})
+
+
+-- Supplementary Characters
+check("𣲷𠜎𠱓𡁻𠵼ab𠺢",
+ {0x23CB7, 0x2070E, 0x20C53, 0x2107B, 0x20D7C, 0x61, 0x62, 0x20EA2,})
+
+check("𨳊𩶘𦧺𨳒𥄫𤓓\xF4\x8F\xBF\xBF",
+ {0x28CCA, 0x29D98, 0x269FA, 0x28CD2, 0x2512B, 0x244D3, 0x10ffff})
+
+
+local i = 0
+for p, c in string.gmatch(x, "()(" .. utf8.charpattern .. ")") do
+ i = i + 1
+ assert(utf8.offset(x, i) == p)
+ assert(utf8.len(x, p) == utf8.len(x) - i + 1)
+ assert(utf8.len(c) == 1)
+ for j = 1, #c - 1 do
+ assert(utf8.offset(x, 0, p + j - 1) == p)
+ end
+end
+
+print'ok'
+
diff --git a/testes/vararg.lua b/testes/vararg.lua
new file mode 100644
index 0000000000..4d5ce4abe8
--- /dev/null
+++ b/testes/vararg.lua
@@ -0,0 +1,151 @@
+-- $Id: vararg.lua,v 1.29 2018/03/12 14:19:36 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print('testing vararg')
+
+function f(a, ...)
+ local x = {n = select('#', ...), ...}
+ for i = 1, x.n do assert(a[i] == x[i]) end
+ return x.n
+end
+
+function c12 (...)
+ assert(arg == _G.arg) -- no local 'arg'
+ local x = {...}; x.n = #x
+ local res = (x.n==2 and x[1] == 1 and x[2] == 2)
+ if res then res = 55 end
+ return res, 2
+end
+
+function vararg (...) return {n = select('#', ...), ...} end
+
+local call = function (f, args) return f(table.unpack(args, 1, args.n)) end
+
+assert(f() == 0)
+assert(f({1,2,3}, 1, 2, 3) == 3)
+assert(f({"alo", nil, 45, f, nil}, "alo", nil, 45, f, nil) == 5)
+
+assert(vararg().n == 0)
+assert(vararg(nil, nil).n == 2)
+
+assert(c12(1,2)==55)
+a,b = assert(call(c12, {1,2}))
+assert(a == 55 and b == 2)
+a = call(c12, {1,2;n=2})
+assert(a == 55 and b == 2)
+a = call(c12, {1,2;n=1})
+assert(not a)
+assert(c12(1,2,3) == false)
+local a = vararg(call(next, {_G,nil;n=2}))
+local b,c = next(_G)
+assert(a[1] == b and a[2] == c and a.n == 2)
+a = vararg(call(call, {c12, {1,2}}))
+assert(a.n == 2 and a[1] == 55 and a[2] == 2)
+a = call(print, {'+'})
+assert(a == nil)
+
+local t = {1, 10}
+function t:f (...) local arg = {...}; return self[...]+#arg end
+assert(t:f(1,4) == 3 and t:f(2) == 11)
+print('+')
+
+lim = 20
+local i, a = 1, {}
+while i <= lim do a[i] = i+0.3; i=i+1 end
+
+function f(a, b, c, d, ...)
+ local more = {...}
+ assert(a == 1.3 and more[1] == 5.3 and
+ more[lim-4] == lim+0.3 and not more[lim-3])
+end
+
+function g(a,b,c)
+ assert(a == 1.3 and b == 2.3 and c == 3.3)
+end
+
+call(f, a)
+call(g, a)
+
+a = {}
+i = 1
+while i <= lim do a[i] = i; i=i+1 end
+assert(call(math.max, a) == lim)
+
+print("+")
+
+
+-- new-style varargs
+
+function oneless (a, ...) return ... end
+
+function f (n, a, ...)
+ local b
+ assert(arg == _G.arg) -- no local 'arg'
+ if n == 0 then
+ local b, c, d = ...
+ return a, b, c, d, oneless(oneless(oneless(...)))
+ else
+ n, b, a = n-1, ..., a
+ assert(b == ...)
+ return f(n, a, ...)
+ end
+end
+
+a,b,c,d,e = assert(f(10,5,4,3,2,1))
+assert(a==5 and b==4 and c==3 and d==2 and e==1)
+
+a,b,c,d,e = f(4)
+assert(a==nil and b==nil and c==nil and d==nil and e==nil)
+
+
+-- varargs for main chunks
+f = load[[ return {...} ]]
+x = f(2,3)
+assert(x[1] == 2 and x[2] == 3 and x[3] == undef)
+
+
+f = load[[
+ local x = {...}
+ for i=1,select('#', ...) do assert(x[i] == select(i, ...)) end
+ assert(x[select('#', ...)+1] == undef)
+ return true
+]]
+
+assert(f("a", "b", nil, {}, assert))
+assert(f())
+
+a = {select(3, table.unpack{10,20,30,40})}
+assert(#a == 2 and a[1] == 30 and a[2] == 40)
+a = {select(1)}
+assert(next(a) == nil)
+a = {select(-1, 3, 5, 7)}
+assert(a[1] == 7 and a[2] == undef)
+a = {select(-2, 3, 5, 7)}
+assert(a[1] == 5 and a[2] == 7 and a[3] == undef)
+pcall(select, 10000)
+pcall(select, -10000)
+
+
+-- bug in 5.2.2
+
+function f(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10,
+p11, p12, p13, p14, p15, p16, p17, p18, p19, p20,
+p21, p22, p23, p24, p25, p26, p27, p28, p29, p30,
+p31, p32, p33, p34, p35, p36, p37, p38, p39, p40,
+p41, p42, p43, p44, p45, p46, p48, p49, p50, ...)
+ local a1,a2,a3,a4,a5,a6,a7
+ local a8,a9,a10,a11,a12,a13,a14
+end
+
+-- assertion fail here
+f()
+
+-- missing arguments in tail call
+do
+ local function f(a,b,c) return c, b end
+ local function g() return f(1,2) end
+ local a, b = g()
+ assert(a == nil and b == 2)
+end
+print('OK')
+
diff --git a/testes/verybig.lua b/testes/verybig.lua
new file mode 100644
index 0000000000..5b29dea7ae
--- /dev/null
+++ b/testes/verybig.lua
@@ -0,0 +1,152 @@
+-- $Id: verybig.lua,v 1.27 2018/03/09 14:23:48 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+print "testing RK"
+
+-- testing opcodes with RK arguments larger than K limit
+local function foo ()
+ local dummy = {
+ -- fill first 256 entries in table of constants
+ 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, 251, 252, 253, 254, 255, 256,
+ }
+ assert(24.5 + 0.6 == 25.1)
+ local t = {foo = function (self, x) return x + self.x end, x = 10}
+ t.t = t
+ assert(t:foo(1.5) == 11.5)
+ assert(t.t:foo(0.5) == 10.5) -- bug in 5.2 alpha
+ assert(24.3 == 24.3)
+ assert((function () return t.x end)() == 10)
+end
+
+
+foo()
+foo = nil
+
+if _soft then return 10 end
+
+print "testing large programs (>64k)"
+
+-- template to create a very big test file
+prog = [[$
+
+local a,b
+
+b = {$1$
+ b30009 = 65534,
+ b30010 = 65535,
+ b30011 = 65536,
+ b30012 = 65537,
+ b30013 = 16777214,
+ b30014 = 16777215,
+ b30015 = 16777216,
+ b30016 = 16777217,
+ b30017 = 0x7fffff,
+ b30018 = -0x7fffff,
+ b30019 = 0x1ffffff,
+ b30020 = -0x1ffffd,
+ b30021 = -65534,
+ b30022 = -65535,
+ b30023 = -65536,
+ b30024 = -0xffffff,
+ b30025 = 15012.5,
+ $2$
+};
+
+assert(b.a50008 == 25004 and b["a11"] == -5.5)
+assert(b.a33007 == -16503.5 and b.a50009 == -25004.5)
+assert(b["b"..30024] == -0xffffff)
+
+function b:xxx (a,b) return a+b end
+assert(b:xxx(10, 12) == 22) -- pushself with non-constant index
+b["xxx"] = undef
+
+s = 0; n=0
+for a,b in pairs(b) do s=s+b; n=n+1 end
+-- with 32-bit floats, exact value of 's' depends on summation order
+assert(81800000.0 < s and s < 81860000 and n == 70001)
+
+a = nil; b = nil
+print'+'
+
+function f(x) b=x end
+
+a = f{$3$} or 10
+
+assert(a==10)
+assert(b[1] == "a10" and b[2] == 5 and b[#b-1] == "a50009")
+
+
+function xxxx (x) return b[x] end
+
+assert(xxxx(3) == "a11")
+
+a = nil; b=nil
+xxxx = nil
+
+return 10
+
+]]
+
+-- functions to fill in the $n$
+
+local function sig (x)
+ return (x % 2 == 0) and '' or '-'
+end
+
+F = {
+function () -- $1$
+ for i=10,50009 do
+ io.write('a', i, ' = ', sig(i), 5+((i-10)/2), ',\n')
+ end
+end,
+
+function () -- $2$
+ for i=30026,50009 do
+ io.write('b', i, ' = ', sig(i), 15013+((i-30026)/2), ',\n')
+ end
+end,
+
+function () -- $3$
+ for i=10,50009 do
+ io.write('"a', i, '", ', sig(i), 5+((i-10)/2), ',\n')
+ end
+end,
+}
+
+file = os.tmpname()
+io.output(file)
+for s in string.gmatch(prog, "$([^$]+)") do
+ local n = tonumber(s)
+ if not n then io.write(s) else F[n]() end
+end
+io.close()
+result = dofile(file)
+assert(os.remove(file))
+print'OK'
+return result
+
From 06e08c6d05b1346df935634be395f4d86035e3ea Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 9 Jul 2018 12:41:24 -0300
Subject: [PATCH 050/889] Fixed bug in OP_IDIVI
Opocode was using 'luai_numdiv' (float division) instead of
'luai_numidiv' (integer division).
---
lvm.c | 4 ++--
testes/math.lua | 13 ++++++++++++-
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/lvm.c b/lvm.c
index 9e8bec0c58..4e4ef270e4 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1,5 +1,5 @@
/*
-** $Id: lvm.c,v 2.358 2018/06/15 14:14:20 roberto Exp roberto $
+** $Id: lvm.c,v 2.359 2018/06/18 17:58:21 roberto Exp roberto $
** Lua virtual machine
** See Copyright Notice in lua.h
*/
@@ -1182,7 +1182,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
}
else if (tonumberns(rb, nb)) {
lua_Number nc = cast_num(ic);
- setfltvalue(s2v(ra), luai_numdiv(L, nb, nc));
+ setfltvalue(s2v(ra), luai_numidiv(L, nb, nc));
}
else
Protect(luaT_trybiniTM(L, rb, ic, 0, ra, TM_IDIV));
diff --git a/testes/math.lua b/testes/math.lua
index 66998460d0..e5c9d8ec5f 100644
--- a/testes/math.lua
+++ b/testes/math.lua
@@ -1,4 +1,4 @@
--- $Id: math.lua,v 1.86 2018/05/09 14:55:52 roberto Exp $
+-- $Id: math.lua,v 1.86 2018/05/09 14:55:52 roberto Exp roberto $
-- See Copyright Notice in file all.lua
print("testing numbers and math lib")
@@ -139,6 +139,17 @@ assert(-1 // 0.0 == -1/0)
assert(eqT(3.5 // 1.5, 2.0))
assert(eqT(3.5 // -1.5, -3.0))
+do -- tests for different kinds of opcodes
+ local x, y
+ x = 1; assert(x // 0.0 == 1/0)
+ x = 1.0; assert(x // 0 == 1/0)
+ x = 3.5; assert(eqT(x // 1, 3.0))
+ assert(eqT(x // -1, -4.0))
+
+ x = 3.5; y = 1.5; assert(eqT(x // y, 2.0))
+ x = 3.5; y = -1.5; assert(eqT(x // y, -3.0))
+end
+
assert(maxint // maxint == 1)
assert(maxint // 1 == maxint)
assert((maxint - 1) // maxint == 0)
From b08c9079c5ab026f07942fb898f791325d270177 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 9 Jul 2018 12:50:51 -0300
Subject: [PATCH 051/889] Opcode names moved to a new header file
The array with the names of the opcodes was moved to a header file
('lopnames.h'), as it is not used by the Lua kernel. Files that need
that array ('luac.c' and 'ltests.c') include the header file to get
a private (static) copy.
---
lopcodes.c | 88 +-------------------------------------------------
lopcodes.h | 5 +--
lopnames.h | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
ltests.c | 7 ++--
4 files changed, 100 insertions(+), 94 deletions(-)
create mode 100644 lopnames.h
diff --git a/lopcodes.c b/lopcodes.c
index 823d348592..1a3b0e6af8 100644
--- a/lopcodes.c
+++ b/lopcodes.c
@@ -1,5 +1,5 @@
/*
-** $Id: lopcodes.c,v 1.81 2018/04/04 14:23:41 roberto Exp roberto $
+** $Id: lopcodes.c,v 1.83 2018/06/26 18:00:55 roberto Exp $
** Opcodes for Lua virtual machine
** See Copyright Notice in lua.h
*/
@@ -17,92 +17,6 @@
/* ORDER OP */
-#if defined(LUAI_DEFOPNAMES)
-
-LUAI_DDEF const char *const luaP_opnames[NUM_OPCODES+1] = {
- "MOVE",
- "LOADI",
- "LOADF",
- "LOADK",
- "LOADKX",
- "LOADBOOL",
- "LOADNIL",
- "GETUPVAL",
- "SETUPVAL",
- "GETTABUP",
- "GETTABLE",
- "GETI",
- "GETFIELD",
- "SETTABUP",
- "SETTABLE",
- "SETI",
- "SETFIELD",
- "NEWTABLE",
- "SELF",
- "ADDI",
- "SUBI",
- "MULI",
- "MODI",
- "POWI",
- "DIVI",
- "IDIVI",
- "BANDK",
- "BORK",
- "BXORK",
- "SHRI",
- "SHLI",
- "ADD",
- "SUB",
- "MUL",
- "MOD",
- "POW",
- "DIV",
- "IDIV",
- "BAND",
- "BOR",
- "BXOR",
- "SHL",
- "SHR",
- "UNM",
- "BNOT",
- "NOT",
- "LEN",
- "CONCAT",
- "CLOSE",
- "JMP",
- "EQ",
- "LT",
- "LE",
- "EQK",
- "EQI",
- "LTI",
- "LEI",
- "GTI",
- "GEI",
- "TEST",
- "TESTSET",
- "CALL",
- "TAILCALL",
- "RETURN",
- "RETURN0",
- "RETURN1",
- "FORLOOP1",
- "FORPREP1",
- "FORLOOP",
- "FORPREP",
- "TFORCALL",
- "TFORLOOP",
- "SETLIST",
- "CLOSURE",
- "VARARG",
- "PREPVARARG",
- "EXTRAARG",
- NULL
-};
-
-#endif
-
-
LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
/* OT IT T A mode opcode */
opmode(0, 0, 0, 1, iABC) /* OP_MOVE */
diff --git a/lopcodes.h b/lopcodes.h
index 821bb196de..d7b5dfe013 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -1,5 +1,5 @@
/*
-** $Id: lopcodes.h,v 1.192 2018/06/08 19:07:27 roberto Exp roberto $
+** $Id: lopcodes.h,v 1.194 2018/06/26 18:00:55 roberto Exp $
** Opcodes for Lua virtual machine
** See Copyright Notice in lua.h
*/
@@ -358,9 +358,6 @@ LUAI_DDEC(const lu_byte luaP_opmodes[NUM_OPCODES];)
#define opmode(ot,it,t,a,m) (((ot)<<6) | ((it)<<5) | ((t)<<4) | ((a)<<3) | (m))
-LUAI_DDEC(const char *const luaP_opnames[NUM_OPCODES+1];) /* opcode names */
-
-
/* number of list items to accumulate before a SETLIST instruction */
#define LFIELDS_PER_FLUSH 50
diff --git a/lopnames.h b/lopnames.h
new file mode 100644
index 0000000000..ab434b5979
--- /dev/null
+++ b/lopnames.h
@@ -0,0 +1,94 @@
+/*
+** $Id: lopnames.h,v 1.1 2018/06/26 18:00:55 roberto Exp $
+** Opcode names
+** See Copyright Notice in lua.h
+*/
+
+#if !defined(lopnames_h)
+#define lopnames_h
+
+/* ORDER OP */
+
+static const char *const opnames[] = {
+ "MOVE",
+ "LOADI",
+ "LOADF",
+ "LOADK",
+ "LOADKX",
+ "LOADBOOL",
+ "LOADNIL",
+ "GETUPVAL",
+ "SETUPVAL",
+ "GETTABUP",
+ "GETTABLE",
+ "GETI",
+ "GETFIELD",
+ "SETTABUP",
+ "SETTABLE",
+ "SETI",
+ "SETFIELD",
+ "NEWTABLE",
+ "SELF",
+ "ADDI",
+ "SUBI",
+ "MULI",
+ "MODI",
+ "POWI",
+ "DIVI",
+ "IDIVI",
+ "BANDK",
+ "BORK",
+ "BXORK",
+ "SHRI",
+ "SHLI",
+ "ADD",
+ "SUB",
+ "MUL",
+ "MOD",
+ "POW",
+ "DIV",
+ "IDIV",
+ "BAND",
+ "BOR",
+ "BXOR",
+ "SHL",
+ "SHR",
+ "UNM",
+ "BNOT",
+ "NOT",
+ "LEN",
+ "CONCAT",
+ "CLOSE",
+ "JMP",
+ "EQ",
+ "LT",
+ "LE",
+ "EQK",
+ "EQI",
+ "LTI",
+ "LEI",
+ "GTI",
+ "GEI",
+ "TEST",
+ "TESTSET",
+ "CALL",
+ "TAILCALL",
+ "RETURN",
+ "RETURN0",
+ "RETURN1",
+ "FORLOOP1",
+ "FORPREP1",
+ "FORLOOP",
+ "FORPREP",
+ "TFORCALL",
+ "TFORLOOP",
+ "SETLIST",
+ "CLOSURE",
+ "VARARG",
+ "PREPVARARG",
+ "EXTRAARG",
+ NULL
+};
+
+#endif
+
diff --git a/ltests.c b/ltests.c
index 82d6463a04..ac548a95c3 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1,5 +1,5 @@
/*
-** $Id: ltests.c,v 2.244 2018/06/11 14:19:50 roberto Exp roberto $
+** $Id: ltests.c,v 2.246 2018/06/26 18:00:55 roberto Exp roberto $
** Internal Module for Debugging of the Lua Implementation
** See Copyright Notice in lua.h
*/
@@ -27,6 +27,7 @@
#include "lfunc.h"
#include "lmem.h"
#include "lopcodes.h"
+#include "lopnames.h"
#include "lstate.h"
#include "lstring.h"
#include "ltable.h"
@@ -523,7 +524,7 @@ int lua_checkmemory (lua_State *L) {
static char *buildop (Proto *p, int pc, char *buff) {
Instruction i = p->code[pc];
OpCode o = GET_OPCODE(i);
- const char *name = luaP_opnames[o];
+ const char *name = opnames[o];
int line = luaG_getfuncline(p, pc);
sprintf(buff, "(%4d) %4d - ", line, pc);
switch (getOpMode(o)) {
@@ -599,7 +600,7 @@ static int printcode (lua_State *L) {
printf("numparams: %d\n", p->numparams);
for (pc=0; pcsizecode; pc++) {
char buff[100];
- printf("%d\t%s\n", pc + 1, buildop(p, pc, buff));
+ printf("%s\n", buildop(p, pc, buff));
}
return 0;
}
From de2caf7ee4dfc7d641d00bf1d74b261482993b2d Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 9 Jul 2018 12:54:51 -0300
Subject: [PATCH 052/889] Bit-library file removed from the project (as it was
deprecated)
This commit only removed the file 'lbitlib.c' from the project; the
makefile already was not using it.
---
lbitlib.c | 7 -------
1 file changed, 7 deletions(-)
delete mode 100644 lbitlib.c
diff --git a/lbitlib.c b/lbitlib.c
deleted file mode 100644
index b9c33c6511..0000000000
--- a/lbitlib.c
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
-** $Id: lbitlib.c,v 1.31 2017/11/16 13:19:06 roberto Exp roberto $
-** Standard library for bitwise operations
-** See Copyright Notice in lua.h
-*/
-
-Deprecated module.
From ccf6d098f6a46053ef671b42100e68f12352c5ec Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 9 Jul 2018 13:29:08 -0300
Subject: [PATCH 053/889] 'searchpath' creates less temporary strings
When creating error messages, package loaders may create dozens of
temporary strings (one or more for each tried template). This change
reduces the number of these strings, and avoid creating some of
them if the search is successful.
---
loadlib.c | 52 ++++++++++++++++++++++++++++++++++------------------
1 file changed, 34 insertions(+), 18 deletions(-)
diff --git a/loadlib.c b/loadlib.c
index a5e10e4506..8c8ab8c586 100644
--- a/loadlib.c
+++ b/loadlib.c
@@ -1,5 +1,5 @@
/*
-** $Id: loadlib.c,v 1.131 2017/12/13 12:51:42 roberto Exp roberto $
+** $Id: loadlib.c,v 1.133 2018/07/06 13:38:38 roberto Exp $
** Dynamic library loader for Lua
** See Copyright Notice in lua.h
**
@@ -421,14 +421,33 @@ static int readable (const char *filename) {
}
-static const char *pushnexttemplate (lua_State *L, const char *path) {
+static const char *pushnextfilename (lua_State *L, const char *path) {
const char *l;
- while (*path == *LUA_PATH_SEP) path++; /* skip separators */
- if (*path == '\0') return NULL; /* no more templates */
+ if (*path == *LUA_PATH_SEP)
+ path++; /* skip separator */
+ if (*path == '\0')
+ return NULL; /* no more names */
l = strchr(path, *LUA_PATH_SEP); /* find next separator */
- if (l == NULL) l = path + strlen(path);
- lua_pushlstring(L, path, l - path); /* template */
- return l;
+ if (l == NULL) /* no more separators? */
+ l = path + strlen(path); /* go until the end */
+ lua_pushlstring(L, path, l - path); /* file name */
+ return l; /* rest of the path */
+}
+
+
+/*
+** Given a path such as ";blabla.so;blublu.so", pushes the string
+**
+** no file 'blabla.so'
+** no file 'blublu.so'
+*/
+static void pusherrornotfound (lua_State *L, const char *path) {
+ if (*path == *LUA_PATH_SEP)
+ path++; /* skip separator */
+ lua_pushstring(L, "\n\tno file '");
+ luaL_gsub(L, path, LUA_PATH_SEP, "'\n\tno file '");
+ lua_pushstring(L, "'");
+ lua_concat(L, 3);
}
@@ -436,21 +455,18 @@ static const char *searchpath (lua_State *L, const char *name,
const char *path,
const char *sep,
const char *dirsep) {
- luaL_Buffer msg; /* to build error message */
- if (*sep != '\0') /* non-empty separator? */
+ /* separator is non-empty and appears in 'name'? */
+ if (*sep != '\0' && strchr(name, *sep) != NULL)
name = luaL_gsub(L, name, sep, dirsep); /* replace it by 'dirsep' */
- luaL_buffinit(L, &msg);
- while ((path = pushnexttemplate(L, path)) != NULL) {
- const char *filename = luaL_gsub(L, lua_tostring(L, -1),
- LUA_PATH_MARK, name);
- lua_remove(L, -2); /* remove path template */
+ /* replace marks ('?') in 'path' by the file name */
+ path = luaL_gsub(L, path, LUA_PATH_MARK, name);
+ while ((path = pushnextfilename(L, path)) != NULL) {
+ const char *filename = lua_tostring(L, -1);
if (readable(filename)) /* does file exist and is readable? */
return filename; /* return that file name */
- lua_pushfstring(L, "\n\tno file '%s'", filename);
- lua_remove(L, -2); /* remove file name */
- luaL_addvalue(&msg); /* concatenate error msg. entry */
+ lua_pop(L, 1); /* else remove file name */
}
- luaL_pushresult(&msg); /* create error message */
+ pusherrornotfound(L, lua_tostring(L, -1)); /* create error message */
return NULL; /* not found */
}
From 626cf0581bc214722033d61c69d5db9e51e53465 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 9 Jul 2018 14:22:09 -0300
Subject: [PATCH 054/889] Generational mode may wait longer after a major
collection
When Lua is building large long-duration structures, frequent small
minor collections just waste time. Trying to avoid this, the
collector will do a larger pause after a major collection when it
does not collect enough garbage (which is a hint that memory is
being used for long-lasting objects).
---
lgc.c | 33 +++++++++++++++++++++++++--------
1 file changed, 25 insertions(+), 8 deletions(-)
diff --git a/lgc.c b/lgc.c
index b0e3c363a2..fb02f015a2 100644
--- a/lgc.c
+++ b/lgc.c
@@ -1,5 +1,5 @@
/*
-** $Id: lgc.c,v 2.253 2018/03/16 14:22:09 roberto Exp roberto $
+** $Id$
** Garbage Collector
** See Copyright Notice in lua.h
*/
@@ -12,6 +12,7 @@
#include
#include
+
#include "lua.h"
#include "ldebug.h"
@@ -1004,6 +1005,8 @@ void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) {
** =======================================================
*/
+static void setpause (global_State *g);
+
/* mask to erase all color bits, not changing gen-related stuff */
#define maskgencolors (~(bitmask(BLACKBIT) | WHITEBITS))
@@ -1275,21 +1278,35 @@ static void fullgen (lua_State *L, global_State *g) {
** than last major collection (kept in 'g->GCestimate'), does a major
** collection. Otherwise, does a minor collection and set debt to make
** another collection when memory grows 'genminormul'% larger.
+** When it does a major collection, it then checks whether it could
+** reclaim at least ?? memory. If not, it sets a long pause for the
+** next collection. (Therefore, the next collection will be a major
+** one, too.)
** 'GCdebt <= 0' means an explicit call to GC step with "size" zero;
** in that case, always do a minor collection.
*/
static void genstep (lua_State *L, global_State *g) {
- lu_mem majorbase = g->GCestimate;
- int majormul = getgcparam(g->genmajormul);
- if (g->GCdebt > 0 &&
- gettotalbytes(g) > (majorbase / 100) * (100 + majormul)) {
+ lu_mem majorbase = g->GCestimate; /* memory after last major collection */
+ lu_mem majorinc = (majorbase / 100) * getgcparam(g->genmajormul);
+ lu_mem memnew = gettotalbytes(g);
+ if (g->GCdebt > 0 && memnew > majorbase + majorinc) {
fullgen(L, g);
+ memnew = gettotalbytes(g);
+ if (memnew < majorbase + (majorinc / 2)) {
+ /* collected at least half of memory growth since last major
+ collection; go back to minor collections */
+ luaE_setdebt(g, -(cast(l_mem, (memnew / 100)) * g->genminormul));
+ }
+ else {
+ /* memory seems to be growing; do a long wait for next (major)
+ collection */
+ setpause(g);
+ }
}
else {
- lu_mem mem;
youngcollection(L, g);
- mem = gettotalbytes(g);
- luaE_setdebt(g, -(cast(l_mem, (mem / 100)) * g->genminormul));
+ memnew = gettotalbytes(g);
+ luaE_setdebt(g, -(cast(l_mem, (memnew / 100)) * g->genminormul));
g->GCestimate = majorbase; /* preserve base value */
}
}
From 21f663d29fd46b0ebf2bbc3f039ed31bef2e29d4 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 10 Jul 2018 13:40:30 -0300
Subject: [PATCH 055/889] Added missing $Id$ to file 'ljumptab.h'
---
ljumptab.h | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/ljumptab.h b/ljumptab.h
index c642c23e1f..01d2c11e5a 100644
--- a/ljumptab.h
+++ b/ljumptab.h
@@ -1,3 +1,10 @@
+/*
+** $Id: ljumptab.h 2018/07/10 13:40:16 $
+** Jump Table for the Lua interpreter
+** See Copyright Notice in lua.h
+*/
+
+
#undef vmdispatch
#undef vmcase
#undef vmbreak
From 941b189d98c7019c1be093bb0b709d90771b72d9 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 10 Jul 2018 13:48:19 -0300
Subject: [PATCH 056/889] Improvements in the manual
- More precise use of 'argument' x 'parameter'.
- Clarification about what the lexer considers 'letter', 'space',
and 'digit'.
---
manual/manual.of | 31 +++++++++++++++++--------------
1 file changed, 17 insertions(+), 14 deletions(-)
diff --git a/manual/manual.of b/manual/manual.of
index 935990d0ed..f92be508f9 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1,4 +1,4 @@
-@Ci{$Id: manual.of,v 1.175 2018/06/18 19:17:35 roberto Exp $}
+@Ci{$Id: manual.of,v 1.175 2018/06/18 19:17:35 roberto Exp roberto $}
@C{[(-------------------------------------------------------------------------}
@manual{
@@ -925,14 +925,17 @@ at the end of this manual.
@sect2{lexical| @title{Lexical Conventions}
Lua is a @x{free-form} language.
-It ignores spaces (including new lines) and comments
-between lexical elements (@x{tokens}),
+It ignores spaces and comments between lexical elements (@x{tokens}),
except as delimiters between @x{names} and @x{keywords}.
+In source code,
+Lua recognizes as spaces the standard ASCII white-space
+characters space, form feed, newline,
+carriage return, horizontal tab, and vertical tab.
@def{Names}
(also called @def{identifiers})
-in Lua can be any string of letters,
-digits, and underscores,
+in Lua can be any string of Latin letters,
+Arabic-Indic digits, and underscores,
not beginning with a digit and
not being a reserved word.
Identifiers are used to name variables, table fields, and labels.
@@ -2436,7 +2439,7 @@ it can do whatever it wants on that Lua state,
as it should be already protected.
However,
when C code operates on other Lua states
-(e.g., a Lua parameter to the function,
+(e.g., a Lua-state argument to the function,
a Lua state stored in the registry, or
the result of @Lid{lua_newthread}),
it should use them only in API calls that cannot raise errors.
@@ -5376,7 +5379,7 @@ In words, if the argument @id{arg} is nil or absent,
the macro results in the default @id{dflt}.
Otherwise, it results in the result of calling @id{func}
with the state @id{L} and the argument index @id{arg} as
-parameters.
+arguments.
Note that it evaluates the expression @id{dflt} only if needed.
}
@@ -6408,7 +6411,7 @@ Each entry in this table is a @def{searcher function}.
When looking for a module,
@Lid{require} calls each of these searchers in ascending order,
with the module name (the argument given to @Lid{require}) as its
-sole parameter.
+sole argument.
The function can return another function (the module @def{loader})
plus an extra value that will be passed to that loader,
or a string explaining why it did not find that module
@@ -7355,7 +7358,7 @@ Returns the arc sine of @id{x} (in radians).
@index{atan2}
Returns the arc tangent of @T{y/x} (in radians),
-but uses the signs of both parameters to find the
+but uses the signs of both arguments to find the
quadrant of the result.
(It also handles correctly the case of @id{x} being zero.)
@@ -7596,7 +7599,7 @@ When called with a file name, it opens the named file (in text mode),
and sets its handle as the default input file.
When called with a file handle,
it simply sets this file handle as the default input file.
-When called without parameters,
+When called without arguments,
it returns the current default input file.
In case of errors this function raises the error,
@@ -7743,7 +7746,7 @@ the function returns a string or a number with the characters read,
or @nil if it cannot read data with the specified format.
(In this latter case,
the function does not read subsequent formats.)
-When called without parameters,
+When called without arguments,
it uses a default format that reads the next line
(see below).
@@ -8166,8 +8169,8 @@ The first parameter or local variable has @N{index 1}, and so on,
following the order that they are declared in the code,
counting only the variables that are active
in the current scope of the function.
-Negative indices refer to vararg parameters;
-@num{-1} is the first vararg parameter.
+Negative indices refer to vararg arguments;
+@num{-1} is the first vararg argument.
The function returns @nil if there is no variable with the given index,
and raises an error when called with a level out of range.
(You can call @Lid{debug.getinfo} to check whether the level is valid.)
@@ -8418,7 +8421,7 @@ $ lua -e "print(arg[1])"
}
will print @St{-e}.
If there is a script,
-the script is called with parameters
+the script is called with arguments
@T{arg[1]}, @Cdots, @T{arg[#arg]}.
(Like all chunks in Lua,
the script is compiled as a vararg function.)
From 9a825f6bb9a141023ac519a73f6a9958c113659e Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 10 Jul 2018 14:55:16 -0300
Subject: [PATCH 057/889] In tests of opcodes, avoid coercion in bitwise
operation
---
testes/code.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/testes/code.lua b/testes/code.lua
index e39c62adc6..1a01c4a22e 100644
--- a/testes/code.lua
+++ b/testes/code.lua
@@ -239,7 +239,7 @@ checkI(function () return ~~-1024.0 end, -1024)
checkI(function () return ((100 << 6) << -4) >> 2 end, 100)
-- borders around MAXARG_sBx ((((1 << 17) - 1) >> 1) == 65535)
-local sbx = ((1 << "17") - 1) >> 1 -- avoid folding
+local a = 17; local sbx = ((1 << a) - 1) >> 1 -- avoid folding
checkI(function () return 65535 end, sbx)
checkI(function () return -65535 end, -sbx)
checkI(function () return 65536 end, sbx + 1)
From 4d5de1c1fb2decb39d74dfb092ca5643ce47176f Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 11 Jul 2018 12:53:23 -0300
Subject: [PATCH 058/889] Fixed bug in line info. when using 'not' operator
When creating code for a jump on a 'not' condition, the code generator
was removing an instruction (the OP_NOT) without adjusting its
corresponding line information.
This fix also added tests for this case and extra functionality in
the test library to debug line info. structures.
---
lcode.c | 76 +++++++++++++++++++++++++++++++++--------------
ltests.c | 22 +++++++++++++-
testes/errors.lua | 21 +++++++++++--
3 files changed, 93 insertions(+), 26 deletions(-)
diff --git a/lcode.c b/lcode.c
index ab91561cae..d00038dd7a 100644
--- a/lcode.c
+++ b/lcode.c
@@ -1,5 +1,5 @@
/*
-** $Id: lcode.c,v 2.160 2018/03/16 14:22:09 roberto Exp roberto $
+** $Id: lcode.c $
** Code generator for Lua
** See Copyright Notice in lua.h
*/
@@ -309,10 +309,19 @@ void luaK_patchclose (FuncState *fs, int list) {
}
+/*
+** MAXimum number of successive Instructions WiTHout ABSolute line
+** information.
+*/
#if !defined(MAXIWTHABS)
#define MAXIWTHABS 120
#endif
+
+/* limit for difference between lines in relative line info. */
+#define LIMLINEDIFF 0x80
+
+
/*
** Save line info for a new instruction. If difference from last line
** does not fit in a byte, of after that many instructions, save a new
@@ -320,14 +329,15 @@ void luaK_patchclose (FuncState *fs, int list) {
** in 'lineinfo' signals the existence of this absolute information.)
** Otherwise, store the difference from last line in 'lineinfo'.
*/
-static void savelineinfo (FuncState *fs, Proto *f, int pc, int line) {
+static void savelineinfo (FuncState *fs, Proto *f, int line) {
int linedif = line - fs->previousline;
- if (abs(linedif) >= 0x80 || fs->iwthabs++ > MAXIWTHABS) {
+ int pc = fs->pc - 1; /* last instruction coded */
+ if (abs(linedif) >= LIMLINEDIFF || fs->iwthabs++ > MAXIWTHABS) {
luaM_growvector(fs->ls->L, f->abslineinfo, fs->nabslineinfo,
f->sizeabslineinfo, AbsLineInfo, MAX_INT, "lines");
f->abslineinfo[fs->nabslineinfo].pc = pc;
f->abslineinfo[fs->nabslineinfo++].line = line;
- linedif = ABSLINEINFO; /* signal there is absolute information */
+ linedif = ABSLINEINFO; /* signal that there is absolute information */
fs->iwthabs = 0; /* restart counter */
}
luaM_growvector(fs->ls->L, f->lineinfo, pc, f->sizelineinfo, ls_byte,
@@ -337,6 +347,37 @@ static void savelineinfo (FuncState *fs, Proto *f, int pc, int line) {
}
+/*
+** Remove line information from the last instruction.
+** If line information for that instruction is absolute, set 'iwthabs'
+** above its max to force the new (replacing) instruction to have
+** absolute line info, too.
+*/
+static void removelastlineinfo (FuncState *fs) {
+ Proto *f = fs->f;
+ int pc = fs->pc - 1; /* last instruction coded */
+ if (f->lineinfo[pc] != ABSLINEINFO) { /* relative line info? */
+ fs->previousline -= f->lineinfo[pc]; /* last line saved */
+ fs->iwthabs--;
+ }
+ else { /* absolute line information */
+ fs->nabslineinfo--; /* remove it */
+ lua_assert(f->abslineinfo[fs->nabslineinfo].pc = pc);
+ fs->iwthabs = MAXIWTHABS + 1; /* force next line info to be absolute */
+ }
+}
+
+
+/*
+** Remove the last instruction created, correcting line information
+** accordingly.
+*/
+static void removelastinstruction (FuncState *fs) {
+ removelastlineinfo(fs);
+ fs->pc--;
+}
+
+
/*
** Emit instruction 'i', checking for array sizes and saving also its
** line information. Return 'i' position.
@@ -346,9 +387,9 @@ static int luaK_code (FuncState *fs, Instruction i) {
/* put new instruction in code array */
luaM_growvector(fs->ls->L, f->code, fs->pc, f->sizecode, Instruction,
MAX_INT, "opcodes");
- f->code[fs->pc] = i;
- savelineinfo(fs, f, fs->pc, fs->ls->lastline);
- return fs->pc++;
+ f->code[fs->pc++] = i;
+ savelineinfo(fs, f, fs->ls->lastline);
+ return fs->pc - 1; /* index of new instruction */
}
@@ -986,7 +1027,7 @@ static int jumponcond (FuncState *fs, expdesc *e, int cond) {
if (e->k == VRELOC) {
Instruction ie = getinstruction(fs, e);
if (GET_OPCODE(ie) == OP_NOT) {
- fs->pc--; /* remove previous OP_NOT */
+ removelastinstruction(fs); /* remove previous OP_NOT */
return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond);
}
/* else go through */
@@ -1572,23 +1613,12 @@ void luaK_posfix (FuncState *fs, BinOpr opr,
/*
-** Change line information associated with current position. If that
-** information is absolute, just change it and correct 'previousline'.
-** Otherwise, restore 'previousline' to its value before saving the
-** current position and than saves the line information again, with the
-** new line.
+** Change line information associated with current position, by removing
+** previous info and adding it again with new line.
*/
void luaK_fixline (FuncState *fs, int line) {
- Proto *f = fs->f;
- if (f->lineinfo[fs->pc - 1] == ABSLINEINFO) {
- lua_assert(f->abslineinfo[fs->nabslineinfo - 1].pc == fs->pc - 1);
- f->abslineinfo[fs->nabslineinfo - 1].line = line;
- fs->previousline = line;
- }
- else {
- fs->previousline -= f->lineinfo[fs->pc - 1]; /* undo previous info. */
- savelineinfo(fs, f, fs->pc - 1, line); /* redo it */
- }
+ removelastlineinfo(fs);
+ savelineinfo(fs, fs->f, line);
}
diff --git a/ltests.c b/ltests.c
index ac548a95c3..13717da9b3 100644
--- a/ltests.c
+++ b/ltests.c
@@ -526,7 +526,8 @@ static char *buildop (Proto *p, int pc, char *buff) {
OpCode o = GET_OPCODE(i);
const char *name = opnames[o];
int line = luaG_getfuncline(p, pc);
- sprintf(buff, "(%4d) %4d - ", line, pc);
+ int lineinfo = (p->lineinfo != NULL) ? p->lineinfo[pc] : 0;
+ sprintf(buff, "(%2d - %4d) %4d - ", lineinfo, line, pc);
switch (getOpMode(o)) {
case iABC:
sprintf(buff+strlen(buff), "%-12s%4d %4d %4d%s", name,
@@ -621,6 +622,24 @@ static int listk (lua_State *L) {
}
+static int listabslineinfo (lua_State *L) {
+ Proto *p;
+ int i;
+ luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1),
+ 1, "Lua function expected");
+ p = getproto(obj_at(L, 1));
+ luaL_argcheck(L, p->abslineinfo != NULL, 1, "function has no debug info");
+ lua_createtable(L, 2 * p->sizeabslineinfo, 0);
+ for (i=0; i < p->sizeabslineinfo; i++) {
+ lua_pushinteger(L, p->abslineinfo[i].pc);
+ lua_rawseti(L, -2, 2 * i + 1);
+ lua_pushinteger(L, p->abslineinfo[i].line);
+ lua_rawseti(L, -2, 2 * i + 2);
+ }
+ return 1;
+}
+
+
static int listlocals (lua_State *L) {
Proto *p;
int pc = cast_int(luaL_checkinteger(L, 2)) - 1;
@@ -1682,6 +1701,7 @@ static const struct luaL_Reg tests_funcs[] = {
{"listcode", listcode},
{"printcode", printcode},
{"listk", listk},
+ {"listabslineinfo", listabslineinfo},
{"listlocals", listlocals},
{"loadlib", loadlib},
{"checkpanic", checkpanic},
diff --git a/testes/errors.lua b/testes/errors.lua
index 63a7b740cd..19a7b6faad 100644
--- a/testes/errors.lua
+++ b/testes/errors.lua
@@ -1,4 +1,4 @@
--- $Id: errors.lua,v 1.97 2017/11/28 15:31:56 roberto Exp $
+-- $Id: errors.lua $
-- See Copyright Notice in file all.lua
print("testing errors")
@@ -299,7 +299,7 @@ end
local function lineerror (s, l)
local err,msg = pcall(load(s))
local line = string.match(msg, ":(%d+):")
- assert((line and line+0) == l)
+ assert(tonumber(line) == l)
end
lineerror("local a\n for i=1,'a' do \n print(i) \n end", 2)
@@ -350,6 +350,23 @@ X=1;lineerror((p), 2)
X=2;lineerror((p), 1)
+lineerror([[
+local b = false
+if not b then
+ error 'test'
+end]], 3)
+
+lineerror([[
+local b = false
+if not b then
+ if not b then
+ if not b then
+ error 'test'
+ end
+ end
+end]], 5)
+
+
if not _soft then
-- several tests that exaust the Lua stack
collectgarbage()
From 84058b15068a382e66f4eeb4e29a2dbf1704fa8b Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 11 Jul 2018 13:17:46 -0300
Subject: [PATCH 059/889] Added definition for LUA_VERSION_RELEASE_NUM
LUA_VERSION_RELEASE_NUM is set to the release number of the Lua
interpreter (e.g., 5.4.0 becomes the integer 50400).
---
lua.h | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lua.h b/lua.h
index 12d9e2ec18..03d92c0ba3 100644
--- a/lua.h
+++ b/lua.h
@@ -18,9 +18,11 @@
#define LUA_VERSION_MAJOR "5"
#define LUA_VERSION_MINOR "4"
-#define LUA_VERSION_NUM 504
#define LUA_VERSION_RELEASE "0"
+#define LUA_VERSION_NUM 504
+#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 0)
+
#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR
#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE
#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2018 Lua.org, PUC-Rio"
From 96f9643f330a4bf0f8cd1973fecdd7161ffbbf68 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 11 Jul 2018 16:11:50 -0300
Subject: [PATCH 060/889] Bug: wrong 'nCcalls' when resuming a coroutine
The counter 'nCcalls' now includes the number of CallInfo structures
pre-allocated (so that these "potential" C calls can be made without
checking 'nCcalls'). So, when copying this value from a thread to
another, in 'lua_resume', it must be corrected to the number of
CallInfo structures in the thread being resumed.
---
ldo.c | 9 ++++++---
lstate.h | 20 ++++++++++++++++++--
2 files changed, 24 insertions(+), 5 deletions(-)
diff --git a/ldo.c b/ldo.c
index fb077211fe..ebb35c971c 100644
--- a/ldo.c
+++ b/ldo.c
@@ -1,5 +1,5 @@
/*
-** $Id: ldo.c,v 2.201 2018/05/22 12:02:36 roberto Exp roberto $
+** $Id: ldo.c $
** Stack and Call structure of Lua
** See Copyright Notice in lua.h
*/
@@ -137,6 +137,7 @@ l_noret luaD_throw (lua_State *L, int errcode) {
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
unsigned short oldnCcalls = L->nCcalls - L->nci;
struct lua_longjmp lj;
+ lua_assert(L->nCcalls >= L->nci);
lj.status = LUA_OK;
lj.previous = L->errorJmp; /* chain new error handler */
L->errorJmp = &lj;
@@ -653,7 +654,10 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
}
else if (L->status != LUA_YIELD)
return resume_error(L, "cannot resume dead coroutine", nargs);
- L->nCcalls = (from) ? from->nCcalls + 1 : 1;
+ if (from == NULL)
+ L->nCcalls = 1;
+ else /* correct 'nCcalls' for this thread */
+ L->nCcalls = from->nCcalls - from->nci + L->nci + 1;
if (L->nCcalls >= LUAI_MAXCCALLS)
return resume_error(L, "C stack overflow", nargs);
luai_userstateresume(L, nargs);
@@ -677,7 +681,6 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
*nresults = (status == LUA_YIELD) ? L->ci->u2.nyield
: cast_int(L->top - (L->ci->func + 1));
L->nny = oldnny; /* restore 'nny' */
- L->nCcalls--;
lua_unlock(L);
return status;
}
diff --git a/lstate.h b/lstate.h
index 0e173b5b00..2a36bd9632 100644
--- a/lstate.h
+++ b/lstate.h
@@ -1,5 +1,5 @@
/*
-** $Id: lstate.h,v 2.159 2018/06/15 19:31:22 roberto Exp roberto $
+** $Id: lstate.h $
** Global State
** See Copyright Notice in lua.h
*/
@@ -49,6 +49,22 @@
*/
+/*
+
+** About 'nCcalls': each thread in Lua (a lua_State) keeps a count of
+** how many "C calls" it has in the C stack, to avoid C-stack overflow.
+** This count is very rough approximation; it considers only recursive
+** functions inside the interpreter, as non-recursive calls can be
+** considered using a fixed (although unknown) amount of stack space.
+**
+** The proper count also includes the number of CallInfo structures
+** allocated by Lua, as a kind of "potential" calls. So, when Lua
+** calls a function (and "consumes" one CallInfo), it needs neither to
+** increment nor to check 'nCcalls', as its use of C stack is already
+** accounted for.
+
+*/
+
struct lua_longjmp; /* defined in ldo.c */
@@ -212,7 +228,7 @@ struct lua_State {
int basehookcount;
int hookcount;
unsigned short nny; /* number of non-yieldable calls in stack */
- unsigned short nCcalls; /* number of nested C calls */
+ unsigned short nCcalls; /* number of nested C calls + 'nny' */
l_signalT hookmask;
lu_byte allowhook;
};
From fb18346dddcb0400d0396111c56a817a8d4bd8bd Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 12 Jul 2018 15:56:44 -0300
Subject: [PATCH 061/889] Avoid using 'int' for UTF-8 values
An 'int' may have only 16 bits, so it may not be big enough for UTF-8
values. The new type 'utfint' (in the utf8 library) ensures at least
21 bits for those values.
---
lutf8lib.c | 33 ++++++++++++++++++++++-----------
1 file changed, 22 insertions(+), 11 deletions(-)
diff --git a/lutf8lib.c b/lutf8lib.c
index 57bd59e01a..dc95b285c9 100644
--- a/lutf8lib.c
+++ b/lutf8lib.c
@@ -1,5 +1,5 @@
/*
-** $Id: lutf8lib.c,v 1.16 2016/12/22 13:08:50 roberto Exp roberto $
+** $Id: lutf8lib.c $
** Standard library for UTF-8 manipulation
** See Copyright Notice in lua.h
*/
@@ -20,8 +20,19 @@
#include "lauxlib.h"
#include "lualib.h"
+
#define MAXUNICODE 0x10FFFF
+/*
+** Integer type for decoded UTF-8 values; MAXUNICODE needs 21 bits.
+*/
+#if LUAI_BITSINT >= 21
+typedef unsigned int utfint;
+#else
+typedef unsigned long utfint;
+#endif
+
+
#define iscont(p) ((*(p) & 0xC0) == 0x80)
@@ -37,11 +48,11 @@ static lua_Integer u_posrelat (lua_Integer pos, size_t len) {
/*
** Decode one UTF-8 sequence, returning NULL if byte sequence is invalid.
*/
-static const char *utf8_decode (const char *o, int *val) {
+static const char *utf8_decode (const char *o, utfint *val) {
static const unsigned int limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFF};
const unsigned char *s = (const unsigned char *)o;
unsigned int c = s[0];
- unsigned int res = 0; /* final result */
+ utfint res = 0; /* final result */
if (c < 0x80) /* ascii? */
res = c;
else {
@@ -53,7 +64,7 @@ static const char *utf8_decode (const char *o, int *val) {
res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */
c <<= 1; /* to test next bit */
}
- res |= ((c & 0x7F) << (count * 5)); /* add first byte */
+ res |= ((utfint)(c & 0x7F) << (count * 5)); /* add first byte */
if (count > 3 || res > MAXUNICODE || res <= limits[count])
return NULL; /* invalid byte sequence */
s += count; /* skip continuation bytes read */
@@ -69,8 +80,8 @@ static const char *utf8_decode (const char *o, int *val) {
** that interval
*/
static int utflen (lua_State *L) {
- int n = 0;
- size_t len;
+ lua_Integer n = 0; /* counter for the number of characters */
+ size_t len; /* string length in bytes */
const char *s = luaL_checklstring(L, 1, &len);
lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len);
lua_Integer posj = u_posrelat(luaL_optinteger(L, 3, -1), len);
@@ -109,12 +120,12 @@ static int codepoint (lua_State *L) {
if (posi > pose) return 0; /* empty interval; return no values */
if (pose - posi >= INT_MAX) /* (lua_Integer -> int) overflow? */
return luaL_error(L, "string slice too long");
- n = (int)(pose - posi) + 1;
+ n = (int)(pose - posi) + 1; /* upper bound for number of returns */
luaL_checkstack(L, n, "string slice too long");
- n = 0;
- se = s + pose;
+ n = 0; /* count the number of returns */
+ se = s + pose; /* string end */
for (s += posi - 1; s < se;) {
- int code;
+ utfint code;
s = utf8_decode(s, &code);
if (s == NULL)
return luaL_error(L, "invalid UTF-8 code");
@@ -211,7 +222,7 @@ static int iter_aux (lua_State *L) {
if (n >= (lua_Integer)len)
return 0; /* no more codepoints */
else {
- int code;
+ utfint code;
const char *next = utf8_decode(s + n, &code);
if (next == NULL || iscont(next))
return luaL_error(L, "invalid UTF-8 code");
From 2e297d6ab37c1bb255b6984b91dd92d9080e02c9 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 13 Jul 2018 15:43:02 -0300
Subject: [PATCH 062/889] Fixed bug in generational collection of userdata
During generational collection, a userdatum must become gray and
go to a gray list after being traversed (like tables), so that
'correctgraylist' can handle it to its next stage.
This commit also added minimum tests for the generational collector,
including one that would detect this bug.
---
lgc.c | 26 ++++++++-------
testes/all.lua | 4 +--
testes/gc.lua | 4 +--
testes/gengc.lua | 83 ++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 102 insertions(+), 15 deletions(-)
create mode 100644 testes/gengc.lua
diff --git a/lgc.c b/lgc.c
index fb02f015a2..c11a528036 100644
--- a/lgc.c
+++ b/lgc.c
@@ -1,5 +1,5 @@
/*
-** $Id$
+** $Id: lgc.c $
** Garbage Collector
** See Copyright Notice in lua.h
*/
@@ -212,7 +212,7 @@ void luaC_barrierback_ (lua_State *L, GCObject *o) {
lua_assert(g->gckind != KGC_GEN || (isold(o) && getage(o) != G_TOUCHED1));
if (getage(o) != G_TOUCHED2) /* not already in gray list? */
linkobjgclist(o, g->grayagain); /* link it in 'grayagain' */
- black2gray(o); /* make table gray (again) */
+ black2gray(o); /* make object gray (again) */
setage(o, G_TOUCHED1); /* touched in current cycle */
}
@@ -515,6 +515,19 @@ static lu_mem traversetable (global_State *g, Table *h) {
}
+static int traverseudata (global_State *g, Udata *u) {
+ int i;
+ markobjectN(g, u->metatable); /* mark its metatable */
+ for (i = 0; i < u->nuvalue; i++)
+ markvalue(g, &u->uv[i].uv);
+ if (g->gckind == KGC_GEN) {
+ linkgclist(u, g->grayagain); /* keep it in some gray list */
+ black2gray(u);
+ }
+ return 1 + u->nuvalue;
+}
+
+
/*
** Check the cache of a prototype, to keep invariants. If the
** cache is white, clear it. (A cache should not prevent the
@@ -613,15 +626,6 @@ static int traversethread (global_State *g, lua_State *th) {
}
-static int traverseudata (global_State *g, Udata *u) {
- int i;
- markobjectN(g, u->metatable); /* mark its metatable */
- for (i = 0; i < u->nuvalue; i++)
- markvalue(g, &u->uv[i].uv);
- return 1 + u->nuvalue;
-}
-
-
/*
** traverse one gray object, turning it to black (except for threads,
** which are always gray).
diff --git a/testes/all.lua b/testes/all.lua
index cfe2160397..3779625400 100755
--- a/testes/all.lua
+++ b/testes/all.lua
@@ -1,5 +1,5 @@
#!../lua
--- $Id: all.lua,v 1.100 2018/03/09 14:23:48 roberto Exp $
+-- $Id: all.lua $
-- See Copyright Notice at the end of this file
@@ -162,7 +162,7 @@ olddofile('strings.lua')
olddofile('literals.lua')
dofile('tpack.lua')
assert(dofile('attrib.lua') == 27)
-
+dofile('gengc.lua')
assert(dofile('locals.lua') == 5)
dofile('constructs.lua')
dofile('code.lua', true)
diff --git a/testes/gc.lua b/testes/gc.lua
index 9647cd542f..05072efd30 100644
--- a/testes/gc.lua
+++ b/testes/gc.lua
@@ -1,7 +1,7 @@
--- $Id: gc.lua,v 1.82 2018/03/12 14:19:36 roberto Exp $
+-- $Id: gc.lua $
-- See Copyright Notice in file all.lua
-print('testing garbage collection')
+print('testing incremental garbage collection')
local debug = require"debug"
diff --git a/testes/gengc.lua b/testes/gengc.lua
new file mode 100644
index 0000000000..36aed806ef
--- /dev/null
+++ b/testes/gengc.lua
@@ -0,0 +1,83 @@
+-- $Id: gengc.lua $
+-- See Copyright Notice in file all.lua
+
+print('testing generational garbage collection')
+
+local debug = require"debug"
+
+assert(collectgarbage("isrunning"))
+
+collectgarbage()
+
+local oldmode = collectgarbage("generational")
+
+
+-- ensure that table barrier evolves correctly
+do
+ local U = {}
+ -- full collection makes 'U' old
+ collectgarbage()
+ assert(not T or T.gcage(U) == "old")
+
+ -- U refers to a new table, so it becomes 'touched1'
+ U[1] = {x = {234}}
+ assert(not T or (T.gcage(U) == "touched1" and T.gcage(U[1]) == "new"))
+
+ -- both U and the table survive one more collection
+ collectgarbage("step", 0)
+ assert(not T or (T.gcage(U) == "touched2" and T.gcage(U[1]) == "survival"))
+
+ -- both U and the table survive yet another collection
+ -- now everything is old
+ collectgarbage("step", 0)
+ assert(not T or (T.gcage(U) == "old" and T.gcage(U[1]) == "old1"))
+
+ -- data was not corrupted
+ assert(U[1].x[1] == 234)
+end
+
+
+if T == nil then
+ (Message or print)('\n >>> testC not active: \z
+ skipping some generational tests <<<\n')
+ print 'OK'
+ return
+end
+
+
+-- ensure that userdata barrier evolves correctly
+do
+ local U = T.newuserdata(0, 1)
+ -- full collection makes 'U' old
+ collectgarbage()
+ assert(T.gcage(U) == "old")
+
+ -- U refers to a new table, so it becomes 'touched1'
+ debug.setuservalue(U, {x = {234}})
+ assert(T.gcage(U) == "touched1" and
+ T.gcage(debug.getuservalue(U)) == "new")
+
+ -- both U and the table survive one more collection
+ collectgarbage("step", 0)
+ assert(T.gcage(U) == "touched2" and
+ T.gcage(debug.getuservalue(U)) == "survival")
+
+ -- both U and the table survive yet another collection
+ -- now everything is old
+ collectgarbage("step", 0)
+ assert(T.gcage(U) == "old" and
+ T.gcage(debug.getuservalue(U)) == "old1")
+
+ -- data was not corrupted
+ assert(debug.getuservalue(U).x[1] == 234)
+end
+
+
+
+-- just to make sure
+assert(collectgarbage'isrunning')
+
+collectgarbage(oldmode)
+
+print('OK')
+
From ccae0f5aad11b448fa630a41b2c7c54e69d134d8 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 18 Jul 2018 11:43:45 -0300
Subject: [PATCH 063/889] Comments about OLD0/OLD1 ages
Improved the comments in file 'lgc.c' explaining the roles of "ages"
OLD0 and OLD1 in the generacional collector.
---
lgc.c | 54 ++++++++++++++++++++++++++++++------------------------
1 file changed, 30 insertions(+), 24 deletions(-)
diff --git a/lgc.c b/lgc.c
index c11a528036..e8429e1b38 100644
--- a/lgc.c
+++ b/lgc.c
@@ -181,9 +181,13 @@ static int iscleared (global_State *g, const GCObject *o) {
/*
** barrier that moves collector forward, that is, mark the white object
-** being pointed by a black object. (If in sweep phase, clear the black
-** object to white [sweep it] to avoid other barrier calls for this
-** same object.)
+** 'v' being pointed by the black object 'o'. (If in sweep phase, clear
+** the black object to white [sweep it] to avoid other barrier calls for
+** this same object.) In the generational mode, 'v' must also become
+** old, if 'o' is old; however, it cannot be changed directly to OLD,
+** because it may still point to non-old objects. So, it is marked as
+** OLD0. In the next cycle it will become OLD1, and in the next it
+** will finally become OLD (regular old).
*/
void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) {
global_State *g = G(L);
@@ -218,13 +222,14 @@ void luaC_barrierback_ (lua_State *L, GCObject *o) {
/*
-** Barrier for prototype's cache of closures. For an 'old1'
-** object, making it gray stops it from being visited by 'markold',
-** so it is linked in the 'grayagain' list to ensure it will be
-** visited. Otherwise, it goes to 'protogray', as only its 'cache' field
-** needs to be revisited. (A prototype to be in this barrier must be
-** already finished, so its other fields cannot change and do not need
-** to be revisited.)
+** Barrier for prototype's cache of closures. It turns the prototype
+** back to gray (it was black). For an 'OLD1' prototype, making it
+** gray stops it from being visited by 'markold', so it is linked in
+** the 'grayagain' list to ensure it will be visited. For other ages,
+** it goes to the 'protogray' list, as only its 'cache' field needs to
+** be revisited. (A prototype to be in this barrier must be already
+** finished, so its other fields cannot change and do not need to be
+** revisited.)
*/
LUAI_FUNC void luaC_protobarrier_ (lua_State *L, Proto *p) {
global_State *g = G(L);
@@ -233,7 +238,7 @@ LUAI_FUNC void luaC_protobarrier_ (lua_State *L, Proto *p) {
linkgclist(p, g->grayagain); /* link it in 'grayagain' */
else
linkgclist(p, g->protogray); /* link it in 'protogray' */
- black2gray(p); /* make prototype gray (to avoid other barriers) */
+ black2gray(p); /* make prototype gray */
}
@@ -533,9 +538,9 @@ static int traverseudata (global_State *g, Udata *u) {
** cache is white, clear it. (A cache should not prevent the
** collection of its reference.) Otherwise, if in generational
** mode, check the generational invariant. If the cache is old,
-** everything is ok. If the prototype is 'old0', everything
+** everything is ok. If the prototype is 'OLD0', everything
** is ok too. (It will naturally be visited again.) If the
-** prototype is older than 'old0', then its cache (which is new)
+** prototype is older than 'OLD0', then its cache (which is new)
** must be visited again in the next collection, so the prototype
** goes to the 'protogray' list. (If the prototype has a cache,
** it is already immutable and does not need other barriers;
@@ -1085,9 +1090,9 @@ static void whitelist (global_State *g, GCObject *p) {
/*
-** Correct a list of gray objects. Because this correction is
-** done after sweeping, young objects can be white and still
-** be in the list. They are only removed.
+** Correct a list of gray objects.
+** Because this correction is done after sweeping, young objects might
+** be turned white and still be in the list. They are only removed.
** For tables and userdata, advance 'touched1' to 'touched2'; 'touched2'
** objects become regular old and are removed from the list.
** For threads, just remove white ones from the list.
@@ -1104,13 +1109,14 @@ static GCObject **correctgraylist (GCObject **p) {
changeage(curr, G_TOUCHED1, G_TOUCHED2);
p = next; /* go to next element */
}
- else {
- if (!iswhite(curr)) {
+ else { /* not touched in this cycle */
+ if (!iswhite(curr)) { /* not white? */
lua_assert(isold(curr));
- if (getage(curr) == G_TOUCHED2)
- changeage(curr, G_TOUCHED2, G_OLD);
+ if (getage(curr) == G_TOUCHED2) /* advance from G_TOUCHED2... */
+ changeage(curr, G_TOUCHED2, G_OLD); /* ... to G_OLD */
gray2black(curr); /* make it black */
}
+ /* else, object is white: just remove it from this list */
*p = *next; /* remove 'curr' from gray list */
}
break;
@@ -1146,7 +1152,7 @@ static void correctgraylists (global_State *g) {
/*
-** Mark 'old1' objects when starting a new young collection.
+** Mark 'OLD1' objects when starting a new young collection.
** Gray objects are already in some gray list, and so will be visited
** in the atomic step.
*/
@@ -1177,9 +1183,9 @@ static void finishgencycle (lua_State *L, global_State *g) {
/*
-** Does a young collection. First, mark 'old1' objects. (Only survival
-** and "recent old" lists can contain 'old1' objects. New lists cannot
-** contain 'old1' objects, at most 'old0' objects that were already
+** Does a young collection. First, mark 'OLD1' objects. (Only survival
+** and "recent old" lists can contain 'OLD1' objects. New lists cannot
+** contain 'OLD1' objects, at most 'OLD0' objects that were already
** visited when marked old.) Then does the atomic step. Then,
** sweep all lists and advance pointers. Finally, finish the collection.
*/
From e885dee5ab4dbee2457ee2023340e848fdabdef9 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 25 Jul 2018 11:44:46 -0300
Subject: [PATCH 064/889] File operations try an "emergency collection" when
failing
If a file operation fails do to lack of resources (too many open
files or not enough memory), it does a full garbage collection and
tries the operation again. Lack of resources are "too many open
files" (process wise and system wise) and "not enough memory".
The code is full of '#if's because error codes are not part
of the standard ISO C.
---
liolib.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
loslib.c | 3 +--
2 files changed, 65 insertions(+), 6 deletions(-)
diff --git a/liolib.c b/liolib.c
index 0884e9ac40..75e10ded0b 100644
--- a/liolib.c
+++ b/liolib.c
@@ -1,5 +1,5 @@
/*
-** $Id: liolib.c,v 2.155 2018/02/21 13:48:44 roberto Exp roberto $
+** $Id: liolib.c $
** Standard I/O (and system) library
** See Copyright Notice in lua.h
*/
@@ -68,7 +68,7 @@ static int l_checkmode (const char *mode) {
/* ISO C definitions */
#define l_popen(L,c,m) \
- ((void)((void)c, m), \
+ ((void)c, (void)m, \
luaL_error(L, "'popen' not supported"), \
(FILE*)0)
#define l_pclose(L,file) ((void)L, (void)file, -1)
@@ -133,6 +133,51 @@ static int l_checkmode (const char *mode) {
/* }====================================================== */
+/*
+** {======================================================
+** 'resourcetryagain'
+** This function uses 'errno' to check whether the last error was
+** related to lack of resources (e.g., not enough memory or too many
+** open files). If so, the function performs a full garbage collection
+** to try to release resources, and then it returns 1 to signal to
+** the caller that it is worth trying again the failed operation.
+** Otherwise, it returns 0. Because error codes are not ANSI C, the
+** code must handle any combination of error codes that are defined.
+** =======================================================
+*/
+
+static int resourcetryagain (lua_State *L) {
+
+/* these are the resource-related errors in Linux */
+#if defined(EMFILE) || defined(ENFILE) || defined(ENOMEM)
+
+#if !defined(EMFILE) /* too many open files in the process */
+#define EMFILE -1 /* if not defined, use an impossible value */
+#endif
+
+#if !defined(ENFILE) /* too many open files in the system */
+#define ENFILE -1
+#endif
+
+#if !defined(ENOMEM) /* not enough memory */
+#define ENOMEM -1
+#endif
+
+ if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
+ lua_gc(L, LUA_GCCOLLECT); /* try to release resources with a full GC */
+ return 1; /* signal to try again the creation */
+ }
+
+#endif
+
+ return 0; /* else, asume errors are not due to lack of resources */
+
+}
+
+/* }====================================================== */
+
+
+
#define IO_PREFIX "_IO_"
#define IOPREF_LEN (sizeof(IO_PREFIX)/sizeof(char) - 1)
#define IO_INPUT (IO_PREFIX "input")
@@ -245,9 +290,22 @@ static LStream *newfile (lua_State *L) {
}
+/*
+** Equivalent to 'fopen', but if it fails due to a lack of resources
+** (see 'resourcetryagain'), do an "emergency" garbage collection to try
+** to close some files and then tries to open the file again.
+*/
+static FILE *trytoopen (lua_State *L, const char *path, const char *mode) {
+ FILE *f = fopen(path, mode);
+ if (f == NULL && resourcetryagain(L)) /* resource failure? */
+ f = fopen(path, mode); /* try to open again */
+ return f;
+}
+
+
static void opencheck (lua_State *L, const char *fname, const char *mode) {
LStream *p = newfile(L);
- p->f = fopen(fname, mode);
+ p->f = trytoopen(L, fname, mode);
if (p->f == NULL)
luaL_error(L, "cannot open file '%s' (%s)", fname, strerror(errno));
}
@@ -259,7 +317,7 @@ static int io_open (lua_State *L) {
LStream *p = newfile(L);
const char *md = mode; /* to traverse/check mode */
luaL_argcheck(L, l_checkmode(md), 2, "invalid mode");
- p->f = fopen(filename, mode);
+ p->f = trytoopen(L, filename, mode);
return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;
}
@@ -278,6 +336,8 @@ static int io_popen (lua_State *L) {
const char *mode = luaL_optstring(L, 2, "r");
LStream *p = newprefile(L);
p->f = l_popen(L, filename, mode);
+ if (p->f == NULL && resourcetryagain(L)) /* resource failure? */
+ p->f = l_popen(L, filename, mode); /* try to open again */
p->closef = &io_pclose;
return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;
}
diff --git a/loslib.c b/loslib.c
index 62988d270b..8809e5ea2a 100644
--- a/loslib.c
+++ b/loslib.c
@@ -1,5 +1,5 @@
/*
-** $Id: loslib.c,v 1.65 2016/07/18 17:58:58 roberto Exp roberto $
+** $Id: loslib.c $
** Standard Operating System library
** See Copyright Notice in lua.h
*/
@@ -10,7 +10,6 @@
#include "lprefix.h"
-#include
#include
#include
#include
From b47f2cd068fb14a27d3da6fb3d08305b7d7b354d Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 25 Jul 2018 14:56:42 -0300
Subject: [PATCH 065/889] Small improvements in the manual
---
manual/manual.of | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/manual/manual.of b/manual/manual.of
index f92be508f9..659daa55b5 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1,4 +1,4 @@
-@Ci{$Id: manual.of,v 1.175 2018/06/18 19:17:35 roberto Exp roberto $}
+@Ci{$Id: manual.of $}
@C{[(-------------------------------------------------------------------------}
@manual{
@@ -4567,7 +4567,8 @@ This is the only option that can raise a memory error.
}
-This function returns 0 if given an invalid option in @id{what}.
+This function returns 0 to signal an invalid option in @id{what};
+even then the valid options are handled correctly.
}
@@ -7488,7 +7489,7 @@ you should call @Lid{math.randomseed} explicitly.
The results from this function have good statistical qualities,
but they are not cryptographically secure.
-(For instance, there are no garanties that it is hard
+(For instance, there are no guarantees that it is hard
to predict future results based on the observation of
some number of previous results.)
@@ -7496,8 +7497,9 @@ some number of previous results.)
@LibEntry{math.randomseed (x [, y])|
-Sets @id{x} and @id{y} as the @Q{seed}
-for the pseudo-random generator:
+The integer parameters @id{x} and @id{y} are
+concatenated into a 128-bit @Q{seed} that
+is used to reinitialize the pseudo-random generator;
equal seeds produce equal sequences of numbers.
The default for @id{y} is zero.
From aa4c5cf190f77ab2730af5e21cfd2b830ff329df Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 25 Jul 2018 15:31:04 -0300
Subject: [PATCH 066/889] Added directory to test file names in '$Id:'
From the point of view of 'git', all names are relative to the root
directory of the project. So, file names in '$Id:' also should be
relative to that directory: the proper name for test file 'all.lua'
is 'testes/all.lua'.
---
testes/all.lua | 2 +-
testes/api.lua | 2 +-
testes/attrib.lua | 2 +-
testes/big.lua | 2 +-
testes/bitwise.lua | 2 +-
testes/calls.lua | 2 +-
testes/closure.lua | 2 +-
testes/code.lua | 2 +-
testes/constructs.lua | 2 +-
testes/coroutine.lua | 2 +-
testes/db.lua | 2 +-
testes/errors.lua | 2 +-
testes/events.lua | 2 +-
testes/files.lua | 2 +-
testes/gc.lua | 2 +-
testes/gengc.lua | 2 +-
testes/goto.lua | 2 +-
testes/literals.lua | 2 +-
testes/locals.lua | 2 +-
testes/main.lua | 2 +-
testes/math.lua | 2 +-
testes/nextvar.lua | 2 +-
testes/pm.lua | 2 +-
testes/sort.lua | 2 +-
testes/strings.lua | 2 +-
testes/tpack.lua | 2 +-
testes/utf8.lua | 2 +-
testes/vararg.lua | 2 +-
testes/verybig.lua | 2 +-
29 files changed, 29 insertions(+), 29 deletions(-)
diff --git a/testes/all.lua b/testes/all.lua
index 3779625400..26d2497659 100755
--- a/testes/all.lua
+++ b/testes/all.lua
@@ -1,5 +1,5 @@
#!../lua
--- $Id: all.lua $
+-- $Id: testes/all.lua $
-- See Copyright Notice at the end of this file
diff --git a/testes/api.lua b/testes/api.lua
index 836a6070a3..bebb6d2d25 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -1,4 +1,4 @@
--- $Id: api.lua,v 1.155 2018/03/09 14:23:48 roberto Exp $
+-- $Id: testes/api.lua $
-- See Copyright Notice in file all.lua
if T==nil then
diff --git a/testes/attrib.lua b/testes/attrib.lua
index 79a08a4f69..dcafd6345d 100644
--- a/testes/attrib.lua
+++ b/testes/attrib.lua
@@ -1,4 +1,4 @@
--- $Id: attrib.lua,v 1.69 2018/03/12 13:51:02 roberto Exp $
+-- $Id: testes/attrib.lua $
-- See Copyright Notice in file all.lua
print "testing require"
diff --git a/testes/big.lua b/testes/big.lua
index ebee1ec0a4..150d15dc06 100644
--- a/testes/big.lua
+++ b/testes/big.lua
@@ -1,4 +1,4 @@
--- $Id: big.lua,v 1.35 2018/03/09 14:23:48 roberto Exp $
+-- $Id: testes/big.lua $
-- See Copyright Notice in file all.lua
if _soft then
diff --git a/testes/bitwise.lua b/testes/bitwise.lua
index 3e7079d386..af542e7f68 100755
--- a/testes/bitwise.lua
+++ b/testes/bitwise.lua
@@ -1,4 +1,4 @@
--- $Id: bitwise.lua,v 1.27 2018/02/21 17:49:39 roberto Exp $
+-- $Id: testes/bitwise.lua $
-- See Copyright Notice in file all.lua
print("testing bitwise operations")
diff --git a/testes/calls.lua b/testes/calls.lua
index 95d9d6d6ed..f5108a470e 100644
--- a/testes/calls.lua
+++ b/testes/calls.lua
@@ -1,4 +1,4 @@
--- $Id: calls.lua,v 1.66 2018/02/09 16:35:21 roberto Exp $
+-- $Id: testes/calls.lua $
-- See Copyright Notice in file all.lua
print("testing functions and calls")
diff --git a/testes/closure.lua b/testes/closure.lua
index 79da3cc0f1..5d090d9116 100644
--- a/testes/closure.lua
+++ b/testes/closure.lua
@@ -1,4 +1,4 @@
--- $Id: closure.lua,v 1.62 2018/03/12 14:19:36 roberto Exp $
+-- $Id: testes/closure.lua $
-- See Copyright Notice in file all.lua
print "testing closures"
diff --git a/testes/code.lua b/testes/code.lua
index 1a01c4a22e..6bd6ebfa8f 100644
--- a/testes/code.lua
+++ b/testes/code.lua
@@ -1,4 +1,4 @@
--- $Id: code.lua,v 1.55 2018/03/12 14:19:36 roberto Exp $
+-- $Id: testes/code.lua $
-- See Copyright Notice in file all.lua
if T==nil then
diff --git a/testes/constructs.lua b/testes/constructs.lua
index 7796c46f72..a83df79eaf 100644
--- a/testes/constructs.lua
+++ b/testes/constructs.lua
@@ -1,4 +1,4 @@
--- $Id: constructs.lua,v 1.43 2018/02/21 17:41:07 roberto Exp $
+-- $Id: testes/constructs.lua $
-- See Copyright Notice in file all.lua
;;print "testing syntax";;
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index 220873200d..182c1e185e 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -1,4 +1,4 @@
--- $Id: coroutine.lua,v 1.48 2018/03/12 14:19:36 roberto Exp $
+-- $Id: testes/coroutine.lua $
-- See Copyright Notice in file all.lua
print "testing coroutines"
diff --git a/testes/db.lua b/testes/db.lua
index 36d1cdaab6..2feaaef183 100644
--- a/testes/db.lua
+++ b/testes/db.lua
@@ -1,4 +1,4 @@
--- $Id: db.lua,v 1.90 2018/04/02 17:55:58 roberto Exp $
+-- $Id: testes/db.lua $
-- See Copyright Notice in file all.lua
-- testing debug library
diff --git a/testes/errors.lua b/testes/errors.lua
index 19a7b6faad..142e8b3325 100644
--- a/testes/errors.lua
+++ b/testes/errors.lua
@@ -1,4 +1,4 @@
--- $Id: errors.lua $
+-- $Id: testes/errors.lua $
-- See Copyright Notice in file all.lua
print("testing errors")
diff --git a/testes/events.lua b/testes/events.lua
index cf064d3d6b..21a822a7cf 100644
--- a/testes/events.lua
+++ b/testes/events.lua
@@ -1,4 +1,4 @@
--- $Id: events.lua,v 1.52 2018/03/12 13:51:02 roberto Exp $
+-- $Id: testes/events.lua $
-- See Copyright Notice in file all.lua
print('testing metatables')
diff --git a/testes/files.lua b/testes/files.lua
index b2c7c202f1..c3e42235bc 100644
--- a/testes/files.lua
+++ b/testes/files.lua
@@ -1,4 +1,4 @@
--- $Id: files.lua,v 1.101 2018/03/12 13:51:02 roberto Exp $
+-- $Id: testes/files.lua $
-- See Copyright Notice in file all.lua
local debug = require "debug"
diff --git a/testes/gc.lua b/testes/gc.lua
index 05072efd30..8b9179c858 100644
--- a/testes/gc.lua
+++ b/testes/gc.lua
@@ -1,4 +1,4 @@
--- $Id: gc.lua $
+-- $Id: testes/gc.lua $
-- See Copyright Notice in file all.lua
print('testing incremental garbage collection')
diff --git a/testes/gengc.lua b/testes/gengc.lua
index 36aed806ef..b02f471b36 100644
--- a/testes/gengc.lua
+++ b/testes/gengc.lua
@@ -1,4 +1,4 @@
--- $Id: gengc.lua $
+-- $Id: testes/gengc.lua $
-- See Copyright Notice in file all.lua
print('testing generational garbage collection')
diff --git a/testes/goto.lua b/testes/goto.lua
index d22601f934..238bc04aba 100644
--- a/testes/goto.lua
+++ b/testes/goto.lua
@@ -1,4 +1,4 @@
--- $Id: goto.lua,v 1.15 2017/11/30 13:31:07 roberto Exp $
+-- $Id: testes/goto.lua $
-- See Copyright Notice in file all.lua
collectgarbage()
diff --git a/testes/literals.lua b/testes/literals.lua
index 3922b3f502..76c08f12b6 100644
--- a/testes/literals.lua
+++ b/testes/literals.lua
@@ -1,4 +1,4 @@
--- $Id: literals.lua,v 1.36 2016/11/07 13:11:28 roberto Exp $
+-- $Id: testes/literals.lua $
-- See Copyright Notice in file all.lua
print('testing scanner')
diff --git a/testes/locals.lua b/testes/locals.lua
index f0780a031b..14e49a7ce6 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -1,4 +1,4 @@
--- $Id: locals.lua,v 1.41 2018/06/19 12:25:39 roberto Exp $
+-- $Id: testes/locals.lua $
-- See Copyright Notice in file all.lua
print('testing local variables and environments')
diff --git a/testes/main.lua b/testes/main.lua
index 582b39c02e..c7bde0d962 100644
--- a/testes/main.lua
+++ b/testes/main.lua
@@ -1,5 +1,5 @@
# testing special comment on first line
--- $Id: main.lua,v 1.69 2018/06/19 12:23:50 roberto Exp $
+-- $Id: testes/main.lua $
-- See Copyright Notice in file all.lua
-- most (all?) tests here assume a reasonable "Unix-like" shell
diff --git a/testes/math.lua b/testes/math.lua
index e5c9d8ec5f..853dc20fb4 100644
--- a/testes/math.lua
+++ b/testes/math.lua
@@ -1,4 +1,4 @@
--- $Id: math.lua,v 1.86 2018/05/09 14:55:52 roberto Exp roberto $
+-- $Id: testes/math.lua $
-- See Copyright Notice in file all.lua
print("testing numbers and math lib")
diff --git a/testes/nextvar.lua b/testes/nextvar.lua
index 3ac3acd94e..d2306ed1ff 100644
--- a/testes/nextvar.lua
+++ b/testes/nextvar.lua
@@ -1,4 +1,4 @@
--- $Id: nextvar.lua,v 1.85 2018/06/19 12:24:19 roberto Exp $
+-- $Id: testes/nextvar.lua $
-- See Copyright Notice in file all.lua
print('testing tables, next, and for')
diff --git a/testes/pm.lua b/testes/pm.lua
index e517c8b668..cdcf3becb8 100644
--- a/testes/pm.lua
+++ b/testes/pm.lua
@@ -1,4 +1,4 @@
--- $Id: pm.lua,v 1.50 2018/03/12 14:19:36 roberto Exp $
+-- $Id: testes/pm.lua $
-- See Copyright Notice in file all.lua
print('testing pattern matching')
diff --git a/testes/sort.lua b/testes/sort.lua
index 6eb9b70643..ef405d9219 100644
--- a/testes/sort.lua
+++ b/testes/sort.lua
@@ -1,4 +1,4 @@
--- $Id: sort.lua,v 1.39 2018/03/12 13:51:02 roberto Exp $
+-- $Id: testes/sort.lua $
-- See Copyright Notice in file all.lua
print "testing (parts of) table library"
diff --git a/testes/strings.lua b/testes/strings.lua
index dd720b65a7..1260dbcc26 100644
--- a/testes/strings.lua
+++ b/testes/strings.lua
@@ -1,4 +1,4 @@
--- $Id: strings.lua,v 1.89 2018/06/19 12:25:15 roberto Exp $
+-- $Id: testes/strings.lua $
-- See Copyright Notice in file all.lua
print('testing strings and string library')
diff --git a/testes/tpack.lua b/testes/tpack.lua
index 0e639cc5b4..4c5fc7f74d 100644
--- a/testes/tpack.lua
+++ b/testes/tpack.lua
@@ -1,4 +1,4 @@
--- $Id: tpack.lua,v 1.14 2018/06/04 14:26:32 roberto Exp $
+-- $Id: testes/tpack.lua $
-- See Copyright Notice in file all.lua
local pack = string.pack
diff --git a/testes/utf8.lua b/testes/utf8.lua
index ebc190b75d..4b6a57fd49 100644
--- a/testes/utf8.lua
+++ b/testes/utf8.lua
@@ -1,4 +1,4 @@
--- $Id: utf8.lua,v 1.12 2016/11/07 13:11:28 roberto Exp $
+-- $Id: testes/utf8.lua $
-- See Copyright Notice in file all.lua
print "testing UTF-8 library"
diff --git a/testes/vararg.lua b/testes/vararg.lua
index 4d5ce4abe8..44848d254f 100644
--- a/testes/vararg.lua
+++ b/testes/vararg.lua
@@ -1,4 +1,4 @@
--- $Id: vararg.lua,v 1.29 2018/03/12 14:19:36 roberto Exp $
+-- $Id: testes/vararg.lua $
-- See Copyright Notice in file all.lua
print('testing vararg')
diff --git a/testes/verybig.lua b/testes/verybig.lua
index 5b29dea7ae..8fb7b13e8a 100644
--- a/testes/verybig.lua
+++ b/testes/verybig.lua
@@ -1,4 +1,4 @@
--- $Id: verybig.lua,v 1.27 2018/03/09 14:23:48 roberto Exp $
+-- $Id: testes/verybig.lua $
-- See Copyright Notice in file all.lua
print "testing RK"
From 3d838f635cc81ec3332f9a904992db1c6d8a46ad Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 27 Jul 2018 15:50:53 -0300
Subject: [PATCH 067/889] Added "emergency collection" to 'io.tmpfile' and
'os.tmpname'
These operations also can give errors for lack of resources, so they
also will try "emergency collections" in case of resource errors.
Because there are now two libraries with that kind of handling,
'resourcetryagain' was moved to the auxiliary library to be shared
by the libraries.
---
lauxlib.c | 46 ++++++++++++++++++++++++++++++++++++++++-
lauxlib.h | 5 ++++-
liolib.c | 54 ++++++------------------------------------------
loslib.c | 2 ++
manual/manual.of | 14 +++++++++++++
5 files changed, 71 insertions(+), 50 deletions(-)
diff --git a/lauxlib.c b/lauxlib.c
index a8f2cc2e7a..53b8c9bb34 100644
--- a/lauxlib.c
+++ b/lauxlib.c
@@ -1,5 +1,5 @@
/*
-** $Id: lauxlib.c,v 1.294 2018/02/27 18:47:32 roberto Exp roberto $
+** $Id: lauxlib.c $
** Auxiliary functions for building Lua libraries
** See Copyright Notice in lua.h
*/
@@ -290,6 +290,50 @@ LUALIB_API int luaL_execresult (lua_State *L, int stat) {
/* }====================================================== */
+/*
+** {======================================================
+** 'luaL_resourcetryagain'
+** This function uses 'errno' to check whether the last error was
+** related to lack of resources (e.g., not enough memory or too many
+** open files). If so, the function performs a full garbage collection
+** to try to release resources, and then it returns 1 to signal to
+** the caller that it is worth trying again the failed operation.
+** Otherwise, it returns 0. Because error codes are not ANSI C, the
+** code must handle any combination of error codes that are defined.
+** =======================================================
+*/
+
+LUALIB_API int luaL_resourcetryagain (lua_State *L) {
+
+/* these are the resource-related errors in Linux */
+#if defined(EMFILE) || defined(ENFILE) || defined(ENOMEM)
+
+#if !defined(EMFILE) /* too many open files in the process */
+#define EMFILE -1 /* if not defined, use an impossible value */
+#endif
+
+#if !defined(ENFILE) /* too many open files in the system */
+#define ENFILE -1
+#endif
+
+#if !defined(ENOMEM) /* not enough memory */
+#define ENOMEM -1
+#endif
+
+ if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
+ lua_gc(L, LUA_GCCOLLECT); /* try to release resources with a full GC */
+ return 1; /* signal to try again the creation */
+ }
+
+#endif
+
+ return 0; /* else, asume errors are not due to lack of resources */
+
+}
+
+/* }====================================================== */
+
+
/*
** {======================================================
** Userdata's metatable manipulation
diff --git a/lauxlib.h b/lauxlib.h
index 9f91f6a6b3..cd4d01e50a 100644
--- a/lauxlib.h
+++ b/lauxlib.h
@@ -1,5 +1,5 @@
/*
-** $Id: lauxlib.h,v 1.133 2017/06/27 18:32:49 roberto Exp roberto $
+** $Id: lauxlib.h $
** Auxiliary functions for building Lua libraries
** See Copyright Notice in lua.h
*/
@@ -77,6 +77,9 @@ LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def,
LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname);
LUALIB_API int (luaL_execresult) (lua_State *L, int stat);
+LUALIB_API int (luaL_resourcetryagain) (lua_State *L);
+
+
/* predefined references */
#define LUA_NOREF (-2)
#define LUA_REFNIL (-1)
diff --git a/liolib.c b/liolib.c
index 75e10ded0b..21305b8c76 100644
--- a/liolib.c
+++ b/liolib.c
@@ -133,50 +133,6 @@ static int l_checkmode (const char *mode) {
/* }====================================================== */
-/*
-** {======================================================
-** 'resourcetryagain'
-** This function uses 'errno' to check whether the last error was
-** related to lack of resources (e.g., not enough memory or too many
-** open files). If so, the function performs a full garbage collection
-** to try to release resources, and then it returns 1 to signal to
-** the caller that it is worth trying again the failed operation.
-** Otherwise, it returns 0. Because error codes are not ANSI C, the
-** code must handle any combination of error codes that are defined.
-** =======================================================
-*/
-
-static int resourcetryagain (lua_State *L) {
-
-/* these are the resource-related errors in Linux */
-#if defined(EMFILE) || defined(ENFILE) || defined(ENOMEM)
-
-#if !defined(EMFILE) /* too many open files in the process */
-#define EMFILE -1 /* if not defined, use an impossible value */
-#endif
-
-#if !defined(ENFILE) /* too many open files in the system */
-#define ENFILE -1
-#endif
-
-#if !defined(ENOMEM) /* not enough memory */
-#define ENOMEM -1
-#endif
-
- if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
- lua_gc(L, LUA_GCCOLLECT); /* try to release resources with a full GC */
- return 1; /* signal to try again the creation */
- }
-
-#endif
-
- return 0; /* else, asume errors are not due to lack of resources */
-
-}
-
-/* }====================================================== */
-
-
#define IO_PREFIX "_IO_"
#define IOPREF_LEN (sizeof(IO_PREFIX)/sizeof(char) - 1)
@@ -292,12 +248,12 @@ static LStream *newfile (lua_State *L) {
/*
** Equivalent to 'fopen', but if it fails due to a lack of resources
-** (see 'resourcetryagain'), do an "emergency" garbage collection to try
-** to close some files and then tries to open the file again.
+** (see 'luaL_resourcetryagain'), do an "emergency" garbage collection
+** to try to close some files and then tries to open the file again.
*/
static FILE *trytoopen (lua_State *L, const char *path, const char *mode) {
FILE *f = fopen(path, mode);
- if (f == NULL && resourcetryagain(L)) /* resource failure? */
+ if (f == NULL && luaL_resourcetryagain(L)) /* resource failure? */
f = fopen(path, mode); /* try to open again */
return f;
}
@@ -336,7 +292,7 @@ static int io_popen (lua_State *L) {
const char *mode = luaL_optstring(L, 2, "r");
LStream *p = newprefile(L);
p->f = l_popen(L, filename, mode);
- if (p->f == NULL && resourcetryagain(L)) /* resource failure? */
+ if (p->f == NULL && luaL_resourcetryagain(L)) /* resource failure? */
p->f = l_popen(L, filename, mode); /* try to open again */
p->closef = &io_pclose;
return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;
@@ -346,6 +302,8 @@ static int io_popen (lua_State *L) {
static int io_tmpfile (lua_State *L) {
LStream *p = newfile(L);
p->f = tmpfile();
+ if (p->f == NULL && luaL_resourcetryagain(L)) /* resource failure? */
+ p->f = tmpfile(); /* try to open again */
return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1;
}
diff --git a/loslib.c b/loslib.c
index 8809e5ea2a..1962f55fb5 100644
--- a/loslib.c
+++ b/loslib.c
@@ -166,6 +166,8 @@ static int os_tmpname (lua_State *L) {
char buff[LUA_TMPNAMBUFSIZE];
int err;
lua_tmpnam(buff, err);
+ if (err && luaL_resourcetryagain(L)) /* resource failure? */
+ lua_tmpnam(buff, err); /* try again */
if (err)
return luaL_error(L, "unable to generate a unique filename");
lua_pushstring(L, buff);
diff --git a/manual/manual.of b/manual/manual.of
index 659daa55b5..5a8b1b2c28 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -5538,6 +5538,20 @@ Leaves a copy of the module on the stack.
}
+@APIEntry{int luaL_resourcetryagain (lua_State *L);|
+@apii{0,0,m}
+
+Try to release resources in case of errors.
+This function uses @id{errno} to check whether the last error was
+related to lack of resources (e.g., not enough memory or too many
+open files).
+If so, the function performs a full garbage collection
+to try to release resources, and then it returns 1 to signal to
+the caller that it is worth trying again the failed operation.
+Otherwise, it returns 0.
+
+}
+
@APIEntry{void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup);|
@apii{nup,0,m}
From faaf7e481fbadc2ef520a96b638dd910f3c9ff14 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 16 Aug 2018 14:38:05 -0300
Subject: [PATCH 068/889] Removed use of 'rl_inhibit_completion' in 'lua.c'
Some old systems (e.g., Mac OS X 10.4) do not define
'rl_inhibit_completion', even when line history is available.
Anyway, the user can configure this option externally, using '~/.inputrc'.
---
lua.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/lua.c b/lua.c
index ae783a0d7e..fa534ba2b4 100644
--- a/lua.c
+++ b/lua.c
@@ -1,5 +1,5 @@
/*
-** $Id: lua.c,v 1.234 2018/03/06 20:30:17 roberto Exp roberto $
+** $Id: lua.c $
** Lua stand-alone interpreter
** See Copyright Notice in lua.h
*/
@@ -383,8 +383,7 @@ static int handle_luainit (lua_State *L) {
#include
#include
-#define lua_initreadline(L) \
- ((void)L, rl_readline_name="lua", rl_inhibit_completion=1)
+#define lua_initreadline(L) ((void)L, rl_readline_name="lua")
#define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL)
#define lua_saveline(L,line) ((void)L, add_history(line))
#define lua_freeline(L,b) ((void)L, free(b))
From 3dcd04ad610d151afbe8cd92c43e2232e621fbb5 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 17 Aug 2018 15:53:39 -0300
Subject: [PATCH 069/889] details
Minor optimizations in 'lvm.c'. (Not exactly optimizations, but more
chances for optimizations.)
---
lvm.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/lvm.c b/lvm.c
index 4e4ef270e4..7550dcc8c0 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1,5 +1,5 @@
/*
-** $Id: lvm.c,v 2.359 2018/06/18 17:58:21 roberto Exp roberto $
+** $Id: lvm.c $
** Lua virtual machine
** See Copyright Notice in lua.h
*/
@@ -621,8 +621,8 @@ void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) {
** otherwise 'floor(q) == trunc(q) - 1'.
*/
lua_Integer luaV_div (lua_State *L, lua_Integer m, lua_Integer n) {
- if (l_castS2U(n) + 1u <= 1u) { /* special cases: -1 or 0 */
- if (unlikely(n == 0))
+ if (unlikely(l_castS2U(n) + 1u <= 1u)) { /* special cases: -1 or 0 */
+ if (n == 0)
luaG_runerror(L, "attempt to divide by zero");
return intop(-, 0, m); /* n==-1; avoid overflow with 0x80000...//-1 */
}
@@ -641,14 +641,14 @@ lua_Integer luaV_div (lua_State *L, lua_Integer m, lua_Integer n) {
** about luaV_div.)
*/
lua_Integer luaV_mod (lua_State *L, lua_Integer m, lua_Integer n) {
- if (l_castS2U(n) + 1u <= 1u) { /* special cases: -1 or 0 */
- if (unlikely(n == 0))
+ if (unlikely(l_castS2U(n) + 1u <= 1u)) { /* special cases: -1 or 0 */
+ if (n == 0)
luaG_runerror(L, "attempt to perform 'n%%0'");
return 0; /* m % -1 == 0; avoid overflow with 0x80000...%-1 */
}
else {
lua_Integer r = m % n;
- if (r != 0 && (m ^ n) < 0) /* 'm/n' would be non-integer negative? */
+ if (r != 0 && (r ^ n) < 0) /* 'm/n' would be non-integer negative? */
r += n; /* correct result for different rounding */
return r;
}
From f99509581ee73c1c2dbddb3398e87c098771d31f Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 23 Aug 2018 14:26:12 -0300
Subject: [PATCH 070/889] Removed extra information from RCS keyword strings
Version numbers and dates (mostly wrong) from RCS keyword strings
removed from all source files; only the file name are kept.
---
lapi.c | 2 +-
lapi.h | 2 +-
lbaselib.c | 2 +-
lcode.h | 2 +-
lcorolib.c | 2 +-
lctype.c | 2 +-
lctype.h | 2 +-
ldblib.c | 2 +-
ldebug.c | 2 +-
ldebug.h | 2 +-
ldo.h | 2 +-
ldump.c | 2 +-
lfunc.c | 2 +-
lfunc.h | 2 +-
lgc.h | 2 +-
linit.c | 2 +-
ljumptab.h | 2 +-
llex.c | 2 +-
llex.h | 2 +-
llimits.h | 2 +-
lmathlib.c | 2 +-
lmem.c | 2 +-
lmem.h | 2 +-
loadlib.c | 2 +-
lobject.c | 2 +-
lobject.h | 2 +-
lopcodes.c | 2 +-
lopcodes.h | 2 +-
lopnames.h | 2 +-
lparser.c | 2 +-
lparser.h | 2 +-
lprefix.h | 2 +-
lstate.c | 2 +-
lstring.c | 2 +-
lstring.h | 2 +-
lstrlib.c | 2 +-
ltable.c | 2 +-
ltable.h | 2 +-
ltablib.c | 2 +-
ltests.c | 2 +-
ltests.h | 2 +-
ltm.c | 2 +-
ltm.h | 2 +-
lua.h | 2 +-
luaconf.h | 2 +-
lualib.h | 2 +-
lundump.c | 2 +-
lundump.h | 2 +-
lvm.h | 2 +-
lzio.c | 2 +-
lzio.h | 2 +-
51 files changed, 51 insertions(+), 51 deletions(-)
diff --git a/lapi.c b/lapi.c
index e69215a881..0ca9a32145 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1,5 +1,5 @@
/*
-** $Id: lapi.c,v 2.294 2018/06/15 19:31:22 roberto Exp roberto $
+** $Id: lapi.c $
** Lua API
** See Copyright Notice in lua.h
*/
diff --git a/lapi.h b/lapi.h
index 77ebb30027..016f78cccd 100644
--- a/lapi.h
+++ b/lapi.h
@@ -1,5 +1,5 @@
/*
-** $Id: lapi.h,v 2.10 2017/11/01 18:20:48 roberto Exp $
+** $Id: lapi.h $
** Auxiliary functions from Lua API
** See Copyright Notice in lua.h
*/
diff --git a/lbaselib.c b/lbaselib.c
index 12a9e888c8..e776c2a25b 100644
--- a/lbaselib.c
+++ b/lbaselib.c
@@ -1,5 +1,5 @@
/*
-** $Id: lbaselib.c,v 1.322 2018/02/27 18:47:32 roberto Exp roberto $
+** $Id: lbaselib.c $
** Basic library
** See Copyright Notice in lua.h
*/
diff --git a/lcode.h b/lcode.h
index 0c4159735c..dd091c7818 100644
--- a/lcode.h
+++ b/lcode.h
@@ -1,5 +1,5 @@
/*
-** $Id: lcode.h,v 1.72 2018/03/19 20:03:44 roberto Exp roberto $
+** $Id: lcode.h $
** Code generator for Lua
** See Copyright Notice in lua.h
*/
diff --git a/lcorolib.c b/lcorolib.c
index 49a3cf28d1..9038f9fb5d 100644
--- a/lcorolib.c
+++ b/lcorolib.c
@@ -1,5 +1,5 @@
/*
-** $Id: lcorolib.c,v 1.10 2016/04/11 19:19:55 roberto Exp roberto $
+** $Id: lcorolib.c $
** Coroutine Library
** See Copyright Notice in lua.h
*/
diff --git a/lctype.c b/lctype.c
index 367640c662..4eaad167ed 100644
--- a/lctype.c
+++ b/lctype.c
@@ -1,5 +1,5 @@
/*
-** $Id: lctype.c,v 1.11 2011/10/03 16:19:23 roberto Exp roberto $
+** $Id: lctype.c $
** 'ctype' functions for Lua
** See Copyright Notice in lua.h
*/
diff --git a/lctype.h b/lctype.h
index de1efd51fc..cbff4d7e19 100644
--- a/lctype.h
+++ b/lctype.h
@@ -1,5 +1,5 @@
/*
-** $Id: lctype.h,v 1.12 2011/07/15 12:50:29 roberto Exp roberto $
+** $Id: lctype.h $
** 'ctype' functions for Lua
** See Copyright Notice in lua.h
*/
diff --git a/ldblib.c b/ldblib.c
index a977654378..200108426e 100644
--- a/ldblib.c
+++ b/ldblib.c
@@ -1,5 +1,5 @@
/*
-** $Id: ldblib.c,v 1.154 2018/03/05 14:15:04 roberto Exp roberto $
+** $Id: ldblib.c $
** Interface from Lua to its debug API
** See Copyright Notice in lua.h
*/
diff --git a/ldebug.c b/ldebug.c
index f3c093a05b..3590010c9a 100644
--- a/ldebug.c
+++ b/ldebug.c
@@ -1,5 +1,5 @@
/*
-** $Id: ldebug.c,v 2.157 2018/05/02 18:17:59 roberto Exp roberto $
+** $Id: ldebug.c $
** Debug Interface
** See Copyright Notice in lua.h
*/
diff --git a/ldebug.h b/ldebug.h
index 3b8e6ae0f5..31ecc2f6cf 100644
--- a/ldebug.h
+++ b/ldebug.h
@@ -1,5 +1,5 @@
/*
-** $Id: ldebug.h,v 2.17 2018/05/02 18:17:59 roberto Exp roberto $
+** $Id: ldebug.h $
** Auxiliary functions from Debug Interface module
** See Copyright Notice in lua.h
*/
diff --git a/ldo.h b/ldo.h
index a3ac6f503a..c836a2a174 100644
--- a/ldo.h
+++ b/ldo.h
@@ -1,5 +1,5 @@
/*
-** $Id: ldo.h,v 2.43 2018/02/17 19:29:29 roberto Exp roberto $
+** $Id: ldo.h $
** Stack and Call structure of Lua
** See Copyright Notice in lua.h
*/
diff --git a/ldump.c b/ldump.c
index 0724aeb5da..aca6245bf4 100644
--- a/ldump.c
+++ b/ldump.c
@@ -1,5 +1,5 @@
/*
-** $Id: ldump.c,v 2.40 2017/11/28 11:19:07 roberto Exp roberto $
+** $Id: ldump.c $
** save precompiled Lua chunks
** See Copyright Notice in lua.h
*/
diff --git a/lfunc.c b/lfunc.c
index 307a857bd9..16e007318d 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -1,5 +1,5 @@
/*
-** $Id: lfunc.c,v 2.50 2017/06/27 11:35:31 roberto Exp roberto $
+** $Id: lfunc.c $
** Auxiliary functions to manipulate prototypes and closures
** See Copyright Notice in lua.h
*/
diff --git a/lfunc.h b/lfunc.h
index 74a9fc2e6e..859ccc1205 100644
--- a/lfunc.h
+++ b/lfunc.h
@@ -1,5 +1,5 @@
/*
-** $Id: lfunc.h,v 2.19 2018/01/28 15:13:26 roberto Exp roberto $
+** $Id: lfunc.h $
** Auxiliary functions to manipulate prototypes and closures
** See Copyright Notice in lua.h
*/
diff --git a/lgc.h b/lgc.h
index d7adf44429..e0a8806b14 100644
--- a/lgc.h
+++ b/lgc.h
@@ -1,5 +1,5 @@
/*
-** $Id: lgc.h,v 2.102 2018/02/19 20:06:56 roberto Exp roberto $
+** $Id: lgc.h $
** Garbage Collector
** See Copyright Notice in lua.h
*/
diff --git a/linit.c b/linit.c
index 2db8d5b30d..69808f84f4 100644
--- a/linit.c
+++ b/linit.c
@@ -1,5 +1,5 @@
/*
-** $Id: linit.c,v 1.40 2017/06/27 18:32:49 roberto Exp roberto $
+** $Id: linit.c $
** Initialization of libraries for lua.c and other clients
** See Copyright Notice in lua.h
*/
diff --git a/ljumptab.h b/ljumptab.h
index 01d2c11e5a..c775f10a4a 100644
--- a/ljumptab.h
+++ b/ljumptab.h
@@ -1,5 +1,5 @@
/*
-** $Id: ljumptab.h 2018/07/10 13:40:16 $
+** $Id: ljumptab.h $
** Jump Table for the Lua interpreter
** See Copyright Notice in lua.h
*/
diff --git a/llex.c b/llex.c
index 9419a8cdf0..4a25607cf3 100644
--- a/llex.c
+++ b/llex.c
@@ -1,5 +1,5 @@
/*
-** $Id: llex.c,v 2.101 2018/03/07 15:55:38 roberto Exp roberto $
+** $Id: llex.c $
** Lexical Analyzer
** See Copyright Notice in lua.h
*/
diff --git a/llex.h b/llex.h
index d1d5a4a8f5..d1a4cba7c2 100644
--- a/llex.h
+++ b/llex.h
@@ -1,5 +1,5 @@
/*
-** $Id: llex.h,v 1.81 2018/03/07 15:55:38 roberto Exp roberto $
+** $Id: llex.h $
** Lexical Analyzer
** See Copyright Notice in lua.h
*/
diff --git a/llimits.h b/llimits.h
index 715d55b811..6afa8997e0 100644
--- a/llimits.h
+++ b/llimits.h
@@ -1,5 +1,5 @@
/*
-** $Id: llimits.h,v 1.150 2018/05/30 14:25:52 roberto Exp roberto $
+** $Id: llimits.h $
** Limits, basic types, and some other 'installation-dependent' definitions
** See Copyright Notice in lua.h
*/
diff --git a/lmathlib.c b/lmathlib.c
index 4101279731..e8e88e7a5b 100644
--- a/lmathlib.c
+++ b/lmathlib.c
@@ -1,5 +1,5 @@
/*
-** $Id: lmathlib.c,v 1.134 2018/05/16 11:27:59 roberto Exp roberto $
+** $Id: lmathlib.c $
** Standard mathematical library
** See Copyright Notice in lua.h
*/
diff --git a/lmem.c b/lmem.c
index 56b4affe0e..7751870741 100644
--- a/lmem.c
+++ b/lmem.c
@@ -1,5 +1,5 @@
/*
-** $Id: lmem.c,v 1.97 2018/05/30 14:25:52 roberto Exp roberto $
+** $Id: lmem.c $
** Interface to Memory Manager
** See Copyright Notice in lua.h
*/
diff --git a/lmem.h b/lmem.h
index f87c913248..8c75a44beb 100644
--- a/lmem.h
+++ b/lmem.h
@@ -1,5 +1,5 @@
/*
-** $Id: lmem.h,v 1.46 2017/12/08 17:28:25 roberto Exp roberto $
+** $Id: lmem.h $
** Interface to Memory Manager
** See Copyright Notice in lua.h
*/
diff --git a/loadlib.c b/loadlib.c
index 8c8ab8c586..a6ce30d4f3 100644
--- a/loadlib.c
+++ b/loadlib.c
@@ -1,5 +1,5 @@
/*
-** $Id: loadlib.c,v 1.133 2018/07/06 13:38:38 roberto Exp $
+** $Id: loadlib.c $
** Dynamic library loader for Lua
** See Copyright Notice in lua.h
**
diff --git a/lobject.c b/lobject.c
index 8b8de37c37..79cf55b8e1 100644
--- a/lobject.c
+++ b/lobject.c
@@ -1,5 +1,5 @@
/*
-** $Id: lobject.c,v 2.125 2018/04/25 16:26:20 roberto Exp roberto $
+** $Id: lobject.c $
** Some generic functions over Lua objects
** See Copyright Notice in lua.h
*/
diff --git a/lobject.h b/lobject.h
index dbb9982d03..ddcf609cf6 100644
--- a/lobject.h
+++ b/lobject.h
@@ -1,5 +1,5 @@
/*
-** $Id: lobject.h,v 2.145 2018/06/15 14:14:20 roberto Exp roberto $
+** $Id: lobject.h $
** Type definitions for Lua objects
** See Copyright Notice in lua.h
*/
diff --git a/lopcodes.c b/lopcodes.c
index 1a3b0e6af8..95347b2728 100644
--- a/lopcodes.c
+++ b/lopcodes.c
@@ -1,5 +1,5 @@
/*
-** $Id: lopcodes.c,v 1.83 2018/06/26 18:00:55 roberto Exp $
+** $Id: lopcodes.c $
** Opcodes for Lua virtual machine
** See Copyright Notice in lua.h
*/
diff --git a/lopcodes.h b/lopcodes.h
index d7b5dfe013..9442a33689 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -1,5 +1,5 @@
/*
-** $Id: lopcodes.h,v 1.194 2018/06/26 18:00:55 roberto Exp $
+** $Id: lopcodes.h $
** Opcodes for Lua virtual machine
** See Copyright Notice in lua.h
*/
diff --git a/lopnames.h b/lopnames.h
index ab434b5979..c40eaeae26 100644
--- a/lopnames.h
+++ b/lopnames.h
@@ -1,5 +1,5 @@
/*
-** $Id: lopnames.h,v 1.1 2018/06/26 18:00:55 roberto Exp $
+** $Id: lopnames.h $
** Opcode names
** See Copyright Notice in lua.h
*/
diff --git a/lparser.c b/lparser.c
index 3c68d1c240..9f156dd973 100644
--- a/lparser.c
+++ b/lparser.c
@@ -1,5 +1,5 @@
/*
-** $Id: lparser.c,v 2.180 2018/04/04 14:23:41 roberto Exp roberto $
+** $Id: lparser.c $
** Lua Parser
** See Copyright Notice in lua.h
*/
diff --git a/lparser.h b/lparser.h
index da6a7483ee..e158c9d918 100644
--- a/lparser.h
+++ b/lparser.h
@@ -1,5 +1,5 @@
/*
-** $Id: lparser.h,v 1.81 2018/03/07 15:55:38 roberto Exp roberto $
+** $Id: lparser.h $
** Lua Parser
** See Copyright Notice in lua.h
*/
diff --git a/lprefix.h b/lprefix.h
index 6b72f0eba7..dd14767ed2 100644
--- a/lprefix.h
+++ b/lprefix.h
@@ -1,5 +1,5 @@
/*
-** $Id: lprefix.h,v 1.1 2014/11/03 15:12:44 roberto Exp roberto $
+** $Id: lprefix.h $
** Definitions for Lua code that must come before any other header file
** See Copyright Notice in lua.h
*/
diff --git a/lstate.c b/lstate.c
index d4b4def80a..8b0219bccd 100644
--- a/lstate.c
+++ b/lstate.c
@@ -1,5 +1,5 @@
/*
-** $Id: lstate.c,v 2.154 2018/06/15 19:31:22 roberto Exp roberto $
+** $Id: lstate.c $
** Global State
** See Copyright Notice in lua.h
*/
diff --git a/lstring.c b/lstring.c
index f1e5d82b16..c52539a6d5 100644
--- a/lstring.c
+++ b/lstring.c
@@ -1,5 +1,5 @@
/*
-** $Id: lstring.c,v 2.65 2018/02/20 16:52:50 roberto Exp roberto $
+** $Id: lstring.c $
** String table (keeps all strings handled by Lua)
** See Copyright Notice in lua.h
*/
diff --git a/lstring.h b/lstring.h
index 2be5a3ff0b..e7e4aedc48 100644
--- a/lstring.h
+++ b/lstring.h
@@ -1,5 +1,5 @@
/*
-** $Id: lstring.h,v 1.63 2017/11/23 19:29:04 roberto Exp roberto $
+** $Id: lstring.h $
** String table (keep all strings handled by Lua)
** See Copyright Notice in lua.h
*/
diff --git a/lstrlib.c b/lstrlib.c
index b43464abcc..a635e9d4d6 100644
--- a/lstrlib.c
+++ b/lstrlib.c
@@ -1,5 +1,5 @@
/*
-** $Id: lstrlib.c,v 1.262 2018/02/21 17:48:31 roberto Exp roberto $
+** $Id: lstrlib.c $
** Standard library for string operations and pattern-matching
** See Copyright Notice in lua.h
*/
diff --git a/ltable.c b/ltable.c
index 6f9c4035dd..e12381b2d8 100644
--- a/ltable.c
+++ b/ltable.c
@@ -1,5 +1,5 @@
/*
-** $Id: ltable.c,v 2.139 2018/06/15 14:14:20 roberto Exp roberto $
+** $Id: ltable.c $
** Lua tables (hash)
** See Copyright Notice in lua.h
*/
diff --git a/ltable.h b/ltable.h
index ba2dddc8f2..9565833f64 100644
--- a/ltable.h
+++ b/ltable.h
@@ -1,5 +1,5 @@
/*
-** $Id: ltable.h,v 2.27 2018/06/01 16:51:34 roberto Exp roberto $
+** $Id: ltable.h $
** Lua tables (hash)
** See Copyright Notice in lua.h
*/
diff --git a/ltablib.c b/ltablib.c
index 7bbc3a0715..29c53e9483 100644
--- a/ltablib.c
+++ b/ltablib.c
@@ -1,5 +1,5 @@
/*
-** $Id: ltablib.c,v 1.96 2018/03/16 14:18:18 roberto Exp roberto $
+** $Id: ltablib.c $
** Library for Table Manipulation
** See Copyright Notice in lua.h
*/
diff --git a/ltests.c b/ltests.c
index 13717da9b3..bc71d937f5 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1,5 +1,5 @@
/*
-** $Id: ltests.c,v 2.246 2018/06/26 18:00:55 roberto Exp roberto $
+** $Id: ltests.c $
** Internal Module for Debugging of the Lua Implementation
** See Copyright Notice in lua.h
*/
diff --git a/ltests.h b/ltests.h
index 8d965e0a6c..54bc4f5f7e 100644
--- a/ltests.h
+++ b/ltests.h
@@ -1,5 +1,5 @@
/*
-** $Id: ltests.h,v 2.58 2018/04/19 15:42:41 roberto Exp roberto $
+** $Id: ltests.h $
** Internal Header for Debugging of the Lua Implementation
** See Copyright Notice in lua.h
*/
diff --git a/ltm.c b/ltm.c
index d4e2b447b9..1c1a18b7a3 100644
--- a/ltm.c
+++ b/ltm.c
@@ -1,5 +1,5 @@
/*
-** $Id: ltm.c,v 2.69 2018/06/08 19:06:59 roberto Exp roberto $
+** $Id: ltm.c $
** Tag methods
** See Copyright Notice in lua.h
*/
diff --git a/ltm.h b/ltm.h
index 12ae09ded7..89aee78adc 100644
--- a/ltm.h
+++ b/ltm.h
@@ -1,5 +1,5 @@
/*
-** $Id: ltm.h,v 2.38 2018/06/08 19:06:59 roberto Exp roberto $
+** $Id: ltm.h $
** Tag methods
** See Copyright Notice in lua.h
*/
diff --git a/lua.h b/lua.h
index 03d92c0ba3..a014be1fdd 100644
--- a/lua.h
+++ b/lua.h
@@ -1,5 +1,5 @@
/*
-** $Id: lua.h,v 1.346 2018/04/04 14:23:41 roberto Exp roberto $
+** $Id: lua.h $
** Lua - A Scripting Language
** Lua.org, PUC-Rio, Brazil (http://www.lua.org)
** See Copyright Notice at the end of this file
diff --git a/luaconf.h b/luaconf.h
index 9cddb263b8..cc8e1bdcff 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -1,5 +1,5 @@
/*
-** $Id: luaconf.h,v 1.270 2018/06/18 12:51:05 roberto Exp roberto $
+** $Id: luaconf.h $
** Configuration file for Lua
** See Copyright Notice in lua.h
*/
diff --git a/lualib.h b/lualib.h
index 12f7d02277..eb08b530a6 100644
--- a/lualib.h
+++ b/lualib.h
@@ -1,5 +1,5 @@
/*
-** $Id: lualib.h,v 1.45 2017/01/12 17:14:26 roberto Exp roberto $
+** $Id: lualib.h $
** Lua standard libraries
** See Copyright Notice in lua.h
*/
diff --git a/lundump.c b/lundump.c
index 64b643623a..00ff6deffb 100644
--- a/lundump.c
+++ b/lundump.c
@@ -1,5 +1,5 @@
/*
-** $Id: lundump.c,v 2.49 2017/12/07 18:59:52 roberto Exp roberto $
+** $Id: lundump.c $
** load precompiled Lua chunks
** See Copyright Notice in lua.h
*/
diff --git a/lundump.h b/lundump.h
index bc9f99a29c..739c7fcde0 100644
--- a/lundump.h
+++ b/lundump.h
@@ -1,5 +1,5 @@
/*
-** $Id: lundump.h,v 1.44 2014/06/19 18:27:20 roberto Exp roberto $
+** $Id: lundump.h $
** load precompiled Lua chunks
** See Copyright Notice in lua.h
*/
diff --git a/lvm.h b/lvm.h
index a789acc402..d58daacdc0 100644
--- a/lvm.h
+++ b/lvm.h
@@ -1,5 +1,5 @@
/*
-** $Id: lvm.h,v 2.51 2018/02/23 13:13:31 roberto Exp roberto $
+** $Id: lvm.h $
** Lua virtual machine
** See Copyright Notice in lua.h
*/
diff --git a/lzio.c b/lzio.c
index eb6151a0b2..cd0a02d5f9 100644
--- a/lzio.c
+++ b/lzio.c
@@ -1,5 +1,5 @@
/*
-** $Id: lzio.c,v 1.36 2014/11/02 19:19:04 roberto Exp roberto $
+** $Id: lzio.c $
** Buffered streams
** See Copyright Notice in lua.h
*/
diff --git a/lzio.h b/lzio.h
index fb310b99c5..38f397fd28 100644
--- a/lzio.h
+++ b/lzio.h
@@ -1,5 +1,5 @@
/*
-** $Id: lzio.h,v 1.30 2014/12/19 17:26:14 roberto Exp roberto $
+** $Id: lzio.h $
** Buffered streams
** See Copyright Notice in lua.h
*/
From 8c8a91f2ef7acccb99e3737913faad8d48b39571 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 24 Aug 2018 10:17:54 -0300
Subject: [PATCH 071/889] Deprecated the emulation of '__le' using '__lt'
As hinted in the manual for Lua 5.3, the emulation of the metamethod
for '__le' using '__le' has been deprecated. It is slow, complicates
the logic, and it is easy to avoid this emulation by defining a proper
'__le' function.
Moreover, often this emulation was used wrongly, with a programmer
assuming that an order is total when it is not (e.g., NaN in
floating-point numbers).
---
lstate.h | 8 +++++---
ltests.h | 1 +
ltm.c | 2 ++
luaconf.h | 8 +++++++-
lvm.c | 2 ++
manual/manual.of | 21 +++++++++------------
testes/coroutine.lua | 8 +++-----
testes/events.lua | 34 +++++++++++++++-------------------
8 files changed, 44 insertions(+), 40 deletions(-)
diff --git a/lstate.h b/lstate.h
index 2a36bd9632..5461b29119 100644
--- a/lstate.h
+++ b/lstate.h
@@ -138,9 +138,11 @@ typedef struct CallInfo {
#define CIST_YPCALL (1<<3) /* call is a yieldable protected call */
#define CIST_TAIL (1<<4) /* call was tail called */
#define CIST_HOOKYIELD (1<<5) /* last hook called yielded */
-#define CIST_LEQ (1<<6) /* using __lt for __le */
-#define CIST_FIN (1<<7) /* call is running a finalizer */
-#define CIST_TRAN (1<<8) /* 'ci' has transfer information */
+#define CIST_FIN (1<<6) /* call is running a finalizer */
+#define CIST_TRAN (1<<7) /* 'ci' has transfer information */
+#if defined(LUA_COMPAT_LT_LE)
+#define CIST_LEQ (1<<8) /* using __lt for __le */
+#endif
/* active function is a Lua function */
#define isLua(ci) (!((ci)->callstatus & CIST_C))
diff --git a/ltests.h b/ltests.h
index 54bc4f5f7e..d44974a444 100644
--- a/ltests.h
+++ b/ltests.h
@@ -13,6 +13,7 @@
/* test Lua with compatibility code */
#define LUA_COMPAT_MATHLIB
+#define LUA_COMPAT_LT_LE
#define LUA_DEBUG
diff --git a/ltm.c b/ltm.c
index 1c1a18b7a3..5c148180aa 100644
--- a/ltm.c
+++ b/ltm.c
@@ -188,6 +188,7 @@ int luaT_callorderTM (lua_State *L, const TValue *p1, const TValue *p2,
TMS event) {
if (callbinTM(L, p1, p2, L->top, event)) /* try original event */
return !l_isfalse(s2v(L->top));
+#if defined(LUA_COMPAT_LT_LE)
else if (event == TM_LE) {
/* try '!(p2 < p1)' for '(p1 <= p2)' */
L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */
@@ -197,6 +198,7 @@ int luaT_callorderTM (lua_State *L, const TValue *p1, const TValue *p2,
}
/* else error will remove this 'ci'; no need to clear mark */
}
+#endif
luaG_ordererror(L, p1, p2); /* no metamethod found */
return 0; /* to avoid warnings */
}
diff --git a/luaconf.h b/luaconf.h
index cc8e1bdcff..126257dce3 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -295,7 +295,7 @@
*/
/*
-@@ LUA_COMPAT_5_3 controls other macros for compatibility with Lua 5.2.
+@@ LUA_COMPAT_5_3 controls other macros for compatibility with Lua 5.3.
** You can define it to get all options, or change specific options
** to fit your specific needs.
*/
@@ -316,6 +316,12 @@
*/
#define LUA_COMPAT_APIINTCASTS
+/*
+@@ LUA_COMPAT_LT_LE controls the emulation of the '__le' metamethod
+** using '__lt'.
+*/
+#define LUA_COMPAT_LT_LE
+
#endif /* } */
diff --git a/lvm.c b/lvm.c
index 7550dcc8c0..add269422c 100644
--- a/lvm.c
+++ b/lvm.c
@@ -754,10 +754,12 @@ void luaV_finishOp (lua_State *L) {
case OP_EQ: { /* note that 'OP_EQI'/'OP_EQK' cannot yield */
int res = !l_isfalse(s2v(L->top - 1));
L->top--;
+#if defined(LUA_COMPAT_LT_LE)
if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */
ci->callstatus ^= CIST_LEQ; /* clear mark */
res = !res; /* negate result */
}
+#endif
lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP);
if (res != GETARG_k(inst)) /* condition failed? */
ci->u.l.savedpc++; /* skip jump instruction */
diff --git a/manual/manual.of b/manual/manual.of
index 5a8b1b2c28..47a551bfa2 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -474,17 +474,7 @@ The result of the call is always converted to a boolean.
@item{@idx{__le}|
the less equal (@T{<=}) operation.
-Unlike other operations,
-the less-equal operation can use two different events.
-First, Lua looks for the @idx{__le} metamethod in both operands,
-like in the less than operation.
-If it cannot find such a metamethod,
-then it will try the @idx{__lt} metamethod,
-assuming that @T{a <= b} is equivalent to @T{not (b < a)}.
-As with the other comparison operators,
-the result is always a boolean.
-(This use of the @idx{__lt} event can be removed in future versions;
-it is also slower than a real @idx{__le} metamethod.)
+Behavior similar to the less than operation.
}
@item{@idx{__index}|
@@ -1643,7 +1633,8 @@ all operations @emphx{wrap around},
according to the usual rules of two-complement arithmetic.
(In other words,
they return the unique representable integer
-that is equal modulo @M{2@sp{64}} to the mathematical result.)
+that is equal modulo @M{2@sp{n}} to the mathematical result,
+where @M{n} is the number of bits of the integer type.)
}
@sect3{bitwise| @title{Bitwise Operators}
@@ -8537,6 +8528,12 @@ For instance, the result of @T{"1" + "2"} now is an integer,
not a float.
}
+@item{
+The use of the @idx{__lt} metamethod to emulate @id{__le}
+has been removed.
+When needed, this metamethod must be explicitly defined.
+}
+
}
}
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index 182c1e185e..36eae44aea 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -1,4 +1,4 @@
--- $Id: testes/coroutine.lua $
+-- $Id: testes/coroutine.lua 2018-07-25 15:31:04 -0300 $
-- See Copyright Notice in file all.lua
print "testing coroutines"
@@ -619,10 +619,8 @@ end
assert(run(function () if (a>=b) then return '>=' else return '<' end end,
{"le", "sub"}) == "<")
--- '<=' using '<'
-mt.__le = nil
assert(run(function () if (a<=b) then return '<=' else return '>' end end,
- {"lt"}) == "<=")
+ {"le", "sub"}) == "<=")
assert(run(function () if (a==b) then return '==' else return '~=' end end,
{"eq"}) == "~=")
@@ -677,7 +675,7 @@ do -- a few more tests for comparsion operators
return val(a) < val(b)
end,
}
- local mt2 = { __lt = mt1.__lt } -- no __le
+ local mt2 = { __lt = mt1.__lt, __le = mt1.__le }
local function run (f)
local co = coroutine.wrap(f)
diff --git a/testes/events.lua b/testes/events.lua
index 21a822a7cf..c4d43d5183 100644
--- a/testes/events.lua
+++ b/testes/events.lua
@@ -1,4 +1,4 @@
--- $Id: testes/events.lua $
+-- $Id: testes/events.lua 2018-07-25 15:31:04 -0300 $
-- See Copyright Notice in file all.lua
print('testing metatables')
@@ -217,6 +217,13 @@ t.__lt = function (a,b,c)
return a= Set{1,2,3,4}))
-assert((Set{1,3} <= Set{3,5})) -- wrong!! model needs a `le' method ;-)
-
t.__le = function (a,b)
for k in pairs(a) do
if not b[k] then return false end
@@ -281,10 +271,15 @@ t.__le = function (a,b)
return true
end
-assert(not (Set{1,3} <= Set{3,5})) -- now its OK!
+assert(Set{1,2,3} < Set{1,2,3,4})
+assert(not(Set{1,2,3,4} < Set{1,2,3,4}))
+assert((Set{1,2,3,4} <= Set{1,2,3,4}))
+assert((Set{1,2,3,4} >= Set{1,2,3,4}))
+assert(not (Set{1,3} <= Set{3,5}))
assert(not(Set{1,3} <= Set{3,5}))
assert(not(Set{1,3} >= Set{3,5}))
+
t.__eq = function (a,b)
for k in pairs(a) do
if not b[k] then return false end
@@ -376,6 +371,7 @@ t1 = {}; c = {}; setmetatable(c, t1)
d = {}
t1.__eq = function () return true end
t1.__lt = function () return true end
+t1.__le = function () return false end
setmetatable(d, t1)
assert(c == d and c < d and not(d <= c))
t2 = {}
From 5382a22e0eea878339c504b2a9a3b36bcd839fcc Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 28 Aug 2018 12:36:58 -0300
Subject: [PATCH 072/889] Corrections in the implementation of '%' for floats.
The multiplication (m*b) used to test whether 'm' is non-zero and
'm' and 'b' have different signs can underflow for very small numbers,
giving a wrong result. The use of explicit comparisons solves this
problem. This commit also adds several new tests for '%' (both for
floats and for integers) to exercise more corner cases, such as
very large and very small values.
---
llimits.h | 14 ++++++-----
lobject.c | 6 +----
lvm.c | 18 +++++++++-----
lvm.h | 1 +
testes/math.lua | 66 ++++++++++++++++++++++++++++++++++++++++++++++++-
5 files changed, 87 insertions(+), 18 deletions(-)
diff --git a/llimits.h b/llimits.h
index 6afa8997e0..e91310a09f 100644
--- a/llimits.h
+++ b/llimits.h
@@ -293,15 +293,17 @@ typedef unsigned long Instruction;
#endif
/*
-** modulo: defined as 'a - floor(a/b)*b'; this definition gives NaN when
-** 'b' is huge, but the result should be 'a'. 'fmod' gives the result of
-** 'a - trunc(a/b)*b', and therefore must be corrected when 'trunc(a/b)
-** ~= floor(a/b)'. That happens when the division has a non-integer
-** negative result, which is equivalent to the test below.
+** modulo: defined as 'a - floor(a/b)*b'; the direct computation
+** using this definition has several problems with rounding errors,
+** so it is better to use 'fmod'. 'fmod' gives the result of
+** 'a - trunc(a/b)*b', and therefore must be corrected when
+** 'trunc(a/b) ~= floor(a/b)'. That happens when the division has a
+** non-integer negative result, which is equivalent to the tests below.
*/
#if !defined(luai_nummod)
#define luai_nummod(L,a,b,m) \
- { (m) = l_mathop(fmod)(a,b); if ((m)*(b) < 0) (m) += (b); }
+ { (void)L; (m) = l_mathop(fmod)(a,b); \
+ if (((m) > 0) ? (b) < 0 : ((m) < 0 && (b) > 0)) (m) += (b); }
#endif
/* exponentiation */
diff --git a/lobject.c b/lobject.c
index 79cf55b8e1..d011c85fc3 100644
--- a/lobject.c
+++ b/lobject.c
@@ -106,11 +106,7 @@ static lua_Number numarith (lua_State *L, int op, lua_Number v1,
case LUA_OPPOW: return luai_numpow(L, v1, v2);
case LUA_OPIDIV: return luai_numidiv(L, v1, v2);
case LUA_OPUNM: return luai_numunm(L, v1);
- case LUA_OPMOD: {
- lua_Number m;
- luai_nummod(L, v1, v2, m);
- return m;
- }
+ case LUA_OPMOD: return luaV_modf(L, v1, v2);
default: lua_assert(0); return 0;
}
}
diff --git a/lvm.c b/lvm.c
index add269422c..dd6a660bd0 100644
--- a/lvm.c
+++ b/lvm.c
@@ -655,6 +655,16 @@ lua_Integer luaV_mod (lua_State *L, lua_Integer m, lua_Integer n) {
}
+/*
+** Float modulus
+*/
+lua_Number luaV_modf (lua_State *L, lua_Number m, lua_Number n) {
+ lua_Number r;
+ luai_nummod(L, m, n, r);
+ return r;
+}
+
+
/* number of bits in an integer */
#define NBITS cast_int(sizeof(lua_Integer) * CHAR_BIT)
@@ -1142,10 +1152,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
setivalue(s2v(ra), luaV_mod(L, ivalue(rb), ic));
}
else if (tonumberns(rb, nb)) {
- lua_Number m;
lua_Number nc = cast_num(ic);
- luai_nummod(L, nb, nc, m);
- setfltvalue(s2v(ra), m);
+ setfltvalue(s2v(ra), luaV_modf(L, nb, nc));
}
else
Protect(luaT_trybiniTM(L, rb, ic, 0, ra, TM_MOD));
@@ -1370,9 +1378,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
setivalue(s2v(ra), luaV_mod(L, ib, ic));
}
else if (tonumberns(rb, nb) && tonumberns(rc, nc)) {
- lua_Number m;
- luai_nummod(L, nb, nc, m);
- setfltvalue(s2v(ra), m);
+ setfltvalue(s2v(ra), luaV_modf(L, nb, nc));
}
else
Protect(luaT_trybinTM(L, rb, rc, ra, TM_MOD));
diff --git a/lvm.h b/lvm.h
index d58daacdc0..8ead0c50b3 100644
--- a/lvm.h
+++ b/lvm.h
@@ -116,6 +116,7 @@ LUAI_FUNC void luaV_execute (lua_State *L, CallInfo *ci);
LUAI_FUNC void luaV_concat (lua_State *L, int total);
LUAI_FUNC lua_Integer luaV_div (lua_State *L, lua_Integer x, lua_Integer y);
LUAI_FUNC lua_Integer luaV_mod (lua_State *L, lua_Integer x, lua_Integer y);
+LUAI_FUNC lua_Number luaV_modf (lua_State *L, lua_Number x, lua_Number y);
LUAI_FUNC lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y);
LUAI_FUNC void luaV_objlen (lua_State *L, StkId ra, const TValue *rb);
diff --git a/testes/math.lua b/testes/math.lua
index 853dc20fb4..b387977e8d 100644
--- a/testes/math.lua
+++ b/testes/math.lua
@@ -1,4 +1,4 @@
--- $Id: testes/math.lua $
+-- $Id: testes/math.lua 2018-07-25 15:31:04 -0300 $
-- See Copyright Notice in file all.lua
print("testing numbers and math lib")
@@ -541,9 +541,73 @@ assert(eqT(-4 % 3, 2))
assert(eqT(4 % -3, -2))
assert(eqT(-4.0 % 3, 2.0))
assert(eqT(4 % -3.0, -2.0))
+assert(eqT(4 % -5, -1))
+assert(eqT(4 % -5.0, -1.0))
+assert(eqT(4 % 5, 4))
+assert(eqT(4 % 5.0, 4.0))
+assert(eqT(-4 % -5, -4))
+assert(eqT(-4 % -5.0, -4.0))
+assert(eqT(-4 % 5, 1))
+assert(eqT(-4 % 5.0, 1.0))
+assert(eqT(4.25 % 4, 0.25))
+assert(eqT(10.0 % 2, 0.0))
+assert(eqT(-10.0 % 2, 0.0))
+assert(eqT(-10.0 % -2, 0.0))
assert(math.pi - math.pi % 1 == 3)
assert(math.pi - math.pi % 0.001 == 3.141)
+do -- very small numbers
+ local i, j = 0, 20000
+ while i < j do
+ local m = (i + j) // 2
+ if 10^-m > 0 then
+ i = m + 1
+ else
+ j = m
+ end
+ end
+ -- 'i' is the smallest possible ten-exponent
+ local b = 10^-(i - (i // 10)) -- a very small number
+ assert(b > 0 and b * b == 0)
+ local delta = b / 1000
+ assert(eq((2.1 * b) % (2 * b), (0.1 * b), delta))
+ assert(eq((-2.1 * b) % (2 * b), (2 * b) - (0.1 * b), delta))
+ assert(eq((2.1 * b) % (-2 * b), (0.1 * b) - (2 * b), delta))
+ assert(eq((-2.1 * b) % (-2 * b), (-0.1 * b), delta))
+end
+
+
+-- basic consistency between integer modulo and float modulo
+for i = -10, 10 do
+ for j = -10, 10 do
+ if j ~= 0 then
+ assert((i + 0.0) % j == i % j)
+ end
+ end
+end
+
+for i = 0, 10 do
+ for j = -10, 10 do
+ if j ~= 0 then
+ assert((2^i) % j == (1 << i) % j)
+ end
+ end
+end
+
+do -- precision of module for large numbers
+ local i = 10
+ while (1 << i) > 0 do
+ assert((1 << i) % 3 == i % 2 + 1)
+ i = i + 1
+ end
+
+ i = 10
+ while 2^i < math.huge do
+ assert(2^i % 3 == i % 2 + 1)
+ i = i + 1
+ end
+end
+
assert(eqT(minint % minint, 0))
assert(eqT(maxint % maxint, 0))
assert((minint + 1) % minint == minint + 1)
From 9cbf17b0f1bb4001b237c4027b271f0db9bde62c Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 11 Sep 2018 08:39:12 -0300
Subject: [PATCH 073/889] Details (comments)
---
lapi.c | 6 +++++-
llimits.h | 5 ++++-
lparser.c | 4 ++--
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/lapi.c b/lapi.c
index 0ca9a32145..ae6b07aebd 100644
--- a/lapi.c
+++ b/lapi.c
@@ -38,7 +38,11 @@ const char lua_ident[] =
-/* test for a valid index */
+/*
+** Test for a valid index.
+** '!ttisnil(o)' implies 'o != &G(L)->nilvalue', so it is not needed.
+** However, it covers the most common cases in a faster way.
+*/
#define isvalid(L, o) (!ttisnil(o) || o != &G(L)->nilvalue)
diff --git a/llimits.h b/llimits.h
index e91310a09f..8ab58b5ce9 100644
--- a/llimits.h
+++ b/llimits.h
@@ -298,7 +298,10 @@ typedef unsigned long Instruction;
** so it is better to use 'fmod'. 'fmod' gives the result of
** 'a - trunc(a/b)*b', and therefore must be corrected when
** 'trunc(a/b) ~= floor(a/b)'. That happens when the division has a
-** non-integer negative result, which is equivalent to the tests below.
+** non-integer negative result: non-integer result is equivalent to
+** a non-zero remainder 'm'; negative result is equivalent to 'a' and
+** 'b' with different signs, or 'm' and 'b' with different signs
+** (as the result 'm' of 'fmod' has the same sign of 'a').
*/
#if !defined(luai_nummod)
#define luai_nummod(L,a,b,m) \
diff --git a/lparser.c b/lparser.c
index 9f156dd973..32500b023a 100644
--- a/lparser.c
+++ b/lparser.c
@@ -1171,9 +1171,9 @@ static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) {
suffixedexp(ls, &nv.v);
if (!vkisindexed(nv.v.k))
check_conflict(ls, lh, &nv.v);
- luaE_incCcalls(ls->L); /* control recursion depth */
+ enterlevel(ls); /* control recursion depth */
assignment(ls, &nv, nvars+1);
- ls->L->nCcalls--;
+ leavelevel(ls);
}
else { /* assignment -> '=' explist */
int nexps;
From b114c7d4871051cbdd7af185a61f35fe4028da79 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 11 Sep 2018 14:24:14 -0300
Subject: [PATCH 074/889] Added "cost" for the use of C stack by a coroutine
invocation.
Resuming a coroutine uses more C stack than other operations (such as
function calls or recursive syntax). So, to avoid stack overflow
in recursive coroutine invocations, either LUAI_MAXCCALLS must be
too small or a coroutine invocation must "pay" a higher price.
New constant LUAL_COROCSTK ("COROutine C STaK") defines how much
is this price.
---
ldo.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/ldo.c b/ldo.c
index ebb35c971c..0d68d36c00 100644
--- a/ldo.c
+++ b/ldo.c
@@ -610,6 +610,13 @@ static int resume_error (lua_State *L, const char *msg, int narg) {
}
+/*
+** "Cost" in the C stack for a coroutine invocation.
+*/
+#if !defined(LUAL_COROCSTK)
+#define LUAL_COROCSTK 3
+#endif
+
/*
** Do the work for 'lua_resume' in protected mode. Most of the work
** depends on the status of the coroutine: initial state, suspended
@@ -642,7 +649,6 @@ static void resume (lua_State *L, void *ud) {
}
}
-
LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
int *nresults) {
int status;
@@ -657,7 +663,7 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
if (from == NULL)
L->nCcalls = 1;
else /* correct 'nCcalls' for this thread */
- L->nCcalls = from->nCcalls - from->nci + L->nci + 1;
+ L->nCcalls = from->nCcalls - from->nci + L->nci + LUAL_COROCSTK;
if (L->nCcalls >= LUAI_MAXCCALLS)
return resume_error(L, "C stack overflow", nargs);
luai_userstateresume(L, nargs);
From 4cd1f4aac01184765818e0cebf02da454ccf6590 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 8 Oct 2018 10:42:07 -0300
Subject: [PATCH 075/889] Towards "to closed" local variables
Start of the implementation of "scoped variables" or "to be closed"
variables, local variables whose '__close' (or themselves) are called
when they go out of scope. This commit implements the syntax, the
opcode, and the creation of the corresponding upvalue, but it still
does not call the finalizations when the variable goes out of scope
(the most important part).
Currently, the syntax is 'local scoped name = exp', but that will
probably change.
---
lcode.c | 4 ++--
ldo.c | 3 +--
lgc.c | 4 +++-
ljumptab.h | 1 +
lobject.h | 4 ++++
lopcodes.c | 1 +
lopcodes.h | 1 +
lopnames.h | 1 +
lparser.c | 34 ++++++++++++++++++++++++++++++----
lparser.h | 1 +
lstate.h | 3 ++-
ltests.c | 25 ++++++++++++++-----------
lvm.c | 6 ++++++
testes/code.lua | 6 +++++-
testes/locals.lua | 9 +++++++++
15 files changed, 81 insertions(+), 22 deletions(-)
diff --git a/lcode.c b/lcode.c
index d00038dd7a..e84b85ac5f 100644
--- a/lcode.c
+++ b/lcode.c
@@ -1673,13 +1673,13 @@ void luaK_finish (FuncState *fs) {
lua_assert(i == 0 || isOT(*(pc - 1)) == isIT(*pc));
switch (GET_OPCODE(*pc)) {
case OP_RETURN0: case OP_RETURN1: {
- if (p->sizep == 0 && !p->is_vararg)
+ if (!(fs->needclose || p->is_vararg))
break; /* no extra work */
/* else use OP_RETURN to do the extra work */
SET_OPCODE(*pc, OP_RETURN);
} /* FALLTHROUGH */
case OP_RETURN: case OP_TAILCALL: {
- if (p->sizep > 0 || p->is_vararg) {
+ if (fs->needclose || p->is_vararg) {
SETARG_C(*pc, p->is_vararg ? p->numparams + 1 : 0);
SETARG_k(*pc, 1); /* signal that there is extra work */
}
diff --git a/ldo.c b/ldo.c
index 0d68d36c00..2349aaed16 100644
--- a/ldo.c
+++ b/ldo.c
@@ -91,8 +91,7 @@ struct lua_longjmp {
static void seterrorobj (lua_State *L, int errcode, StkId oldtop) {
switch (errcode) {
case LUA_ERRMEM: { /* memory error? */
- TString *memerrmsg = luaS_newliteral(L, MEMERRMSG);
- setsvalue2s(L, oldtop, memerrmsg); /* reuse preregistered msg. */
+ setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */
break;
}
case LUA_ERRERR: {
diff --git a/lgc.c b/lgc.c
index e8429e1b38..39b3ab7397 100644
--- a/lgc.c
+++ b/lgc.c
@@ -293,7 +293,8 @@ static void reallymarkobject (global_State *g, GCObject *o) {
gray2black(o);
break;
}
- case LUA_TUPVAL: {
+ case LUA_TUPVAL:
+ case LUA_TUPVALTBC: {
UpVal *uv = gco2upv(o);
if (!upisopen(uv)) /* open upvalues are kept gray */
gray2black(o);
@@ -760,6 +761,7 @@ static void freeobj (lua_State *L, GCObject *o) {
luaF_freeproto(L, gco2p(o));
break;
case LUA_TUPVAL:
+ case LUA_TUPVALTBC:
freeupval(L, gco2upv(o));
break;
case LUA_TLCL:
diff --git a/ljumptab.h b/ljumptab.h
index c775f10a4a..da4cf7b70d 100644
--- a/ljumptab.h
+++ b/ljumptab.h
@@ -74,6 +74,7 @@ static void *disptab[] = {
&&L_OP_LEN,
&&L_OP_CONCAT,
&&L_OP_CLOSE,
+&&L_OP_TBC,
&&L_OP_JMP,
&&L_OP_EQ,
&&L_OP_LT,
diff --git a/lobject.h b/lobject.h
index ddcf609cf6..ea3511deba 100644
--- a/lobject.h
+++ b/lobject.h
@@ -588,6 +588,10 @@ typedef struct UpVal {
} UpVal;
+/* variant for "To Be Closed" upvalues */
+#define LUA_TUPVALTBC (LUA_TUPVAL | (1 << 4))
+
+
#define ClosureHeader \
CommonHeader; lu_byte nupvalues; GCObject *gclist
diff --git a/lopcodes.c b/lopcodes.c
index 95347b2728..f6915beb88 100644
--- a/lopcodes.c
+++ b/lopcodes.c
@@ -68,6 +68,7 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
,opmode(0, 0, 0, 1, iABC) /* OP_LEN */
,opmode(0, 0, 0, 1, iABC) /* OP_CONCAT */
,opmode(0, 0, 0, 0, iABC) /* OP_CLOSE */
+ ,opmode(0, 0, 0, 0, iABC) /* OP_TBC */
,opmode(0, 0, 0, 0, isJ) /* OP_JMP */
,opmode(0, 0, 1, 0, iABC) /* OP_EQ */
,opmode(0, 0, 1, 0, iABC) /* OP_LT */
diff --git a/lopcodes.h b/lopcodes.h
index 9442a33689..4d144000fe 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -251,6 +251,7 @@ OP_LEN,/* A B R(A) := length of R(B) */
OP_CONCAT,/* A B R(A) := R(A).. ... ..R(A + B - 1) */
OP_CLOSE,/* A close all upvalues >= R(A) */
+OP_TBC,/* A mark variable A "to be closed" */
OP_JMP,/* k sJ pc += sJ (k is used in code generation) */
OP_EQ,/* A B if ((R(A) == R(B)) ~= k) then pc++ */
OP_LT,/* A B if ((R(A) < R(B)) ~= k) then pc++ */
diff --git a/lopnames.h b/lopnames.h
index c40eaeae26..304d3b6cac 100644
--- a/lopnames.h
+++ b/lopnames.h
@@ -59,6 +59,7 @@ static const char *const opnames[] = {
"LEN",
"CONCAT",
"CLOSE",
+ "TBC",
"JMP",
"EQ",
"LT",
diff --git a/lparser.c b/lparser.c
index 32500b023a..84abeb90b7 100644
--- a/lparser.c
+++ b/lparser.c
@@ -255,6 +255,7 @@ static void markupval (FuncState *fs, int level) {
while (bl->nactvar > level)
bl = bl->previous;
bl->upval = 1;
+ fs->needclose = 1;
}
@@ -547,6 +548,7 @@ static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) {
fs->nups = 0;
fs->nlocvars = 0;
fs->nactvar = 0;
+ fs->needclose = 0;
fs->firstlocal = ls->dyd->actvar.n;
fs->bl = NULL;
f->source = ls->source;
@@ -1509,15 +1511,16 @@ static void localfunc (LexState *ls) {
}
-static void localstat (LexState *ls) {
+static void commonlocalstat (LexState *ls, TString *firstvar) {
/* stat -> LOCAL NAME {',' NAME} ['=' explist] */
- int nvars = 0;
+ int nvars = 1;
int nexps;
expdesc e;
- do {
+ new_localvar(ls, firstvar);
+ while (testnext(ls, ',')) {
new_localvar(ls, str_checkname(ls));
nvars++;
- } while (testnext(ls, ','));
+ }
if (testnext(ls, '='))
nexps = explist(ls, &e);
else {
@@ -1529,6 +1532,29 @@ static void localstat (LexState *ls) {
}
+static void scopedlocalstat (LexState *ls) {
+ FuncState *fs = ls->fs;
+ new_localvar(ls, str_checkname(ls));
+ checknext(ls, '=');
+ luaK_codeABC(fs, OP_TBC, fs->nactvar, 0, 0);
+ markupval(fs, fs->nactvar);
+ exp1(ls, 0);
+ adjustlocalvars(ls, 1);
+}
+
+
+static void localstat (LexState *ls) {
+ /* stat -> LOCAL NAME {',' NAME} ['=' explist]
+ | LOCAL SCOPED NAME '=' exp */
+ TString *firstvar = str_checkname(ls);
+ if (ls->t.token == TK_NAME &&
+ eqshrstr(firstvar, luaS_newliteral(ls->L, "scoped")))
+ scopedlocalstat(ls);
+ else
+ commonlocalstat(ls, firstvar);
+}
+
+
static int funcname (LexState *ls, expdesc *v) {
/* funcname -> NAME {fieldsel} [':' NAME] */
int ismethod = 0;
diff --git a/lparser.h b/lparser.h
index e158c9d918..1b94a97a6f 100644
--- a/lparser.h
+++ b/lparser.h
@@ -133,6 +133,7 @@ typedef struct FuncState {
lu_byte nups; /* number of upvalues */
lu_byte freereg; /* first free register */
lu_byte iwthabs; /* instructions issued since last absolute line info */
+ lu_byte needclose; /* function needs to close upvalues when returning */
} FuncState;
diff --git a/lstate.h b/lstate.h
index 5461b29119..f08c23554c 100644
--- a/lstate.h
+++ b/lstate.h
@@ -267,7 +267,8 @@ union GCUnion {
#define gco2t(o) check_exp((o)->tt == LUA_TTABLE, &((cast_u(o))->h))
#define gco2p(o) check_exp((o)->tt == LUA_TPROTO, &((cast_u(o))->p))
#define gco2th(o) check_exp((o)->tt == LUA_TTHREAD, &((cast_u(o))->th))
-#define gco2upv(o) check_exp((o)->tt == LUA_TUPVAL, &((cast_u(o))->upv))
+#define gco2upv(o) \
+ check_exp(novariant((o)->tt) == LUA_TUPVAL, &((cast_u(o))->upv))
/*
diff --git a/ltests.c b/ltests.c
index bc71d937f5..ff962543cb 100644
--- a/ltests.c
+++ b/ltests.c
@@ -357,7 +357,8 @@ static void checkrefs (global_State *g, GCObject *o) {
checkudata(g, gco2u(o));
break;
}
- case LUA_TUPVAL: {
+ case LUA_TUPVAL:
+ case LUA_TUPVALTBC: {
checkvalref(g, o, gco2upv(o)->v);
break;
}
@@ -522,35 +523,37 @@ int lua_checkmemory (lua_State *L) {
static char *buildop (Proto *p, int pc, char *buff) {
+ char *obuff = buff;
Instruction i = p->code[pc];
OpCode o = GET_OPCODE(i);
const char *name = opnames[o];
int line = luaG_getfuncline(p, pc);
int lineinfo = (p->lineinfo != NULL) ? p->lineinfo[pc] : 0;
- sprintf(buff, "(%2d - %4d) %4d - ", lineinfo, line, pc);
+ if (lineinfo == ABSLINEINFO)
+ buff += sprintf(buff, "(__");
+ else
+ buff += sprintf(buff, "(%2d", lineinfo);
+ buff += sprintf(buff, " - %4d) %4d - ", line, pc);
switch (getOpMode(o)) {
case iABC:
- sprintf(buff+strlen(buff), "%-12s%4d %4d %4d%s", name,
+ sprintf(buff, "%-12s%4d %4d %4d%s", name,
GETARG_A(i), GETARG_B(i), GETARG_C(i),
GETARG_k(i) ? " (k)" : "");
break;
case iABx:
- sprintf(buff+strlen(buff), "%-12s%4d %4d", name, GETARG_A(i),
- GETARG_Bx(i));
+ sprintf(buff, "%-12s%4d %4d", name, GETARG_A(i), GETARG_Bx(i));
break;
case iAsBx:
- sprintf(buff+strlen(buff), "%-12s%4d %4d", name, GETARG_A(i),
- GETARG_sBx(i));
+ sprintf(buff, "%-12s%4d %4d", name, GETARG_A(i), GETARG_sBx(i));
break;
case iAx:
- sprintf(buff+strlen(buff), "%-12s%4d", name, GETARG_Ax(i));
+ sprintf(buff, "%-12s%4d", name, GETARG_Ax(i));
break;
case isJ:
- sprintf(buff+strlen(buff), "%-12s%4d (%1d)", name, GETARG_sJ(i),
- !!GETARG_m(i));
+ sprintf(buff, "%-12s%4d (%1d)", name, GETARG_sJ(i), !!GETARG_m(i));
break;
}
- return buff;
+ return obuff;
}
diff --git a/lvm.c b/lvm.c
index dd6a660bd0..fdd99a4282 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1455,6 +1455,12 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
luaF_close(L, ra);
vmbreak;
}
+ vmcase(OP_TBC) {
+ UpVal *up = luaF_findupval(L, ra); /* create new upvalue */
+ up->tt = LUA_TUPVALTBC; /* mark it to be closed */
+ setnilvalue(s2v(ra)); /* intialize it with nil */
+ vmbreak;
+ }
vmcase(OP_JMP) {
dojump(ci, i, 0);
vmbreak;
diff --git a/testes/code.lua b/testes/code.lua
index 6bd6ebfa8f..ad48448594 100644
--- a/testes/code.lua
+++ b/testes/code.lua
@@ -64,8 +64,12 @@ end
-- some basic instructions
-check(function ()
+check(function () -- function does not create upvalues
(function () end){f()}
+end, 'CLOSURE', 'NEWTABLE', 'GETTABUP', 'CALL', 'SETLIST', 'CALL', 'RETURN0')
+
+check(function (x) -- function creates upvalues
+ (function () return x end){f()}
end, 'CLOSURE', 'NEWTABLE', 'GETTABUP', 'CALL', 'SETLIST', 'CALL', 'RETURN')
diff --git a/testes/locals.lua b/testes/locals.lua
index 14e49a7ce6..20ecae4bc5 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -173,6 +173,15 @@ end
assert(x==20)
+-- tests for to-be-closed variables
+do
+ local scoped x = 3
+ local a
+ local scoped y = 5
+ assert(x == 3 and y == 5)
+end
+
+
print('OK')
return 5,f
From bd96330d037660d9a1769c6c0d989f017e5f0278 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 17 Oct 2018 10:44:42 -0300
Subject: [PATCH 076/889] First "complete" implementation of to-be-closed
variables
Still missing:
- handling of memory errors when creating upvalue (must run closing
method all the same)
- interaction with coroutines
---
ldo.c | 20 +++++++++------
ldo.h | 1 +
lfunc.c | 47 +++++++++++++++++++++++++++++++---
lfunc.h | 2 +-
lgc.c | 5 ++++
lparser.c | 2 +-
lstate.c | 4 +--
ltests.c | 2 +-
ltm.c | 2 +-
ltm.h | 1 +
lvm.c | 7 +++---
testes/api.lua | 14 ++++++++++-
testes/locals.lua | 64 +++++++++++++++++++++++++++++++++++++++++++----
13 files changed, 145 insertions(+), 26 deletions(-)
diff --git a/ldo.c b/ldo.c
index 2349aaed16..f2d12f0430 100644
--- a/ldo.c
+++ b/ldo.c
@@ -88,7 +88,7 @@ struct lua_longjmp {
};
-static void seterrorobj (lua_State *L, int errcode, StkId oldtop) {
+void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {
switch (errcode) {
case LUA_ERRMEM: { /* memory error? */
setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */
@@ -121,7 +121,7 @@ l_noret luaD_throw (lua_State *L, int errcode) {
}
else { /* no handler at all; abort */
if (g->panic) { /* panic function? */
- seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */
+ luaD_seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */
if (L->ci->top < L->top)
L->ci->top = L->top; /* pushing msg. can break this invariant */
lua_unlock(L);
@@ -584,8 +584,8 @@ static int recover (lua_State *L, int status) {
if (ci == NULL) return 0; /* no recovery point */
/* "finish" luaD_pcall */
oldtop = restorestack(L, ci->u2.funcidx);
- luaF_close(L, oldtop);
- seterrorobj(L, status, oldtop);
+ luaF_close(L, oldtop, status);
+ luaD_seterrorobj(L, status, oldtop);
L->ci = ci;
L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */
L->nny = 0; /* should be zero to be yieldable */
@@ -678,7 +678,7 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
}
if (unlikely(errorstatus(status))) { /* unrecoverable error? */
L->status = cast_byte(status); /* mark thread as 'dead' */
- seterrorobj(L, status, L->top); /* push error message */
+ luaD_seterrorobj(L, status, L->top); /* push error message */
L->ci->top = L->top;
}
else lua_assert(status == L->status); /* normal end or yield */
@@ -726,6 +726,11 @@ LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx,
}
+/*
+** Call the C function 'func' in protected mode, restoring basic
+** thread information ('allowhook', 'nny', etc.) and in particular
+** its stack level in case of errors.
+*/
int luaD_pcall (lua_State *L, Pfunc func, void *u,
ptrdiff_t old_top, ptrdiff_t ef) {
int status;
@@ -737,11 +742,12 @@ int luaD_pcall (lua_State *L, Pfunc func, void *u,
status = luaD_rawrunprotected(L, func, u);
if (unlikely(status != LUA_OK)) { /* an error occurred? */
StkId oldtop = restorestack(L, old_top);
- luaF_close(L, oldtop); /* close possible pending closures */
- seterrorobj(L, status, oldtop);
L->ci = old_ci;
L->allowhook = old_allowhooks;
L->nny = old_nny;
+ status = luaF_close(L, oldtop, status);
+ oldtop = restorestack(L, old_top); /* previous call may change stack */
+ luaD_seterrorobj(L, status, oldtop);
luaD_shrinkstack(L);
}
L->errfunc = old_errfunc;
diff --git a/ldo.h b/ldo.h
index c836a2a174..7760f853b2 100644
--- a/ldo.h
+++ b/ldo.h
@@ -50,6 +50,7 @@
/* type of protected functions, to be ran by 'runprotected' */
typedef void (*Pfunc) (lua_State *L, void *ud);
+LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop);
LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name,
const char *mode);
LUAI_FUNC void luaD_hook (lua_State *L, int event, int line,
diff --git a/lfunc.c b/lfunc.c
index 16e007318d..fde72b8c46 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -14,6 +14,7 @@
#include "lua.h"
+#include "ldo.h"
#include "lfunc.h"
#include "lgc.h"
#include "lmem.h"
@@ -83,6 +84,40 @@ UpVal *luaF_findupval (lua_State *L, StkId level) {
}
+static void callclose (lua_State *L, void *ud) {
+ luaD_callnoyield(L, cast(StkId, ud), 0);
+}
+
+
+static int closeupval (lua_State *L, UpVal *uv, StkId level, int status) {
+ StkId func = level + 1; /* save slot for old error message */
+ if (status != LUA_OK) /* was there an error? */
+ luaD_seterrorobj(L, status, level); /* save error message */
+ else
+ setnilvalue(s2v(level));
+ if (ttisfunction(uv->v)) { /* object to-be-closed is a function? */
+ setobj2s(L, func, uv->v); /* will call it */
+ setobjs2s(L, func + 1, level); /* error msg. as argument */
+ }
+ else { /* try '__close' metamethod */
+ const TValue *tm = luaT_gettmbyobj(L, uv->v, TM_CLOSE);
+ if (ttisnil(tm))
+ return status; /* no metamethod */
+ setobj2s(L, func, tm); /* will call metamethod */
+ setobj2s(L, func + 1, uv->v); /* with 'self' as argument */
+ }
+ L->top = func + 2; /* add function and argument */
+ if (status == LUA_OK) /* not in "error mode"? */
+ callclose(L, func); /* call closing method */
+ else { /* already inside error handler; cannot raise another error */
+ int newstatus = luaD_pcall(L, callclose, func, savestack(L, level), 0);
+ if (newstatus != LUA_OK) /* error when closing? */
+ status = newstatus; /* this will be the new error */
+ }
+ return status;
+}
+
+
void luaF_unlinkupval (UpVal *uv) {
lua_assert(upisopen(uv));
*uv->u.open.previous = uv->u.open.next;
@@ -91,10 +126,10 @@ void luaF_unlinkupval (UpVal *uv) {
}
-void luaF_close (lua_State *L, StkId level) {
+int luaF_close (lua_State *L, StkId level, int status) {
UpVal *uv;
- while (L->openupval != NULL &&
- (uv = L->openupval, uplevel(uv) >= level)) {
+ while ((uv = L->openupval) != NULL && uplevel(uv) >= level) {
+ StkId upl = uplevel(uv);
TValue *slot = &uv->u.value; /* new position for value */
luaF_unlinkupval(uv);
setobj(L, slot, uv->v); /* move value to upvalue slot */
@@ -102,7 +137,13 @@ void luaF_close (lua_State *L, StkId level) {
if (!iswhite(uv))
gray2black(uv); /* closed upvalues cannot be gray */
luaC_barrier(L, uv, slot);
+ if (status >= 0 && uv->tt == LUA_TUPVALTBC) { /* must be closed? */
+ ptrdiff_t levelrel = savestack(L, level);
+ status = closeupval(L, uv, upl, status); /* may reallocate the stack */
+ level = restorestack(L, levelrel);
+ }
}
+ return status;
}
diff --git a/lfunc.h b/lfunc.h
index 859ccc1205..4c78800568 100644
--- a/lfunc.h
+++ b/lfunc.h
@@ -47,7 +47,7 @@ LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems);
LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems);
LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl);
LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level);
-LUAI_FUNC void luaF_close (lua_State *L, StkId level);
+LUAI_FUNC int luaF_close (lua_State *L, StkId level, int status);
LUAI_FUNC void luaF_unlinkupval (UpVal *uv);
LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f);
LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number,
diff --git a/lgc.c b/lgc.c
index 39b3ab7397..9d196a18b7 100644
--- a/lgc.c
+++ b/lgc.c
@@ -609,6 +609,7 @@ static int traverseLclosure (global_State *g, LClosure *cl) {
** That ensures that the entire stack have valid (non-dead) objects.
*/
static int traversethread (global_State *g, lua_State *th) {
+ UpVal *uv;
StkId o = th->stack;
if (o == NULL)
return 1; /* stack not completely built yet */
@@ -616,6 +617,10 @@ static int traversethread (global_State *g, lua_State *th) {
th->openupval == NULL || isintwups(th));
for (; o < th->top; o++) /* mark live elements in the stack */
markvalue(g, s2v(o));
+ for (uv = th->openupval; uv != NULL; uv = uv->u.open.next) {
+ if (uv->tt == LUA_TUPVALTBC) /* to be closed? */
+ markobject(g, uv); /* cannot be collected */
+ }
if (g->gcstate == GCSatomic) { /* final traversal? */
StkId lim = th->stack + th->stacksize; /* real end of stack */
for (; o < lim; o++) /* clear not-marked stack slice */
diff --git a/lparser.c b/lparser.c
index 84abeb90b7..6b14b80047 100644
--- a/lparser.c
+++ b/lparser.c
@@ -1536,9 +1536,9 @@ static void scopedlocalstat (LexState *ls) {
FuncState *fs = ls->fs;
new_localvar(ls, str_checkname(ls));
checknext(ls, '=');
+ exp1(ls, 0);
luaK_codeABC(fs, OP_TBC, fs->nactvar, 0, 0);
markupval(fs, fs->nactvar);
- exp1(ls, 0);
adjustlocalvars(ls, 1);
}
diff --git a/lstate.c b/lstate.c
index 8b0219bccd..4a2453d15a 100644
--- a/lstate.c
+++ b/lstate.c
@@ -258,7 +258,7 @@ static void preinit_thread (lua_State *L, global_State *g) {
static void close_state (lua_State *L) {
global_State *g = G(L);
- luaF_close(L, L->stack); /* close all upvalues for this thread */
+ luaF_close(L, L->stack, -1); /* close all upvalues for this thread */
luaC_freeallobjects(L); /* collect all objects */
if (ttisnil(&g->nilvalue)) /* closing a fully built state? */
luai_userstateclose(L);
@@ -301,7 +301,7 @@ LUA_API lua_State *lua_newthread (lua_State *L) {
void luaE_freethread (lua_State *L, lua_State *L1) {
LX *l = fromstate(L1);
- luaF_close(L1, L1->stack); /* close all upvalues for this thread */
+ luaF_close(L1, L1->stack, -1); /* close all upvalues for this thread */
lua_assert(L1->openupval == NULL);
luai_userstatefree(L, L1);
freestack(L1);
diff --git a/ltests.c b/ltests.c
index ff962543cb..a6968653a5 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1208,7 +1208,7 @@ static int getindex_aux (lua_State *L, lua_State *L1, const char **pc) {
static void pushcode (lua_State *L, int code) {
static const char *const codes[] = {"OK", "YIELD", "ERRRUN",
- "ERRSYNTAX", "ERRMEM", "ERRGCMM", "ERRERR"};
+ "ERRSYNTAX", MEMERRMSG, "ERRGCMM", "ERRERR"};
lua_pushstring(L, codes[code]);
}
diff --git a/ltm.c b/ltm.c
index 5c148180aa..53e15c7fdc 100644
--- a/ltm.c
+++ b/ltm.c
@@ -43,7 +43,7 @@ void luaT_init (lua_State *L) {
"__div", "__idiv",
"__band", "__bor", "__bxor", "__shl", "__shr",
"__unm", "__bnot", "__lt", "__le",
- "__concat", "__call"
+ "__concat", "__call", "__close"
};
int i;
for (i=0; itt = LUA_TUPVALTBC; /* mark it to be closed */
- setnilvalue(s2v(ra)); /* intialize it with nil */
vmbreak;
}
vmcase(OP_JMP) {
@@ -1591,7 +1590,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
int nparams1 = GETARG_C(i);
if (nparams1) /* vararg function? */
delta = ci->u.l.nextraargs + nparams1;
- luaF_close(L, base); /* close upvalues from current call */
+ luaF_close(L, base, LUA_OK); /* close upvalues from current call */
}
if (!ttisfunction(s2v(ra))) { /* not a function? */
luaD_tryfuncTM(L, ra); /* try '__call' metamethod */
@@ -1625,7 +1624,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
int nparams1 = GETARG_C(i);
if (nparams1) /* vararg function? */
ci->func -= ci->u.l.nextraargs + nparams1;
- luaF_close(L, base); /* there may be open upvalues */
+ luaF_close(L, base, LUA_OK); /* there may be open upvalues */
}
halfProtect(luaD_poscall(L, ci, n));
return;
diff --git a/testes/api.lua b/testes/api.lua
index bebb6d2d25..925a80c1ea 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -1,4 +1,4 @@
--- $Id: testes/api.lua $
+-- $Id: testes/api.lua 2018-07-25 15:31:04 -0300 $
-- See Copyright Notice in file all.lua
if T==nil then
@@ -1027,6 +1027,18 @@ testamem("coroutine creation", function()
end)
+-- testing to-be-closed variables
+testamem("to-be-closed variables", function()
+ local flag
+ do
+ local scoped x = function () flag = true end
+ flag = false
+ local x = {}
+ end
+ return flag
+end)
+
+
-- testing threads
-- get main thread from registry (at index LUA_RIDX_MAINTHREAD == 1)
diff --git a/testes/locals.lua b/testes/locals.lua
index 20ecae4bc5..8d55e9f5bf 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -173,15 +173,69 @@ end
assert(x==20)
--- tests for to-be-closed variables
+print"testing to-be-closed variables"
+
+do
+ local a = {}
+ do
+ local scoped x = setmetatable({"x"}, {__close = function (self)
+ a[#a + 1] = self[1] end})
+ local scoped y = function () a[#a + 1] = "y" end
+ a[#a + 1] = "in"
+ end
+ a[#a + 1] = "out"
+ assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out")
+end
+
+
+do -- errors in __close
+ local log = {}
+ local function foo (err)
+ local scoped x = function (msg) log[#log + 1] = msg; error(1) end
+ local scoped x1 = function (msg) log[#log + 1] = msg; end
+ local scoped gc = function () collectgarbage() end
+ local scoped y = function (msg) log[#log + 1] = msg; error(2) end
+ local scoped z = function (msg) log[#log + 1] = msg or 10; error(3) end
+ if err then error(4) end
+ end
+ local stat, msg = pcall(foo, false)
+ assert(msg == 1)
+ assert(log[1] == 10 and log[2] == 3 and log[3] == 2 and log[4] == 2
+ and #log == 4)
+
+ log = {}
+ local stat, msg = pcall(foo, true)
+ assert(msg == 1)
+ assert(log[1] == 4 and log[2] == 3 and log[3] == 2 and log[4] == 2
+ and #log == 4)
+end
+
do
- local scoped x = 3
- local a
- local scoped y = 5
- assert(x == 3 and y == 5)
+ -- memory error inside closing function
+ local function foo ()
+ local scoped y = function () io.write(2); T.alloccount() end
+ local scoped x = setmetatable({}, {__close = function ()
+ T.alloccount(0); local x = {} -- force a memory error
+ end})
+ io.write("1\n")
+ error("a") -- common error inside the function's body
+ end
+
+ local _, msg = pcall(foo)
+T.alloccount()
+ assert(msg == "not enough memory")
+
end
+-- a suspended coroutine should not close its variables when collected
+local co = coroutine.wrap(function()
+ local scoped x = function () os.exit(1) end -- should not run
+ coroutine.yield()
+end)
+co()
+co = nil
+
print('OK')
return 5,f
From 3c7dc52909ce0688bdb20cacaf686413a79aaf48 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 18 Oct 2018 16:15:09 -0300
Subject: [PATCH 077/889] Handling of memory errors when creating to-be-closed
upvalues
---
lfunc.c | 126 ++++++++++++++++++++++++++++++++++------------
lfunc.h | 1 +
lvm.c | 3 +-
testes/locals.lua | 58 ++++++++++++++++++---
4 files changed, 148 insertions(+), 40 deletions(-)
diff --git a/lfunc.c b/lfunc.c
index fde72b8c46..4f9362f3c4 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -40,6 +40,7 @@ LClosure *luaF_newLclosure (lua_State *L, int n) {
return c;
}
+
/*
** fill a closure with new closed upvalues
*/
@@ -56,31 +57,43 @@ void luaF_initupvals (lua_State *L, LClosure *cl) {
}
+/*
+** Create a new upvalue with the given tag at the given level,
+** and link it to the list of open upvalues of 'L' after entry 'prev'.
+**/
+static UpVal *newupval (lua_State *L, int tag, StkId level, UpVal **prev) {
+ GCObject *o = luaC_newobj(L, tag, sizeof(UpVal));
+ UpVal *uv = gco2upv(o);
+ UpVal *next = *prev;
+ uv->v = s2v(level); /* current value lives in the stack */
+ uv->u.open.next = next; /* link it to list of open upvalues */
+ uv->u.open.previous = prev;
+ if (next)
+ next->u.open.previous = &uv->u.open.next;
+ *prev = uv;
+ if (!isintwups(L)) { /* thread not in list of threads with upvalues? */
+ L->twups = G(L)->twups; /* link it to the list */
+ G(L)->twups = L;
+ }
+ return uv;
+}
+
+
+/*
+** Find and reuse, or create if it does not exist, a regular upvalue
+** at the given level.
+*/
UpVal *luaF_findupval (lua_State *L, StkId level) {
UpVal **pp = &L->openupval;
- GCObject *o;
UpVal *p;
- UpVal *uv;
lua_assert(isintwups(L) || L->openupval == NULL);
- while ((p = *pp) != NULL && uplevel(p) >= level) {
+ while ((p = *pp) != NULL && uplevel(p) >= level) { /* search for it */
if (uplevel(p) == level && !isdead(G(L), p)) /* corresponding upvalue? */
return p; /* return it */
pp = &p->u.open.next;
}
- /* not found: create a new upvalue between 'pp' and 'p' */
- o = luaC_newobj(L, LUA_TUPVAL, sizeof(UpVal));
- uv = gco2upv(o);
- uv->u.open.next = p; /* link it to list of open upvalues */
- uv->u.open.previous = pp;
- if (p)
- p->u.open.previous = &uv->u.open.next;
- *pp = uv;
- uv->v = s2v(level); /* current value lives in the stack */
- if (!isintwups(L)) { /* thread not in list of threads with upvalues? */
- L->twups = G(L)->twups; /* link it to the list */
- G(L)->twups = L;
- }
- return uv;
+ /* not found: create a new upvalue after 'pp' */
+ return newupval(L, LUA_TUPVAL, level, pp);
}
@@ -89,25 +102,44 @@ static void callclose (lua_State *L, void *ud) {
}
-static int closeupval (lua_State *L, UpVal *uv, StkId level, int status) {
- StkId func = level + 1; /* save slot for old error message */
- if (status != LUA_OK) /* was there an error? */
- luaD_seterrorobj(L, status, level); /* save error message */
- else
- setnilvalue(s2v(level));
- if (ttisfunction(uv->v)) { /* object to-be-closed is a function? */
- setobj2s(L, func, uv->v); /* will call it */
- setobjs2s(L, func + 1, level); /* error msg. as argument */
+/*
+** Prepare closing method with its argument for object at
+** index 'func' in the stack. Assume there is an error message
+** (or nil) just below the object.
+*/
+static int prepclosingmethod (lua_State *L, StkId func) {
+ if (ttisfunction(s2v(func))) { /* object to-be-closed is a function? */
+ setobjs2s(L, func + 1, func - 1); /* push error msg. as argument */
}
else { /* try '__close' metamethod */
- const TValue *tm = luaT_gettmbyobj(L, uv->v, TM_CLOSE);
- if (ttisnil(tm))
- return status; /* no metamethod */
+ const TValue *tm = luaT_gettmbyobj(L, s2v(func), TM_CLOSE);
+ if (ttisnil(tm)) /* no metamethod? */
+ return 0; /* nothing to call */
+ setobjs2s(L, func + 1, func); /* 'self' is the argument */
setobj2s(L, func, tm); /* will call metamethod */
- setobj2s(L, func + 1, uv->v); /* with 'self' as argument */
}
L->top = func + 2; /* add function and argument */
- if (status == LUA_OK) /* not in "error mode"? */
+ return 1;
+}
+
+
+/*
+** Prepare and call a closing method. If status is OK, code is
+** still inside the original protected call, and so any error
+** will be handled there. Otherwise, a previous error already
+** activated original protected call, and so the call to the
+** closing method must be protected here.
+*/
+static int closeupval (lua_State *L, TValue *uv, StkId level, int status) {
+ StkId func = level + 1; /* save slot for old error message */
+ if (unlikely(status != LUA_OK)) /* was there an error? */
+ luaD_seterrorobj(L, status, level); /* save error message */
+ else
+ setnilvalue(s2v(level)); /* no error message */
+ setobj2s(L, func, uv); /* put object on top of error message */
+ if (!prepclosingmethod(L, func))
+ return status; /* nothing to call */
+ if (likely(status == LUA_OK)) /* not in "error mode"? */
callclose(L, func); /* call closing method */
else { /* already inside error handler; cannot raise another error */
int newstatus = luaD_pcall(L, callclose, func, savestack(L, level), 0);
@@ -118,6 +150,36 @@ static int closeupval (lua_State *L, UpVal *uv, StkId level, int status) {
}
+/*
+** Try to create a to-be-closed upvalue
+** (can raise a memory-allocation error)
+*/
+static void trynewtbcupval (lua_State *L, void *ud) {
+ StkId level = cast(StkId, ud);
+ lua_assert(L->openupval == NULL || uplevel(L->openupval) < level);
+ newupval(L, LUA_TUPVALTBC, level, &L->openupval);
+}
+
+
+/*
+** Create a to-be-closed upvalue. If there is a memory error
+** when creating the upvalue, the closing method must be called here,
+** as there is no upvalue to call it later.
+*/
+void luaF_newtbcupval (lua_State *L, StkId level) {
+ int status = luaD_rawrunprotected(L, trynewtbcupval, level);
+ if (unlikely(status != LUA_OK)) { /* memory error creating upvalue? */
+ StkId func = level + 1;
+ lua_assert(status == LUA_ERRMEM);
+ setobjs2s(L, func, level); /* open space for error message */
+ luaD_seterrorobj(L, status, level); /* save error message */
+ if (prepclosingmethod(L, func))
+ callclose(L, func); /* call closing method */
+ luaD_throw(L, LUA_ERRMEM); /* throw memory error */
+ }
+}
+
+
void luaF_unlinkupval (UpVal *uv) {
lua_assert(upisopen(uv));
*uv->u.open.previous = uv->u.open.next;
@@ -139,7 +201,7 @@ int luaF_close (lua_State *L, StkId level, int status) {
luaC_barrier(L, uv, slot);
if (status >= 0 && uv->tt == LUA_TUPVALTBC) { /* must be closed? */
ptrdiff_t levelrel = savestack(L, level);
- status = closeupval(L, uv, upl, status); /* may reallocate the stack */
+ status = closeupval(L, uv->v, upl, status); /* may reallocate the stack */
level = restorestack(L, levelrel);
}
}
diff --git a/lfunc.h b/lfunc.h
index 4c78800568..c9fe13148a 100644
--- a/lfunc.h
+++ b/lfunc.h
@@ -47,6 +47,7 @@ LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems);
LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems);
LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl);
LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level);
+LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level);
LUAI_FUNC int luaF_close (lua_State *L, StkId level, int status);
LUAI_FUNC void luaF_unlinkupval (UpVal *uv);
LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f);
diff --git a/lvm.c b/lvm.c
index e2994aa9fe..0d82756b93 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1456,8 +1456,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_TBC) {
- UpVal *up = luaF_findupval(L, ra); /* create new upvalue */
- up->tt = LUA_TUPVALTBC; /* mark it to be closed */
+ luaF_newtbcupval(L, ra); /* create new to-be-closed upvalue */
vmbreak;
}
vmcase(OP_JMP) {
diff --git a/testes/locals.lua b/testes/locals.lua
index 8d55e9f5bf..d12c70a087 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -180,7 +180,7 @@ do
do
local scoped x = setmetatable({"x"}, {__close = function (self)
a[#a + 1] = self[1] end})
- local scoped y = function () a[#a + 1] = "y" end
+ local scoped y = function (x) assert(x == nil); a[#a + 1] = "y" end
a[#a + 1] = "in"
end
a[#a + 1] = "out"
@@ -210,27 +210,73 @@ do -- errors in __close
and #log == 4)
end
-do
+if rawget(_G, "T") then
+ local function stack(n) n = (n == 0) or stack(n - 1); end;
-- memory error inside closing function
local function foo ()
- local scoped y = function () io.write(2); T.alloccount() end
+ local scoped y = function () T.alloccount() end
local scoped x = setmetatable({}, {__close = function ()
T.alloccount(0); local x = {} -- force a memory error
end})
- io.write("1\n")
error("a") -- common error inside the function's body
end
+ stack(5) -- ensure a minimal number of CI structures
+
+ -- despite memory error, 'y' will be executed and
+ -- memory limit will be lifted
local _, msg = pcall(foo)
-T.alloccount()
assert(msg == "not enough memory")
+ local function close (msg)
+ T.alloccount()
+ assert(msg == "not enough memory")
+ end
+
+ -- set a memory limit and return a closing function to remove the limit
+ local function enter (count)
+ stack(10) -- reserve some stack space
+ T.alloccount(count)
+ return close
+ end
+
+ local function test ()
+ local scoped x = enter(0) -- set a memory limit
+ -- creation of previous upvalue will raise a memory error
+ os.exit(false) -- should not run
+ end
+
+ local _, msg = pcall(test)
+ assert(msg == "not enough memory")
+
+ -- now use metamethod for closing
+ close = setmetatable({}, {__close = function ()
+ T.alloccount()
+ end})
+
+ -- repeat test with extra closing upvalues
+ local function test ()
+ local scoped xxx = function (msg)
+ assert(msg == "not enough memory");
+ error(1000) -- raise another error
+ end
+ local scoped xx = function (msg)
+ assert(msg == "not enough memory");
+ end
+ local scoped x = enter(0) -- set a memory limit
+ -- creation of previous upvalue will raise a memory error
+ os.exit(false) -- should not run
+ end
+
+ local _, msg = pcall(test)
+ assert(msg == 1000)
+
end
-- a suspended coroutine should not close its variables when collected
local co = coroutine.wrap(function()
- local scoped x = function () os.exit(1) end -- should not run
+ local scoped x = function () os.exit(false) end -- should not run
coroutine.yield()
end)
co()
From c90176f96924ee7d207501b32f216925773d3bdb Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 22 Oct 2018 14:55:51 -0300
Subject: [PATCH 078/889] Complete implementation of to-be-closed variables
---
ldo.c | 9 ++++++---
testes/db.lua | 9 ++++-----
testes/locals.lua | 33 +++++++++++++++++++++++++++++----
3 files changed, 39 insertions(+), 12 deletions(-)
diff --git a/ldo.c b/ldo.c
index f2d12f0430..78e4c5c3fb 100644
--- a/ldo.c
+++ b/ldo.c
@@ -676,12 +676,15 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
/* unroll continuation */
status = luaD_rawrunprotected(L, unroll, &status);
}
- if (unlikely(errorstatus(status))) { /* unrecoverable error? */
+ if (likely(!errorstatus(status)))
+ lua_assert(status == L->status); /* normal end or yield */
+ else { /* unrecoverable error */
+ status = luaF_close(L, L->stack, status); /* close all upvalues */
L->status = cast_byte(status); /* mark thread as 'dead' */
- luaD_seterrorobj(L, status, L->top); /* push error message */
+ luaD_seterrorobj(L, status, L->stack + 1); /* push error message */
+ L->ci = &L->base_ci; /* back to the original C level */
L->ci->top = L->top;
}
- else lua_assert(status == L->status); /* normal end or yield */
}
*nresults = (status == LUA_YIELD) ? L->ci->u2.nyield
: cast_int(L->top - (L->ci->func + 1));
diff --git a/testes/db.lua b/testes/db.lua
index 2feaaef183..9da682102e 100644
--- a/testes/db.lua
+++ b/testes/db.lua
@@ -734,19 +734,18 @@ a, b = coroutine.resume(co, 100)
assert(a and b == 30)
--- check traceback of suspended (or dead with error) coroutines
+-- check traceback of suspended coroutines
-function f(i) if i==0 then error(i) else coroutine.yield(); f(i-1) end end
+function f(i) coroutine.yield(i == 0); f(i - 1) end
co = coroutine.create(function (x) f(x) end)
a, b = coroutine.resume(co, 3)
t = {"'coroutine.yield'", "'f'", "in function <"}
-while coroutine.status(co) == "suspended" do
+repeat
checktraceback(co, t)
a, b = coroutine.resume(co)
table.insert(t, 2, "'f'") -- one more recursive call to 'f'
-end
-t[1] = "'error'"
+until b
checktraceback(co, t)
diff --git a/testes/locals.lua b/testes/locals.lua
index d12c70a087..f21fa2ec92 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -274,13 +274,38 @@ if rawget(_G, "T") then
end
+-- to-be-closed variables in coroutines
+do
+ -- an error in a coroutine closes variables
+ local x = false
+ local y = false
+ local co = coroutine.create(function ()
+ local scoped xv = function () x = true end
+ do
+ local scoped yv = function () y = true end
+ coroutine.yield(100) -- yield doesn't close variable
+ end
+ coroutine.yield(200) -- yield doesn't close variable
+ error(23) -- error does
+ end)
+
+ local a, b = coroutine.resume(co)
+ assert(a and b == 100 and not x and not y)
+ a, b = coroutine.resume(co)
+ assert(a and b == 200 and not x and y)
+ a, b = coroutine.resume(co)
+ assert(not a and b == 23 and x and y)
+end
+
-- a suspended coroutine should not close its variables when collected
-local co = coroutine.wrap(function()
+local co
+co = coroutine.wrap(function()
local scoped x = function () os.exit(false) end -- should not run
- coroutine.yield()
+ co = nil
+ coroutine.yield()
end)
-co()
-co = nil
+co() -- start coroutine
+assert(co == nil) -- eventually it will be collected
print('OK')
From 7c8146d556714508224dc3f3a68677c18ece00b7 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 22 Oct 2018 15:02:09 -0300
Subject: [PATCH 079/889] Small improvements in the manual
---
manual/manual.of | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/manual/manual.of b/manual/manual.of
index 47a551bfa2..d8bac5dad9 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -664,7 +664,9 @@ The default value is 20; the maximum value is 200.
You can set garbage-collector metamethods for tables
and, using the @N{C API},
for full userdata @see{metatable}.
-These metamethods are also called @def{finalizers}.
+These metamethods, called @def{finalizers},
+are called when the garbage collector detects that the
+corresponding table or userdata is unreachable.
Finalizers allow you to coordinate Lua's garbage collection
with external resource management
(such as closing files, network or database connections,
@@ -720,6 +722,10 @@ Lua calls the finalizers of all objects marked for finalization,
following the reverse order that they were marked.
If any finalizer marks objects for collection during that phase,
these marks have no effect.
+If any finalizer raises an error during that phase,
+its execution is interrupted but the error is ignored.
+
+Finalizers cannot yield.
}
@@ -5911,17 +5917,21 @@ If there are no syntactic errors,
returns the compiled chunk as a function;
otherwise, returns @nil plus the error message.
-If the resulting function has upvalues,
-the first upvalue is set to the value of @id{env},
-if that parameter is given,
-or to the value of the @x{global environment}.
-Other upvalues are initialized with @nil.
-(When you load a main chunk,
+When you load a main chunk,
the resulting function will always have exactly one upvalue,
the @id{_ENV} variable @see{globalenv}.
However,
when you load a binary chunk created from a function @seeF{string.dump},
-the resulting function can have an arbitrary number of upvalues.)
+the resulting function can have an arbitrary number of upvalues,
+and there is no guarantee that its first upvalue will be
+the @id{_ENV} variable.
+(A non-main function may not even have an @id{_ENV} upvalue.)
+
+Regardless, if the resulting function has any upvalues,
+its first upvalue is set to the value of @id{env},
+if that parameter is given,
+or to the value of the @x{global environment}.
+Other upvalues are initialized with @nil.
All upvalues are fresh, that is,
they are not shared with any other function.
From 6a4b9bb2b47a9ca564b986b27d2451e602bc2c5b Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 22 Oct 2018 15:20:07 -0300
Subject: [PATCH 080/889] Removed extra information from RCS keyword strings in
tests
Version numbers and dates (mostly wrong) from RCS keyword strings
removed from all test files; only the file name are kept.
---
testes/all.lua | 0
testes/api.lua | 2 +-
testes/bitwise.lua | 0
testes/coroutine.lua | 2 +-
testes/events.lua | 2 +-
testes/math.lua | 2 +-
6 files changed, 4 insertions(+), 4 deletions(-)
mode change 100755 => 100644 testes/all.lua
mode change 100755 => 100644 testes/bitwise.lua
diff --git a/testes/all.lua b/testes/all.lua
old mode 100755
new mode 100644
diff --git a/testes/api.lua b/testes/api.lua
index 925a80c1ea..6e11c6ed76 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -1,4 +1,4 @@
--- $Id: testes/api.lua 2018-07-25 15:31:04 -0300 $
+-- $Id: testes/api.lua $
-- See Copyright Notice in file all.lua
if T==nil then
diff --git a/testes/bitwise.lua b/testes/bitwise.lua
old mode 100755
new mode 100644
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index 36eae44aea..7d42eadde0 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -1,4 +1,4 @@
--- $Id: testes/coroutine.lua 2018-07-25 15:31:04 -0300 $
+-- $Id: testes/coroutine.lua $
-- See Copyright Notice in file all.lua
print "testing coroutines"
diff --git a/testes/events.lua b/testes/events.lua
index c4d43d5183..b071c2a314 100644
--- a/testes/events.lua
+++ b/testes/events.lua
@@ -1,4 +1,4 @@
--- $Id: testes/events.lua 2018-07-25 15:31:04 -0300 $
+-- $Id: testes/events.lua $
-- See Copyright Notice in file all.lua
print('testing metatables')
diff --git a/testes/math.lua b/testes/math.lua
index b387977e8d..7c780e592a 100644
--- a/testes/math.lua
+++ b/testes/math.lua
@@ -1,4 +1,4 @@
--- $Id: testes/math.lua 2018-07-25 15:31:04 -0300 $
+-- $Id: testes/math.lua $
-- See Copyright Notice in file all.lua
print("testing numbers and math lib")
From ea1322ef5438aa38f16fa00d3ab377811bba5a76 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 23 Oct 2018 12:58:38 -0300
Subject: [PATCH 081/889] Detail: bad assertion in 'luaM_free_'
---
lmem.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lmem.c b/lmem.c
index 7751870741..53f8dcb9d6 100644
--- a/lmem.c
+++ b/lmem.c
@@ -108,7 +108,7 @@ l_noret luaM_toobig (lua_State *L) {
*/
void luaM_free_ (lua_State *L, void *block, size_t osize) {
global_State *g = G(L);
- lua_assert((block == 0) == (block == NULL));
+ lua_assert((osize == 0) == (block == NULL));
(*g->frealloc)(g->ud, block, osize, 0);
g->GCdebt -= osize;
}
From 0a9aca56caa925c42aaa683b43560357ab736ea4 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 23 Oct 2018 13:57:25 -0300
Subject: [PATCH 082/889] Added a '__close' metamethod to file handles
---
liolib.c | 1 +
testes/files.lua | 57 +++++++++++++++++++++++++++++-------------------
2 files changed, 36 insertions(+), 22 deletions(-)
diff --git a/liolib.c b/liolib.c
index 21305b8c76..3dc509bdb8 100644
--- a/liolib.c
+++ b/liolib.c
@@ -743,6 +743,7 @@ static const luaL_Reg flib[] = {
{"setvbuf", f_setvbuf},
{"write", f_write},
{"__gc", f_gc},
+ {"__close", f_gc},
{"__tostring", f_tostring},
{NULL, NULL}
};
diff --git a/testes/files.lua b/testes/files.lua
index c3e42235bc..9aae591324 100644
--- a/testes/files.lua
+++ b/testes/files.lua
@@ -120,31 +120,45 @@ io.output(io.open(otherfile, "ab"))
assert(io.write("\n\n\t\t ", 3450, "\n"));
io.close()
--- test writing/reading numbers
-f = assert(io.open(file, "w"))
-f:write(maxint, '\n')
-f:write(string.format("0X%x\n", maxint))
-f:write("0xABCp-3", '\n')
-f:write(0, '\n')
-f:write(-maxint, '\n')
-f:write(string.format("0x%X\n", -maxint))
-f:write("-0xABCp-3", '\n')
-assert(f:close())
-f = assert(io.open(file, "r"))
-assert(f:read("n") == maxint)
-assert(f:read("n") == maxint)
-assert(f:read("n") == 0xABCp-3)
-assert(f:read("n") == 0)
-assert(f:read("*n") == -maxint) -- test old format (with '*')
-assert(f:read("n") == -maxint)
-assert(f:read("*n") == -0xABCp-3) -- test old format (with '*')
-assert(f:close())
+
+do
+ -- closing file by scope
+ local F = nil
+ do
+ local scoped f = assert(io.open(file, "w"))
+ F = f
+ end
+ assert(tostring(F) == "file (closed)")
+end
+assert(os.remove(file))
+
+
+do
+ -- test writing/reading numbers
+ local scoped f = assert(io.open(file, "w"))
+ f:write(maxint, '\n')
+ f:write(string.format("0X%x\n", maxint))
+ f:write("0xABCp-3", '\n')
+ f:write(0, '\n')
+ f:write(-maxint, '\n')
+ f:write(string.format("0x%X\n", -maxint))
+ f:write("-0xABCp-3", '\n')
+ assert(f:close())
+ f = assert(io.open(file, "r"))
+ assert(f:read("n") == maxint)
+ assert(f:read("n") == maxint)
+ assert(f:read("n") == 0xABCp-3)
+ assert(f:read("n") == 0)
+ assert(f:read("*n") == -maxint) -- test old format (with '*')
+ assert(f:read("n") == -maxint)
+ assert(f:read("*n") == -0xABCp-3) -- test old format (with '*')
+end
assert(os.remove(file))
-- testing multiple arguments to io.read
do
- local f = assert(io.open(file, "w"))
+ local scoped f = assert(io.open(file, "w"))
f:write[[
a line
another line
@@ -171,9 +185,8 @@ three
-- second item failing
l1, n1, n2, dummy = f:read("l", "n", "n", "l")
assert(l1 == "a line" and n1 == nil)
- assert(f:close())
- assert(os.remove(file))
end
+assert(os.remove(file))
From 41c800b352149e037bdebd5f20d2f25ed2a0e2a5 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 25 Oct 2018 12:50:20 -0300
Subject: [PATCH 083/889] Closing methods should not interfere with returning
values
A closing method cannot be called in its own stack slot, as there may
be returning values in the stack after that slot, and the call would
corrupt those values. Instead, the closing method must be copied to the
top of the stack to be called.
Moreover, even when a function returns no value, its return istruction
still has to have its position (which will set the stack top) after
the local variables, otherwise a closing method might corrupt another
not-yet-called closing method.
---
lfunc.c | 64 ++++++++++++++++++++++++-----------------------
lparser.c | 15 ++++++-----
lvm.c | 6 +++--
testes/locals.lua | 55 +++++++++++++++++++++++++++++++++++++++-
4 files changed, 98 insertions(+), 42 deletions(-)
diff --git a/lfunc.c b/lfunc.c
index 4f9362f3c4..15874f88b2 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -98,27 +98,29 @@ UpVal *luaF_findupval (lua_State *L, StkId level) {
static void callclose (lua_State *L, void *ud) {
- luaD_callnoyield(L, cast(StkId, ud), 0);
+ UNUSED(ud);
+ luaD_callnoyield(L, L->top - 2, 0);
}
/*
-** Prepare closing method with its argument for object at
-** index 'func' in the stack. Assume there is an error message
-** (or nil) just below the object.
+** Prepare closing method plus its argument for object 'obj' with
+** error message 'err'. (This function assumes EXTRA_STACK.)
*/
-static int prepclosingmethod (lua_State *L, StkId func) {
- if (ttisfunction(s2v(func))) { /* object to-be-closed is a function? */
- setobjs2s(L, func + 1, func - 1); /* push error msg. as argument */
+static int prepclosingmethod (lua_State *L, TValue *obj, TValue *err) {
+ StkId top = L->top;
+ if (ttisfunction(obj)) { /* object to-be-closed is a function? */
+ setobj2s(L, top, obj); /* push function */
+ setobj2s(L, top + 1, err); /* push error msg. as argument */
}
else { /* try '__close' metamethod */
- const TValue *tm = luaT_gettmbyobj(L, s2v(func), TM_CLOSE);
+ const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE);
if (ttisnil(tm)) /* no metamethod? */
return 0; /* nothing to call */
- setobjs2s(L, func + 1, func); /* 'self' is the argument */
- setobj2s(L, func, tm); /* will call metamethod */
+ setobj2s(L, top, tm); /* will call metamethod... */
+ setobj2s(L, top + 1, obj); /* with 'self' as the argument */
}
- L->top = func + 2; /* add function and argument */
+ L->top = top + 2; /* add function and argument */
return 1;
}
@@ -129,22 +131,24 @@ static int prepclosingmethod (lua_State *L, StkId func) {
** will be handled there. Otherwise, a previous error already
** activated original protected call, and so the call to the
** closing method must be protected here.
+** If status is OK, the call to the closing method will be pushed
+** at the top of the stack. Otherwise, values are pushed after
+** the 'level' of the upvalue being closed, as everything after
+** that won't be used again.
*/
static int closeupval (lua_State *L, TValue *uv, StkId level, int status) {
- StkId func = level + 1; /* save slot for old error message */
- if (unlikely(status != LUA_OK)) /* was there an error? */
- luaD_seterrorobj(L, status, level); /* save error message */
- else
- setnilvalue(s2v(level)); /* no error message */
- setobj2s(L, func, uv); /* put object on top of error message */
- if (!prepclosingmethod(L, func))
- return status; /* nothing to call */
- if (likely(status == LUA_OK)) /* not in "error mode"? */
- callclose(L, func); /* call closing method */
- else { /* already inside error handler; cannot raise another error */
- int newstatus = luaD_pcall(L, callclose, func, savestack(L, level), 0);
- if (newstatus != LUA_OK) /* error when closing? */
- status = newstatus; /* this will be the new error */
+ if (likely(status == LUA_OK)) {
+ if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */
+ callclose(L, NULL); /* call closing method */
+ }
+ else { /* there was an error */
+ /* save error message and set stack top to 'level + 1' */
+ luaD_seterrorobj(L, status, level);
+ if (prepclosingmethod(L, uv, s2v(level))) { /* something to call? */
+ int newstatus = luaD_pcall(L, callclose, NULL, savestack(L, level), 0);
+ if (newstatus != LUA_OK) /* another error when closing? */
+ status = newstatus; /* this will be the new error */
+ }
}
return status;
}
@@ -169,12 +173,10 @@ static void trynewtbcupval (lua_State *L, void *ud) {
void luaF_newtbcupval (lua_State *L, StkId level) {
int status = luaD_rawrunprotected(L, trynewtbcupval, level);
if (unlikely(status != LUA_OK)) { /* memory error creating upvalue? */
- StkId func = level + 1;
lua_assert(status == LUA_ERRMEM);
- setobjs2s(L, func, level); /* open space for error message */
- luaD_seterrorobj(L, status, level); /* save error message */
- if (prepclosingmethod(L, func))
- callclose(L, func); /* call closing method */
+ luaD_seterrorobj(L, LUA_ERRMEM, level + 1); /* save error message */
+ if (prepclosingmethod(L, s2v(level), s2v(level + 1)))
+ callclose(L, NULL); /* call closing method */
luaD_throw(L, LUA_ERRMEM); /* throw memory error */
}
}
@@ -201,7 +203,7 @@ int luaF_close (lua_State *L, StkId level, int status) {
luaC_barrier(L, uv, slot);
if (status >= 0 && uv->tt == LUA_TUPVALTBC) { /* must be closed? */
ptrdiff_t levelrel = savestack(L, level);
- status = closeupval(L, uv->v, upl, status); /* may reallocate the stack */
+ status = closeupval(L, uv->v, upl, status); /* may realloc. the stack */
level = restorestack(L, levelrel);
}
}
diff --git a/lparser.c b/lparser.c
index 6b14b80047..c0c40eaeec 100644
--- a/lparser.c
+++ b/lparser.c
@@ -561,7 +561,7 @@ static void close_func (LexState *ls) {
lua_State *L = ls->L;
FuncState *fs = ls->fs;
Proto *f = fs->f;
- luaK_ret(fs, 0, 0); /* final return */
+ luaK_ret(fs, fs->nactvar, 0); /* final return */
leaveblock(fs);
lua_assert(fs->bl == NULL);
luaK_finish(fs);
@@ -1602,9 +1602,10 @@ static void retstat (LexState *ls) {
/* stat -> RETURN [explist] [';'] */
FuncState *fs = ls->fs;
expdesc e;
- int first, nret; /* registers with returned values */
+ int nret; /* number of values being returned */
+ int first = fs->nactvar; /* first slot to be returned */
if (block_follow(ls, 1) || ls->t.token == ';')
- first = nret = 0; /* return no values */
+ nret = 0; /* return no values */
else {
nret = explist(ls, &e); /* optional return values */
if (hasmultret(e.k)) {
@@ -1613,15 +1614,13 @@ static void retstat (LexState *ls) {
SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL);
lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar);
}
- first = fs->nactvar;
nret = LUA_MULTRET; /* return all values */
}
else {
if (nret == 1) /* only one single value? */
- first = luaK_exp2anyreg(fs, &e);
- else {
- luaK_exp2nextreg(fs, &e); /* values must go to the stack */
- first = fs->nactvar; /* return all active values */
+ first = luaK_exp2anyreg(fs, &e); /* can use original slot */
+ else { /* values must go to the top of the stack */
+ luaK_exp2nextreg(fs, &e);
lua_assert(nret == fs->freereg - first);
}
}
diff --git a/lvm.c b/lvm.c
index 0d82756b93..2a1ee175a7 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1452,7 +1452,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_CLOSE) {
- luaF_close(L, ra, LUA_OK);
+ L->top = ra + 1; /* everything is free after this slot */
+ ProtectNT(luaF_close(L, ra, LUA_OK));
vmbreak;
}
vmcase(OP_TBC) {
@@ -1619,13 +1620,14 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
n = cast_int(L->top - ra); /* get what is available */
else
L->top = ra + n; /* set call for 'luaD_poscall' */
+ savepc(ci);
if (TESTARG_k(i)) {
int nparams1 = GETARG_C(i);
if (nparams1) /* vararg function? */
ci->func -= ci->u.l.nextraargs + nparams1;
luaF_close(L, base, LUA_OK); /* there may be open upvalues */
}
- halfProtect(luaD_poscall(L, ci, n));
+ luaD_poscall(L, ci, n);
return;
}
vmcase(OP_RETURN0) {
diff --git a/testes/locals.lua b/testes/locals.lua
index f21fa2ec92..65b145dbb5 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -175,6 +175,9 @@ assert(x==20)
print"testing to-be-closed variables"
+local function stack(n) n = ((n == 0) or stack(n - 1)) end
+
+
do
local a = {}
do
@@ -187,6 +190,57 @@ do
assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out")
end
+do
+ local X = false
+
+ local function closescope () stack(10); X = true end
+
+ -- closing functions do not corrupt returning values
+ local function foo (x)
+ local scoped _ = closescope
+ return x, X, 23
+ end
+
+ local a, b, c = foo(1.5)
+ assert(a == 1.5 and b == false and c == 23 and X == true)
+
+ X = false
+ foo = function (x)
+ local scoped _ = closescope
+ local y = 15
+ return y
+ end
+
+ assert(foo() == 15 and X == true)
+
+ X = false
+ foo = function ()
+ local scoped x = closescope
+ return x
+ end
+
+ assert(foo() == closescope and X == true)
+
+end
+
+
+do
+ -- to-be-closed variables must be closed in tail calls
+ local X, Y
+ local function foo ()
+ local scoped _ = function () Y = 10 end
+ assert(X == 20 and Y == nil)
+ return 1,2,3
+ end
+
+ local function bar ()
+ local scoped _ = function () X = 20 end
+ return foo()
+ end
+
+ local a, b, c, d = bar()
+ assert(a == 1 and b == 2 and c == 3 and X == 20 and Y == 10 and d == nil)
+end
do -- errors in __close
local log = {}
@@ -211,7 +265,6 @@ do -- errors in __close
end
if rawget(_G, "T") then
- local function stack(n) n = (n == 0) or stack(n - 1); end;
-- memory error inside closing function
local function foo ()
local scoped y = function () T.alloccount() end
From 34840301b529686ce8168828b140a478a5d44b53 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 25 Oct 2018 15:30:15 -0300
Subject: [PATCH 084/889] To-be-closed variables in the C API
---
lapi.c | 15 +++++++++--
lapi.h | 15 ++++++++++-
ldo.c | 32 ++++++++++++++---------
ltests.c | 3 +++
lua.h | 2 ++
testes/api.lua | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 122 insertions(+), 16 deletions(-)
diff --git a/lapi.c b/lapi.c
index ae6b07aebd..4fef43b713 100644
--- a/lapi.c
+++ b/lapi.c
@@ -173,15 +173,17 @@ LUA_API void lua_settop (lua_State *L, int idx) {
StkId func = L->ci->func;
lua_lock(L);
if (idx >= 0) {
+ StkId newtop = (func + 1) + idx;
api_check(L, idx <= L->stack_last - (func + 1), "new top too large");
- while (L->top < (func + 1) + idx)
+ while (L->top < newtop)
setnilvalue(s2v(L->top++));
- L->top = (func + 1) + idx;
+ L->top = newtop;
}
else {
api_check(L, -(idx+1) <= (L->top - (func + 1)), "invalid new top");
L->top += idx+1; /* 'subtract' index (index is negative) */
}
+ luaF_close(L, L->top, LUA_OK);
lua_unlock(L);
}
@@ -1205,6 +1207,15 @@ LUA_API int lua_next (lua_State *L, int idx) {
}
+LUA_API void lua_tobeclosed (lua_State *L) {
+ int nresults = L->ci->nresults;
+ luaF_newtbcupval(L, L->top - 1); /* create new to-be-closed upvalue */
+ if (!hastocloseCfunc(nresults)) /* function not marked yet? */
+ L->ci->nresults = codeNresults(nresults); /* mark it */
+ lua_assert(hastocloseCfunc(L->ci->nresults));
+}
+
+
LUA_API void lua_concat (lua_State *L, int n) {
lua_lock(L);
api_checknelems(L, n);
diff --git a/lapi.h b/lapi.h
index 016f78cccd..5a4206f1ae 100644
--- a/lapi.h
+++ b/lapi.h
@@ -15,10 +15,23 @@
"stack overflow");}
#define adjustresults(L,nres) \
- { if ((nres) == LUA_MULTRET && L->ci->top < L->top) L->ci->top = L->top; }
+ { if ((nres) <= LUA_MULTRET && L->ci->top < L->top) L->ci->top = L->top; }
#define api_checknelems(L,n) api_check(L, (n) < (L->top - L->ci->func), \
"not enough elements in the stack")
+/*
+** To reduce the overhead of returning from C functions, the presence of
+** to-be-closed variables in these functions is coded in the CallInfo's
+** field 'nresults', in a way that functions with no to-be-closed variables
+** with zero, one, or "all" wanted results have no overhead. Functions
+** with other number of wanted results, as well as functions with
+** variables to be closed, have an extra check.
+*/
+
+#define hastocloseCfunc(n) ((n) < LUA_MULTRET)
+
+#define codeNresults(n) (-(n) - 3)
+
#endif
diff --git a/ldo.c b/ldo.c
index 78e4c5c3fb..b7a76ef6f7 100644
--- a/ldo.c
+++ b/ldo.c
@@ -366,32 +366,38 @@ void luaD_tryfuncTM (lua_State *L, StkId func) {
** separated.
*/
static void moveresults (lua_State *L, StkId res, int nres, int wanted) {
+ StkId firstresult;
+ int i;
switch (wanted) { /* handle typical cases separately */
case 0: /* no values needed */
L->top = res;
- break;
+ return;
case 1: /* one value needed */
if (nres == 0) /* no results? */
setnilvalue(s2v(res)); /* adjust with nil */
else
setobjs2s(L, res, L->top - nres); /* move it to proper place */
L->top = res + 1;
- break;
+ return;
case LUA_MULTRET:
wanted = nres; /* we want all results */
- /* FALLTHROUGH */
- default: { /* multiple results */
- StkId firstresult = L->top - nres; /* index of first result */
- int i;
- /* move all results to correct place */
- for (i = 0; i < nres && i < wanted; i++)
- setobjs2s(L, res + i, firstresult + i);
- for (; i < wanted; i++) /* complete wanted number of results */
- setnilvalue(s2v(res + i));
- L->top = res + wanted; /* top points after the last result */
break;
- }
+ default: /* multiple results (or to-be-closed variables) */
+ if (hastocloseCfunc(wanted)) {
+ luaF_close(L, res, LUA_OK);
+ wanted = codeNresults(wanted); /* correct value */
+ if (wanted == LUA_MULTRET)
+ wanted = nres;
+ }
+ break;
}
+ firstresult = L->top - nres; /* index of first result */
+ /* move all results to correct place */
+ for (i = 0; i < nres && i < wanted; i++)
+ setobjs2s(L, res + i, firstresult + i);
+ for (; i < wanted; i++) /* complete wanted number of results */
+ setnilvalue(s2v(res + i));
+ L->top = res + wanted; /* top points after the last result */
}
diff --git a/ltests.c b/ltests.c
index a6968653a5..fbcb747571 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1554,6 +1554,9 @@ static struct X { int x; } x;
int i = getindex;
return lua_yieldk(L1, nres, i, Cfunck);
}
+ else if EQ("tobeclosed") {
+ lua_tobeclosed(L);
+ }
else luaL_error(L, "unknown instruction %s", buff);
}
return 0;
diff --git a/lua.h b/lua.h
index a014be1fdd..16d685ccb0 100644
--- a/lua.h
+++ b/lua.h
@@ -333,6 +333,8 @@ LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s);
LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud);
LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
+LUA_API void (lua_tobeclosed) (lua_State *L);
+
/*
** {==============================================================
diff --git a/testes/api.lua b/testes/api.lua
index 6e11c6ed76..988250f719 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -967,6 +967,77 @@ T.closestate(L1)
L1 = nil
print('+')
+-------------------------------------------------------------------------
+-- testing to-be-closed variables
+-------------------------------------------------------------------------
+print"testing to-be-closed variables"
+
+do
+ local openresource = {}
+
+ local function newresource ()
+ local x = setmetatable({10}, {__close = function(y)
+ assert(openresource[#openresource] == y)
+ openresource[#openresource] = nil
+ y[1] = y[1] + 1
+ end})
+ openresource[#openresource + 1] = x
+ return x
+ end
+
+ local a = T.testC([[
+ call 0 1 # create resource
+ tobeclosed # mark it to be closed
+ return 1
+ ]], newresource)
+ assert(a[1] == 11)
+ assert(#openresource == 0) -- was closed
+
+ -- repeat the test, but calling function in a 'multret' context
+ local a = {T.testC([[
+ call 0 1 # create resource
+ tobeclosed # mark it to be closed
+ return 2
+ ]], newresource)}
+ assert(type(a[1]) == "string" and a[2][1] == 11)
+ assert(#openresource == 0) -- was closed
+
+ -- error
+ local a, b = pcall(T.testC, [[
+ call 0 1 # create resource
+ tobeclosed # mark it to be closed
+ error # resource is the error object
+ ]], newresource)
+ assert(a == false and b[1] == 11)
+ assert(#openresource == 0) -- was closed
+
+ local function check (n)
+ assert(#openresource == n)
+ end
+
+ -- closing resources with 'settop'
+ local a = T.testC([[
+ pushvalue 2
+ call 0 1 # create resource
+ tobeclosed # mark it to be closed
+ pushvalue 2
+ call 0 1 # create another resource
+ tobeclosed # mark it to be closed
+ pushvalue 3
+ pushint 2 # there should be two open resources
+ call 1 0
+ pop 1 # pop second resource from the stack
+ pushvalue 3
+ pushint 1 # there should be one open resource
+ call 1 0
+ pop 1 # pop second resource from the stack
+ pushint *
+ return 1 # return stack size
+ ]], newresource, check)
+ assert(a == 3) -- no extra items left in the stack
+
+end
+
-------------------------------------------------------------------------
-- testing memory limits
From 6e9b719694bffb8de711f182d405ec37d32ae0b1 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 26 Oct 2018 10:38:50 -0300
Subject: [PATCH 085/889] More uniformity in code generation for 'for' loops
Added new instruction 'OP_TFORPREP' to prepare a generic for loop.
Currently it is equivalent to a jump (but with a format 'iABx',
similar to other for-loop preparing instructions), but soon it will
be the place to create upvalues for closing loop states.
---
ljumptab.h | 1 +
lopcodes.c | 1 +
lopcodes.h | 1 +
lopnames.h | 1 +
lparser.c | 16 ++++++----------
lvm.c | 4 ++++
6 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/ljumptab.h b/ljumptab.h
index da4cf7b70d..6767e95b18 100644
--- a/ljumptab.h
+++ b/ljumptab.h
@@ -96,6 +96,7 @@ static void *disptab[] = {
&&L_OP_FORPREP1,
&&L_OP_FORLOOP,
&&L_OP_FORPREP,
+&&L_OP_TFORPREP,
&&L_OP_TFORCALL,
&&L_OP_TFORLOOP,
&&L_OP_SETLIST,
diff --git a/lopcodes.c b/lopcodes.c
index f6915beb88..11a73c2957 100644
--- a/lopcodes.c
+++ b/lopcodes.c
@@ -90,6 +90,7 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
,opmode(0, 0, 0, 1, iABx) /* OP_FORPREP1 */
,opmode(0, 0, 0, 1, iABx) /* OP_FORLOOP */
,opmode(0, 0, 0, 1, iABx) /* OP_FORPREP */
+ ,opmode(0, 0, 0, 0, iABx) /* OP_TFORPREP */
,opmode(0, 0, 0, 0, iABC) /* OP_TFORCALL */
,opmode(0, 0, 0, 1, iABx) /* OP_TFORLOOP */
,opmode(0, 1, 0, 0, iABC) /* OP_SETLIST */
diff --git a/lopcodes.h b/lopcodes.h
index 4d144000fe..4797d7c37c 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -282,6 +282,7 @@ OP_FORLOOP,/* A Bx R(A)+=R(A+2);
if R(A) = R(A+1) then { pc-=Bx; R(A+3)=R(A) } */
OP_FORPREP,/* A Bx R(A)-=R(A+2); pc+=Bx */
+OP_TFORPREP,/* A Bx create upvalue A; pc+=Bx */
OP_TFORCALL,/* A C R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2)); */
OP_TFORLOOP,/* A Bx if R(A+1) ~= nil then { R(A)=R(A+1); pc -= Bx } */
diff --git a/lopnames.h b/lopnames.h
index 304d3b6cac..b2c4fe21fb 100644
--- a/lopnames.h
+++ b/lopnames.h
@@ -81,6 +81,7 @@ static const char *const opnames[] = {
"FORPREP1",
"FORLOOP",
"FORPREP",
+ "TFORPREP",
"TFORCALL",
"TFORLOOP",
"SETLIST",
diff --git a/lparser.c b/lparser.c
index c0c40eaeec..c3dc636211 100644
--- a/lparser.c
+++ b/lparser.c
@@ -1350,30 +1350,26 @@ static void fixforjump (FuncState *fs, int pc, int dest, int back) {
*/
static void forbody (LexState *ls, int base, int line, int nvars, int kind) {
/* forbody -> DO block */
+ static OpCode forprep[3] = {OP_FORPREP, OP_FORPREP1, OP_TFORPREP};
+ static OpCode forloop[3] = {OP_FORLOOP, OP_FORLOOP1, OP_TFORLOOP};
BlockCnt bl;
FuncState *fs = ls->fs;
int prep, endfor;
adjustlocalvars(ls, 3); /* control variables */
checknext(ls, TK_DO);
- prep = (kind == 0) ? luaK_codeABx(fs, OP_FORPREP, base, 0)
- : (kind == 1) ? luaK_codeABx(fs, OP_FORPREP1, base, 0)
- : luaK_jump(fs);
+ prep = luaK_codeABx(fs, forprep[kind], base, 0);
enterblock(fs, &bl, 0); /* scope for declared variables */
adjustlocalvars(ls, nvars);
luaK_reserveregs(fs, nvars);
block(ls);
leaveblock(fs); /* end of scope for declared variables */
+ fixforjump(fs, prep, luaK_getlabel(fs), 0);
if (kind == 2) { /* generic for? */
- luaK_patchtohere(fs, prep);
luaK_codeABC(fs, OP_TFORCALL, base, 0, nvars);
luaK_fixline(fs, line);
- endfor = luaK_codeABx(fs, OP_TFORLOOP, base + 2, 0);
- }
- else {
- fixforjump(fs, prep, luaK_getlabel(fs), 0);
- endfor = (kind == 0) ? luaK_codeABx(fs, OP_FORLOOP, base, 0)
- : luaK_codeABx(fs, OP_FORLOOP1, base, 0);
+ base += 2; /* base for 'OP_TFORLOOP' (skips function and state) */
}
+ endfor = luaK_codeABx(fs, forloop[kind], base, 0);
fixforjump(fs, endfor, prep + 1, 1);
luaK_fixline(fs, line);
}
diff --git a/lvm.c b/lvm.c
index 2a1ee175a7..35a58089ca 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1744,6 +1744,10 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
pc += GETARG_Bx(i);
vmbreak;
}
+ vmcase(OP_TFORPREP) {
+ pc += GETARG_Bx(i);
+ vmbreak;
+ }
vmcase(OP_TFORCALL) {
StkId cb = ra + 3; /* call base */
setobjs2s(L, cb+2, ra+2);
From a006514ea138a29b6031058d9002b48a572b5dd6 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 29 Oct 2018 14:26:48 -0300
Subject: [PATCH 086/889] Big revamp in the implmentation of labels/gotos
Added restriction that, when a label is created, there cannot be
another label with the same name visible. That allows backward goto's
to be resolved when they are read. Backward goto's get a close if
they jump out of the scope of some variable; labels get a close only
if previous goto to it jumps out of the scope of some upvalue.
---
lcode.c | 34 -------
lcode.h | 3 -
lopcodes.h | 9 +-
lparser.c | 241 +++++++++++++++++++++++-------------------------
lparser.h | 2 +
ltests.c | 2 +-
testes/code.lua | 19 ++--
testes/goto.lua | 3 +-
8 files changed, 131 insertions(+), 182 deletions(-)
diff --git a/lcode.c b/lcode.c
index e84b85ac5f..054b28fd1b 100644
--- a/lcode.c
+++ b/lcode.c
@@ -275,40 +275,6 @@ void luaK_patchtohere (FuncState *fs, int list) {
}
-/*
-** Correct a jump list to jump to 'target'. If 'hasclose' is true,
-** 'target' contains an OP_CLOSE instruction (see first assert).
-** Only the jumps with ('m' == true) need that close; other jumps
-** avoid it jumping to the next instruction.
-*/
-void luaK_patchgoto (FuncState *fs, int list, int target, int hasclose) {
- lua_assert(!hasclose || GET_OPCODE(fs->f->code[target]) == OP_CLOSE);
- while (list != NO_JUMP) {
- int next = getjump(fs, list);
- lua_assert(!GETARG_m(fs->f->code[list]) || hasclose);
- patchtestreg(fs, list, NO_REG); /* do not generate values */
- if (!hasclose || GETARG_m(fs->f->code[list]))
- fixjump(fs, list, target);
- else /* there is a CLOSE instruction but jump does not need it */
- fixjump(fs, list, target + 1); /* avoid CLOSE instruction */
- list = next;
- }
-}
-
-
-/*
-** Mark (using the 'm' arg) all jumps in 'list' to close upvalues. Mark
-** will instruct 'luaK_patchgoto' to make these jumps go to OP_CLOSE
-** instructions.
-*/
-void luaK_patchclose (FuncState *fs, int list) {
- for (; list != NO_JUMP; list = getjump(fs, list)) {
- lua_assert(GET_OPCODE(fs->f->code[list]) == OP_JMP);
- SETARG_m(fs->f->code[list], 1);
- }
-}
-
-
/*
** MAXimum number of successive Instructions WiTHout ABSolute line
** information.
diff --git a/lcode.h b/lcode.h
index dd091c7818..0758f88dee 100644
--- a/lcode.h
+++ b/lcode.h
@@ -78,10 +78,7 @@ LUAI_FUNC void luaK_setoneret (FuncState *fs, expdesc *e);
LUAI_FUNC int luaK_jump (FuncState *fs);
LUAI_FUNC void luaK_ret (FuncState *fs, int first, int nret);
LUAI_FUNC void luaK_patchlist (FuncState *fs, int list, int target);
-LUAI_FUNC void luaK_patchgoto (FuncState *fs, int list, int target,
- int hasclose);
LUAI_FUNC void luaK_patchtohere (FuncState *fs, int list);
-LUAI_FUNC void luaK_patchclose (FuncState *fs, int list);
LUAI_FUNC void luaK_concat (FuncState *fs, int *l1, int l2);
LUAI_FUNC int luaK_getlabel (FuncState *fs);
LUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v, int line);
diff --git a/lopcodes.h b/lopcodes.h
index 4797d7c37c..5a38f76746 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -21,7 +21,7 @@ iABC C(8) | B(8) |k| A(8) | Op(7) |
iABx Bx(17) | A(8) | Op(7) |
iAsB sBx (signed)(17) | A(8) | Op(7) |
iAx Ax(25) | Op(7) |
-isJ sJ(24) |m| Op(7) |
+isJ sJ(25) | Op(7) |
A signed argument is represented in excess K: the represented value is
the written unsigned value minus K, where K is half the maximum for the
@@ -40,7 +40,7 @@ enum OpMode {iABC, iABx, iAsBx, iAx, isJ}; /* basic instruction formats */
#define SIZE_Bx (SIZE_C + SIZE_B + 1)
#define SIZE_A 8
#define SIZE_Ax (SIZE_Bx + SIZE_A)
-#define SIZE_sJ (SIZE_Bx + SIZE_A - 1)
+#define SIZE_sJ (SIZE_Bx + SIZE_A)
#define SIZE_OP 7
@@ -55,8 +55,7 @@ enum OpMode {iABC, iABx, iAsBx, iAx, isJ}; /* basic instruction formats */
#define POS_Ax POS_A
-#define POS_m POS_A
-#define POS_sJ (POS_A + 1)
+#define POS_sJ POS_A
/*
** limits for opcode arguments.
@@ -144,8 +143,6 @@ enum OpMode {iABC, iABx, iAsBx, iAx, isJ}; /* basic instruction formats */
check_exp(checkopm(i, isJ), getarg(i, POS_sJ, SIZE_sJ) - OFFSET_sJ)
#define SETARG_sJ(i,j) \
setarg(i, cast_uint((j)+OFFSET_sJ), POS_sJ, SIZE_sJ)
-#define GETARG_m(i) check_exp(checkopm(i, isJ), getarg(i, POS_m, 1))
-#define SETARG_m(i,m) setarg(i, m, POS_m, 1)
#define CREATE_ABCk(o,a,b,c,k) ((cast(Instruction, o)<L->nCcalls--)
-static void closegoto (LexState *ls, int g, Labeldesc *label) {
+/*
+** Generates an error that a goto jumps into the scope of some
+** local variable.
+*/
+static l_noret jumpscopeerror (LexState *ls, Labeldesc *gt) {
+ const char *varname = getstr(getlocvar(ls->fs, gt->nactvar)->varname);
+ const char *msg = " at line %d jumps into the scope of local '%s'";
+ msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line, varname);
+ luaK_semerror(ls, msg); /* raise the error */
+}
+
+
+/*
+** Solves the goto at index 'g' to given 'label' and removes it
+** from the list of pending goto's.
+** If it jumps into the scope of some variable, raises an error.
+*/
+static void solvegoto (LexState *ls, int g, Labeldesc *label) {
int i;
- FuncState *fs = ls->fs;
- Labellist *gl = &ls->dyd->gt;
- Labeldesc *gt = &gl->arr[g];
+ Labellist *gl = &ls->dyd->gt; /* list of goto's */
+ Labeldesc *gt = &gl->arr[g]; /* goto to be resolved */
lua_assert(eqstr(gt->name, label->name));
- if (gt->nactvar < label->nactvar) {
- TString *vname = getlocvar(fs, gt->nactvar)->varname;
- const char *msg = luaO_pushfstring(ls->L,
- " at line %d jumps into the scope of local '%s'",
- getstr(gt->name), gt->line, getstr(vname));
- luaK_semerror(ls, msg);
- }
- luaK_patchgoto(fs, gt->pc, label->pc, 1);
- /* remove goto from pending list */
- for (i = g; i < gl->n - 1; i++)
+ if (gt->nactvar < label->nactvar) /* enter some scope? */
+ jumpscopeerror(ls, gt);
+ luaK_patchlist(ls->fs, gt->pc, label->pc);
+ for (i = g; i < gl->n - 1; i++) /* remove goto from pending list */
gl->arr[i] = gl->arr[i + 1];
gl->n--;
}
/*
-** try to close a goto with existing labels; this solves backward jumps
+** Search for an active label with the given name.
*/
-static int solvelabel (LexState *ls, int g) {
+static Labeldesc *findlabel (LexState *ls, TString *name) {
int i;
- BlockCnt *bl = ls->fs->bl;
Dyndata *dyd = ls->dyd;
- Labeldesc *gt = &dyd->gt.arr[g];
- /* check labels in current block for a match */
- for (i = bl->firstlabel; i < dyd->label.n; i++) {
+ /* check labels in current function for a match */
+ for (i = ls->fs->firstlabel; i < dyd->label.n; i++) {
Labeldesc *lb = &dyd->label.arr[i];
- if (eqstr(lb->name, gt->name)) { /* correct label? */
- if (gt->nactvar > lb->nactvar &&
- (bl->upval || dyd->label.n > bl->firstlabel))
- luaK_patchclose(ls->fs, gt->pc);
- closegoto(ls, g, lb); /* close it */
- return 1;
- }
+ if (eqstr(lb->name, name)) /* correct label? */
+ return lb;
}
- return 0; /* label not found; cannot close goto */
+ return NULL; /* label not found */
}
+/*
+** Adds a new label/goto in the corresponding list.
+*/
static int newlabelentry (LexState *ls, Labellist *l, TString *name,
int line, int pc) {
int n = l->n;
@@ -382,6 +386,7 @@ static int newlabelentry (LexState *ls, Labellist *l, TString *name,
l->arr[n].name = name;
l->arr[n].line = line;
l->arr[n].nactvar = ls->fs->nactvar;
+ l->arr[n].close = 0;
l->arr[n].pc = pc;
l->n = n + 1;
return n;
@@ -389,51 +394,64 @@ static int newlabelentry (LexState *ls, Labellist *l, TString *name,
/*
-** check whether new label 'lb' matches any pending gotos in current
-** block; solves forward jumps
+** Solves forward jumps. Check whether new label 'lb' matches any
+** pending gotos in current block and solves them. Return true
+** if any of the goto's need to close upvalues.
*/
-static void solvegotos (LexState *ls, Labeldesc *lb) {
+static int solvegotos (LexState *ls, Labeldesc *lb) {
Labellist *gl = &ls->dyd->gt;
int i = ls->fs->bl->firstgoto;
+ int needsclose = 0;
while (i < gl->n) {
- if (eqstr(gl->arr[i].name, lb->name))
- closegoto(ls, i, lb); /* will remove 'i' from the list */
+ if (eqstr(gl->arr[i].name, lb->name)) {
+ needsclose |= gl->arr[i].close;
+ solvegoto(ls, i, lb); /* will remove 'i' from the list */
+ }
else
i++;
}
+ return needsclose;
+}
+
+
+/*
+** Create a new label with the given 'name' at the given 'line'.
+** 'last' tells whether label is the last non-op statement in its
+** block. Solves all pending goto's to this new label and adds
+** a close instruction if necessary.
+** Returns true iff it added a close instruction.
+*/
+static int createlabel (LexState *ls, TString *name, int line,
+ int last) {
+ FuncState *fs = ls->fs;
+ Labellist *ll = &ls->dyd->label;
+ int l = newlabelentry(ls, ll, name, line, luaK_getlabel(fs));
+ if (last) { /* label is last no-op statement in the block? */
+ /* assume that locals are already out of scope */
+ ll->arr[l].nactvar = fs->bl->nactvar;
+ }
+ if (solvegotos(ls, &ll->arr[l])) { /* need close? */
+ luaK_codeABC(fs, OP_CLOSE, fs->nactvar, 0, 0);
+ return 1;
+ }
+ return 0;
}
/*
-** export pending gotos to outer level, to check them against
-** outer labels; if the block being exited has upvalues, and
-** the goto exits the scope of any variable (which can be the
-** upvalue), close those variables being exited. Also export
-** break list.
+** Adjust pending gotos to outer level of a block.
*/
static void movegotosout (FuncState *fs, BlockCnt *bl) {
- int i = bl->firstgoto;
+ int i;
Labellist *gl = &fs->ls->dyd->gt;
- /* correct pending gotos to current block and try to close it
- with visible labels */
- while (i < gl->n) { /* for each pending goto */
+ /* correct pending gotos to current block */
+ for (i = bl->firstgoto; i < gl->n; i++) { /* for each pending goto */
Labeldesc *gt = &gl->arr[i];
if (gt->nactvar > bl->nactvar) { /* leaving a variable scope? */
- if (bl->upval) /* variable may be an upvalue? */
- luaK_patchclose(fs, gt->pc); /* jump will need a close */
gt->nactvar = bl->nactvar; /* update goto level */
+ gt->close |= bl->upval; /* jump may need a close */
}
- if (!solvelabel(fs->ls, i))
- i++; /* move to next one */
- /* else, 'solvelabel' removed current goto from the list
- and 'i' now points to next one */
}
- /* handles break list */
- if (bl->upval) /* exiting the scope of an upvalue? */
- luaK_patchclose(fs, bl->brks); /* breaks will need OP_CLOSE */
- /* move breaks to outer block */
- luaK_concat(fs, &bl->previous->brks, bl->brks);
- bl->previous->brkcls |= bl->brkcls;
}
@@ -442,8 +460,6 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) {
bl->nactvar = fs->nactvar;
bl->firstlabel = fs->ls->dyd->label.n;
bl->firstgoto = fs->ls->dyd->gt.n;
- bl->brks = NO_JUMP;
- bl->brkcls = 0;
bl->upval = 0;
bl->previous = fs->bl;
fs->bl = bl;
@@ -451,20 +467,6 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) {
}
-/*
-** Fix all breaks in block 'bl' to jump to the end of the block.
-*/
-static void fixbreaks (FuncState *fs, BlockCnt *bl) {
- int target = fs->pc;
- if (bl->brkcls) /* does the block need to close upvalues? */
- luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0);
- luaK_patchgoto(fs, bl->brks, target, bl->brkcls);
- bl->brks = NO_JUMP; /* no more breaks to fix */
- bl->brkcls = 0; /* no more need to close upvalues */
- lua_assert(!bl->upval); /* loop body cannot have local variables */
-}
-
-
/*
** generates an error for an undefined 'goto'.
*/
@@ -478,11 +480,10 @@ static l_noret undefgoto (LexState *ls, Labeldesc *gt) {
static void leaveblock (FuncState *fs) {
BlockCnt *bl = fs->bl;
LexState *ls = fs->ls;
- if (bl->upval && bl->brks != NO_JUMP) /* breaks in upvalue scopes? */
- bl->brkcls = 1; /* these breaks must close the upvalues */
- if (bl->isloop)
- fixbreaks(fs, bl); /* fix pending breaks */
- if (bl->previous && bl->upval)
+ int hasclose = 0;
+ if (bl->isloop) /* fix pending breaks? */
+ hasclose = createlabel(ls, luaS_newliteral(ls->L, "break"), 0, 0);
+ if (!hasclose && bl->previous && bl->upval)
luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0);
fs->bl = bl->previous;
removevars(fs, bl->nactvar);
@@ -492,7 +493,6 @@ static void leaveblock (FuncState *fs) {
if (bl->previous) /* inner block? */
movegotosout(fs, bl); /* update pending gotos to outer block */
else {
- lua_assert(bl->brks == NO_JUMP); /* no pending breaks */
if (bl->firstgoto < ls->dyd->gt.n) /* pending gotos in outer block? */
undefgoto(ls, &ls->dyd->gt.arr[bl->firstgoto]); /* error */
}
@@ -550,6 +550,7 @@ static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) {
fs->nactvar = 0;
fs->needclose = 0;
fs->firstlocal = ls->dyd->actvar.n;
+ fs->firstlabel = ls->dyd->label.n;
fs->bl = NULL;
f->source = ls->source;
f->maxstacksize = 2; /* registers 0/1 are always valid */
@@ -1204,63 +1205,59 @@ static int cond (LexState *ls) {
}
-static void gotostat (LexState *ls, int pc) {
+static void gotostat (LexState *ls) {
+ FuncState *fs = ls->fs;
int line = ls->linenumber;
- int g;
- luaX_next(ls); /* skip 'goto' */
- g = newlabelentry(ls, &ls->dyd->gt, str_checkname(ls), line, pc);
- solvelabel(ls, g); /* close it if label already defined */
+ TString *name = str_checkname(ls); /* label's name */
+ Labeldesc *lb = findlabel(ls, name);
+ if (lb == NULL) /* no label? */
+ /* forward jump; will be resolved when the label is declared */
+ newlabelentry(ls, &ls->dyd->gt, name, line, luaK_jump(fs));
+ else { /* found a label */
+ /* backward jump; will be resolved here */
+ if (fs->nactvar > lb->nactvar) /* leaving the scope of some variable? */
+ luaK_codeABC(fs, OP_CLOSE, lb->nactvar, 0, 0);
+ /* create jump and link it to the label */
+ luaK_patchlist(fs, luaK_jump(fs), lb->pc);
+ }
}
+/*
+** Break statement. Semantically equivalent to "goto break".
+*/
static void breakstat (LexState *ls, int pc) {
FuncState *fs = ls->fs;
+ int line = ls->linenumber;
BlockCnt *bl = fs->bl;
luaX_next(ls); /* skip break */
+ newlabelentry(ls, &ls->dyd->gt, luaS_newliteral(ls->L, "break"), line, pc);
while (bl && !bl->isloop) { bl = bl->previous; }
if (!bl)
luaX_syntaxerror(ls, "no loop to break");
- luaK_concat(fs, &fs->bl->brks, pc);
}
-/* check for repeated labels on the same block */
-static void checkrepeated (FuncState *fs, Labellist *ll, TString *label) {
- int i;
- for (i = fs->bl->firstlabel; i < ll->n; i++) {
- if (eqstr(label, ll->arr[i].name)) {
- const char *msg = luaO_pushfstring(fs->ls->L,
- "label '%s' already defined on line %d",
- getstr(label), ll->arr[i].line);
- luaK_semerror(fs->ls, msg);
- }
+/*
+** Check whether there is already a label with the given 'name'.
+*/
+static void checkrepeated (LexState *ls, TString *name) {
+ Labeldesc *lb = findlabel(ls, name);
+ if (lb != NULL) { /* already defined? */
+ const char *msg = "label '%s' already defined on line %d";
+ msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line);
+ luaK_semerror(ls, msg); /* error */
}
}
-/* skip no-op statements */
-static void skipnoopstat (LexState *ls) {
- while (ls->t.token == ';' || ls->t.token == TK_DBCOLON)
- statement(ls);
-}
-
-
-static void labelstat (LexState *ls, TString *label, int line) {
+static void labelstat (LexState *ls, TString *name, int line) {
/* label -> '::' NAME '::' */
- FuncState *fs = ls->fs;
- Labellist *ll = &ls->dyd->label;
- int l; /* index of new label being created */
- checkrepeated(fs, ll, label); /* check for repeated labels */
checknext(ls, TK_DBCOLON); /* skip double colon */
- /* create new entry for this label */
- l = newlabelentry(ls, ll, label, line, luaK_getlabel(fs));
- luaK_codeABC(fs, OP_CLOSE, fs->nactvar, 0, 0);
- skipnoopstat(ls); /* skip other no-op statements */
- if (block_follow(ls, 0)) { /* label is last no-op statement in the block? */
- /* assume that locals are already out of scope */
- ll->arr[l].nactvar = fs->bl->nactvar;
- }
- solvegotos(ls, &ll->arr[l]);
+ while (ls->t.token == ';' || ls->t.token == TK_DBCOLON)
+ statement(ls); /* skip other no-op statements */
+ checkrepeated(ls, name); /* check for repeated labels */
+ createlabel(ls, name, line, block_follow(ls, 0));
}
@@ -1295,8 +1292,6 @@ static void repeatstat (LexState *ls, int line) {
statlist(ls);
check_match(ls, TK_UNTIL, TK_REPEAT, line);
condexit = cond(ls); /* read condition (inside scope block) */
- if (bl2.upval) /* upvalues? */
- luaK_patchclose(fs, condexit);
leaveblock(fs); /* finish scope */
if (bl2.upval) { /* upvalues? */
int exit = luaK_jump(fs); /* normal exit must jump over fix */
@@ -1453,15 +1448,12 @@ static void test_then_block (LexState *ls, int *escapelist) {
luaX_next(ls); /* skip IF or ELSEIF */
expr(ls, &v); /* read condition */
checknext(ls, TK_THEN);
- if (ls->t.token == TK_GOTO || ls->t.token == TK_BREAK) {
+ if (ls->t.token == TK_BREAK) {
luaK_goiffalse(ls->fs, &v); /* will jump to label if condition is true */
enterblock(fs, &bl, 0); /* must enter block before 'goto' */
- if (ls->t.token == TK_GOTO)
- gotostat(ls, v.t); /* handle goto */
- else
- breakstat(ls, v.t); /* handle break */
+ breakstat(ls, v.t); /* handle break */
while (testnext(ls, ';')) {} /* skip semicolons */
- if (block_follow(ls, 0)) { /* 'goto'/'break' is the entire block? */
+ if (block_follow(ls, 0)) { /* 'break' is the entire block? */
leaveblock(fs);
return; /* and that is it */
}
@@ -1683,7 +1675,8 @@ static void statement (LexState *ls) {
break;
}
case TK_GOTO: { /* stat -> 'goto' NAME */
- gotostat(ls, luaK_jump(ls->fs));
+ luaX_next(ls); /* skip 'goto' */
+ gotostat(ls);
break;
}
default: { /* stat -> func | assignment */
diff --git a/lparser.h b/lparser.h
index 1b94a97a6f..8b070b0e42 100644
--- a/lparser.h
+++ b/lparser.h
@@ -88,6 +88,7 @@ typedef struct Labeldesc {
int pc; /* position in code */
int line; /* line where it appeared */
lu_byte nactvar; /* local level where it appears in current block */
+ lu_byte close; /* goto that escapes upvalues */
} Labeldesc;
@@ -128,6 +129,7 @@ typedef struct FuncState {
int np; /* number of elements in 'p' */
int nabslineinfo; /* number of elements in 'abslineinfo' */
int firstlocal; /* index of first local var (in Dyndata array) */
+ int firstlabel; /* index of first label (in 'dyd->label->arr') */
short nlocvars; /* number of elements in 'f->locvars' */
lu_byte nactvar; /* number of active local variables */
lu_byte nups; /* number of upvalues */
diff --git a/ltests.c b/ltests.c
index fbcb747571..7cf0889197 100644
--- a/ltests.c
+++ b/ltests.c
@@ -550,7 +550,7 @@ static char *buildop (Proto *p, int pc, char *buff) {
sprintf(buff, "%-12s%4d", name, GETARG_Ax(i));
break;
case isJ:
- sprintf(buff, "%-12s%4d (%1d)", name, GETARG_sJ(i), !!GETARG_m(i));
+ sprintf(buff, "%-12s%4d", name, GETARG_sJ(i));
break;
}
return obuff;
diff --git a/testes/code.lua b/testes/code.lua
index ad48448594..9b3f2b6864 100644
--- a/testes/code.lua
+++ b/testes/code.lua
@@ -297,7 +297,7 @@ check(function ()
b[a], a = c, b
a, b = c, a
a = a
-end,
+end,
'LOADNIL',
'MOVE', 'MOVE', 'SETTABLE',
'MOVE', 'MOVE', 'MOVE', 'SETTABLE',
@@ -329,18 +329,13 @@ checkequal(function (l) local a; return 0 <= a and a <= l end,
function (l) local a; return not (not(a >= 0) or not(a <= l)) end)
--- if-goto optimizations
-check(function (a, b, c, d, e)
- if a == b then goto l1;
- elseif a == c then goto l2;
- elseif a == d then goto l2;
- else if a == e then goto l3;
- else goto l3
- end
+-- if-break optimizations
+check(function (a, b)
+ while a do
+ if b then break else a = a + 1 end
end
- ::l1:: ::l2:: ::l3:: ::l4::
-end, 'EQ', 'JMP', 'EQ', 'JMP', 'EQ', 'JMP', 'EQ', 'JMP', 'JMP',
-'CLOSE', 'CLOSE', 'CLOSE', 'CLOSE', 'RETURN0')
+ end,
+'TEST', 'JMP', 'TEST', 'JMP', 'ADDI', 'JMP', 'RETURN0')
checkequal(
function (a) while a < 10 do a = a + 1 end end,
diff --git a/testes/goto.lua b/testes/goto.lua
index 238bc04aba..85038d0269 100644
--- a/testes/goto.lua
+++ b/testes/goto.lua
@@ -14,6 +14,7 @@ errmsg([[ do ::l1:: end goto l1; ]], "label 'l1'")
-- repeated label
errmsg([[ ::l1:: ::l1:: ]], "label 'l1'")
+errmsg([[ ::l1:: do ::l1:: end]], "label 'l1'")
-- undefined label
@@ -67,8 +68,6 @@ do
assert(assert(load(prog))() == 31)
end
--- goto to correct label when nested
-do goto l3; ::l3:: end -- does not loop jumping to previous label 'l3'
-- ok to jump over local dec. to end of block
do
From 2316ec4c24a475e091ec3153a5bd908801a3a109 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 30 Oct 2018 15:04:19 -0300
Subject: [PATCH 087/889] Back with optimization for 'if cond then goto'
Statements like 'if cond then goto label' generate code so that the
jump in the 'if' goes directly to the given label. This optimization
cannot be done when the jump is backwards leaving the scope of some
variable, as it cannot add the needed 'close' instruction. (The jumps
were already generated by the 'if'.)
This commit also added 'likely'/'unlikely' for tests for errors in
the parser, and it changed the way breaks outside loops are detected.
(Now they are detected like other goto's with undefined labels.)
---
lparser.c | 84 ++++++++++++++++++++++++++++++++++++++-----------
testes/code.lua | 20 +++++++++++-
testes/goto.lua | 16 ++++++++++
3 files changed, 100 insertions(+), 20 deletions(-)
diff --git a/lparser.c b/lparser.c
index 499e95313a..9419f880bd 100644
--- a/lparser.c
+++ b/lparser.c
@@ -113,7 +113,7 @@ static void checknext (LexState *ls, int c) {
static void check_match (LexState *ls, int what, int who, int where) {
- if (!testnext(ls, what)) {
+ if (unlikely(!testnext(ls, what))) {
if (where == ls->linenumber)
error_expected(ls, what);
else {
@@ -350,7 +350,7 @@ static void solvegoto (LexState *ls, int g, Labeldesc *label) {
Labellist *gl = &ls->dyd->gt; /* list of goto's */
Labeldesc *gt = &gl->arr[g]; /* goto to be resolved */
lua_assert(eqstr(gt->name, label->name));
- if (gt->nactvar < label->nactvar) /* enter some scope? */
+ if (unlikely(gt->nactvar < label->nactvar)) /* enter some scope? */
jumpscopeerror(ls, gt);
luaK_patchlist(ls->fs, gt->pc, label->pc);
for (i = g; i < gl->n - 1; i++) /* remove goto from pending list */
@@ -393,6 +393,11 @@ static int newlabelentry (LexState *ls, Labellist *l, TString *name,
}
+static int newgotoentry (LexState *ls, TString *name, int line, int pc) {
+ return newlabelentry(ls, &ls->dyd->gt, name, line, pc);
+}
+
+
/*
** Solves forward jumps. Check whether new label 'lb' matches any
** pending gotos in current block and solves them. Return true
@@ -471,8 +476,15 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) {
** generates an error for an undefined 'goto'.
*/
static l_noret undefgoto (LexState *ls, Labeldesc *gt) {
- const char *msg = "no visible label '%s' for at line %d";
- msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line);
+ const char *msg;
+ if (eqstr(gt->name, luaS_newliteral(ls->L, "break"))) {
+ msg = "break outside loop at line %d";
+ msg = luaO_pushfstring(ls->L, msg, gt->line);
+ }
+ else {
+ msg = "no visible label '%s' for at line %d";
+ msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line);
+ }
luaK_semerror(ls, msg);
}
@@ -1212,7 +1224,7 @@ static void gotostat (LexState *ls) {
Labeldesc *lb = findlabel(ls, name);
if (lb == NULL) /* no label? */
/* forward jump; will be resolved when the label is declared */
- newlabelentry(ls, &ls->dyd->gt, name, line, luaK_jump(fs));
+ newgotoentry(ls, name, line, luaK_jump(fs));
else { /* found a label */
/* backward jump; will be resolved here */
if (fs->nactvar > lb->nactvar) /* leaving the scope of some variable? */
@@ -1226,15 +1238,10 @@ static void gotostat (LexState *ls) {
/*
** Break statement. Semantically equivalent to "goto break".
*/
-static void breakstat (LexState *ls, int pc) {
- FuncState *fs = ls->fs;
+static void breakstat (LexState *ls) {
int line = ls->linenumber;
- BlockCnt *bl = fs->bl;
luaX_next(ls); /* skip break */
- newlabelentry(ls, &ls->dyd->gt, luaS_newliteral(ls->L, "break"), line, pc);
- while (bl && !bl->isloop) { bl = bl->previous; }
- if (!bl)
- luaX_syntaxerror(ls, "no loop to break");
+ newgotoentry(ls, luaS_newliteral(ls->L, "break"), line, luaK_jump(ls->fs));
}
@@ -1243,7 +1250,7 @@ static void breakstat (LexState *ls, int pc) {
*/
static void checkrepeated (LexState *ls, TString *name) {
Labeldesc *lb = findlabel(ls, name);
- if (lb != NULL) { /* already defined? */
+ if (unlikely(lb != NULL)) { /* already defined? */
const char *msg = "label '%s' already defined on line %d";
msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line);
luaK_semerror(ls, msg); /* error */
@@ -1332,7 +1339,7 @@ static void fixforjump (FuncState *fs, int pc, int dest, int back) {
int offset = dest - (pc + 1);
if (back)
offset = -offset;
- if (offset > MAXARG_Bx)
+ if (unlikely(offset > MAXARG_Bx))
luaX_syntaxerror(fs->ls, "control structure too long");
SETARG_Bx(*jmp, offset);
}
@@ -1439,28 +1446,67 @@ static void forstat (LexState *ls, int line) {
}
+/*
+** Check whether next instruction is a single jump (a 'break', a 'goto'
+** to a forward label, or a 'goto' to a backward label with no variable
+** to close). If so, set the name of the 'label' it is jumping to
+** ("break" for a 'break') or to where it is jumping to ('target') and
+** return true. If not a single jump, leave input unchanged, to be
+** handled as a regular statement.
+*/
+static int issinglejump (LexState *ls, TString **label, int *target) {
+ if (testnext(ls, TK_BREAK)) { /* a break? */
+ *label = luaS_newliteral(ls->L, "break");
+ return 1;
+ }
+ else if (ls->t.token != TK_GOTO || luaX_lookahead(ls) != TK_NAME)
+ return 0; /* not a valid goto */
+ else {
+ TString *lname = ls->lookahead.seminfo.ts; /* label's id */
+ Labeldesc *lb = findlabel(ls, lname);
+ if (lb) { /* a backward jump? */
+ if (ls->fs->nactvar > lb->nactvar) /* needs to close variables? */
+ return 0; /* not a single jump; cannot optimize */
+ *target = lb->pc;
+ }
+ else /* jump forward */
+ *label = lname;
+ luaX_next(ls); /* skip goto */
+ luaX_next(ls); /* skip name */
+ return 1;
+ }
+}
+
+
static void test_then_block (LexState *ls, int *escapelist) {
/* test_then_block -> [IF | ELSEIF] cond THEN block */
BlockCnt bl;
+ int line;
FuncState *fs = ls->fs;
+ TString *jlb = NULL;
+ int target = NO_JUMP;
expdesc v;
int jf; /* instruction to skip 'then' code (if condition is false) */
luaX_next(ls); /* skip IF or ELSEIF */
expr(ls, &v); /* read condition */
checknext(ls, TK_THEN);
- if (ls->t.token == TK_BREAK) {
+ line = ls->linenumber;
+ if (issinglejump(ls, &jlb, &target)) { /* 'if x then goto' ? */
luaK_goiffalse(ls->fs, &v); /* will jump to label if condition is true */
enterblock(fs, &bl, 0); /* must enter block before 'goto' */
- breakstat(ls, v.t); /* handle break */
+ if (jlb != NULL) /* forward jump? */
+ newgotoentry(ls, jlb, line, v.t); /* will be resolved later */
+ else /* backward jump */
+ luaK_patchlist(fs, v.t, target); /* jump directly to 'target' */
while (testnext(ls, ';')) {} /* skip semicolons */
- if (block_follow(ls, 0)) { /* 'break' is the entire block? */
+ if (block_follow(ls, 0)) { /* jump is the entire block? */
leaveblock(fs);
return; /* and that is it */
}
else /* must skip over 'then' part if condition is false */
jf = luaK_jump(fs);
}
- else { /* regular case (not goto/break) */
+ else { /* regular case (not a jump) */
luaK_goiftrue(ls->fs, &v); /* skip over block if condition is false */
enterblock(fs, &bl, 0);
jf = v.f;
@@ -1671,7 +1717,7 @@ static void statement (LexState *ls) {
break;
}
case TK_BREAK: { /* stat -> breakstat */
- breakstat(ls, luaK_jump(ls->fs));
+ breakstat(ls);
break;
}
case TK_GOTO: { /* stat -> 'goto' NAME */
diff --git a/testes/code.lua b/testes/code.lua
index 9b3f2b6864..4d44fa6aa3 100644
--- a/testes/code.lua
+++ b/testes/code.lua
@@ -339,8 +339,26 @@ check(function (a, b)
checkequal(
function (a) while a < 10 do a = a + 1 end end,
-function (a) while true do if not(a < 10) then break end; a = a + 1; end end
+function (a)
+ ::loop::
+ if not (a < 10) then goto exit end
+ a = a + 1
+ goto loop
+::exit::
+end
)
+checkequal(
+function (a) repeat local x = a + 1; a = x until a > 0 end,
+function (a)
+ ::loop:: do
+ local x = a + 1
+ a = x
+ end
+ if not (a > 0) then goto loop end
+end
+)
+
+
print 'OK'
diff --git a/testes/goto.lua b/testes/goto.lua
index 85038d0269..92f048fb69 100644
--- a/testes/goto.lua
+++ b/testes/goto.lua
@@ -249,6 +249,22 @@ assert(testG(2) == "2")
assert(testG(3) == "3")
assert(testG(4) == 5)
assert(testG(5) == 10)
+
+do
+ -- if x back goto out of scope of upvalue
+ local X
+ goto L1
+
+ ::L2:: goto L3
+
+ ::L1:: do
+ local scoped a = function () X = true end
+ assert(X == nil)
+ if a then goto L2 end -- jumping back out of scope of 'a'
+ end
+
+ ::L3:: assert(X == true) -- checks that 'a' was correctly closed
+end
--------------------------------------------------------------------------------
From e073cbc2e538369e0611abfc9948f301aea6aef3 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 30 Oct 2018 15:46:56 -0300
Subject: [PATCH 088/889] Better error messages for invalid operands in numeric
'for'
"Better" and similar to error messages for invalid function arguments.
*old message: 'for' limit must be a number
*new message: bad 'for' limit (number expected, got table)
---
ldebug.c | 6 ++++++
ldebug.h | 2 ++
lvm.c | 8 ++++----
testes/errors.lua | 10 ++++++++++
4 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/ldebug.c b/ldebug.c
index 3590010c9a..ee1b87d901 100644
--- a/ldebug.c
+++ b/ldebug.c
@@ -696,6 +696,12 @@ l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) {
}
+l_noret luaG_forerror (lua_State *L, const TValue *o, const char *what) {
+ luaG_runerror(L, "bad 'for' %s (number expected, got %s)",
+ what, luaT_objtypename(L, o));
+}
+
+
l_noret luaG_concaterror (lua_State *L, const TValue *p1, const TValue *p2) {
if (ttisstring(p1) || cvt2str(p1)) p1 = p2;
luaG_typeerror(L, p1, "concatenate");
diff --git a/ldebug.h b/ldebug.h
index 31ecc2f6cf..f080711d59 100644
--- a/ldebug.h
+++ b/ldebug.h
@@ -24,6 +24,8 @@
LUAI_FUNC int luaG_getfuncline (const Proto *f, int pc);
LUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o,
const char *opname);
+LUAI_FUNC l_noret luaG_forerror (lua_State *L, const TValue *o,
+ const char *what);
LUAI_FUNC l_noret luaG_concaterror (lua_State *L, const TValue *p1,
const TValue *p2);
LUAI_FUNC l_noret luaG_opinterror (lua_State *L, const TValue *p1,
diff --git a/lvm.c b/lvm.c
index 35a58089ca..aad965d608 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1681,7 +1681,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
int stopnow;
if (unlikely(!forlimit(plimit, &ilimit, 1, &stopnow))) {
savestate(L, ci); /* for the error message */
- luaG_runerror(L, "'for' limit must be a number");
+ luaG_forerror(L, plimit, "limit");
}
initv = (stopnow ? 0 : ivalue(init));
setivalue(plimit, ilimit);
@@ -1732,13 +1732,13 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
lua_Number ninit; lua_Number nlimit; lua_Number nstep;
savestate(L, ci); /* in case of errors */
if (unlikely(!tonumber(plimit, &nlimit)))
- luaG_runerror(L, "'for' limit must be a number");
+ luaG_forerror(L, plimit, "limit");
setfltvalue(plimit, nlimit);
if (unlikely(!tonumber(pstep, &nstep)))
- luaG_runerror(L, "'for' step must be a number");
+ luaG_forerror(L, pstep, "step");
setfltvalue(pstep, nstep);
if (unlikely(!tonumber(init, &ninit)))
- luaG_runerror(L, "'for' initial value must be a number");
+ luaG_forerror(L, init, "initial value");
setfltvalue(init, luai_numsub(L, ninit, nstep));
}
pc += GETARG_Bx(i);
diff --git a/testes/errors.lua b/testes/errors.lua
index 142e8b3325..74975e3143 100644
--- a/testes/errors.lua
+++ b/testes/errors.lua
@@ -154,6 +154,16 @@ checkmessage("a = 24 // 0", "divide by zero")
checkmessage("a = 1 % 0", "'n%0'")
+-- numeric for loops
+checkmessage("for i = {}, 10 do end", "table")
+checkmessage("for i = io.stdin, 10 do end", "FILE")
+checkmessage("for i = {}, 10 do end", "initial value")
+checkmessage("for i = 1, 'x', 10 do end", "string")
+checkmessage("for i = 1, {}, 10 do end", "limit")
+checkmessage("for i = 1, {} do end", "limit")
+checkmessage("for i = 1, 10, print do end", "step")
+checkmessage("for i = 1, 10, print do end", "function")
+
-- passing light userdata instead of full userdata
_G.D = debug
checkmessage([[
From 947a372f5860a76fcafb4a2845abc322e440d6fc Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 31 Oct 2018 14:54:45 -0300
Subject: [PATCH 089/889] State in generic 'for' acts as a to-be-closed
variable
The implicit variable 'state' in a generic 'for' is marked as a
to-be-closed variable, so that the state will be closed as soon
as the loop ends, no matter how.
Taking advantage of this new facility, the call 'io.lines(filename)'
now returns the open file as a second result. Therefore,
an iteraction like 'for l in io.lines(name)...' will close the
file even when the loop ends with a break or an error.
---
liolib.c | 35 +++++++++++++++++++++++++++------
lparser.c | 1 +
lvm.c | 41 ++++++++++++++++++++++++++------------
testes/files.lua | 8 ++++----
testes/locals.lua | 50 ++++++++++++++++++++++++++++++++++++++++++++++-
5 files changed, 111 insertions(+), 24 deletions(-)
diff --git a/liolib.c b/liolib.c
index 3dc509bdb8..5881b0293a 100644
--- a/liolib.c
+++ b/liolib.c
@@ -354,12 +354,22 @@ static int io_readline (lua_State *L);
*/
#define MAXARGLINE 250
+/*
+** Auxiliar function to create the iteration function for 'lines'.
+** The iteration function is a closure over 'io_readline', with
+** the following upvalues:
+** 1) The file being read (first value in the stack)
+** 2) the number of arguments to read
+** 3) a boolean, true iff file has to be closed when finished ('toclose')
+** *) a variable number of format arguments (rest of the stack)
+*/
static void aux_lines (lua_State *L, int toclose) {
int n = lua_gettop(L) - 1; /* number of arguments to read */
luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments");
+ lua_pushvalue(L, 1); /* file */
lua_pushinteger(L, n); /* number of arguments to read */
lua_pushboolean(L, toclose); /* close/not close file when finished */
- lua_rotate(L, 2, 2); /* move 'n' and 'toclose' to their positions */
+ lua_rotate(L, 2, 3); /* move the three values to their positions */
lua_pushcclosure(L, io_readline, 3 + n);
}
@@ -371,6 +381,11 @@ static int f_lines (lua_State *L) {
}
+/*
+** Return an iteration function for 'io.lines'. If file has to be
+** closed, also returns the file itself as a second result (to be
+** closed as the state at the exit of a generic for).
+*/
static int io_lines (lua_State *L) {
int toclose;
if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */
@@ -386,8 +401,13 @@ static int io_lines (lua_State *L) {
lua_replace(L, 1); /* put file at index 1 */
toclose = 1; /* close it after iteration */
}
- aux_lines(L, toclose);
- return 1;
+ aux_lines(L, toclose); /* push iteration function */
+ if (toclose) {
+ lua_pushvalue(L, 1); /* file will be second result */
+ return 2;
+ }
+ else
+ return 1;
}
@@ -453,7 +473,7 @@ static int readdigits (RN *rn, int hex) {
/*
** Read a number: first reads a valid prefix of a numeral into a buffer.
** Then it calls 'lua_stringtonumber' to check whether the format is
-** correct and to convert it to a Lua number
+** correct and to convert it to a Lua number.
*/
static int read_number (lua_State *L, FILE *f) {
RN rn;
@@ -604,6 +624,9 @@ static int f_read (lua_State *L) {
}
+/*
+** Iteration function for 'lines'.
+*/
static int io_readline (lua_State *L) {
LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1));
int i;
@@ -624,8 +647,8 @@ static int io_readline (lua_State *L) {
return luaL_error(L, "%s", lua_tostring(L, -n + 1));
}
if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */
- lua_settop(L, 0);
- lua_pushvalue(L, lua_upvalueindex(1));
+ lua_settop(L, 0); /* clear stack */
+ lua_pushvalue(L, lua_upvalueindex(1)); /* push file at index 1 */
aux_close(L); /* close it */
}
return 0;
diff --git a/lparser.c b/lparser.c
index 9419f880bd..a5b84aa1e8 100644
--- a/lparser.c
+++ b/lparser.c
@@ -1413,6 +1413,7 @@ static void forlist (LexState *ls, TString *indexname) {
/* create control variables */
new_localvarliteral(ls, "(for generator)");
new_localvarliteral(ls, "(for state)");
+ markupval(fs, fs->nactvar); /* state may create an upvalue */
new_localvarliteral(ls, "(for control)");
/* create declared variables */
new_localvar(ls, indexname);
diff --git a/lvm.c b/lvm.c
index aad965d608..1535700fc9 100644
--- a/lvm.c
+++ b/lvm.c
@@ -866,7 +866,8 @@ void luaV_finishOp (lua_State *L) {
#define ProtectNT(exp) (savepc(L), (exp), updatetrap(ci))
/*
-** Protect code that will finish the loop (returns).
+** Protect code that will finish the loop (returns) or can only raise
+** errors.
*/
#define halfProtect(exp) (savepc(L), (exp))
@@ -1457,7 +1458,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_TBC) {
- luaF_newtbcupval(L, ra); /* create new to-be-closed upvalue */
+ /* create new to-be-closed upvalue */
+ halfProtect(luaF_newtbcupval(L, ra));
vmbreak;
}
vmcase(OP_JMP) {
@@ -1745,21 +1747,34 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_TFORPREP) {
+ /* is 'state' a function or has a '__close' metamethod? */
+ if (ttisfunction(s2v(ra + 1)) ||
+ !ttisnil(luaT_gettmbyobj(L, s2v(ra + 1), TM_CLOSE))) {
+ /* create to-be-closed upvalue for it */
+ halfProtect(luaF_newtbcupval(L, ra + 1));
+ }
pc += GETARG_Bx(i);
- vmbreak;
+ i = *(pc++); /* go to next instruction */
+ lua_assert(GET_OPCODE(i) == OP_TFORCALL && ra == RA(i));
+ goto l_tforcall;
}
vmcase(OP_TFORCALL) {
- StkId cb = ra + 3; /* call base */
- setobjs2s(L, cb+2, ra+2);
- setobjs2s(L, cb+1, ra+1);
- setobjs2s(L, cb, ra);
- L->top = cb + 3; /* func. + 2 args (state and index) */
- Protect(luaD_call(L, cb, GETARG_C(i)));
- if (trap) /* keep 'base' correct for next instruction */
- updatebase(ci);
+ l_tforcall:
+ /* 'ra' has the iterator function, 'ra + 1' has the state,
+ and 'ra + 2' has the control variable. The call will use
+ the stack after these values (starting at 'ra + 3')
+ */
+ /* push function, state, and control variable */
+ memcpy(ra + 3, ra, 3 * sizeof(*ra));
+ L->top = ra + 6;
+ Protect(luaD_call(L, ra + 3, GETARG_C(i))); /* do the call */
+ if (trap) { /* stack may have changed? */
+ updatebase(ci); /* keep 'base' correct */
+ ra = RA(i); /* keep 'ra' correct for next instruction */
+ }
i = *(pc++); /* go to next instruction */
- ra = RA(i); /* get its 'ra' */
- lua_assert(GET_OPCODE(i) == OP_TFORLOOP);
+ ra += 2; /* adjust for next instruction */
+ lua_assert(GET_OPCODE(i) == OP_TFORLOOP && ra == RA(i));
goto l_tforloop;
}
vmcase(OP_TFORLOOP) {
diff --git a/testes/files.lua b/testes/files.lua
index 9aae591324..a11c5e617d 100644
--- a/testes/files.lua
+++ b/testes/files.lua
@@ -462,13 +462,13 @@ X
- y;
]]:close()
_G.X = 1
-assert(not load(io.lines(file)))
+assert(not load((io.lines(file))))
collectgarbage() -- to close file in previous iteration
-load(io.lines(file, "L"))()
+load((io.lines(file, "L")))()
assert(_G.X == 2)
-load(io.lines(file, 1))()
+load((io.lines(file, 1)))()
assert(_G.X == 4)
-load(io.lines(file, 3))()
+load((io.lines(file, 3)))()
assert(_G.X == 8)
print('+')
diff --git a/testes/locals.lua b/testes/locals.lua
index 65b145dbb5..1e0f525ba4 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -108,7 +108,7 @@ print'+'
if rawget(_G, "T") then
-- testing clearing of dead elements from tables
collectgarbage("stop") -- stop GC
- local a = {[{}] = 4, [3] = 0, alo = 1,
+ local a = {[{}] = 4, [3] = 0, alo = 1,
a1234567890123456789012345678901234567890 = 10}
local t = T.querytab(a)
@@ -360,6 +360,54 @@ end)
co() -- start coroutine
assert(co == nil) -- eventually it will be collected
+
+-- to-be-closed variables in generic for loops
+do
+ local numopen = 0
+ local function open (x)
+ numopen = numopen + 1
+ return
+ function () -- iteraction function
+ x = x - 1
+ if x > 0 then return x end
+ end,
+ function () -- closing function
+ numopen = numopen - 1
+ end
+ end
+
+ local s = 0
+ for i in open(10) do
+ s = s + i
+ end
+ assert(s == 45 and numopen == 0)
+
+ local s = 0
+ for i in open(10) do
+ if i < 5 then break end
+ s = s + i
+ end
+ assert(s == 35 and numopen == 0)
+
+ -- repeat test with '__open' metamethod instead of a function
+ local function open (x)
+ numopen = numopen + 1
+ return
+ function (t) -- iteraction function
+ t[1] = t[1] - 1
+ if t[1] > 0 then return t[1] end
+ end,
+ setmetatable({x}, {__close = function () numopen = numopen - 1 end})
+ end
+
+ local s = 0
+ for i in open(10) do
+ if (i < 5) then break end
+ s = s + i
+ end
+ assert(s == 35 and numopen == 0)
+end
+
print('OK')
return 5,f
From 2fc6b55dae7a120b4272ca0e9c356d1f96053bd9 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 31 Oct 2018 16:25:29 -0300
Subject: [PATCH 090/889] Removed resource-related "emergency collections"
New to-be-closed variables is a better way to ensure the proper release
of resources.
---
lauxlib.c | 43 -------------------------------------------
lauxlib.h | 2 --
liolib.c | 21 ++-------------------
loslib.c | 2 --
manual/manual.of | 14 --------------
5 files changed, 2 insertions(+), 80 deletions(-)
diff --git a/lauxlib.c b/lauxlib.c
index 53b8c9bb34..78dfb4e952 100644
--- a/lauxlib.c
+++ b/lauxlib.c
@@ -290,49 +290,6 @@ LUALIB_API int luaL_execresult (lua_State *L, int stat) {
/* }====================================================== */
-/*
-** {======================================================
-** 'luaL_resourcetryagain'
-** This function uses 'errno' to check whether the last error was
-** related to lack of resources (e.g., not enough memory or too many
-** open files). If so, the function performs a full garbage collection
-** to try to release resources, and then it returns 1 to signal to
-** the caller that it is worth trying again the failed operation.
-** Otherwise, it returns 0. Because error codes are not ANSI C, the
-** code must handle any combination of error codes that are defined.
-** =======================================================
-*/
-
-LUALIB_API int luaL_resourcetryagain (lua_State *L) {
-
-/* these are the resource-related errors in Linux */
-#if defined(EMFILE) || defined(ENFILE) || defined(ENOMEM)
-
-#if !defined(EMFILE) /* too many open files in the process */
-#define EMFILE -1 /* if not defined, use an impossible value */
-#endif
-
-#if !defined(ENFILE) /* too many open files in the system */
-#define ENFILE -1
-#endif
-
-#if !defined(ENOMEM) /* not enough memory */
-#define ENOMEM -1
-#endif
-
- if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
- lua_gc(L, LUA_GCCOLLECT); /* try to release resources with a full GC */
- return 1; /* signal to try again the creation */
- }
-
-#endif
-
- return 0; /* else, asume errors are not due to lack of resources */
-
-}
-
-/* }====================================================== */
-
/*
** {======================================================
diff --git a/lauxlib.h b/lauxlib.h
index cd4d01e50a..9ec0f5317b 100644
--- a/lauxlib.h
+++ b/lauxlib.h
@@ -77,8 +77,6 @@ LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def,
LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname);
LUALIB_API int (luaL_execresult) (lua_State *L, int stat);
-LUALIB_API int (luaL_resourcetryagain) (lua_State *L);
-
/* predefined references */
#define LUA_NOREF (-2)
diff --git a/liolib.c b/liolib.c
index 5881b0293a..b2a2fec8a1 100644
--- a/liolib.c
+++ b/liolib.c
@@ -246,22 +246,9 @@ static LStream *newfile (lua_State *L) {
}
-/*
-** Equivalent to 'fopen', but if it fails due to a lack of resources
-** (see 'luaL_resourcetryagain'), do an "emergency" garbage collection
-** to try to close some files and then tries to open the file again.
-*/
-static FILE *trytoopen (lua_State *L, const char *path, const char *mode) {
- FILE *f = fopen(path, mode);
- if (f == NULL && luaL_resourcetryagain(L)) /* resource failure? */
- f = fopen(path, mode); /* try to open again */
- return f;
-}
-
-
static void opencheck (lua_State *L, const char *fname, const char *mode) {
LStream *p = newfile(L);
- p->f = trytoopen(L, fname, mode);
+ p->f = fopen(fname, mode);
if (p->f == NULL)
luaL_error(L, "cannot open file '%s' (%s)", fname, strerror(errno));
}
@@ -273,7 +260,7 @@ static int io_open (lua_State *L) {
LStream *p = newfile(L);
const char *md = mode; /* to traverse/check mode */
luaL_argcheck(L, l_checkmode(md), 2, "invalid mode");
- p->f = trytoopen(L, filename, mode);
+ p->f = fopen(filename, mode);
return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;
}
@@ -292,8 +279,6 @@ static int io_popen (lua_State *L) {
const char *mode = luaL_optstring(L, 2, "r");
LStream *p = newprefile(L);
p->f = l_popen(L, filename, mode);
- if (p->f == NULL && luaL_resourcetryagain(L)) /* resource failure? */
- p->f = l_popen(L, filename, mode); /* try to open again */
p->closef = &io_pclose;
return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;
}
@@ -302,8 +287,6 @@ static int io_popen (lua_State *L) {
static int io_tmpfile (lua_State *L) {
LStream *p = newfile(L);
p->f = tmpfile();
- if (p->f == NULL && luaL_resourcetryagain(L)) /* resource failure? */
- p->f = tmpfile(); /* try to open again */
return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1;
}
diff --git a/loslib.c b/loslib.c
index 1962f55fb5..8809e5ea2a 100644
--- a/loslib.c
+++ b/loslib.c
@@ -166,8 +166,6 @@ static int os_tmpname (lua_State *L) {
char buff[LUA_TMPNAMBUFSIZE];
int err;
lua_tmpnam(buff, err);
- if (err && luaL_resourcetryagain(L)) /* resource failure? */
- lua_tmpnam(buff, err); /* try again */
if (err)
return luaL_error(L, "unable to generate a unique filename");
lua_pushstring(L, buff);
diff --git a/manual/manual.of b/manual/manual.of
index d8bac5dad9..91ba8c46cf 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -5535,20 +5535,6 @@ Leaves a copy of the module on the stack.
}
-@APIEntry{int luaL_resourcetryagain (lua_State *L);|
-@apii{0,0,m}
-
-Try to release resources in case of errors.
-This function uses @id{errno} to check whether the last error was
-related to lack of resources (e.g., not enough memory or too many
-open files).
-If so, the function performs a full garbage collection
-to try to release resources, and then it returns 1 to signal to
-the caller that it is worth trying again the failed operation.
-Otherwise, it returns 0.
-
-}
-
@APIEntry{void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup);|
@apii{nup,0,m}
From e8c779736f3029df353038352c14c8ab63728811 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 1 Nov 2018 13:21:00 -0300
Subject: [PATCH 091/889] Removed internal cache for closures
The mechanism of "caching the last closure created for a prototype to
try to reuse it the next time a closure for that prototype is created"
was removed. There are several reasons:
- It is hard to find a natural example where this cache has a measurable
impact on performance.
- Programmers already perceive closure creation as something slow,
so they tend to avoid it inside hot paths. (Any case where the cache
could reuse a closure can be rewritten predefining the closure in some
variable and using that variable.)
- The implementation was somewhat complex, due to a bad interaction
with the generational collector. (Typically, new closures are new,
while prototypes are old. So, the cache breaks the invariant that
old objects should not point to new ones.)
---
lfunc.c | 2 --
lgc.c | 64 +---------------------------------------------
lgc.h | 4 ---
lobject.h | 2 --
lstate.c | 2 +-
lstate.h | 3 ---
ltests.c | 4 ---
lvm.c | 39 ++--------------------------
testes/closure.lua | 13 +++++-----
9 files changed, 10 insertions(+), 123 deletions(-)
diff --git a/lfunc.c b/lfunc.c
index 15874f88b2..aa6ce58f17 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -219,8 +219,6 @@ Proto *luaF_newproto (lua_State *L) {
f->p = NULL;
f->sizep = 0;
f->code = NULL;
- f->cache = NULL;
- f->cachemiss = 0;
f->sizecode = 0;
f->lineinfo = NULL;
f->sizelineinfo = 0;
diff --git a/lgc.c b/lgc.c
index 9d196a18b7..95a8ad5b97 100644
--- a/lgc.c
+++ b/lgc.c
@@ -221,27 +221,6 @@ void luaC_barrierback_ (lua_State *L, GCObject *o) {
}
-/*
-** Barrier for prototype's cache of closures. It turns the prototype
-** back to gray (it was black). For an 'OLD1' prototype, making it
-** gray stops it from being visited by 'markold', so it is linked in
-** the 'grayagain' list to ensure it will be visited. For other ages,
-** it goes to the 'protogray' list, as only its 'cache' field needs to
-** be revisited. (A prototype to be in this barrier must be already
-** finished, so its other fields cannot change and do not need to be
-** revisited.)
-*/
-LUAI_FUNC void luaC_protobarrier_ (lua_State *L, Proto *p) {
- global_State *g = G(L);
- lua_assert(g->gckind != KGC_GEN || isold(p));
- if (getage(p) == G_OLD1) /* still need to be visited? */
- linkgclist(p, g->grayagain); /* link it in 'grayagain' */
- else
- linkgclist(p, g->protogray); /* link it in 'protogray' */
- black2gray(p); /* make prototype gray */
-}
-
-
void luaC_fix (lua_State *L, GCObject *o) {
global_State *g = G(L);
lua_assert(g->allgc == o); /* object must be 1st in 'allgc' list! */
@@ -379,7 +358,7 @@ static int remarkupvals (global_State *g) {
*/
static void restartcollection (global_State *g) {
g->gray = g->grayagain = NULL;
- g->weak = g->allweak = g->ephemeron = g->protogray = NULL;
+ g->weak = g->allweak = g->ephemeron = NULL;
markobject(g, g->mainthread);
markvalue(g, &g->l_registry);
markmt(g);
@@ -534,32 +513,6 @@ static int traverseudata (global_State *g, Udata *u) {
}
-/*
-** Check the cache of a prototype, to keep invariants. If the
-** cache is white, clear it. (A cache should not prevent the
-** collection of its reference.) Otherwise, if in generational
-** mode, check the generational invariant. If the cache is old,
-** everything is ok. If the prototype is 'OLD0', everything
-** is ok too. (It will naturally be visited again.) If the
-** prototype is older than 'OLD0', then its cache (which is new)
-** must be visited again in the next collection, so the prototype
-** goes to the 'protogray' list. (If the prototype has a cache,
-** it is already immutable and does not need other barriers;
-** then, it can become gray without problems for its other fields.)
-*/
-static void checkprotocache (global_State *g, Proto *p) {
- if (p->cache) {
- if (iswhite(p->cache))
- p->cache = NULL; /* allow cache to be collected */
- else if (g->gckind == KGC_GEN && !isold(p->cache) && getage(p) >= G_OLD1) {
- linkgclist(p, g->protogray); /* link it in 'protogray' */
- black2gray(p); /* make prototype gray */
- }
- }
- p->cachemiss = 0; /* restart counting */
-}
-
-
/*
** Traverse a prototype. (While a prototype is being build, its
** arrays can be larger than needed; the extra slots are filled with
@@ -567,7 +520,6 @@ static void checkprotocache (global_State *g, Proto *p) {
*/
static int traverseproto (global_State *g, Proto *f) {
int i;
- checkprotocache(g, f);
markobjectN(g, f->source);
for (i = 0; i < f->sizek; i++) /* mark literals */
markvalue(g, &f->k[i]);
@@ -696,19 +648,6 @@ static void convergeephemerons (global_State *g) {
** =======================================================
*/
-static void clearprotolist (global_State *g) {
- GCObject *p = g->protogray;
- g->protogray = NULL;
- while (p != NULL) {
- Proto *pp = gco2p(p);
- GCObject *next = pp->gclist;
- lua_assert(isgray(pp) && (pp->cache != NULL || pp->cachemiss >= MAXMISS));
- gray2black(pp);
- checkprotocache(g, pp);
- p = next;
- }
-}
-
/*
** clear entries with unmarked keys from all weaktables in list 'l'
@@ -1439,7 +1378,6 @@ static lu_mem atomic (lua_State *L) {
clearbyvalues(g, g->weak, origweak);
clearbyvalues(g, g->allweak, origall);
luaS_clearcache(g);
- clearprotolist(g);
g->currentwhite = cast_byte(otherwhite(g)); /* flip current white */
lua_assert(g->gray == NULL);
return work; /* estimate of slots marked by 'atomic' */
diff --git a/lgc.h b/lgc.h
index e0a8806b14..6b1c286153 100644
--- a/lgc.h
+++ b/lgc.h
@@ -164,9 +164,6 @@
(isblack(p) && iswhite(o)) ? \
luaC_barrier_(L,obj2gco(p),obj2gco(o)) : cast_void(0))
-#define luaC_protobarrier(L,p,o) \
- (isblack(p) ? luaC_protobarrier_(L,p) : cast_void(0))
-
LUAI_FUNC void luaC_fix (lua_State *L, GCObject *o);
LUAI_FUNC void luaC_freeallobjects (lua_State *L);
LUAI_FUNC void luaC_step (lua_State *L);
@@ -175,7 +172,6 @@ LUAI_FUNC void luaC_fullgc (lua_State *L, int isemergency);
LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz);
LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v);
LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o);
-LUAI_FUNC void luaC_protobarrier_ (lua_State *L, Proto *p);
LUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt);
LUAI_FUNC void luaC_changemode (lua_State *L, int newmode);
diff --git a/lobject.h b/lobject.h
index ea3511deba..df31a1a0d1 100644
--- a/lobject.h
+++ b/lobject.h
@@ -505,7 +505,6 @@ typedef struct Proto {
lu_byte numparams; /* number of fixed (named) parameters */
lu_byte is_vararg;
lu_byte maxstacksize; /* number of registers needed by this function */
- lu_byte cachemiss; /* count for successive misses for 'cache' field */
int sizeupvalues; /* size of 'upvalues' */
int sizek; /* size of 'k' */
int sizecode;
@@ -516,7 +515,6 @@ typedef struct Proto {
int linedefined; /* debug information */
int lastlinedefined; /* debug information */
TValue *k; /* constants used by the function */
- struct LClosure *cache; /* last-created closure with this prototype */
Instruction *code; /* opcodes */
struct Proto **p; /* functions defined inside the function */
Upvaldesc *upvalues; /* upvalue information */
diff --git a/lstate.c b/lstate.c
index 4a2453d15a..9d39995964 100644
--- a/lstate.c
+++ b/lstate.c
@@ -340,7 +340,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
g->finobjsur = g->finobjold = g->finobjrold = NULL;
g->sweepgc = NULL;
g->gray = g->grayagain = NULL;
- g->weak = g->ephemeron = g->allweak = g->protogray = NULL;
+ g->weak = g->ephemeron = g->allweak = NULL;
g->twups = NULL;
g->totalbytes = sizeof(LG);
g->GCdebt = 0;
diff --git a/lstate.h b/lstate.h
index f08c23554c..ce33770748 100644
--- a/lstate.h
+++ b/lstate.h
@@ -43,8 +43,6 @@
** 'weak': tables with weak values to be cleared;
** 'ephemeron': ephemeron tables with white->white entries;
** 'allweak': tables with weak keys and/or weak values to be cleared.
-** There is also a list 'protogray' for prototypes that need to have
-** their caches cleared.
*/
@@ -186,7 +184,6 @@ typedef struct global_State {
GCObject *weak; /* list of tables with weak values */
GCObject *ephemeron; /* list of ephemeron tables (weak keys) */
GCObject *allweak; /* list of all-weak tables */
- GCObject *protogray; /* list of prototypes with "new" caches */
GCObject *tobefnz; /* list of userdata to be GC */
GCObject *fixedgc; /* list of objects not to be collected */
/* fields for generational collector */
diff --git a/ltests.c b/ltests.c
index 7cf0889197..aba44b8731 100644
--- a/ltests.c
+++ b/ltests.c
@@ -283,7 +283,6 @@ static void checkudata (global_State *g, Udata *u) {
static void checkproto (global_State *g, Proto *f) {
int i;
GCObject *fgc = obj2gco(f);
- checkobjref(g, fgc, f->cache);
checkobjref(g, fgc, f->source);
for (i=0; isizek; i++) {
if (ttisstring(f->k + i))
@@ -417,8 +416,6 @@ static void checkobject (global_State *g, GCObject *o, int maybedead,
getage(o) == G_TOUCHED1 ||
getage(o) == G_OLD0 ||
o->tt == LUA_TTHREAD ||
- (o->tt == LUA_TPROTO &&
- (gco2p(o)->cache != NULL || gco2p(o)->cachemiss >= MAXMISS)) ||
(o->tt == LUA_TUPVAL && upisopen(gco2upv(o))));
}
}
@@ -452,7 +449,6 @@ static void checkgrays (global_State *g) {
checkgraylist(g, g->grayagain);
checkgraylist(g, g->weak);
checkgraylist(g, g->ephemeron);
- checkgraylist(g, g->protogray);
}
diff --git a/lvm.c b/lvm.c
index 1535700fc9..d96710558f 100644
--- a/lvm.c
+++ b/lvm.c
@@ -683,31 +683,9 @@ lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y) {
}
-/*
-** check whether cached closure in prototype 'p' may be reused, that is,
-** whether there is a cached closure with the same upvalues needed by
-** new closure to be created.
-*/
-static LClosure *getcached (Proto *p, UpVal **encup, StkId base) {
- LClosure *c = p->cache;
- if (c != NULL) { /* is there a cached closure? */
- int nup = p->sizeupvalues;
- Upvaldesc *uv = p->upvalues;
- int i;
- for (i = 0; i < nup; i++) { /* check whether it has right upvalues */
- TValue *v = uv[i].instack ? s2v(base + uv[i].idx) : encup[uv[i].idx]->v;
- if (c->upvals[i]->v != v)
- return NULL; /* wrong upvalue; cannot reuse closure */
- }
- p->cachemiss = 0; /* got a hit */
- }
- return c; /* return cached closure (or NULL if no cached closure) */
-}
-
-
/*
** create a new Lua closure, push it in the stack, and initialize
-** its upvalues. ???
+** its upvalues.
*/
static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base,
StkId ra) {
@@ -724,13 +702,6 @@ static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base,
ncl->upvals[i] = encup[uv[i].idx];
luaC_objbarrier(L, ncl, ncl->upvals[i]);
}
- if (p->cachemiss >= MAXMISS) /* too many missings? */
- p->cache = NULL; /* give up cache */
- else {
- p->cache = ncl; /* save it on cache for reuse */
- luaC_protobarrier(L, p, ncl);
- p->cachemiss++;
- }
}
@@ -1811,13 +1782,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
}
vmcase(OP_CLOSURE) {
Proto *p = cl->p->p[GETARG_Bx(i)];
- LClosure *ncl = getcached(p, cl->upvals, base); /* cached closure */
- if (ncl == NULL) { /* no match? */
- savestate(L, ci); /* in case of allocation errors */
- pushclosure(L, p, cl->upvals, base, ra); /* create a new one */
- }
- else
- setclLvalue2s(L, ra, ncl); /* push cashed closure */
+ halfProtect(pushclosure(L, p, cl->upvals, base, ra));
checkGC(L, ra + 1);
vmbreak;
}
diff --git a/testes/closure.lua b/testes/closure.lua
index 5d090d9116..cdeaebaa02 100644
--- a/testes/closure.lua
+++ b/testes/closure.lua
@@ -44,18 +44,17 @@ assert(B.g == 19)
-- testing equality
a = {}
-collectgarbage"stop"
-for i = 1, 5 do a[i] = function (x) return x + a + _ENV end end
-collectgarbage"restart"
-assert(a[3] == a[4] and a[4] == a[5])
for i = 1, 5 do a[i] = function (x) return i + a + _ENV end end
assert(a[3] ~= a[4] and a[4] ~= a[5])
-local function f()
- return function (x) return math.sin(_ENV[x]) end
+do
+ local a = function (x) return math.sin(_ENV[x]) end
+ local function f()
+ return a
+ end
+ assert(f() == f())
end
-assert(f() == f())
-- testing closures with 'for' control variable
From 5e76a4fd313a8690d300085c4e8fcb9dca50c01a Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 5 Nov 2018 16:10:42 -0200
Subject: [PATCH 092/889] New macros for arithmetic/bitwise operations in
'luaV_execute'
The repetitive code of the arithmetic and bitwise operators in
the main iterpreter loop was moved to appropriate macros.
(As a detail, the function 'luaV_div' was renamed 'luaV_idiv',
as it does an "integer division" (floor division).
---
lobject.c | 2 +-
ltm.c | 2 +-
ltm.h | 2 +-
lvm.c | 368 ++++++++++++++++++++++--------------------------------
lvm.h | 2 +-
5 files changed, 156 insertions(+), 220 deletions(-)
diff --git a/lobject.c b/lobject.c
index d011c85fc3..3ce052c29a 100644
--- a/lobject.c
+++ b/lobject.c
@@ -83,7 +83,7 @@ static lua_Integer intarith (lua_State *L, int op, lua_Integer v1,
case LUA_OPSUB:return intop(-, v1, v2);
case LUA_OPMUL:return intop(*, v1, v2);
case LUA_OPMOD: return luaV_mod(L, v1, v2);
- case LUA_OPIDIV: return luaV_div(L, v1, v2);
+ case LUA_OPIDIV: return luaV_idiv(L, v1, v2);
case LUA_OPBAND: return intop(&, v1, v2);
case LUA_OPBOR: return intop(|, v1, v2);
case LUA_OPBXOR: return intop(^, v1, v2);
diff --git a/ltm.c b/ltm.c
index 53e15c7fdc..23a97a62cc 100644
--- a/ltm.c
+++ b/ltm.c
@@ -176,7 +176,7 @@ void luaT_trybinassocTM (lua_State *L, const TValue *p1, const TValue *p2,
}
-void luaT_trybiniTM (lua_State *L, const TValue *p1, int i2,
+void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2,
int inv, StkId res, TMS event) {
TValue aux;
setivalue(&aux, i2);
diff --git a/ltm.h b/ltm.h
index e8560f82cb..fad47842a1 100644
--- a/ltm.h
+++ b/ltm.h
@@ -77,7 +77,7 @@ LUAI_FUNC void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2,
StkId res, TMS event);
LUAI_FUNC void luaT_trybinassocTM (lua_State *L, const TValue *p1,
const TValue *p2, StkId res, int inv, TMS event);
-LUAI_FUNC void luaT_trybiniTM (lua_State *L, const TValue *p1, int i2,
+LUAI_FUNC void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2,
int inv, StkId res, TMS event);
LUAI_FUNC int luaT_callorderTM (lua_State *L, const TValue *p1,
const TValue *p2, TMS event);
diff --git a/lvm.c b/lvm.c
index d96710558f..9977bda4f7 100644
--- a/lvm.c
+++ b/lvm.c
@@ -620,7 +620,7 @@ void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) {
** 'floor(q) == trunc(q)' when 'q >= 0' or when 'q' is integer,
** otherwise 'floor(q) == trunc(q) - 1'.
*/
-lua_Integer luaV_div (lua_State *L, lua_Integer m, lua_Integer n) {
+lua_Integer luaV_idiv (lua_State *L, lua_Integer m, lua_Integer n) {
if (unlikely(l_castS2U(n) + 1u <= 1u)) { /* special cases: -1 or 0 */
if (n == 0)
luaG_runerror(L, "attempt to divide by zero");
@@ -638,7 +638,7 @@ lua_Integer luaV_div (lua_State *L, lua_Integer m, lua_Integer n) {
/*
** Integer modulus; return 'm % n'. (Assume that C '%' with
** negative operands follows C99 behavior. See previous comment
-** about luaV_div.)
+** about luaV_idiv.)
*/
lua_Integer luaV_mod (lua_State *L, lua_Integer m, lua_Integer n) {
if (unlikely(l_castS2U(n) + 1u <= 1u)) { /* special cases: -1 or 0 */
@@ -770,11 +770,126 @@ void luaV_finishOp (lua_State *L) {
/*
** {==================================================================
-** Function 'luaV_execute': main interpreter loop
+** Macros for arithmetic/bitwise opcodes in 'luaV_execute'
** ===================================================================
*/
+#define l_addi(L,a,b) intop(+, a, b)
+#define l_subi(L,a,b) intop(-, a, b)
+#define l_muli(L,a,b) intop(*, a, b)
+#define l_band(L,a,b) intop(&, a, b)
+#define l_bor(L,a,b) intop(|, a, b)
+#define l_bxor(L,a,b) intop(^, a, b)
+
+
+/*
+** Auxiliary macro for arithmetic operations over floats and others
+** with immediate operand. 'fop' is the float operation; 'tm' is the
+** corresponding metamethod; 'flip' is true if operands were flipped.
+*/
+#define op_arithfI_aux(L,v1,imm,fop,tm,flip) { \
+ lua_Number nb; \
+ if (tonumberns(v1, nb)) { \
+ setfltvalue(s2v(ra), fop(L, nb, cast_num(imm))); \
+ } \
+ else \
+ Protect(luaT_trybiniTM(L, v1, imm, flip, ra, tm)); }
+
+
+/*
+** Arithmetic operations over floats and others with immediate operand.
+*/
+#define op_arithfI(L,fop,tm) { \
+ TValue *v1 = vRB(i); \
+ int imm = GETARG_sC(i); \
+ op_arithfI_aux(L, v1, imm, fop, tm, 0); }
+
+/*
+** Arithmetic operations with immediate operands. 'iop' is the integer
+** operation.
+*/
+#define op_arithI(L,iop,fop,tm,flip) { \
+ TValue *v1 = vRB(i); \
+ int imm = GETARG_sC(i); \
+ if (ttisinteger(v1)) { \
+ setivalue(s2v(ra), iop(L, ivalue(v1), imm)); \
+ } \
+ else op_arithfI_aux(L, v1, imm, fop, tm, flip); }
+
+
+/*
+** Auxiliary function for arithmetic operations over floats and others
+** with two register operands.
+*/
+#define op_arithf_aux(L,v1,v2,fop,tm) { \
+ lua_Number n1; lua_Number n2; \
+ if (tonumberns(v1, n1) && tonumberns(v2, n2)) { \
+ setfltvalue(s2v(ra), fop(L, n1, n2)); \
+ } \
+ else \
+ Protect(luaT_trybinTM(L, v1, v2, ra, tm)); }
+
+
+/*
+** Arithmetic operations over floats and others with register operands.
+*/
+#define op_arithf(L,fop,tm) { \
+ TValue *v1 = vRB(i); \
+ TValue *v2 = vRC(i); \
+ op_arithf_aux(L, v1, v2, fop, tm); }
+
+
+/*
+** Arithmetic operations with register operands.
+*/
+#define op_arith(L,iop,fop,tm) { \
+ TValue *v1 = vRB(i); \
+ TValue *v2 = vRC(i); \
+ if (ttisinteger(v1) && ttisinteger(v2)) { \
+ lua_Integer i1 = ivalue(v1); lua_Integer i2 = ivalue(v2); \
+ setivalue(s2v(ra), iop(L, i1, i2)); \
+ } \
+ else op_arithf_aux(L, v1, v2, fop, tm); }
+
+
+/*
+** Bitwise operations with constant operand.
+*/
+#define op_bitwiseK(L,op,tm) { \
+ TValue *v1 = vRB(i); \
+ TValue *v2 = KC(i); \
+ lua_Integer i1; \
+ lua_Integer i2 = ivalue(v2); \
+ if (tointegerns(v1, &i1)) { \
+ setivalue(s2v(ra), op(L, i1, i2)); \
+ } \
+ else \
+ Protect(luaT_trybiniTM(L, v1, i2, TESTARG_k(i), ra, tm)); }
+
+
+/*
+** Bitwise operations with register operands.
+*/
+#define op_bitwise(L,op,tm) { \
+ TValue *v1 = vRB(i); \
+ TValue *v2 = vRC(i); \
+ lua_Integer i1; lua_Integer i2; \
+ if (tointegerns(v1, &i1) && tointegerns(v2, &i2)) { \
+ setivalue(s2v(ra), op(L, i1, i2)); \
+ } \
+ else \
+ Protect(luaT_trybinTM(L, v1, v2, ra, tm)); }
+
+/* }================================================================== */
+
+
+/*
+** {==================================================================
+** Function 'luaV_execute': main interpreter loop
+** ===================================================================
+*/
+
/*
** some macros for common tasks in 'luaV_execute'
*/
@@ -1075,221 +1190,83 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_ADDI) {
- TValue *rb = vRB(i);
- int ic = GETARG_sC(i);
- lua_Number nb;
- if (ttisinteger(rb)) {
- setivalue(s2v(ra), intop(+, ivalue(rb), ic));
- }
- else if (tonumberns(rb, nb)) {
- setfltvalue(s2v(ra), luai_numadd(L, nb, cast_num(ic)));
- }
- else
- Protect(luaT_trybiniTM(L, rb, ic, GETARG_k(i), ra, TM_ADD));
+ op_arithI(L, l_addi, luai_numadd, TM_ADD, GETARG_k(i));
vmbreak;
}
vmcase(OP_SUBI) {
- TValue *rb = vRB(i);
- int ic = GETARG_sC(i);
- lua_Number nb;
- if (ttisinteger(rb)) {
- setivalue(s2v(ra), intop(-, ivalue(rb), ic));
- }
- else if (tonumberns(rb, nb)) {
- setfltvalue(s2v(ra), luai_numsub(L, nb, cast_num(ic)));
- }
- else
- Protect(luaT_trybiniTM(L, rb, ic, 0, ra, TM_SUB));
+ op_arithI(L, l_subi, luai_numsub, TM_SUB, 0);
vmbreak;
}
vmcase(OP_MULI) {
- TValue *rb = vRB(i);
- int ic = GETARG_sC(i);
- lua_Number nb;
- if (ttisinteger(rb)) {
- setivalue(s2v(ra), intop(*, ivalue(rb), ic));
- }
- else if (tonumberns(rb, nb)) {
- setfltvalue(s2v(ra), luai_nummul(L, nb, cast_num(ic)));
- }
- else
- Protect(luaT_trybiniTM(L, rb, ic, GETARG_k(i), ra, TM_MUL));
+ op_arithI(L, l_muli, luai_nummul, TM_MUL, GETARG_k(i));
vmbreak;
}
vmcase(OP_MODI) {
- TValue *rb = vRB(i);
- int ic = GETARG_sC(i);
- lua_Number nb;
- if (ttisinteger(rb)) {
- setivalue(s2v(ra), luaV_mod(L, ivalue(rb), ic));
- }
- else if (tonumberns(rb, nb)) {
- lua_Number nc = cast_num(ic);
- setfltvalue(s2v(ra), luaV_modf(L, nb, nc));
- }
- else
- Protect(luaT_trybiniTM(L, rb, ic, 0, ra, TM_MOD));
+ op_arithI(L, luaV_mod, luaV_modf, TM_MOD, 0);
vmbreak;
}
vmcase(OP_POWI) {
- TValue *rb = vRB(i);
- int ic = GETARG_sC(i);
- lua_Number nb;
- if (tonumberns(rb, nb)) {
- lua_Number nc = cast_num(ic);
- setfltvalue(s2v(ra), luai_numpow(L, nb, nc));
- }
- else
- Protect(luaT_trybiniTM(L, rb, ic, 0, ra, TM_POW));
+ op_arithfI(L, luai_numpow, TM_POW);
vmbreak;
}
vmcase(OP_DIVI) {
- TValue *rb = vRB(i);
- int ic = GETARG_sC(i);
- lua_Number nb;
- if (tonumberns(rb, nb)) {
- lua_Number nc = cast_num(ic);
- setfltvalue(s2v(ra), luai_numdiv(L, nb, nc));
- }
- else
- Protect(luaT_trybiniTM(L, rb, ic, 0, ra, TM_DIV));
+ op_arithfI(L, luai_numdiv, TM_DIV);
vmbreak;
}
vmcase(OP_IDIVI) {
- TValue *rb = vRB(i);
- int ic = GETARG_sC(i);
- lua_Number nb;
- if (ttisinteger(rb)) {
- setivalue(s2v(ra), luaV_div(L, ivalue(rb), ic));
- }
- else if (tonumberns(rb, nb)) {
- lua_Number nc = cast_num(ic);
- setfltvalue(s2v(ra), luai_numidiv(L, nb, nc));
- }
- else
- Protect(luaT_trybiniTM(L, rb, ic, 0, ra, TM_IDIV));
+ op_arithI(L, luaV_idiv, luai_numidiv, TM_IDIV, 0);
vmbreak;
}
vmcase(OP_ADD) {
- TValue *rb = vRB(i);
- TValue *rc = vRC(i);
- lua_Number nb; lua_Number nc;
- if (ttisinteger(rb) && ttisinteger(rc)) {
- lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc);
- setivalue(s2v(ra), intop(+, ib, ic));
- }
- else if (tonumberns(rb, nb) && tonumberns(rc, nc)) {
- setfltvalue(s2v(ra), luai_numadd(L, nb, nc));
- }
- else
- Protect(luaT_trybinTM(L, rb, rc, ra, TM_ADD));
+ op_arith(L, l_addi, luai_numadd, TM_ADD);
vmbreak;
}
vmcase(OP_SUB) {
- TValue *rb = vRB(i);
- TValue *rc = vRC(i);
- lua_Number nb; lua_Number nc;
- if (ttisinteger(rb) && ttisinteger(rc)) {
- lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc);
- setivalue(s2v(ra), intop(-, ib, ic));
- }
- else if (tonumberns(rb, nb) && tonumberns(rc, nc)) {
- setfltvalue(s2v(ra), luai_numsub(L, nb, nc));
- }
- else
- Protect(luaT_trybinTM(L, rb, rc, ra, TM_SUB));
+ op_arith(L, l_subi, luai_numsub, TM_SUB);
vmbreak;
}
vmcase(OP_MUL) {
- TValue *rb = vRB(i);
- TValue *rc = vRC(i);
- lua_Number nb; lua_Number nc;
- if (ttisinteger(rb) && ttisinteger(rc)) {
- lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc);
- setivalue(s2v(ra), intop(*, ib, ic));
- }
- else if (tonumberns(rb, nb) && tonumberns(rc, nc)) {
- setfltvalue(s2v(ra), luai_nummul(L, nb, nc));
- }
- else
- Protect(luaT_trybinTM(L, rb, rc, ra, TM_MUL));
+ op_arith(L, l_muli, luai_nummul, TM_MUL);
+ vmbreak;
+ }
+ vmcase(OP_MOD) {
+ op_arith(L, luaV_mod, luaV_modf, TM_MOD);
+ vmbreak;
+ }
+ vmcase(OP_POW) {
+ op_arithf(L, luai_numpow, TM_POW);
vmbreak;
}
vmcase(OP_DIV) { /* float division (always with floats) */
- TValue *rb = vRB(i);
- TValue *rc = vRC(i);
- lua_Number nb; lua_Number nc;
- if (tonumberns(rb, nb) && tonumberns(rc, nc)) {
- setfltvalue(s2v(ra), luai_numdiv(L, nb, nc));
- }
- else
- Protect(luaT_trybinTM(L, rb, rc, ra, TM_DIV));
+ op_arithf(L, luai_numdiv, TM_DIV);
+ vmbreak;
+ }
+ vmcase(OP_IDIV) { /* floor division */
+ op_arith(L, luaV_idiv, luai_numidiv, TM_IDIV);
vmbreak;
}
vmcase(OP_BANDK) {
- TValue *p1 = vRB(i);
- TValue *p2 = KC(i);
- lua_Integer i1;
- if (tointegerns(p1, &i1)) {
- setivalue(s2v(ra), intop(&, i1, ivalue(p2)));
- }
- else
- Protect(luaT_trybinassocTM(L, p1, p2, ra, TESTARG_k(i), TM_BAND));
+ op_bitwiseK(L, l_band, TM_BAND);
vmbreak;
}
vmcase(OP_BORK) {
- TValue *p1 = vRB(i);
- TValue *p2 = KC(i);
- lua_Integer i1;
- if (tointegerns(p1, &i1)) {
- setivalue(s2v(ra), intop(|, i1, ivalue(p2)));
- }
- else
- Protect(luaT_trybinassocTM(L, p1, p2, ra, TESTARG_k(i), TM_BOR));
+ op_bitwiseK(L, l_bor, TM_BOR);
vmbreak;
}
vmcase(OP_BXORK) {
- TValue *p1 = vRB(i);
- TValue *p2 = KC(i);
- lua_Integer i1;
- if (tointegerns(p1, &i1)) {
- setivalue(s2v(ra), intop(^, i1, ivalue(p2)));
- }
- else
- Protect(luaT_trybinassocTM(L, p1, p2, ra, TESTARG_k(i), TM_BXOR));
+ op_bitwiseK(L, l_bxor, TM_BXOR);
vmbreak;
}
vmcase(OP_BAND) {
- TValue *rb = vRB(i);
- TValue *rc = vRC(i);
- lua_Integer ib; lua_Integer ic;
- if (tointegerns(rb, &ib) && tointegerns(rc, &ic)) {
- setivalue(s2v(ra), intop(&, ib, ic));
- }
- else
- Protect(luaT_trybinTM(L, rb, rc, ra, TM_BAND));
+ op_bitwise(L, l_band, TM_BAND);
vmbreak;
}
vmcase(OP_BOR) {
- TValue *rb = vRB(i);
- TValue *rc = vRC(i);
- lua_Integer ib; lua_Integer ic;
- if (tointegerns(rb, &ib) && tointegerns(rc, &ic)) {
- setivalue(s2v(ra), intop(|, ib, ic));
- }
- else
- Protect(luaT_trybinTM(L, rb, rc, ra, TM_BOR));
+ op_bitwise(L, l_bor, TM_BOR);
vmbreak;
}
vmcase(OP_BXOR) {
- TValue *rb = vRB(i);
- TValue *rc = vRC(i);
- lua_Integer ib; lua_Integer ic;
- if (tointegerns(rb, &ib) && tointegerns(rc, &ic)) {
- setivalue(s2v(ra), intop(^, ib, ic));
- }
- else
- Protect(luaT_trybinTM(L, rb, rc, ra, TM_BXOR));
+ op_bitwise(L, l_bxor, TM_BXOR);
vmbreak;
}
vmcase(OP_SHRI) {
@@ -1319,17 +1296,6 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
Protect(luaT_trybiniTM(L, rb, ic, 1, ra, TM_SHL));
vmbreak;
}
- vmcase(OP_SHL) {
- TValue *rb = vRB(i);
- TValue *rc = vRC(i);
- lua_Integer ib; lua_Integer ic;
- if (tointegerns(rb, &ib) && tointegerns(rc, &ic)) {
- setivalue(s2v(ra), luaV_shiftl(ib, ic));
- }
- else
- Protect(luaT_trybinTM(L, rb, rc, ra, TM_SHL));
- vmbreak;
- }
vmcase(OP_SHR) {
TValue *rb = vRB(i);
TValue *rc = vRC(i);
@@ -1341,45 +1307,15 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
Protect(luaT_trybinTM(L, rb, rc, ra, TM_SHR));
vmbreak;
}
- vmcase(OP_MOD) {
- TValue *rb = vRB(i);
- TValue *rc = vRC(i);
- lua_Number nb; lua_Number nc;
- if (ttisinteger(rb) && ttisinteger(rc)) {
- lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc);
- setivalue(s2v(ra), luaV_mod(L, ib, ic));
- }
- else if (tonumberns(rb, nb) && tonumberns(rc, nc)) {
- setfltvalue(s2v(ra), luaV_modf(L, nb, nc));
- }
- else
- Protect(luaT_trybinTM(L, rb, rc, ra, TM_MOD));
- vmbreak;
- }
- vmcase(OP_IDIV) { /* floor division */
- TValue *rb = vRB(i);
- TValue *rc = vRC(i);
- lua_Number nb; lua_Number nc;
- if (ttisinteger(rb) && ttisinteger(rc)) {
- lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc);
- setivalue(s2v(ra), luaV_div(L, ib, ic));
- }
- else if (tonumberns(rb, nb) && tonumberns(rc, nc)) {
- setfltvalue(s2v(ra), luai_numidiv(L, nb, nc));
- }
- else
- Protect(luaT_trybinTM(L, rb, rc, ra, TM_IDIV));
- vmbreak;
- }
- vmcase(OP_POW) {
+ vmcase(OP_SHL) {
TValue *rb = vRB(i);
TValue *rc = vRC(i);
- lua_Number nb; lua_Number nc;
- if (tonumberns(rb, nb) && tonumberns(rc, nc)) {
- setfltvalue(s2v(ra), luai_numpow(L, nb, nc));
+ lua_Integer ib; lua_Integer ic;
+ if (tointegerns(rb, &ib) && tointegerns(rc, &ic)) {
+ setivalue(s2v(ra), luaV_shiftl(ib, ic));
}
else
- Protect(luaT_trybinTM(L, rb, rc, ra, TM_POW));
+ Protect(luaT_trybinTM(L, rb, rc, ra, TM_SHL));
vmbreak;
}
vmcase(OP_UNM) {
diff --git a/lvm.h b/lvm.h
index 8ead0c50b3..7e8ec7155e 100644
--- a/lvm.h
+++ b/lvm.h
@@ -114,7 +114,7 @@ LUAI_FUNC void luaV_finishset (lua_State *L, const TValue *t, TValue *key,
LUAI_FUNC void luaV_finishOp (lua_State *L);
LUAI_FUNC void luaV_execute (lua_State *L, CallInfo *ci);
LUAI_FUNC void luaV_concat (lua_State *L, int total);
-LUAI_FUNC lua_Integer luaV_div (lua_State *L, lua_Integer x, lua_Integer y);
+LUAI_FUNC lua_Integer luaV_idiv (lua_State *L, lua_Integer x, lua_Integer y);
LUAI_FUNC lua_Integer luaV_mod (lua_State *L, lua_Integer x, lua_Integer y);
LUAI_FUNC lua_Number luaV_modf (lua_State *L, lua_Number x, lua_Number y);
LUAI_FUNC lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y);
From b8fed93215a23a3f443c5b0126f0de1725771b44 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 7 Nov 2018 10:03:05 -0200
Subject: [PATCH 093/889] New syntax for to-be-closed variables
The new syntax is . The mark '*' allows other
attributes to be added later without the need of new keywords; it
also allows better error messages. The API function was also renamed
('lua_tobeclosed' -> 'lua_toclose').
---
lapi.c | 2 +-
lparser.c | 25 +++++++++++++------------
ltests.c | 4 ++--
lua.h | 2 +-
testes/api.lua | 12 ++++++------
testes/files.lua | 6 +++---
testes/goto.lua | 2 +-
testes/locals.lua | 42 +++++++++++++++++++++---------------------
8 files changed, 48 insertions(+), 47 deletions(-)
diff --git a/lapi.c b/lapi.c
index 4fef43b713..91a6e38916 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1207,7 +1207,7 @@ LUA_API int lua_next (lua_State *L, int idx) {
}
-LUA_API void lua_tobeclosed (lua_State *L) {
+LUA_API void lua_toclose (lua_State *L) {
int nresults = L->ci->nresults;
luaF_newtbcupval(L, L->top - 1); /* create new to-be-closed upvalue */
if (!hastocloseCfunc(nresults)) /* function not marked yet? */
diff --git a/lparser.c b/lparser.c
index a5b84aa1e8..e4f11cb615 100644
--- a/lparser.c
+++ b/lparser.c
@@ -1546,16 +1546,15 @@ static void localfunc (LexState *ls) {
}
-static void commonlocalstat (LexState *ls, TString *firstvar) {
+static void commonlocalstat (LexState *ls) {
/* stat -> LOCAL NAME {',' NAME} ['=' explist] */
- int nvars = 1;
+ int nvars = 0;
int nexps;
expdesc e;
- new_localvar(ls, firstvar);
- while (testnext(ls, ',')) {
+ do {
new_localvar(ls, str_checkname(ls));
nvars++;
- }
+ } while (testnext(ls, ','));
if (testnext(ls, '='))
nexps = explist(ls, &e);
else {
@@ -1567,8 +1566,12 @@ static void commonlocalstat (LexState *ls, TString *firstvar) {
}
-static void scopedlocalstat (LexState *ls) {
+static void tocloselocalstat (LexState *ls) {
FuncState *fs = ls->fs;
+ TString *attr = str_checkname(ls);
+ if (strcmp(getstr(attr), "toclose") != 0)
+ luaK_semerror(ls,
+ luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr)));
new_localvar(ls, str_checkname(ls));
checknext(ls, '=');
exp1(ls, 0);
@@ -1580,13 +1583,11 @@ static void scopedlocalstat (LexState *ls) {
static void localstat (LexState *ls) {
/* stat -> LOCAL NAME {',' NAME} ['=' explist]
- | LOCAL SCOPED NAME '=' exp */
- TString *firstvar = str_checkname(ls);
- if (ls->t.token == TK_NAME &&
- eqshrstr(firstvar, luaS_newliteral(ls->L, "scoped")))
- scopedlocalstat(ls);
+ | LOCAL *toclose NAME '=' exp */
+ if (testnext(ls, '*'))
+ tocloselocalstat(ls);
else
- commonlocalstat(ls, firstvar);
+ commonlocalstat(ls);
}
diff --git a/ltests.c b/ltests.c
index aba44b8731..192ae86193 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1550,8 +1550,8 @@ static struct X { int x; } x;
int i = getindex;
return lua_yieldk(L1, nres, i, Cfunck);
}
- else if EQ("tobeclosed") {
- lua_tobeclosed(L);
+ else if EQ("toclose") {
+ lua_toclose(L);
}
else luaL_error(L, "unknown instruction %s", buff);
}
diff --git a/lua.h b/lua.h
index 16d685ccb0..fe468c8ef4 100644
--- a/lua.h
+++ b/lua.h
@@ -333,7 +333,7 @@ LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s);
LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud);
LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
-LUA_API void (lua_tobeclosed) (lua_State *L);
+LUA_API void (lua_toclose) (lua_State *L);
/*
diff --git a/testes/api.lua b/testes/api.lua
index 988250f719..a6ddca8e3c 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -987,7 +987,7 @@ do
local a = T.testC([[
call 0 1 # create resource
- tobeclosed # mark it to be closed
+ toclose # mark it to be closed
return 1
]], newresource)
assert(a[1] == 11)
@@ -996,7 +996,7 @@ do
-- repeat the test, but calling function in a 'multret' context
local a = {T.testC([[
call 0 1 # create resource
- tobeclosed # mark it to be closed
+ toclose # mark it to be closed
return 2
]], newresource)}
assert(type(a[1]) == "string" and a[2][1] == 11)
@@ -1005,7 +1005,7 @@ do
-- error
local a, b = pcall(T.testC, [[
call 0 1 # create resource
- tobeclosed # mark it to be closed
+ toclose # mark it to be closed
error # resource is the error object
]], newresource)
assert(a == false and b[1] == 11)
@@ -1019,10 +1019,10 @@ do
local a = T.testC([[
pushvalue 2
call 0 1 # create resource
- tobeclosed # mark it to be closed
+ toclose # mark it to be closed
pushvalue 2
call 0 1 # create another resource
- tobeclosed # mark it to be closed
+ toclose # mark it to be closed
pushvalue 3
pushint 2 # there should be two open resources
call 1 0
@@ -1102,7 +1102,7 @@ end)
testamem("to-be-closed variables", function()
local flag
do
- local scoped x = function () flag = true end
+ local *toclose x = function () flag = true end
flag = false
local x = {}
end
diff --git a/testes/files.lua b/testes/files.lua
index a11c5e617d..e68eb9b865 100644
--- a/testes/files.lua
+++ b/testes/files.lua
@@ -125,7 +125,7 @@ do
-- closing file by scope
local F = nil
do
- local scoped f = assert(io.open(file, "w"))
+ local *toclose f = assert(io.open(file, "w"))
F = f
end
assert(tostring(F) == "file (closed)")
@@ -135,7 +135,7 @@ assert(os.remove(file))
do
-- test writing/reading numbers
- local scoped f = assert(io.open(file, "w"))
+ local *toclose f = assert(io.open(file, "w"))
f:write(maxint, '\n')
f:write(string.format("0X%x\n", maxint))
f:write("0xABCp-3", '\n')
@@ -158,7 +158,7 @@ assert(os.remove(file))
-- testing multiple arguments to io.read
do
- local scoped f = assert(io.open(file, "w"))
+ local *toclose f = assert(io.open(file, "w"))
f:write[[
a line
another line
diff --git a/testes/goto.lua b/testes/goto.lua
index 92f048fb69..5d863a428d 100644
--- a/testes/goto.lua
+++ b/testes/goto.lua
@@ -258,7 +258,7 @@ do
::L2:: goto L3
::L1:: do
- local scoped a = function () X = true end
+ local *toclose a = function () X = true end
assert(X == nil)
if a then goto L2 end -- jumping back out of scope of 'a'
end
diff --git a/testes/locals.lua b/testes/locals.lua
index 1e0f525ba4..869ac1ff2e 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -181,9 +181,9 @@ local function stack(n) n = ((n == 0) or stack(n - 1)) end
do
local a = {}
do
- local scoped x = setmetatable({"x"}, {__close = function (self)
+ local *toclose x = setmetatable({"x"}, {__close = function (self)
a[#a + 1] = self[1] end})
- local scoped y = function (x) assert(x == nil); a[#a + 1] = "y" end
+ local *toclose y = function (x) assert(x == nil); a[#a + 1] = "y" end
a[#a + 1] = "in"
end
a[#a + 1] = "out"
@@ -197,7 +197,7 @@ do
-- closing functions do not corrupt returning values
local function foo (x)
- local scoped _ = closescope
+ local *toclose _ = closescope
return x, X, 23
end
@@ -206,7 +206,7 @@ do
X = false
foo = function (x)
- local scoped _ = closescope
+ local *toclose _ = closescope
local y = 15
return y
end
@@ -215,7 +215,7 @@ do
X = false
foo = function ()
- local scoped x = closescope
+ local *toclose x = closescope
return x
end
@@ -228,13 +228,13 @@ do
-- to-be-closed variables must be closed in tail calls
local X, Y
local function foo ()
- local scoped _ = function () Y = 10 end
+ local *toclose _ = function () Y = 10 end
assert(X == 20 and Y == nil)
return 1,2,3
end
local function bar ()
- local scoped _ = function () X = 20 end
+ local *toclose _ = function () X = 20 end
return foo()
end
@@ -245,11 +245,11 @@ end
do -- errors in __close
local log = {}
local function foo (err)
- local scoped x = function (msg) log[#log + 1] = msg; error(1) end
- local scoped x1 = function (msg) log[#log + 1] = msg; end
- local scoped gc = function () collectgarbage() end
- local scoped y = function (msg) log[#log + 1] = msg; error(2) end
- local scoped z = function (msg) log[#log + 1] = msg or 10; error(3) end
+ local *toclose x = function (msg) log[#log + 1] = msg; error(1) end
+ local *toclose x1 = function (msg) log[#log + 1] = msg; end
+ local *toclose gc = function () collectgarbage() end
+ local *toclose y = function (msg) log[#log + 1] = msg; error(2) end
+ local *toclose z = function (msg) log[#log + 1] = msg or 10; error(3) end
if err then error(4) end
end
local stat, msg = pcall(foo, false)
@@ -267,8 +267,8 @@ end
if rawget(_G, "T") then
-- memory error inside closing function
local function foo ()
- local scoped y = function () T.alloccount() end
- local scoped x = setmetatable({}, {__close = function ()
+ local *toclose y = function () T.alloccount() end
+ local *toclose x = setmetatable({}, {__close = function ()
T.alloccount(0); local x = {} -- force a memory error
end})
error("a") -- common error inside the function's body
@@ -294,7 +294,7 @@ if rawget(_G, "T") then
end
local function test ()
- local scoped x = enter(0) -- set a memory limit
+ local *toclose x = enter(0) -- set a memory limit
-- creation of previous upvalue will raise a memory error
os.exit(false) -- should not run
end
@@ -309,14 +309,14 @@ if rawget(_G, "T") then
-- repeat test with extra closing upvalues
local function test ()
- local scoped xxx = function (msg)
+ local *toclose xxx = function (msg)
assert(msg == "not enough memory");
error(1000) -- raise another error
end
- local scoped xx = function (msg)
+ local *toclose xx = function (msg)
assert(msg == "not enough memory");
end
- local scoped x = enter(0) -- set a memory limit
+ local *toclose x = enter(0) -- set a memory limit
-- creation of previous upvalue will raise a memory error
os.exit(false) -- should not run
end
@@ -333,9 +333,9 @@ do
local x = false
local y = false
local co = coroutine.create(function ()
- local scoped xv = function () x = true end
+ local *toclose xv = function () x = true end
do
- local scoped yv = function () y = true end
+ local *toclose yv = function () y = true end
coroutine.yield(100) -- yield doesn't close variable
end
coroutine.yield(200) -- yield doesn't close variable
@@ -353,7 +353,7 @@ end
-- a suspended coroutine should not close its variables when collected
local co
co = coroutine.wrap(function()
- local scoped x = function () os.exit(false) end -- should not run
+ local *toclose x = function () os.exit(false) end -- should not run
co = nil
coroutine.yield()
end)
From 7f6f70853c8a2730fca2e95d5968ad52cf470bda Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 7 Nov 2018 14:42:05 -0200
Subject: [PATCH 094/889] To-be-closed variable in 'for' loop separated from
the state
The variable to be closed in a generic 'for' loop now is the
4th value produced in the loop initialization, instead of being
the loop state (the 2nd value produced). That allows a loop to
use a state with a '__toclose' metamethod but do not close it.
(As an example, 'f:lines()' might use the file 'f' as a state
for the loop, but it should not close the file when the loop ends.)
---
liolib.c | 6 ++++--
lopcodes.h | 6 +++---
lparser.c | 25 ++++++++++++++-----------
lvm.c | 23 ++++++++++++-----------
testes/files.lua | 37 ++++++++++++++++++++++++++++++++++++-
testes/locals.lua | 8 +++++++-
6 files changed, 76 insertions(+), 29 deletions(-)
diff --git a/liolib.c b/liolib.c
index b2a2fec8a1..7d6d51e672 100644
--- a/liolib.c
+++ b/liolib.c
@@ -386,8 +386,10 @@ static int io_lines (lua_State *L) {
}
aux_lines(L, toclose); /* push iteration function */
if (toclose) {
- lua_pushvalue(L, 1); /* file will be second result */
- return 2;
+ lua_pushnil(L); /* state */
+ lua_pushnil(L); /* control */
+ lua_pushvalue(L, 1); /* file is the to-be-closed variable (4th result) */
+ return 4;
}
else
return 1;
diff --git a/lopcodes.h b/lopcodes.h
index 5a38f76746..7bb83a1e6e 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -279,9 +279,9 @@ OP_FORLOOP,/* A Bx R(A)+=R(A+2);
if R(A) = R(A+1) then { pc-=Bx; R(A+3)=R(A) } */
OP_FORPREP,/* A Bx R(A)-=R(A+2); pc+=Bx */
-OP_TFORPREP,/* A Bx create upvalue A; pc+=Bx */
-OP_TFORCALL,/* A C R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2)); */
-OP_TFORLOOP,/* A Bx if R(A+1) ~= nil then { R(A)=R(A+1); pc -= Bx } */
+OP_TFORPREP,/* A Bx create upvalue for R(A + 3); pc+=Bx */
+OP_TFORCALL,/* A C R(A+4), ... ,R(A+3+C) := R(A)(R(A+1), R(A+2)); */
+OP_TFORLOOP,/* A Bx if R(A+2) ~= nil then { R(A)=R(A+2); pc -= Bx } */
OP_SETLIST,/* A B C R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B */
diff --git a/lparser.c b/lparser.c
index e4f11cb615..afc5aeab61 100644
--- a/lparser.c
+++ b/lparser.c
@@ -165,6 +165,9 @@ static int registerlocalvar (LexState *ls, TString *varname) {
}
+/*
+** Create a new local variable with the given 'name'.
+*/
static void new_localvar (LexState *ls, TString *name) {
FuncState *fs = ls->fs;
Dyndata *dyd = ls->dyd;
@@ -176,13 +179,8 @@ static void new_localvar (LexState *ls, TString *name) {
dyd->actvar.arr[dyd->actvar.n++].idx = cast(short, reg);
}
-
-static void new_localvarliteral_ (LexState *ls, const char *name, size_t sz) {
- new_localvar(ls, luaX_newstring(ls, name, sz));
-}
-
#define new_localvarliteral(ls,v) \
- new_localvarliteral_(ls, "" v, (sizeof(v)/sizeof(char))-1)
+ new_localvar(ls, luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1));
static LocVar *getlocvar (FuncState *fs, int i) {
@@ -192,6 +190,9 @@ static LocVar *getlocvar (FuncState *fs, int i) {
}
+/*
+** Start the scope for the last 'nvars' created variables.
+*/
static void adjustlocalvars (LexState *ls, int nvars) {
FuncState *fs = ls->fs;
fs->nactvar = cast_byte(fs->nactvar + nvars);
@@ -1357,7 +1358,6 @@ static void forbody (LexState *ls, int base, int line, int nvars, int kind) {
BlockCnt bl;
FuncState *fs = ls->fs;
int prep, endfor;
- adjustlocalvars(ls, 3); /* control variables */
checknext(ls, TK_DO);
prep = luaK_codeABx(fs, forprep[kind], base, 0);
enterblock(fs, &bl, 0); /* scope for declared variables */
@@ -1399,6 +1399,7 @@ static void fornum (LexState *ls, TString *varname, int line) {
luaK_int(fs, fs->freereg, 1);
luaK_reserveregs(fs, 1);
}
+ adjustlocalvars(ls, 3); /* control variables */
forbody(ls, base, line, 1, basicfor);
}
@@ -1407,7 +1408,7 @@ static void forlist (LexState *ls, TString *indexname) {
/* forlist -> NAME {,NAME} IN explist forbody */
FuncState *fs = ls->fs;
expdesc e;
- int nvars = 4; /* gen, state, control, plus at least one declared var */
+ int nvars = 5; /* gen, state, control, toclose, 'indexname' */
int line;
int base = fs->freereg;
/* create control variables */
@@ -1415,6 +1416,7 @@ static void forlist (LexState *ls, TString *indexname) {
new_localvarliteral(ls, "(for state)");
markupval(fs, fs->nactvar); /* state may create an upvalue */
new_localvarliteral(ls, "(for control)");
+ new_localvarliteral(ls, "(for toclose)");
/* create declared variables */
new_localvar(ls, indexname);
while (testnext(ls, ',')) {
@@ -1423,9 +1425,10 @@ static void forlist (LexState *ls, TString *indexname) {
}
checknext(ls, TK_IN);
line = ls->linenumber;
- adjust_assign(ls, 3, explist(ls, &e), &e);
+ adjust_assign(ls, 4, explist(ls, &e), &e);
+ adjustlocalvars(ls, 4); /* control variables */
luaK_checkstack(fs, 3); /* extra space to call generator */
- forbody(ls, base, line, nvars - 3, 2);
+ forbody(ls, base, line, nvars - 4, 2);
}
@@ -1575,9 +1578,9 @@ static void tocloselocalstat (LexState *ls) {
new_localvar(ls, str_checkname(ls));
checknext(ls, '=');
exp1(ls, 0);
- luaK_codeABC(fs, OP_TBC, fs->nactvar, 0, 0);
markupval(fs, fs->nactvar);
adjustlocalvars(ls, 1);
+ luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0);
}
diff --git a/lvm.c b/lvm.c
index 9977bda4f7..7d5ab9db85 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1654,11 +1654,11 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_TFORPREP) {
- /* is 'state' a function or has a '__close' metamethod? */
- if (ttisfunction(s2v(ra + 1)) ||
- !ttisnil(luaT_gettmbyobj(L, s2v(ra + 1), TM_CLOSE))) {
+ /* is 'toclose' a function or has a '__close' metamethod? */
+ if (ttisfunction(s2v(ra + 3)) ||
+ !ttisnil(luaT_gettmbyobj(L, s2v(ra + 3), TM_CLOSE))) {
/* create to-be-closed upvalue for it */
- halfProtect(luaF_newtbcupval(L, ra + 1));
+ halfProtect(luaF_newtbcupval(L, ra + 3));
}
pc += GETARG_Bx(i);
i = *(pc++); /* go to next instruction */
@@ -1668,13 +1668,14 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmcase(OP_TFORCALL) {
l_tforcall:
/* 'ra' has the iterator function, 'ra + 1' has the state,
- and 'ra + 2' has the control variable. The call will use
- the stack after these values (starting at 'ra + 3')
+ 'ra + 2' has the control variable, and 'ra + 3' has the
+ to-be-closed variable. The call will use the stack after
+ these values (starting at 'ra + 4')
*/
/* push function, state, and control variable */
- memcpy(ra + 3, ra, 3 * sizeof(*ra));
- L->top = ra + 6;
- Protect(luaD_call(L, ra + 3, GETARG_C(i))); /* do the call */
+ memcpy(ra + 4, ra, 3 * sizeof(*ra));
+ L->top = ra + 4 + 3;
+ Protect(luaD_call(L, ra + 4, GETARG_C(i))); /* do the call */
if (trap) { /* stack may have changed? */
updatebase(ci); /* keep 'base' correct */
ra = RA(i); /* keep 'ra' correct for next instruction */
@@ -1686,8 +1687,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
}
vmcase(OP_TFORLOOP) {
l_tforloop:
- if (!ttisnil(s2v(ra + 1))) { /* continue loop? */
- setobjs2s(L, ra, ra + 1); /* save control variable */
+ if (!ttisnil(s2v(ra + 2))) { /* continue loop? */
+ setobjs2s(L, ra, ra + 2); /* save control variable */
pc -= GETARG_Bx(i); /* jump back */
}
vmbreak;
diff --git a/testes/files.lua b/testes/files.lua
index e68eb9b865..34fcf85134 100644
--- a/testes/files.lua
+++ b/testes/files.lua
@@ -200,7 +200,7 @@ return x + y * z
assert(f:close())
f = coroutine.wrap(dofile)
assert(f(file) == 10)
-print(f(100, 101) == 20)
+assert(f(100, 101) == 20)
assert(f(200) == 100 + 200 * 101)
assert(os.remove(file))
@@ -422,6 +422,41 @@ assert(load(io.lines(file, "L"), nil, nil, t))()
assert(t.a == -((10 + 34) * 2))
+do -- testing closing file in line iteration
+
+ -- get the to-be-closed variable from a loop
+ local function gettoclose (lv)
+ lv = lv + 1
+ for i = 1, math.maxinteger do
+ local n, v = debug.getlocal(lv, i)
+ if n == "(for toclose)" then
+ return v
+ end
+ end
+ end
+
+ local f
+ for l in io.lines(file) do
+ f = gettoclose(1)
+ assert(io.type(f) == "file")
+ break
+ end
+ assert(io.type(f) == "closed file")
+
+ f = nil
+ local function foo (name)
+ for l in io.lines(name) do
+ f = gettoclose(1)
+ assert(io.type(f) == "file")
+ error(f) -- exit loop with an error
+ end
+ end
+ local st, msg = pcall(foo, file)
+ assert(st == false and io.type(msg) == "closed file")
+
+end
+
+
-- test for multipe arguments in 'lines'
io.output(file); io.write"0123456789\n":close()
for a,b in io.lines(file, 1, 1) do
diff --git a/testes/locals.lua b/testes/locals.lua
index 869ac1ff2e..28f88e5480 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -371,6 +371,8 @@ do
x = x - 1
if x > 0 then return x end
end,
+ nil, -- state
+ nil, -- control variable
function () -- closing function
numopen = numopen - 1
end
@@ -392,12 +394,16 @@ do
-- repeat test with '__open' metamethod instead of a function
local function open (x)
numopen = numopen + 1
+ local state = setmetatable({x},
+ {__close = function () numopen = numopen - 1 end})
return
function (t) -- iteraction function
t[1] = t[1] - 1
if t[1] > 0 then return t[1] end
end,
- setmetatable({x}, {__close = function () numopen = numopen - 1 end})
+ state,
+ nil,
+ state -- to-be-closed
end
local s = 0
From 9eafe9c053ef17a0980ab32082bf229bd58e963b Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 9 Nov 2018 17:03:52 -0200
Subject: [PATCH 095/889] New implementation for 'luaL_addvalue'
The function 'luaL_addvalue' (from the buffer system) was rewritten
so that it does not change the position of the box (if present)
in the stack.
---
lauxlib.c | 81 ++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 59 insertions(+), 22 deletions(-)
diff --git a/lauxlib.c b/lauxlib.c
index 78dfb4e952..68842a9855 100644
--- a/lauxlib.c
+++ b/lauxlib.c
@@ -27,6 +27,12 @@
#include "lauxlib.h"
+#if !defined(MAX_SIZET)
+/* maximum value for size_t */
+#define MAX_SIZET ((size_t)(~(size_t)0))
+#endif
+
+
/*
** {======================================================
** Traceback
@@ -501,34 +507,56 @@ static void *newbox (lua_State *L, size_t newsize) {
/*
-** returns a pointer to a free area with at least 'sz' bytes
+** Compute new size for buffer 'B', enough to accommodate extra 'sz'
+** bytes.
*/
-LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) {
- lua_State *L = B->L;
- if (B->size - B->n < sz) { /* not enough space? */
+static size_t newbuffsize (luaL_Buffer *B, size_t sz) {
+ size_t newsize = B->size * 2; /* double buffer size */
+ if (MAX_SIZET - sz < B->n) /* overflow in (B->n + sz)? */
+ return luaL_error(B->L, "buffer too large");
+ if (newsize < B->n + sz) /* double is not big enough? */
+ newsize = B->n + sz;
+ return newsize;
+}
+
+
+/*
+** Returns a pointer to a free area with at least 'sz' bytes in buffer
+** 'B'. 'boxidx' is the position in the stack where the buffer's box is
+** or should be.
+*/
+static char *prepbuffsize (luaL_Buffer *B, size_t sz, int boxidx) {
+ if (B->size - B->n >= sz) /* enough space? */
+ return B->b + B->n;
+ else {
+ lua_State *L = B->L;
char *newbuff;
- size_t newsize = B->size * 2; /* double buffer size */
- if (newsize - B->n < sz) /* not big enough? */
- newsize = B->n + sz;
- if (newsize < B->n || newsize - B->n < sz)
- luaL_error(L, "buffer too large");
+ size_t newsize = newbuffsize(B, sz);
/* create larger buffer */
- if (buffonstack(B))
- newbuff = (char *)resizebox(L, -1, newsize);
- else { /* no buffer yet */
- newbuff = (char *)newbox(L, newsize);
+ if (buffonstack(B)) /* buffer already has a box? */
+ newbuff = (char *)resizebox(L, boxidx, newsize); /* resize it */
+ else { /* no box yet */
+ newbuff = (char *)newbox(L, newsize); /* create a new box */
memcpy(newbuff, B->b, B->n * sizeof(char)); /* copy original content */
+ lua_insert(L, boxidx); /* move box to its intended position */
}
B->b = newbuff;
B->size = newsize;
+ return newbuff + B->n;
}
- return &B->b[B->n];
+}
+
+/*
+** returns a pointer to a free area with at least 'sz' bytes
+*/
+LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) {
+ return prepbuffsize(B, sz, -1);
}
LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) {
if (l > 0) { /* avoid 'memcpy' when 's' can be NULL */
- char *b = luaL_prepbuffsize(B, l);
+ char *b = prepbuffsize(B, l, -1);
memcpy(b, s, l * sizeof(char));
luaL_addsize(B, l);
}
@@ -556,14 +584,23 @@ LUALIB_API void luaL_pushresultsize (luaL_Buffer *B, size_t sz) {
}
+/*
+** 'luaL_addvalue' is the only function in the Buffer system where the
+** box (if existent) is not on the top of the stack. So, instead of
+** calling 'luaL_addlstring', it replicates the code using -2 as the
+** last argument to 'prepbuffsize', signaling that the box is (or will
+** be) bellow the string being added to the buffer. (Box creation can
+** trigger an emergency GC, so we should not remove the string from the
+** stack before we have the space guaranteed.)
+*/
LUALIB_API void luaL_addvalue (luaL_Buffer *B) {
lua_State *L = B->L;
- size_t l;
- const char *s = lua_tolstring(L, -1, &l);
- if (buffonstack(B))
- lua_insert(L, -2); /* put value below buffer */
- luaL_addlstring(B, s, l);
- lua_remove(L, (buffonstack(B)) ? -2 : -1); /* remove value */
+ size_t len;
+ const char *s = lua_tolstring(L, -1, &len);
+ char *b = prepbuffsize(B, len, -2);
+ memcpy(b, s, len * sizeof(char));
+ luaL_addsize(B, len);
+ lua_pop(L, 1); /* pop string */
}
@@ -577,7 +614,7 @@ LUALIB_API void luaL_buffinit (lua_State *L, luaL_Buffer *B) {
LUALIB_API char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz) {
luaL_buffinit(L, B);
- return luaL_prepbuffsize(B, sz);
+ return prepbuffsize(B, sz, -1);
}
/* }====================================================== */
From 5fda30b4f9f82b901113a6e666c797f835c708eb Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 12 Nov 2018 14:15:50 -0200
Subject: [PATCH 096/889] 'lua_toclose' gets the index to be closed as an
argument
Sometimes it is useful to mark to-be-closed an index that is not
at the top of the stack (e.g., if the value to be closed came from
a function call returning multiple values).
---
lapi.c | 13 ++++++++++---
ltests.c | 2 +-
lua.h | 2 +-
testes/api.lua | 18 ++++++++++--------
4 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/lapi.c b/lapi.c
index 91a6e38916..da866a660b 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1207,12 +1207,19 @@ LUA_API int lua_next (lua_State *L, int idx) {
}
-LUA_API void lua_toclose (lua_State *L) {
- int nresults = L->ci->nresults;
- luaF_newtbcupval(L, L->top - 1); /* create new to-be-closed upvalue */
+LUA_API void lua_toclose (lua_State *L, int idx) {
+ int nresults;
+ StkId o;
+ lua_lock(L);
+ o = index2stack(L, idx);
+ nresults = L->ci->nresults;
+ api_check(L, L->openupval == NULL || uplevel(L->openupval) < o,
+ "there is an already marked index below");
+ luaF_newtbcupval(L, o); /* create new to-be-closed upvalue */
if (!hastocloseCfunc(nresults)) /* function not marked yet? */
L->ci->nresults = codeNresults(nresults); /* mark it */
lua_assert(hastocloseCfunc(L->ci->nresults));
+ lua_unlock(L);
}
diff --git a/ltests.c b/ltests.c
index 192ae86193..c5c1040ad6 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1551,7 +1551,7 @@ static struct X { int x; } x;
return lua_yieldk(L1, nres, i, Cfunck);
}
else if EQ("toclose") {
- lua_toclose(L);
+ lua_toclose(L, getnum);
}
else luaL_error(L, "unknown instruction %s", buff);
}
diff --git a/lua.h b/lua.h
index fe468c8ef4..6aa184d18b 100644
--- a/lua.h
+++ b/lua.h
@@ -333,7 +333,7 @@ LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s);
LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud);
LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
-LUA_API void (lua_toclose) (lua_State *L);
+LUA_API void (lua_toclose) (lua_State *L, int idx);
/*
diff --git a/testes/api.lua b/testes/api.lua
index a6ddca8e3c..ed857fd055 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -985,18 +985,20 @@ do
return x
end
- local a = T.testC([[
+ local a, b = T.testC([[
call 0 1 # create resource
- toclose # mark it to be closed
- return 1
+ pushint 34
+ toclose -2 # mark call result to be closed
+ toclose -1 # mark number to be closed (will be ignored)
+ return 2
]], newresource)
- assert(a[1] == 11)
+ assert(a[1] == 11 and b == 34)
assert(#openresource == 0) -- was closed
-- repeat the test, but calling function in a 'multret' context
local a = {T.testC([[
call 0 1 # create resource
- toclose # mark it to be closed
+ toclose 2 # mark it to be closed
return 2
]], newresource)}
assert(type(a[1]) == "string" and a[2][1] == 11)
@@ -1005,7 +1007,7 @@ do
-- error
local a, b = pcall(T.testC, [[
call 0 1 # create resource
- toclose # mark it to be closed
+ toclose -1 # mark it to be closed
error # resource is the error object
]], newresource)
assert(a == false and b[1] == 11)
@@ -1019,10 +1021,10 @@ do
local a = T.testC([[
pushvalue 2
call 0 1 # create resource
- toclose # mark it to be closed
+ toclose -1 # mark it to be closed
pushvalue 2
call 0 1 # create another resource
- toclose # mark it to be closed
+ toclose -1 # mark it to be closed
pushvalue 3
pushint 2 # there should be two open resources
call 1 0
From 8cb84210ab95e882c01363a6794508ec923ade90 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 13 Nov 2018 13:50:33 -0200
Subject: [PATCH 097/889] String buffer using to-be-closed variable
The string buffers in the C API now mark their boxes as to-be-closed
variables, to release their buffers in case of errors.
---
lauxlib.c | 26 +++++++++++++++-----------
testes/locals.lua | 32 ++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+), 11 deletions(-)
diff --git a/lauxlib.c b/lauxlib.c
index 68842a9855..6069ed1b24 100644
--- a/lauxlib.c
+++ b/lauxlib.c
@@ -470,10 +470,8 @@ static void *resizebox (lua_State *L, int idx, size_t newsize) {
lua_Alloc allocf = lua_getallocf(L, &ud);
UBox *box = (UBox *)lua_touserdata(L, idx);
void *temp = allocf(ud, box->box, box->bsize, newsize);
- if (temp == NULL && newsize > 0) { /* allocation error? */
- resizebox(L, idx, 0); /* free buffer */
+ if (temp == NULL && newsize > 0) /* allocation error? */
luaL_error(L, "not enough memory for buffer allocation");
- }
box->box = temp;
box->bsize = newsize;
return temp;
@@ -486,16 +484,20 @@ static int boxgc (lua_State *L) {
}
-static void *newbox (lua_State *L, size_t newsize) {
+static const luaL_Reg boxmt[] = { /* box metamethods */
+ {"__gc", boxgc},
+ {"__close", boxgc},
+ {NULL, NULL}
+};
+
+
+static void newbox (lua_State *L) {
UBox *box = (UBox *)lua_newuserdatauv(L, sizeof(UBox), 0);
box->box = NULL;
box->bsize = 0;
- if (luaL_newmetatable(L, "_UBOX*")) { /* creating metatable? */
- lua_pushcfunction(L, boxgc);
- lua_setfield(L, -2, "__gc"); /* metatable.__gc = boxgc */
- }
+ if (luaL_newmetatable(L, "_UBOX*")) /* creating metatable? */
+ luaL_setfuncs(L, boxmt, 0); /* set its metamethods */
lua_setmetatable(L, -2);
- return resizebox(L, -1, newsize);
}
@@ -536,9 +538,11 @@ static char *prepbuffsize (luaL_Buffer *B, size_t sz, int boxidx) {
if (buffonstack(B)) /* buffer already has a box? */
newbuff = (char *)resizebox(L, boxidx, newsize); /* resize it */
else { /* no box yet */
- newbuff = (char *)newbox(L, newsize); /* create a new box */
- memcpy(newbuff, B->b, B->n * sizeof(char)); /* copy original content */
+ newbox(L); /* create a new box */
lua_insert(L, boxidx); /* move box to its intended position */
+ lua_toclose(L, boxidx);
+ newbuff = (char *)resizebox(L, boxidx, newsize);
+ memcpy(newbuff, B->b, B->n * sizeof(char)); /* copy original content */
}
B->b = newbuff;
B->size = newsize;
diff --git a/testes/locals.lua b/testes/locals.lua
index 28f88e5480..8766b3d5ce 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -324,6 +324,38 @@ if rawget(_G, "T") then
local _, msg = pcall(test)
assert(msg == 1000)
+
+ do -- testing 'toclose' in C string buffer
+ local s = string.rep("a", 10000)
+ local a = {s, s}
+
+ -- ensure proper initialization (stack space, metatable)
+ table.concat(a)
+ collectgarbage(); collectgarbage()
+
+ local m = T.totalmem()
+
+ -- error in the second buffer allocation
+ T.alloccount(3)
+ assert(not pcall(table.concat, a))
+ T.alloccount()
+ -- first buffer was released by 'toclose'
+ assert(T.totalmem() - m <= 5000)
+
+ -- error in creation of final string
+ T.alloccount(4)
+ assert(not pcall(table.concat, a))
+ T.alloccount()
+ -- second buffer was released by 'toclose'
+ assert(T.totalmem() - m <= 5000)
+
+ -- userdata, upvalue, buffer, buffer, string
+ T.alloccount(5)
+ assert(#table.concat(a) == 20000)
+ T.alloccount()
+
+ print'+'
+ end
end
From d40cd315f50538e9dbd4362a4763c01527f4eb3a Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 13 Nov 2018 13:58:46 -0200
Subject: [PATCH 098/889] Visibility of non-API functions changed to "internal"
The visibility for functions marked as LUAI_FUNC was changed from
"hidden" to "internal". These functions cannot be called from
outside the Lua kernel, and "internal" visibility offers more
chances for optimizations.
---
luaconf.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/luaconf.h b/luaconf.h
index 126257dce3..ff7085131a 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -277,7 +277,7 @@
*/
#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \
defined(__ELF__) /* { */
-#define LUAI_FUNC __attribute__((visibility("hidden"))) extern
+#define LUAI_FUNC __attribute__((visibility("internal"))) extern
#else /* }{ */
#define LUAI_FUNC extern
#endif /* } */
From bb0185b196773b878f0bf6b6c6a4a01dbf8c2b83 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 13 Nov 2018 14:33:14 -0200
Subject: [PATCH 099/889] Documentation for to-be-closed variables
---
manual/manual.of | 148 ++++++++++++++++++++++++++++++++++++++---------
1 file changed, 122 insertions(+), 26 deletions(-)
diff --git a/manual/manual.of b/manual/manual.of
index 91ba8c46cf..f891c33e94 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1448,7 +1448,9 @@ for @rep{var_1}, @Cdots, @rep{var_n} in @rep{explist} do @rep{block} end
is equivalent to the code:
@verbatim{
do
- local @rep{f}, @rep{s}, @rep{var} = @rep{explist}
+ local @rep{f}, @rep{s}, @rep{var}
+ local *toclose @rep{tbc} = nil
+ @rep{f}, @rep{s}, @rep{var}, @rep{tbc} = @rep{explist}
while true do
local @rep{var_1}, @Cdots, @rep{var_n} = @rep{f}(@rep{s}, @rep{var})
if @rep{var_1} == nil then break end
@@ -1464,11 +1466,14 @@ Note the following:
@T{@rep{explist}} is evaluated only once.
Its results are an @emph{iterator} function,
a @emph{state},
-and an initial value for the first @emph{iterator variable}.
+an initial value for the first @emph{iterator variable},
+and a to-be-closed variable @see{to-be-closed},
+which can be used to release resources when the loop ends.
}
@item{
-@T{@rep{f}}, @T{@rep{s}}, and @T{@rep{var}} are invisible variables.
+@T{@rep{f}}, @T{@rep{s}}, @T{@rep{var}}, and @T{@rep{tbc}}
+are invisible variables.
The names are here for explanatory purposes only.
}
@@ -1515,6 +1520,52 @@ The visibility rules for local variables are explained in @See{visibility}.
}
+@sect3{to-be-closed| @title{To-be-closed Variables}
+
+A local variable can be declared as a @def{to-be-closed} variable,
+with the following syntax:
+@Produc{
+@producname{stat}@producbody{
+ @Rw{local} @bnfter{*} @bnfter{toclose} Name @bnfter{=} exp
+}}
+A to-be-closed variable behaves like a normal local variable,
+except that its value is @emph{closed} whenever the variable
+goes out of scope, including normal block termination,
+exiting its block by @Rw{break}/@Rw{goto}/@Rw{return},
+or exiting by an error.
+If a block ends in a tail call @see{functioncall},
+all variables of the caller function go out of scope
+before the start of the callee function.
+
+To \emph{close} a value has the following meaning here:
+If the value of the variable when it goes out of scope is a function,
+that function is called;
+otherwise, if the value has a @idx{__close} metamethod,
+that metamethod is called;
+otherwise, nothing is done.
+In the function case,
+if the scope is being closed by an error,
+the error object is passed as an argument to the function;
+if there is no error, the function gets @nil.
+In the metamethod case,
+the value itself always is passed as an argument to the metamethod.
+
+If several to-be-closed variables go out of scope at the same event,
+they are closed in the reverse order that they were declared.
+If there is any error while running a closing function,
+that error is handled like an error in the regular code
+where the variable was defined;
+in particular,
+the other pending closing functions will still be called.
+
+If a coroutine yields inside a block and is never resumed again,
+the variables visible at that block will never go out of scope,
+and therefore they will not be closed.
+Similarly, if a script is interrupted by an unprotected error,
+its to-be-closed variables will not be closed.
+
+}
+
}
@sect2{expressions| @title{Expressions}
@@ -2442,7 +2493,7 @@ the result of @Lid{lua_newthread}),
it should use them only in API calls that cannot raise errors.
The panic function runs as if it were a @x{message handler} @see{error};
-in particular, the error object is at the top of the stack.
+in particular, the error object is on the top of the stack.
However, there is no guarantee about stack space.
To push anything on the stack,
the panic function must first check the available space @see{stacksize}.
@@ -2593,9 +2644,9 @@ tells whether the function may raise errors:
@Char{m} means the function may raise out-of-memory errors
and errors running a finalizer;
@Char{v} means the function may raise the errors explained in the text;
-@Char{e} means the function may raise any errors
-(because it can run arbitrary Lua code,
-either directly or through metamethods).
+@Char{e} means the function can run arbitrary Lua code,
+either directly or through metamethods,
+and therefore may raise any errors.
@APIEntry{int lua_absindex (lua_State *L, int idx);|
@@ -2675,7 +2726,7 @@ that @T{free(NULL)} has no effect and that
Performs an arithmetic or bitwise operation over the two values
(or one, in the case of negations)
at the top of the stack,
-with the value at the top being the second operand,
+with the value on the top being the second operand,
pops these values, and pushes the result of the operation.
The function follows the semantics of the corresponding Lua operator
(that is, it may call metamethods).
@@ -2875,7 +2926,7 @@ The value of @id{op} must be one of the following constants:
@apii{n,1,e}
Concatenates the @id{n} values at the top of the stack,
-pops them, and leaves the result at the top.
+pops them, and leaves the result on the top.
If @N{@T{n} is 1}, the result is the single value on the stack
(that is, the function does nothing);
if @id{n} is 0, the result is the empty string.
@@ -2942,7 +2993,7 @@ This function does not pop the Lua function from the stack.
@apii{1,0,v}
Generates a Lua error,
-using the value at the top of the stack as the error object.
+using the value on the top of the stack as the error object.
This function does a long jump,
and therefore never returns
@seeC{luaL_error}.
@@ -3081,7 +3132,7 @@ the function @N{returns 0} and pushes nothing on the stack.
Pushes onto the stack the value @T{t[k]},
where @id{t} is the value at the given index
-and @id{k} is the value at the top of the stack.
+and @id{k} is the value on the top of the stack.
This function pops the key from the stack,
pushing the resulting value in its place.
@@ -3798,7 +3849,7 @@ Similar to @Lid{lua_settable}, but does a raw assignment
Does the equivalent of @T{t[i] = v},
where @id{t} is the table at the given index
-and @id{v} is the value at the top of the stack.
+and @id{v} is the value on the top of the stack.
This function pops the value from the stack.
The assignment is raw,
@@ -3812,7 +3863,7 @@ that is, it does not invoke the @idx{__newindex} metamethod.
Does the equivalent of @T{t[p] = v},
where @id{t} is the table at the given index,
@id{p} is encoded as a light userdata,
-and @id{v} is the value at the top of the stack.
+and @id{v} is the value on the top of the stack.
This function pops the value from the stack.
The assignment is raw,
@@ -3939,7 +3990,7 @@ with user data @id{ud}.
Does the equivalent to @T{t[k] = v},
where @id{t} is the value at the given index
-and @id{v} is the value at the top of the stack.
+and @id{v} is the value on the top of the stack.
This function pops the value from the stack.
As in Lua, this function may trigger a metamethod
@@ -3960,7 +4011,7 @@ sets it as the new value of global @id{name}.
Does the equivalent to @T{t[n] = v},
where @id{t} is the value at the given index
-and @id{v} is the value at the top of the stack.
+and @id{v} is the value on the top of the stack.
This function pops the value from the stack.
As in Lua, this function may trigger a metamethod
@@ -3981,7 +4032,7 @@ sets it as the new metatable for the value at the given index.
Does the equivalent to @T{t[k] = v},
where @id{t} is the value at the given index,
-@id{v} is the value at the top of the stack,
+@id{v} is the value on the top of the stack,
and @id{k} is the value just below the top.
This function pops both the key and the value from the stack.
@@ -4082,6 +4133,31 @@ otherwise, returns @id{NULL}.
}
+@APIEntry{void lua_toclose (lua_State *L, int index);|
+@apii{0,0,v}
+
+Marks the given index in the stack as a
+to-be-closed @Q{variable} @see{to-be-closed}.
+Like a to-be-closed variable in Lua,
+the value at that index in the stack will be closed
+when it goes out of scope.
+Here, in the context of a C function,
+to go out of scope means that the running function returns (to Lua),
+there is an error,
+or the index is removed from the stack through
+@Lid{lua_settop} or @Lid{lua_pop}.
+An index marked as to-be-closed should not be removed from the stack
+by any other function in the API except @Lid{lua_settop} or @Lid{lua_pop}.
+
+This function should not be called for an index
+that is equal to or below an already marked to-be-closed index.
+
+This function can raise an out-of-memory error.
+In that case, the value in the given index is immediately closed,
+as if it was already marked.
+
+}
+
@APIEntry{lua_Integer lua_tointeger (lua_State *L, int index);|
@apii{0,0,-}
@@ -4587,7 +4663,7 @@ and names.
and returns its name.
In the second case, @id{ar} must be @id{NULL} and the function
-to be inspected must be at the top of the stack.
+to be inspected must be on the top of the stack.
In this case, only parameters of Lua functions are visible
(as there is no information about what variables are active)
and no values are pushed onto the stack.
@@ -4720,7 +4796,7 @@ A hook is disabled by setting @id{mask} to zero.
@apii{0|1,0,-}
Sets the value of a local variable of a given activation record.
-It assigns the value at the top of the stack
+It assigns the value on the top of the stack
to the variable and returns its name.
It also pops the value from the stack.
@@ -4736,7 +4812,7 @@ Parameters @id{ar} and @id{n} are as in function @Lid{lua_getlocal}.
@apii{0|1,0,-}
Sets the value of a closure's upvalue.
-It assigns the value at the top of the stack
+It assigns the value on the top of the stack
to the upvalue and returns its name.
It also pops the value from the stack.
@@ -4861,7 +4937,7 @@ to the buffer @id{B}
@APIEntry{void luaL_addvalue (luaL_Buffer *B);|
@apii{1,?,m}
-Adds the value at the top of the stack
+Adds the value on the top of the stack
to the buffer @id{B}
@seeC{luaL_Buffer}.
Pops the value.
@@ -5486,7 +5562,7 @@ Equivalent to the sequence @Lid{luaL_addsize}, @Lid{luaL_pushresult}.
Creates and returns a @def{reference},
in the table at index @id{t},
-for the object at the top of the stack (and pops the object).
+for the object on the top of the stack (and pops the object).
A reference is a unique integer key.
As long as you do not manually add integer keys into table @id{t},
@@ -5495,7 +5571,7 @@ You can retrieve an object referred by reference @id{r}
by calling @T{lua_rawgeti(L, t, r)}.
Function @Lid{luaL_unref} frees a reference and its associated object.
-If the object at the top of the stack is @nil,
+If the object on the top of the stack is @nil,
@Lid{luaL_ref} returns the constant @defid{LUA_REFNIL}.
The constant @defid{LUA_NOREF} is guaranteed to be different
from any reference returned by @Lid{luaL_ref}.
@@ -5554,7 +5630,7 @@ These values are popped from the stack after the registration.
@APIEntry{void luaL_setmetatable (lua_State *L, const char *tname);|
@apii{0,0,-}
-Sets the metatable of the object at the top of the stack
+Sets the metatable of the object on the top of the stack
as the metatable associated with name @id{tname}
in the registry @seeC{luaL_newmetatable}.
@@ -7571,6 +7647,9 @@ The table @id{io} also provides
three predefined file handles with their usual meanings from C:
@defid{io.stdin}, @defid{io.stdout}, and @defid{io.stderr}.
The I/O library never closes these files.
+The metatable for file handles provides metamethods
+for @idx{__gc} and @idx{__close} that try
+to close the file when called.
Unless otherwise stated,
all I/O functions return @nil on failure
@@ -7617,6 +7696,13 @@ and returns an iterator function that
works like @T{file:lines(@Cdots)} over the opened file.
When the iterator function detects the end of file,
it returns no values (to finish the loop) and automatically closes the file.
+Besides the iterator function,
+@id{io.lines} returns three other values:
+two @nil values as placeholders,
+plus the created file handle.
+Therefore, when used in a generic @Rw{for} loop,
+the file is closed also if the loop is interrupted by an
+error or a @Rw{break}.
The call @T{io.lines()} (with no file name) is equivalent
to @T{io.input():lines("l")};
@@ -8543,6 +8629,13 @@ now starts with a somewhat random seed.
Moreover, it uses a different algorithm.
}
+@item{
+The function @Lid{io.lines} now returns three extra values,
+besides the iterator function.
+You can enclose the call in parentheses if you need to
+discard these extra results.
+}
+
}
}
@@ -8559,15 +8652,17 @@ replaced by @Lid{lua_newuserdatauv},
@Lid{lua_setiuservalue}, and @Lid{lua_getiuservalue},
which have an extra argument.
-(For compatibility, the old names still work as macros assuming
-one single user value.)
+For compatibility, the old names still work as macros assuming
+one single user value.
+Note, however, that the call @T{lua_newuserdatauv(L,size,0)}
+produces a smaller userdata.
}
@item{
The function @Lid{lua_resume} has an extra parameter.
This out parameter returns the number of values on
the top of the stack that were yielded or returned by the coroutine.
-(In older versions,
+(In previous versions,
those values were the entire stack.)
}
@@ -8626,6 +8721,7 @@ and @bnfNter{LiteralString}, see @See{lexical}.)
@OrNL @Rw{function} funcname funcbody
@OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody
@OrNL @Rw{local} namelist @bnfopt{@bnfter{=} explist}
+@OrNL @Rw{local} @bnfter{*} @bnfter{toclose} Name @bnfter{=} exp
}
@producname{retstat}@producbody{@Rw{return}
From 35296e1fde705b3ac8356a4f4ce426497cf7b64c Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 22 Nov 2018 13:56:04 -0200
Subject: [PATCH 100/889] Details
comments and other janitorial work.
---
lparser.c | 118 +++++++++++++++++++++++++++++++++++++++---------------
lparser.h | 2 +-
lvm.c | 12 +++---
3 files changed, 93 insertions(+), 39 deletions(-)
diff --git a/lparser.c b/lparser.c
index afc5aeab61..e525e57816 100644
--- a/lparser.c
+++ b/lparser.c
@@ -10,6 +10,7 @@
#include "lprefix.h"
+#include
#include
#include "lua.h"
@@ -87,6 +88,9 @@ static void checklimit (FuncState *fs, int v, int l, const char *what) {
}
+/*
+** Test whether next token is 'c'; if so, skip it.
+*/
static int testnext (LexState *ls, int c) {
if (ls->t.token == c) {
luaX_next(ls);
@@ -96,12 +100,18 @@ static int testnext (LexState *ls, int c) {
}
+/*
+** Check that next token is 'c'.
+*/
static void check (LexState *ls, int c) {
if (ls->t.token != c)
error_expected(ls, c);
}
+/*
+** Check that next token is 'c' and skip it.
+*/
static void checknext (LexState *ls, int c) {
check(ls, c);
luaX_next(ls);
@@ -111,11 +121,15 @@ static void checknext (LexState *ls, int c) {
#define check_condition(ls,c,msg) { if (!(c)) luaX_syntaxerror(ls, msg); }
-
+/*
+** Check that next token is 'what' and skip it. In case of error,
+** raise an error that the expected 'what' should match a 'who'
+** in line 'where' (if that is not the current line).
+*/
static void check_match (LexState *ls, int what, int who, int where) {
if (unlikely(!testnext(ls, what))) {
- if (where == ls->linenumber)
- error_expected(ls, what);
+ if (where == ls->linenumber) /* all in the same line? */
+ error_expected(ls, what); /* do not need a complex message */
else {
luaX_syntaxerror(ls, luaO_pushfstring(ls->L,
"%s expected (to close %s at line %d)",
@@ -146,11 +160,15 @@ static void codestring (LexState *ls, expdesc *e, TString *s) {
}
-static void checkname (LexState *ls, expdesc *e) {
+static void codename (LexState *ls, expdesc *e) {
codestring(ls, e, str_checkname(ls));
}
+/*
+** Register a new local variable in the active 'Proto' (for debug
+** information).
+*/
static int registerlocalvar (LexState *ls, TString *varname) {
FuncState *fs = ls->fs;
Proto *f = fs->f;
@@ -183,6 +201,9 @@ static void new_localvar (LexState *ls, TString *name) {
new_localvar(ls, luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1));
+/*
+** Get the debug-information entry for current variable 'i'.
+*/
static LocVar *getlocvar (FuncState *fs, int i) {
int idx = fs->ls->dyd->actvar.arr[fs->firstlocal + i].idx;
lua_assert(idx < fs->nlocvars);
@@ -192,6 +213,7 @@ static LocVar *getlocvar (FuncState *fs, int i) {
/*
** Start the scope for the last 'nvars' created variables.
+** (debug info.)
*/
static void adjustlocalvars (LexState *ls, int nvars) {
FuncState *fs = ls->fs;
@@ -202,6 +224,10 @@ static void adjustlocalvars (LexState *ls, int nvars) {
}
+/*
+** Close the scope for all variables up to level 'tolevel'.
+** (debug info.)
+*/
static void removevars (FuncState *fs, int tolevel) {
fs->ls->dyd->actvar.n -= (fs->nactvar - tolevel);
while (fs->nactvar > tolevel)
@@ -209,6 +235,10 @@ static void removevars (FuncState *fs, int tolevel) {
}
+/*
+** Search the upvalues of the function 'fs' for one
+** with the given 'name'.
+*/
static int searchupvalue (FuncState *fs, TString *name) {
int i;
Upvaldesc *up = fs->f->upvalues;
@@ -235,6 +265,10 @@ static int newupvalue (FuncState *fs, TString *name, expdesc *v) {
}
+/*
+** Look for an active local variable with the name 'n' in the
+** function 'fs'.
+*/
static int searchvar (FuncState *fs, TString *n) {
int i;
for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) {
@@ -246,8 +280,8 @@ static int searchvar (FuncState *fs, TString *n) {
/*
- Mark block where variable at given level was defined
- (to emit close instructions later).
+** Mark block where variable at given level was defined
+** (to emit close instructions later).
*/
static void markupval (FuncState *fs, int level) {
BlockCnt *bl = fs->bl;
@@ -259,8 +293,9 @@ static void markupval (FuncState *fs, int level) {
/*
- Find variable with given name 'n'. If it is an upvalue, add this
- upvalue into all intermediate functions.
+** Find a variable with the given name 'n'. If it is an upvalue, add
+** this upvalue into all intermediate functions. If it is a global, set
+** 'var' as 'void' as a flag.
*/
static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
if (fs == NULL) /* no more levels? */
@@ -287,6 +322,10 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
}
+/*
+** Find a variable with the given name 'n', handling global variables
+** too.
+*/
static void singlevar (LexState *ls, expdesc *var) {
TString *varname = str_checkname(ls);
FuncState *fs = ls->fs;
@@ -301,25 +340,29 @@ static void singlevar (LexState *ls, expdesc *var) {
}
+/*
+** Adjust the number of results from an expression list 'e' with 'nexps'
+** expressions to 'nvars' values.
+*/
static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) {
FuncState *fs = ls->fs;
- int extra = nvars - nexps;
- if (hasmultret(e->k)) {
- extra++; /* includes call itself */
- if (extra < 0) extra = 0;
+ int needed = nvars - nexps; /* extra values needed */
+ if (hasmultret(e->k)) { /* last expression has multiple returns? */
+ int extra = needed + 1; /* discount last expression itself */
+ if (extra < 0)
+ extra = 0;
luaK_setreturns(fs, e, extra); /* last exp. provides the difference */
- if (extra > 1) luaK_reserveregs(fs, extra-1);
}
else {
- if (e->k != VVOID) luaK_exp2nextreg(fs, e); /* close last expression */
- if (extra > 0) {
- int reg = fs->freereg;
- luaK_reserveregs(fs, extra);
- luaK_nil(fs, reg, extra);
- }
+ if (e->k != VVOID) /* at least one expression? */
+ luaK_exp2nextreg(fs, e); /* close last expression */
+ if (needed > 0) /* missing values? */
+ luaK_nil(fs, fs->freereg, needed); /* complete with nils */
}
- if (nexps > nvars)
- ls->fs->freereg -= nexps - nvars; /* remove extra values */
+ if (needed > 0)
+ luaK_reserveregs(fs, needed); /* registers for extra values */
+ else /* adding 'needed' is actually a subtraction */
+ fs->freereg += needed; /* remove extra values */
}
@@ -632,7 +675,7 @@ static void fieldsel (LexState *ls, expdesc *v) {
expdesc key;
luaK_exp2anyregup(fs, v);
luaX_next(ls); /* skip the dot or colon */
- checkname(ls, &key);
+ codename(ls, &key);
luaK_indexed(fs, v, &key);
}
@@ -669,7 +712,7 @@ static void recfield (LexState *ls, struct ConsControl *cc) {
expdesc tab, key, val;
if (ls->t.token == TK_NAME) {
checklimit(fs, cc->nh, MAX_INT, "items in a constructor");
- checkname(ls, &key);
+ codename(ls, &key);
}
else /* ls->t.token == '[' */
yindex(ls, &key);
@@ -938,7 +981,7 @@ static void suffixedexp (LexState *ls, expdesc *v) {
case ':': { /* ':' NAME funcargs */
expdesc key;
luaX_next(ls);
- checkname(ls, &key);
+ codename(ls, &key);
luaK_self(fs, v, &key);
funcargs(ls, v, line);
break;
@@ -1048,6 +1091,9 @@ static BinOpr getbinopr (int op) {
}
+/*
+** Priority table for binary operators.
+*/
static const struct {
lu_byte left; /* left priority for each binary operator */
lu_byte right; /* right priority */
@@ -1076,9 +1122,9 @@ static BinOpr subexpr (LexState *ls, expdesc *v, int limit) {
UnOpr uop;
enterlevel(ls);
uop = getunopr(ls->t.token);
- if (uop != OPR_NOUNOPR) {
+ if (uop != OPR_NOUNOPR) { /* prefix (unary) operator? */
int line = ls->linenumber;
- luaX_next(ls);
+ luaX_next(ls); /* skip operator */
subexpr(ls, v, UNARY_PRIORITY);
luaK_prefix(ls->fs, uop, v, line);
}
@@ -1089,7 +1135,7 @@ static BinOpr subexpr (LexState *ls, expdesc *v, int limit) {
expdesc v2;
BinOpr nextop;
int line = ls->linenumber;
- luaX_next(ls);
+ luaX_next(ls); /* skip operator */
luaK_infix(ls->fs, op, v);
/* read sub-expression with higher priority */
nextop = subexpr(ls, &v2, priority[op].right);
@@ -1177,21 +1223,27 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) {
}
}
-
-static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) {
+/*
+** Parse and compile a mulitple assignment. The first "variable"
+** (a 'suffixedexp') was already read by the caller.
+**
+** assignment -> suffixedexp restassign
+** restassign -> ',' suffixedexp restassign | '=' explist
+*/
+static void restassign (LexState *ls, struct LHS_assign *lh, int nvars) {
expdesc e;
check_condition(ls, vkisvar(lh->v.k), "syntax error");
- if (testnext(ls, ',')) { /* assignment -> ',' suffixedexp assignment */
+ if (testnext(ls, ',')) { /* restassign -> ',' suffixedexp restassign */
struct LHS_assign nv;
nv.prev = lh;
suffixedexp(ls, &nv.v);
if (!vkisindexed(nv.v.k))
check_conflict(ls, lh, &nv.v);
enterlevel(ls); /* control recursion depth */
- assignment(ls, &nv, nvars+1);
+ restassign(ls, &nv, nvars+1);
leavelevel(ls);
}
- else { /* assignment -> '=' explist */
+ else { /* restassign -> '=' explist */
int nexps;
checknext(ls, '=');
nexps = explist(ls, &e);
@@ -1627,7 +1679,7 @@ static void exprstat (LexState *ls) {
suffixedexp(ls, &v.v);
if (ls->t.token == '=' || ls->t.token == ',') { /* stat -> assignment ? */
v.prev = NULL;
- assignment(ls, &v, 1);
+ restassign(ls, &v, 1);
}
else { /* stat -> func */
Instruction *inst = &getinstruction(fs, &v.v);
diff --git a/lparser.h b/lparser.h
index 8b070b0e42..3d6bd97853 100644
--- a/lparser.h
+++ b/lparser.h
@@ -78,7 +78,7 @@ typedef struct expdesc {
/* description of active local variable */
typedef struct Vardesc {
- short idx; /* variable index in stack */
+ short idx; /* index of the variable in the Proto's 'locvars' array */
} Vardesc;
diff --git a/lvm.c b/lvm.c
index 7d5ab9db85..ece7d77a80 100644
--- a/lvm.c
+++ b/lvm.c
@@ -583,7 +583,7 @@ void luaV_concat (lua_State *L, int total) {
/*
-** Main operation 'ra' = #rb'.
+** Main operation 'ra = #rb'.
*/
void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) {
const TValue *tm;
@@ -757,11 +757,13 @@ void luaV_finishOp (lua_State *L) {
}
break;
}
- case OP_TFORCALL: case OP_CALL: case OP_TAILCALL:
- case OP_SETTABUP: case OP_SETTABLE:
- case OP_SETI: case OP_SETFIELD:
+ default: {
+ /* only these other opcodes can yield */
+ lua_assert(op == OP_TFORCALL || op == OP_CALL ||
+ op == OP_TAILCALL || op == OP_SETTABUP || op == OP_SETTABLE ||
+ op == OP_SETI || op == OP_SETFIELD);
break;
- default: lua_assert(0);
+ }
}
}
From 84e32ad2ebd6bd160c1320456743a5b1d8f233e9 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 23 Nov 2018 12:23:45 -0200
Subject: [PATCH 101/889] Added opcodes for arithmetic with K operands
Added opcodes for all seven arithmetic operators with K operands
(that is, operands that are numbers in the array of constants of
the function). They cover the cases of constant float operands
(e.g., 'x + .0.0', 'x^0.5') and large integer operands (e.g.,
'x % 10000').
---
lcode.c | 126 +++++++++++++++++++++++++++-------------------
ldebug.c | 10 +++-
ljumptab.h | 7 +++
lopcodes.c | 7 +++
lopcodes.h | 10 ++++
lopnames.h | 7 +++
ltests.h | 1 -
lvm.c | 61 ++++++++++++++++++++++
testes/code.lua | 82 ++++++++++++++++++------------
testes/db.lua | 2 +
testes/events.lua | 2 +
11 files changed, 228 insertions(+), 87 deletions(-)
diff --git a/lcode.c b/lcode.c
index 054b28fd1b..1872ede2ba 100644
--- a/lcode.c
+++ b/lcode.c
@@ -371,10 +371,6 @@ int luaK_codeABCk (FuncState *fs, OpCode o, int a, int b, int c, int k) {
}
-#define codeABsC(fs,o,a,b,c,k) luaK_codeABCk(fs,o,a,b,((c) + OFFSET_sC),k)
-
-
-
/*
** Format and emit an 'iABx' instruction.
*/
@@ -884,31 +880,46 @@ void luaK_exp2val (FuncState *fs, expdesc *e) {
}
+/*
+** Try to make 'e' a K expression with an index in the range of R/K
+** indices. Return true iff succeeded.
+*/
+static int luaK_exp2K (FuncState *fs, expdesc *e) {
+ if (!hasjumps(e)) {
+ int info;
+ switch (e->k) { /* move constants to 'k' */
+ case VTRUE: info = boolK(fs, 1); break;
+ case VFALSE: info = boolK(fs, 0); break;
+ case VNIL: info = nilK(fs); break;
+ case VKINT: info = luaK_intK(fs, e->u.ival); break;
+ case VKFLT: info = luaK_numberK(fs, e->u.nval); break;
+ case VK: info = e->u.info; break;
+ default: return 0; /* not a constant */
+ }
+ if (info <= MAXINDEXRK) { /* does constant fit in 'argC'? */
+ e->k = VK; /* make expression a 'K' expression */
+ e->u.info = info;
+ return 1;
+ }
+ }
+ /* else, expression doesn't fit; leave it unchanged */
+ return 0;
+}
+
+
/*
** Ensures final expression result is in a valid R/K index
** (that is, it is either in a register or in 'k' with an index
** in the range of R/K indices).
-** Returns 1 if expression is K, 0 otherwise.
+** Returns 1 iff expression is K.
*/
int luaK_exp2RK (FuncState *fs, expdesc *e) {
- luaK_exp2val(fs, e);
- switch (e->k) { /* move constants to 'k' */
- case VTRUE: e->u.info = boolK(fs, 1); goto vk;
- case VFALSE: e->u.info = boolK(fs, 0); goto vk;
- case VNIL: e->u.info = nilK(fs); goto vk;
- case VKINT: e->u.info = luaK_intK(fs, e->u.ival); goto vk;
- case VKFLT: e->u.info = luaK_numberK(fs, e->u.nval); goto vk;
- case VK:
- vk:
- e->k = VK;
- if (e->u.info <= MAXINDEXRK) /* constant fits in 'argC'? */
- return 1;
- else break;
- default: break;
+ if (luaK_exp2K(fs, e))
+ return 1;
+ else { /* not a constant in the right range: put it in a register */
+ luaK_exp2anyreg(fs, e);
+ return 0;
}
- /* not a constant in the right range: put it in a register */
- luaK_exp2anyreg(fs, e);
- return 0;
}
@@ -1232,8 +1243,19 @@ static void codeunexpval (FuncState *fs, OpCode op, expdesc *e, int line) {
}
+/*
+** Emit code for binary expressions that "produce values"
+** (everything but logical operators 'and'/'or' and comparison
+** operators).
+** Expression to produce final result will be encoded in 'e1'.
+** Because 'luaK_exp2anyreg' can free registers, its calls must be
+** in "stack order" (that is, first on 'e2', which may have more
+** recent registers to be released).
+*/
static void finishbinexpval (FuncState *fs, expdesc *e1, expdesc *e2,
- int pc, int line) {
+ OpCode op, int v2, int k, int line) {
+ int v1 = luaK_exp2anyreg(fs, e1);
+ int pc = luaK_codeABCk(fs, op, 0, v1, v2, k);
freeexps(fs, e1, e2);
e1->u.info = pc;
e1->k = VRELOC; /* all those operations are relocatable */
@@ -1242,20 +1264,13 @@ static void finishbinexpval (FuncState *fs, expdesc *e1, expdesc *e2,
/*
-** Emit code for binary expressions that "produce values"
-** (everything but logical operators 'and'/'or' and comparison
-** operators).
-** Expression to produce final result will be encoded in 'e1'.
-** Because 'luaK_exp2anyreg' can free registers, its calls must be
-** in "stack order" (that is, first on 'e2', which may have more
-** recent registers to be released).
+** Emit code for binary expressions that "produce values" over
+** two registers.
*/
static void codebinexpval (FuncState *fs, OpCode op,
expdesc *e1, expdesc *e2, int line) {
int v2 = luaK_exp2anyreg(fs, e2); /* both operands are in registers */
- int v1 = luaK_exp2anyreg(fs, e1);
- int pc = luaK_codeABC(fs, op, 0, v1, v2); /* generate opcode */
- finishbinexpval(fs, e1, e2, pc, line);
+ finishbinexpval(fs, e1, e2, op, v2, 0, line);
}
@@ -1264,41 +1279,48 @@ static void codebinexpval (FuncState *fs, OpCode op,
*/
static void codebini (FuncState *fs, OpCode op,
expdesc *e1, expdesc *e2, int k, int line) {
- int v2 = cast_int(e2->u.ival); /* immediate operand */
- int v1 = luaK_exp2anyreg(fs, e1);
- int pc = codeABsC(fs, op, 0, v1, v2, k); /* generate opcode */
- finishbinexpval(fs, e1, e2, pc, line);
+ int v2 = cast_int(e2->u.ival) + OFFSET_sC; /* immediate operand */
+ finishbinexpval(fs, e1, e2, op, v2, k, line);
+}
+
+
+static void swapexps (expdesc *e1, expdesc *e2) {
+ expdesc temp = *e1; *e1 = *e2; *e2 = temp; /* swap 'e1' and 'e2' */
}
/*
** Code arithmetic operators ('+', '-', ...). If second operand is a
** constant in the proper range, use variant opcodes with immediate
-** operands.
+** operands or K operands.
*/
static void codearith (FuncState *fs, OpCode op,
expdesc *e1, expdesc *e2, int flip, int line) {
- if (!isSCint(e2))
- codebinexpval(fs, op, e1, e2, line); /* use standard operators */
- else /* use immediate operators */
+ if (isSCint(e2)) /* immediate operand? */
codebini(fs, cast(OpCode, op - OP_ADD + OP_ADDI), e1, e2, flip, line);
-}
-
-
-static void swapexps (expdesc *e1, expdesc *e2) {
- expdesc temp = *e1; *e1 = *e2; *e2 = temp; /* swap 'e1' and 'e2' */
+ else if (tonumeral(e2, NULL) && luaK_exp2K(fs, e2)) { /* K operand? */
+ int v2 = e2->u.info; /* K index */
+ op = cast(OpCode, op - OP_ADD + OP_ADDK);
+ finishbinexpval(fs, e1, e2, op, v2, flip, line);
+ }
+ else { /* 'e2' is neither an immediate nor a K operand */
+ if (flip)
+ swapexps(e1, e2); /* back to original order */
+ codebinexpval(fs, op, e1, e2, line); /* use standard operators */
+ }
}
/*
** Code commutative operators ('+', '*'). If first operand is a
-** constant, change order of operands to use immediate operator.
+** numeric constant, change order of operands to try to use an
+** immediate or K operator.
*/
static void codecommutative (FuncState *fs, OpCode op,
expdesc *e1, expdesc *e2, int line) {
int flip = 0;
- if (isSCint(e1)) {
- swapexps(e1, e2);
+ if (tonumeral(e1, NULL)) { /* is first operand a numeric constant? */
+ swapexps(e1, e2); /* change order */
flip = 1;
}
codearith(fs, op, e1, e2, flip, line);
@@ -1312,7 +1334,7 @@ static void codecommutative (FuncState *fs, OpCode op,
static void codebitwise (FuncState *fs, BinOpr opr,
expdesc *e1, expdesc *e2, int line) {
int inv = 0;
- int v1, v2, pc;
+ int v2;
OpCode op;
if (e1->k == VKINT && luaK_exp2RK(fs, e1)) {
swapexps(e1, e2); /* 'e2' will be the constant operand */
@@ -1323,12 +1345,10 @@ static void codebitwise (FuncState *fs, BinOpr opr,
codebinexpval(fs, op, e1, e2, line); /* all-register opcodes */
return;
}
- v1 = luaK_exp2anyreg(fs, e1);
v2 = e2->u.info; /* index in K array */
op = cast(OpCode, opr - OPR_BAND + OP_BANDK);
lua_assert(ttisinteger(&fs->f->k[v2]));
- pc = luaK_codeABCk(fs, op, 0, v1, v2, inv);
- finishbinexpval(fs, e1, e2, pc, line);
+ finishbinexpval(fs, e1, e2, op, v2, inv, line);
}
diff --git a/ldebug.c b/ldebug.c
index ee1b87d901..766a52319f 100644
--- a/ldebug.c
+++ b/ldebug.c
@@ -610,12 +610,18 @@ static const char *funcnamefromcode (lua_State *L, CallInfo *ci,
tm = TM_NEWINDEX;
break;
case OP_ADDI: case OP_SUBI: case OP_MULI: case OP_MODI:
- case OP_POWI: case OP_DIVI: case OP_IDIVI:
- case OP_BANDK: case OP_BORK: case OP_BXORK: {
+ case OP_POWI: case OP_DIVI: case OP_IDIVI: {
int offset = GET_OPCODE(i) - OP_ADDI; /* ORDER OP */
tm = cast(TMS, offset + TM_ADD); /* ORDER TM */
break;
}
+ case OP_ADDK: case OP_SUBK: case OP_MULK: case OP_MODK:
+ case OP_POWK: case OP_DIVK: case OP_IDIVK:
+ case OP_BANDK: case OP_BORK: case OP_BXORK: {
+ int offset = GET_OPCODE(i) - OP_ADDK; /* ORDER OP */
+ tm = cast(TMS, offset + TM_ADD); /* ORDER TM */
+ break;
+ }
case OP_ADD: case OP_SUB: case OP_MUL: case OP_MOD:
case OP_POW: case OP_DIV: case OP_IDIV: case OP_BAND:
case OP_BOR: case OP_BXOR: case OP_SHL: case OP_SHR: {
diff --git a/ljumptab.h b/ljumptab.h
index 6767e95b18..0af997d03d 100644
--- a/ljumptab.h
+++ b/ljumptab.h
@@ -51,6 +51,13 @@ static void *disptab[] = {
&&L_OP_POWI,
&&L_OP_DIVI,
&&L_OP_IDIVI,
+&&L_OP_ADDK,
+&&L_OP_SUBK,
+&&L_OP_MULK,
+&&L_OP_MODK,
+&&L_OP_POWK,
+&&L_OP_DIVK,
+&&L_OP_IDIVK,
&&L_OP_BANDK,
&&L_OP_BORK,
&&L_OP_BXORK,
diff --git a/lopcodes.c b/lopcodes.c
index 11a73c2957..3f0d551a99 100644
--- a/lopcodes.c
+++ b/lopcodes.c
@@ -45,6 +45,13 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
,opmode(0, 0, 0, 1, iABC) /* OP_POWI */
,opmode(0, 0, 0, 1, iABC) /* OP_DIVI */
,opmode(0, 0, 0, 1, iABC) /* OP_IDIVI */
+ ,opmode(0, 0, 0, 1, iABC) /* OP_ADDK */
+ ,opmode(0, 0, 0, 1, iABC) /* OP_SUBK */
+ ,opmode(0, 0, 0, 1, iABC) /* OP_MULK */
+ ,opmode(0, 0, 0, 1, iABC) /* OP_MODK */
+ ,opmode(0, 0, 0, 1, iABC) /* OP_POWK */
+ ,opmode(0, 0, 0, 1, iABC) /* OP_DIVK */
+ ,opmode(0, 0, 0, 1, iABC) /* OP_IDIVK */
,opmode(0, 0, 0, 1, iABC) /* OP_BANDK */
,opmode(0, 0, 0, 1, iABC) /* OP_BORK */
,opmode(0, 0, 0, 1, iABC) /* OP_BXORK */
diff --git a/lopcodes.h b/lopcodes.h
index 7bb83a1e6e..d7403caf4b 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -221,6 +221,14 @@ OP_POWI,/* A B sC R(A) := R(B) ^ C */
OP_DIVI,/* A B sC R(A) := R(B) / C */
OP_IDIVI,/* A B sC R(A) := R(B) // C */
+OP_ADDK,/* A B C R(A) := R(B) + K(C) */
+OP_SUBK,/* A B C R(A) := R(B) - K(C) */
+OP_MULK,/* A B C R(A) := R(B) * K(C) */
+OP_MODK,/* A B C R(A) := R(B) % K(C) */
+OP_POWK,/* A B C R(A) := R(B) ^ K(C) */
+OP_DIVK,/* A B C R(A) := R(B) / K(C) */
+OP_IDIVK,/* A B C R(A) := R(B) // K(C) */
+
OP_BANDK,/* A B C R(A) := R(B) & K(C):integer */
OP_BORK,/* A B C R(A) := R(B) | K(C):integer */
OP_BXORK,/* A B C R(A) := R(B) ~ K(C):integer */
@@ -235,11 +243,13 @@ OP_MOD,/* A B C R(A) := R(B) % R(C) */
OP_POW,/* A B C R(A) := R(B) ^ R(C) */
OP_DIV,/* A B C R(A) := R(B) / R(C) */
OP_IDIV,/* A B C R(A) := R(B) // R(C) */
+
OP_BAND,/* A B C R(A) := R(B) & R(C) */
OP_BOR,/* A B C R(A) := R(B) | R(C) */
OP_BXOR,/* A B C R(A) := R(B) ~ R(C) */
OP_SHL,/* A B C R(A) := R(B) << R(C) */
OP_SHR,/* A B C R(A) := R(B) >> R(C) */
+
OP_UNM,/* A B R(A) := -R(B) */
OP_BNOT,/* A B R(A) := ~R(B) */
OP_NOT,/* A B R(A) := not R(B) */
diff --git a/lopnames.h b/lopnames.h
index b2c4fe21fb..96d901ac68 100644
--- a/lopnames.h
+++ b/lopnames.h
@@ -36,6 +36,13 @@ static const char *const opnames[] = {
"POWI",
"DIVI",
"IDIVI",
+ "ADDK",
+ "SUBK",
+ "MULK",
+ "MODK",
+ "POWK",
+ "DIVK",
+ "IDIVK",
"BANDK",
"BORK",
"BXORK",
diff --git a/ltests.h b/ltests.h
index d44974a444..9d409c8d43 100644
--- a/ltests.h
+++ b/ltests.h
@@ -119,7 +119,6 @@ LUA_API void *debug_realloc (void *ud, void *block,
#undef LUAL_BUFFERSIZE
#define LUAL_BUFFERSIZE 23
#define MINSTRTABSIZE 2
-#define MAXINDEXRK 1
#define MAXIWTHABS 3
diff --git a/lvm.c b/lvm.c
index ece7d77a80..27ef68cfe6 100644
--- a/lvm.c
+++ b/lvm.c
@@ -855,6 +855,39 @@ void luaV_finishOp (lua_State *L) {
else op_arithf_aux(L, v1, v2, fop, tm); }
+/*
+** Arithmetic operations with K operands.
+*/
+#define op_arithK(L,iop,fop,tm,flip) { \
+ TValue *v1 = vRB(i); \
+ TValue *v2 = KC(i); \
+ if (ttisinteger(v1) && ttisinteger(v2)) { \
+ lua_Integer i1 = ivalue(v1); lua_Integer i2 = ivalue(v2); \
+ setivalue(s2v(ra), iop(L, i1, i2)); \
+ } \
+ else { \
+ lua_Number n1; lua_Number n2; \
+ if (tonumberns(v1, n1) && tonumberns(v2, n2)) { \
+ setfltvalue(s2v(ra), fop(L, n1, n2)); \
+ } \
+ else \
+ Protect(luaT_trybinassocTM(L, v1, v2, ra, flip, tm)); } }
+
+
+/*
+** Arithmetic operations with K operands for floats.
+*/
+#define op_arithfK(L,fop,tm) { \
+ TValue *v1 = vRB(i); \
+ TValue *v2 = KC(i); \
+ lua_Number n1; lua_Number n2; \
+ if (tonumberns(v1, n1) && tonumberns(v2, n2)) { \
+ setfltvalue(s2v(ra), fop(L, n1, n2)); \
+ } \
+ else \
+ Protect(luaT_trybinTM(L, v1, v2, ra, tm)); }
+
+
/*
** Bitwise operations with constant operand.
*/
@@ -1219,6 +1252,34 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
op_arithI(L, luaV_idiv, luai_numidiv, TM_IDIV, 0);
vmbreak;
}
+ vmcase(OP_ADDK) {
+ op_arithK(L, l_addi, luai_numadd, TM_ADD, GETARG_k(i));
+ vmbreak;
+ }
+ vmcase(OP_SUBK) {
+ op_arithK(L, l_subi, luai_numsub, TM_SUB, 0);
+ vmbreak;
+ }
+ vmcase(OP_MULK) {
+ op_arithK(L, l_muli, luai_nummul, TM_MUL, GETARG_k(i));
+ vmbreak;
+ }
+ vmcase(OP_MODK) {
+ op_arithK(L, luaV_mod, luaV_modf, TM_MOD, 0);
+ vmbreak;
+ }
+ vmcase(OP_POWK) {
+ op_arithfK(L, luai_numpow, TM_POW);
+ vmbreak;
+ }
+ vmcase(OP_DIVK) {
+ op_arithfK(L, luai_numdiv, TM_DIV);
+ vmbreak;
+ }
+ vmcase(OP_IDIVK) {
+ op_arithK(L, luaV_idiv, luai_numidiv, TM_IDIV, 0);
+ vmbreak;
+ }
vmcase(OP_ADD) {
op_arith(L, l_addi, luai_numadd, TM_ADD);
vmbreak;
diff --git a/testes/code.lua b/testes/code.lua
index 4d44fa6aa3..834ff5e23d 100644
--- a/testes/code.lua
+++ b/testes/code.lua
@@ -40,6 +40,7 @@ checkKlist(foo, {3.78/4, -3.78/4, -3.79/4})
-- testing opcodes
+-- check that 'f' opcodes match '...'
function check (f, ...)
local arg = {...}
local c = T.listcode(f)
@@ -52,9 +53,19 @@ function check (f, ...)
end
+-- check that 'f' opcodes match '...' and that 'f(p) == r'.
+function checkR (f, p, r, ...)
+ local r1 = f(p)
+ assert(r == r1 and math.type(r) == math.type(r1))
+ check(f, ...)
+end
+
+
+-- check that 'a' and 'b' has the same opcodes
function checkequal (a, b)
a = T.listcode(a)
b = T.listcode(b)
+ assert(#a == #b)
for i = 1, #a do
a[i] = string.gsub(a[i], '%b()', '') -- remove line number
b[i] = string.gsub(b[i], '%b()', '') -- remove line number
@@ -165,65 +176,64 @@ end,
-- equalities
-check(function (a) if a == 1 then return 2 end end,
+checkR(function (a) if a == 1 then return 2 end end, 1, 2,
'EQI', 'JMP', 'LOADI', 'RETURN1')
-check(function (a) if -4.0 == a then return 2 end end,
+checkR(function (a) if -4.0 == a then return 2 end end, -4, 2,
'EQI', 'JMP', 'LOADI', 'RETURN1')
-check(function (a) if a == "hi" then return 2 end end,
+checkR(function (a) if a == "hi" then return 2 end end, 10, nil,
'EQK', 'JMP', 'LOADI', 'RETURN1')
-check(function (a) if a == 10000 then return 2 end end,
+checkR(function (a) if a == 10000 then return 2 end end, 1, nil,
'EQK', 'JMP', 'LOADI', 'RETURN1') -- number too large
-check(function (a) if -10000 == a then return 2 end end,
+checkR(function (a) if -10000 == a then return 2 end end, -10000, 2,
'EQK', 'JMP', 'LOADI', 'RETURN1') -- number too large
-- comparisons
-check(function (a) if -10 <= a then return 2 end end,
+checkR(function (a) if -10 <= a then return 2 end end, -10, 2,
'GEI', 'JMP', 'LOADI', 'RETURN1')
-check(function (a) if 128.0 > a then return 2 end end,
+checkR(function (a) if 128.0 > a then return 2 end end, 129, nil,
'LTI', 'JMP', 'LOADI', 'RETURN1')
-check(function (a) if -127.0 < a then return 2 end end,
+checkR(function (a) if -127.0 < a then return 2 end end, -127, nil,
'GTI', 'JMP', 'LOADI', 'RETURN1')
-check(function (a) if 10 < a then return 2 end end,
+checkR(function (a) if 10 < a then return 2 end end, 11, 2,
'GTI', 'JMP', 'LOADI', 'RETURN1')
-check(function (a) if 129 < a then return 2 end end,
+checkR(function (a) if 129 < a then return 2 end end, 130, 2,
'LOADI', 'LT', 'JMP', 'LOADI', 'RETURN1')
-check(function (a) if a >= 23.0 then return 2 end end,
+checkR(function (a) if a >= 23.0 then return 2 end end, 25, 2,
'GEI', 'JMP', 'LOADI', 'RETURN1')
-check(function (a) if a >= 23.1 then return 2 end end,
+checkR(function (a) if a >= 23.1 then return 2 end end, 0, nil,
'LOADK', 'LE', 'JMP', 'LOADI', 'RETURN1')
-check(function (a) if a > 2300.0 then return 2 end end,
+checkR(function (a) if a > 2300.0 then return 2 end end, 0, nil,
'LOADF', 'LT', 'JMP', 'LOADI', 'RETURN1')
-- constant folding
local function checkK (func, val)
check(func, 'LOADK', 'RETURN1')
- local k = T.listk(func)
- assert(#k == 1 and k[1] == val and math.type(k[1]) == math.type(val))
+ checkKlist(func, {val})
assert(func() == val)
end
local function checkI (func, val)
check(func, 'LOADI', 'RETURN1')
- assert(#T.listk(func) == 0)
+ checkKlist(func, {})
assert(func() == val)
end
local function checkF (func, val)
check(func, 'LOADF', 'RETURN1')
- assert(#T.listk(func) == 0)
+ checkKlist(func, {})
assert(func() == val)
end
@@ -258,20 +268,30 @@ checkK(function () return -65536.0 end, -(sbx + 1.0))
-- immediate operands
-check(function (x) return x + 1 end, 'ADDI', 'RETURN1')
-check(function (x) return 128 + x end, 'ADDI', 'RETURN1')
-check(function (x) return x * -127 end, 'MULI', 'RETURN1')
-check(function (x) return 20 * x end, 'MULI', 'RETURN1')
-check(function (x) return x ^ -2 end, 'POWI', 'RETURN1')
-check(function (x) return x / 40 end, 'DIVI', 'RETURN1')
-check(function (x) return x // 1 end, 'IDIVI', 'RETURN1')
-check(function (x) return x % (100 - 10) end, 'MODI', 'RETURN1')
-check(function (x) return 1 << x end, 'SHLI', 'RETURN1')
-check(function (x) return x << 2 end, 'SHRI', 'RETURN1')
-check(function (x) return x >> 2 end, 'SHRI', 'RETURN1')
-check(function (x) return x & 1 end, 'BANDK', 'RETURN1')
-check(function (x) return 10 | x end, 'BORK', 'RETURN1')
-check(function (x) return -10 ~ x end, 'BXORK', 'RETURN1')
+checkR(function (x) return x + 1 end, 10, 11, 'ADDI', 'RETURN1')
+checkR(function (x) return 128 + x end, 0.0, 128.0, 'ADDI', 'RETURN1')
+checkR(function (x) return x * -127 end, -1.0, 127.0, 'MULI', 'RETURN1')
+checkR(function (x) return 20 * x end, 2, 40, 'MULI', 'RETURN1')
+checkR(function (x) return x ^ -2 end, 2, 0.25, 'POWI', 'RETURN1')
+checkR(function (x) return x / 40 end, 40, 1.0, 'DIVI', 'RETURN1')
+checkR(function (x) return x // 1 end, 10.0, 10.0, 'IDIVI', 'RETURN1')
+checkR(function (x) return x % (100 - 10) end, 91, 1, 'MODI', 'RETURN1')
+checkR(function (x) return 1 << x end, 3, 8, 'SHLI', 'RETURN1')
+checkR(function (x) return x << 2 end, 10, 40, 'SHRI', 'RETURN1')
+checkR(function (x) return x >> 2 end, 8, 2, 'SHRI', 'RETURN1')
+checkR(function (x) return x & 1 end, 9, 1, 'BANDK', 'RETURN1')
+checkR(function (x) return 10 | x end, 1, 11, 'BORK', 'RETURN1')
+checkR(function (x) return -10 ~ x end, -1, 9, 'BXORK', 'RETURN1')
+
+-- K operands in arithmetic operations
+checkR(function (x) return x + 0.0 end, 1, 1.0, 'ADDK', 'RETURN1')
+-- check(function (x) return 128 + x end, 'ADDK', 'RETURN1')
+checkR(function (x) return x * -10000 end, 2, -20000, 'MULK', 'RETURN1')
+-- check(function (x) return 20 * x end, 'MULK', 'RETURN1')
+checkR(function (x) return x ^ 0.5 end, 4, 2.0, 'POWK', 'RETURN1')
+checkR(function (x) return x / 2.0 end, 4, 2.0, 'DIVK', 'RETURN1')
+checkR(function (x) return x // 10000 end, 10000, 1, 'IDIVK', 'RETURN1')
+checkR(function (x) return x % (100.0 - 10) end, 91, 1.0, 'MODK', 'RETURN1')
-- no foldings (and immediate operands)
check(function () return -0.0 end, 'LOADF', 'UNM', 'RETURN1')
diff --git a/testes/db.lua b/testes/db.lua
index 9da682102e..5b243c3993 100644
--- a/testes/db.lua
+++ b/testes/db.lua
@@ -794,6 +794,8 @@ assert(a[3] == "index" and a^3 == "pow" and a..a == "concat")
assert(a/3 == "div" and 3%a == "mod")
assert(a+3 == "add" and 3-a == "sub" and a*3 == "mul" and
-a == "unm" and #a == "len" and a&3 == "band")
+assert(a + 30000 == "add" and a - 3.0 == "sub" and a * 3.0 == "mul" and
+ -a == "unm" and #a == "len" and a & 3 == "band")
assert(a|3 == "bor" and 3~a == "bxor" and a<<3 == "shift" and
a>>1 == "shift")
assert (a==b and a.op == "eq")
diff --git a/testes/events.lua b/testes/events.lua
index b071c2a314..ac630d895c 100644
--- a/testes/events.lua
+++ b/testes/events.lua
@@ -144,6 +144,8 @@ t.__bnot = f("bnot")
-- when the constant table is very small.
assert(b+5 == b)
assert(cap[0] == "add" and cap[1] == b and cap[2] == 5 and cap[3]==undef)
+assert(5.2 + b == 5.2)
+assert(cap[0] == "add" and cap[1] == 5.2 and cap[2] == b and cap[3]==undef)
assert(b+'5' == b)
assert(cap[0] == "add" and cap[1] == b and cap[2] == '5' and cap[3]==undef)
assert(5+b == 5)
From 7e63d3da0240325db4011f5d2f2e8abfb5d60288 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Sat, 24 Nov 2018 11:59:15 -0200
Subject: [PATCH 102/889] Some bugs with stack reallocation by 'luaF_close'
(Long time without testing with '-DHARDSTACKTESTS'...)
With the introduction of to-be-closed variables, calls to 'luaF_close'
can move the stack, but some call sites where keeping pointers to the
stack without correcting them.
---
ldo.c | 9 ++++++---
ljumptab.h | 2 +-
ltests.c | 2 +-
lvm.c | 21 +++++++++------------
4 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/ldo.c b/ldo.c
index b7a76ef6f7..2762fefa0b 100644
--- a/ldo.c
+++ b/ldo.c
@@ -383,8 +383,10 @@ static void moveresults (lua_State *L, StkId res, int nres, int wanted) {
wanted = nres; /* we want all results */
break;
default: /* multiple results (or to-be-closed variables) */
- if (hastocloseCfunc(wanted)) {
- luaF_close(L, res, LUA_OK);
+ if (hastocloseCfunc(wanted)) { /* to-be-closed variables? */
+ ptrdiff_t savedres = savestack(L, res);
+ luaF_close(L, res, LUA_OK); /* may change the stack */
+ res = restorestack(L, savedres);
wanted = codeNresults(wanted); /* correct value */
if (wanted == LUA_MULTRET)
wanted = nres;
@@ -590,7 +592,8 @@ static int recover (lua_State *L, int status) {
if (ci == NULL) return 0; /* no recovery point */
/* "finish" luaD_pcall */
oldtop = restorestack(L, ci->u2.funcidx);
- luaF_close(L, oldtop, status);
+ luaF_close(L, oldtop, status); /* may change the stack */
+ oldtop = restorestack(L, ci->u2.funcidx);
luaD_seterrorobj(L, status, oldtop);
L->ci = ci;
L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */
diff --git a/ljumptab.h b/ljumptab.h
index 0af997d03d..9fa72a73c8 100644
--- a/ljumptab.h
+++ b/ljumptab.h
@@ -16,7 +16,7 @@
#define vmbreak vmfetch(); vmdispatch(GET_OPCODE(i));
-static void *disptab[] = {
+static void *disptab[NUM_OPCODES] = {
#if 0
** you can update the following list with this command:
diff --git a/ltests.c b/ltests.c
index c5c1040ad6..63d423e08e 100644
--- a/ltests.c
+++ b/ltests.c
@@ -142,7 +142,7 @@ void *debug_realloc (void *ud, void *b, size_t oldsize, size_t size) {
freeblock(mc, block);
return NULL;
}
- if (mc->countlimit != ~0UL && size > 0) { /* count limit in use? */
+ if (mc->countlimit != ~0UL && size != oldsize) { /* count limit in use? */
if (mc->countlimit == 0)
return NULL; /* fake a memory allocation error */
mc->countlimit--;
diff --git a/lvm.c b/lvm.c
index 27ef68cfe6..a6dcc9ffe9 100644
--- a/lvm.c
+++ b/lvm.c
@@ -946,6 +946,9 @@ void luaV_finishOp (lua_State *L) {
#define updatebase(ci) (base = ci->func + 1)
+#define updatestack(ci) { if (trap) { updatebase(ci); ra = RA(i); } }
+
+
/*
** Execute a jump instruction. The 'updatetrap' allows signals to stop
** tight loops. (Without it, the local copy of 'trap' could never change.)
@@ -1557,24 +1560,21 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
L->top = ra + b;
else /* previous instruction set top */
b = cast_int(L->top - ra);
- savepc(ci);
if (TESTARG_k(i)) {
int nparams1 = GETARG_C(i);
if (nparams1) /* vararg function? */
delta = ci->u.l.nextraargs + nparams1;
- luaF_close(L, base, LUA_OK); /* close upvalues from current call */
+ /* close upvalues from current call */
+ ProtectNT(luaF_close(L, base, LUA_OK));
+ updatestack(ci);
}
if (!ttisfunction(s2v(ra))) { /* not a function? */
luaD_tryfuncTM(L, ra); /* try '__call' metamethod */
b++; /* there is now one extra argument */
}
if (!ttisLclosure(s2v(ra))) { /* C function? */
- luaD_call(L, ra, LUA_MULTRET); /* call it */
- updatetrap(ci);
- if (trap) { /* stack may have been relocated */
- updatebase(ci);
- ra = RA(i);
- }
+ ProtectNT(luaD_call(L, ra, LUA_MULTRET)); /* call it */
+ updatestack(ci); /* stack may have been relocated */
ci->func -= delta;
luaD_poscall(L, ci, cast_int(L->top - ra));
return;
@@ -1739,10 +1739,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
memcpy(ra + 4, ra, 3 * sizeof(*ra));
L->top = ra + 4 + 3;
Protect(luaD_call(L, ra + 4, GETARG_C(i))); /* do the call */
- if (trap) { /* stack may have changed? */
- updatebase(ci); /* keep 'base' correct */
- ra = RA(i); /* keep 'ra' correct for next instruction */
- }
+ updatestack(ci); /* stack may have changed */
i = *(pc++); /* go to next instruction */
ra += 2; /* adjust for next instruction */
lua_assert(GET_OPCODE(i) == OP_TFORLOOP && ra == RA(i));
From 7696c6474fe51ed59fee324e78c1233af74febdd Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 26 Nov 2018 14:16:17 -0200
Subject: [PATCH 103/889] Auxiliary buffer cannot close box with 'lua_remove'
To remove a to-be-closed variable from the stack in the C API a
function must use 'lua_settop' or 'lua_pop'. Previous implementation of
'luaL_pushresult' was not closing the box. (This commit also added
tests to check that box is being closed "as soon as possible".)
---
lapi.c | 4 ++--
lauxlib.c | 12 +++++-----
testes/locals.lua | 56 +++++++++++++++++++++++++++++++++--------------
3 files changed, 49 insertions(+), 23 deletions(-)
diff --git a/lapi.c b/lapi.c
index da866a660b..147ed0ff0f 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1213,8 +1213,8 @@ LUA_API void lua_toclose (lua_State *L, int idx) {
lua_lock(L);
o = index2stack(L, idx);
nresults = L->ci->nresults;
- api_check(L, L->openupval == NULL || uplevel(L->openupval) < o,
- "there is an already marked index below");
+ api_check(L, L->openupval == NULL || uplevel(L->openupval) <= o,
+ "marked index below or equal new one");
luaF_newtbcupval(L, o); /* create new to-be-closed upvalue */
if (!hastocloseCfunc(nresults)) /* function not marked yet? */
L->ci->nresults = codeNresults(nresults); /* mark it */
diff --git a/lauxlib.c b/lauxlib.c
index 6069ed1b24..fd4acbd109 100644
--- a/lauxlib.c
+++ b/lauxlib.c
@@ -524,8 +524,8 @@ static size_t newbuffsize (luaL_Buffer *B, size_t sz) {
/*
** Returns a pointer to a free area with at least 'sz' bytes in buffer
-** 'B'. 'boxidx' is the position in the stack where the buffer's box is
-** or should be.
+** 'B'. 'boxidx' is the relative position in the stack where the
+** buffer's box is or should be.
*/
static char *prepbuffsize (luaL_Buffer *B, size_t sz, int boxidx) {
if (B->size - B->n >= sz) /* enough space? */
@@ -538,8 +538,10 @@ static char *prepbuffsize (luaL_Buffer *B, size_t sz, int boxidx) {
if (buffonstack(B)) /* buffer already has a box? */
newbuff = (char *)resizebox(L, boxidx, newsize); /* resize it */
else { /* no box yet */
+ lua_pushnil(L); /* reserve slot for final result */
newbox(L); /* create a new box */
- lua_insert(L, boxidx); /* move box to its intended position */
+ /* move box (and slot) to its intended position */
+ lua_rotate(L, boxidx - 1, 2);
lua_toclose(L, boxidx);
newbuff = (char *)resizebox(L, boxidx, newsize);
memcpy(newbuff, B->b, B->n * sizeof(char)); /* copy original content */
@@ -576,8 +578,8 @@ LUALIB_API void luaL_pushresult (luaL_Buffer *B) {
lua_State *L = B->L;
lua_pushlstring(L, B->b, B->n);
if (buffonstack(B)) {
- resizebox(L, -2, 0); /* delete old buffer */
- lua_remove(L, -2); /* remove its header from the stack */
+ lua_copy(L, -1, -3); /* move string to reserved slot */
+ lua_pop(L, 2); /* pop string and box (closing the box) */
}
}
diff --git a/testes/locals.lua b/testes/locals.lua
index 8766b3d5ce..90a8b8456e 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -242,6 +242,7 @@ do
assert(a == 1 and b == 2 and c == 3 and X == 20 and Y == 10 and d == nil)
end
+
do -- errors in __close
local log = {}
local function foo (err)
@@ -264,7 +265,9 @@ do -- errors in __close
and #log == 4)
end
+
if rawget(_G, "T") then
+
-- memory error inside closing function
local function foo ()
local *toclose y = function () T.alloccount() end
@@ -296,7 +299,7 @@ if rawget(_G, "T") then
local function test ()
local *toclose x = enter(0) -- set a memory limit
-- creation of previous upvalue will raise a memory error
- os.exit(false) -- should not run
+ assert(false) -- should not run
end
local _, msg = pcall(test)
@@ -326,33 +329,54 @@ if rawget(_G, "T") then
do -- testing 'toclose' in C string buffer
- local s = string.rep("a", 10000)
+ collectgarbage()
+ local s = string.rep('a', 10000) -- large string
+ local m = T.totalmem()
+ collectgarbage("stop")
+ s = string.upper(s) -- allocate buffer + new string (10K each)
+ -- ensure buffer was deallocated
+ assert(T.totalmem() - m <= 11000)
+ collectgarbage("restart")
+ end
+
+ do -- now some tests for freeing buffer in case of errors
+ local lim = 10000 -- some size larger than the static buffer
+ local extra = 2000 -- some extra memory (for callinfo, etc.)
+
+ local s = string.rep("a", lim)
+
+ -- concat this table needs two buffer resizes (one for each 's')
local a = {s, s}
- -- ensure proper initialization (stack space, metatable)
- table.concat(a)
- collectgarbage(); collectgarbage()
+ collectgarbage()
- local m = T.totalmem()
+ m = T.totalmem()
+ collectgarbage("stop")
+
+ -- error in the first buffer allocation
+ T. totalmem(m + extra)
+ assert(not pcall(table.concat, a))
+ -- first buffer was not even allocated
+ assert(T.totalmem() - m <= extra)
-- error in the second buffer allocation
- T.alloccount(3)
+ T. totalmem(m + lim + extra)
assert(not pcall(table.concat, a))
- T.alloccount()
-- first buffer was released by 'toclose'
- assert(T.totalmem() - m <= 5000)
+ assert(T.totalmem() - m <= extra)
-- error in creation of final string
- T.alloccount(4)
+ T.totalmem(m + 2 * lim + extra)
assert(not pcall(table.concat, a))
- T.alloccount()
-- second buffer was released by 'toclose'
- assert(T.totalmem() - m <= 5000)
+ assert(T.totalmem() - m <= extra)
- -- userdata, upvalue, buffer, buffer, string
- T.alloccount(5)
- assert(#table.concat(a) == 20000)
- T.alloccount()
+ -- userdata, upvalue, buffer, buffer, final string
+ T.totalmem(m + 4*lim + extra)
+ assert(#table.concat(a) == 2*lim)
+
+ T.totalmem(0) -- remove memory limit
+ collectgarbage("restart")
print'+'
end
From 6d04537ea660fd12fc16c328366b701fabaf4919 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 29 Nov 2018 16:02:44 -0200
Subject: [PATCH 104/889] A to-be-closed variable must have a closable value
(or be nil)
It is an error for a to-be-closed variable to have a non-closable
non-nil value when it is being closed. This situation does not seem to
be useful and often hints to an error. (Particularly in the C API, it is
easy to change a to-be-closed index by mistake.)
---
lapi.c | 2 +-
ldebug.c | 18 ++++++++++--------
ldebug.h | 2 ++
lfunc.c | 6 ++++++
lvm.c | 7 +++----
manual/manual.of | 25 ++++++++++++++++---------
testes/api.lua | 21 ++++++++++++++-------
testes/db.lua | 20 ++++++++++----------
testes/locals.lua | 21 +++++++++++++++++++++
9 files changed, 83 insertions(+), 39 deletions(-)
diff --git a/lapi.c b/lapi.c
index 147ed0ff0f..9cabe7ca4e 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1299,7 +1299,7 @@ static const char *aux_upvalue (TValue *fi, int n, TValue **val,
*val = f->upvals[n-1]->v;
if (owner) *owner = obj2gco(f->upvals[n - 1]);
name = p->upvalues[n-1].name;
- return (name == NULL) ? "(*no name)" : getstr(name);
+ return (name == NULL) ? "(no name)" : getstr(name);
}
default: return NULL; /* not a closure */
}
diff --git a/ldebug.c b/ldebug.c
index 766a52319f..bd471e0c08 100644
--- a/ldebug.c
+++ b/ldebug.c
@@ -192,15 +192,14 @@ static const char *findvararg (CallInfo *ci, int n, StkId *pos) {
int nextra = ci->u.l.nextraargs;
if (n <= nextra) {
*pos = ci->func - nextra + (n - 1);
- return "(*vararg)"; /* generic name for any vararg */
+ return "(vararg)"; /* generic name for any vararg */
}
}
return NULL; /* no such vararg */
}
-static const char *findlocal (lua_State *L, CallInfo *ci, int n,
- StkId *pos) {
+const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n, StkId *pos) {
StkId base = ci->func + 1;
const char *name = NULL;
if (isLua(ci)) {
@@ -211,12 +210,15 @@ static const char *findlocal (lua_State *L, CallInfo *ci, int n,
}
if (name == NULL) { /* no 'standard' name? */
StkId limit = (ci == L->ci) ? L->top : ci->next->func;
- if (limit - base >= n && n > 0) /* is 'n' inside 'ci' stack? */
- name = "(*temporary)"; /* generic name for any valid slot */
+ if (limit - base >= n && n > 0) { /* is 'n' inside 'ci' stack? */
+ /* generic name for any valid slot */
+ name = isLua(ci) ? "(temporary)" : "(C temporary)";
+ }
else
return NULL; /* no name */
}
- *pos = base + (n - 1);
+ if (pos)
+ *pos = base + (n - 1);
return name;
}
@@ -232,7 +234,7 @@ LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) {
}
else { /* active function; get information through 'ar' */
StkId pos = NULL; /* to avoid warnings */
- name = findlocal(L, ar->i_ci, n, &pos);
+ name = luaG_findlocal(L, ar->i_ci, n, &pos);
if (name) {
setobjs2s(L, L->top, pos);
api_incr_top(L);
@@ -247,7 +249,7 @@ LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) {
StkId pos = NULL; /* to avoid warnings */
const char *name;
lua_lock(L);
- name = findlocal(L, ar->i_ci, n, &pos);
+ name = luaG_findlocal(L, ar->i_ci, n, &pos);
if (name) {
setobjs2s(L, pos, L->top - 1);
L->top--; /* pop value */
diff --git a/ldebug.h b/ldebug.h
index f080711d59..1fe0efab08 100644
--- a/ldebug.h
+++ b/ldebug.h
@@ -22,6 +22,8 @@
#define ABSLINEINFO (-0x80)
LUAI_FUNC int luaG_getfuncline (const Proto *f, int pc);
+LUAI_FUNC const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n,
+ StkId *pos);
LUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o,
const char *opname);
LUAI_FUNC l_noret luaG_forerror (lua_State *L, const TValue *o,
diff --git a/lfunc.c b/lfunc.c
index aa6ce58f17..11d2850f76 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -14,6 +14,7 @@
#include "lua.h"
+#include "ldebug.h"
#include "ldo.h"
#include "lfunc.h"
#include "lgc.h"
@@ -140,6 +141,11 @@ static int closeupval (lua_State *L, TValue *uv, StkId level, int status) {
if (likely(status == LUA_OK)) {
if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */
callclose(L, NULL); /* call closing method */
+ else if (!ttisnil(uv)) { /* non-closable non-nil value? */
+ const char *vname = luaG_findlocal(L, L->ci, level - L->ci->func, NULL);
+ if (vname == NULL) vname = "?";
+ luaG_runerror(L, "attempt to close non-closable variable '%s'", vname);
+ }
}
else { /* there was an error */
/* save error message and set stack top to 'level + 1' */
diff --git a/lvm.c b/lvm.c
index a6dcc9ffe9..50d967c6b8 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1427,7 +1427,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
}
vmcase(OP_CLOSE) {
L->top = ra + 1; /* everything is free after this slot */
- ProtectNT(luaF_close(L, ra, LUA_OK));
+ Protect(luaF_close(L, ra, LUA_OK));
vmbreak;
}
vmcase(OP_TBC) {
@@ -1717,9 +1717,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_TFORPREP) {
- /* is 'toclose' a function or has a '__close' metamethod? */
- if (ttisfunction(s2v(ra + 3)) ||
- !ttisnil(luaT_gettmbyobj(L, s2v(ra + 3), TM_CLOSE))) {
+ /* is 'toclose' not nil? */
+ if (!ttisnil(s2v(ra + 3))) {
/* create to-be-closed upvalue for it */
halfProtect(luaF_newtbcupval(L, ra + 3));
}
diff --git a/manual/manual.of b/manual/manual.of
index f891c33e94..3902f2f359 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1063,11 +1063,16 @@ which start with @T{0x} or @T{0X}.
Hexadecimal constants also accept an optional fractional part
plus an optional binary exponent,
marked by a letter @Char{p} or @Char{P}.
+
A numeric constant with a radix point or an exponent
denotes a float;
otherwise,
-if its value fits in an integer,
-it denotes an integer.
+if its value fits in an integer or it is a hexadecimal constant,
+it denotes an integer;
+otherwise (that is, a decimal integer numeral that overflows),
+it denotes a float.
+(Hexadecimal integer numerals that overflow @emph{wrap around};
+they always denote an integer value.)
Examples of valid integer constants are
@verbatim{
3 345 0xff 0xBEBADA
@@ -1542,7 +1547,8 @@ If the value of the variable when it goes out of scope is a function,
that function is called;
otherwise, if the value has a @idx{__close} metamethod,
that metamethod is called;
-otherwise, nothing is done.
+otherwise, if the value is @nil, nothing is done;
+otherwise, an error is raised.
In the function case,
if the scope is being closed by an error,
the error object is passed as an argument to the function;
@@ -1665,7 +1671,7 @@ If both operands are integers,
the operation is performed over integers and the result is an integer.
Otherwise, if both operands are numbers,
then they are converted to floats,
-the operation is performed following the usual rules
+the operation is performed following the machine's rules
for floating-point arithmetic
(usually the @x{IEEE 754} standard),
and the result is a float.
@@ -4998,7 +5004,7 @@ This call leaves the final string on the top of the stack.
}
-If you know beforehand the total size of the resulting string,
+If you know beforehand the maximum size of the resulting string,
you can use the buffer like this:
@itemize{
@@ -5012,7 +5018,8 @@ size @id{sz} with a call @T{luaL_buffinitsize(L, &b, sz)}.}
@item{
Finish by calling @T{luaL_pushresultsize(&b, sz)},
where @id{sz} is the total size of the resulting string
-copied into that space.
+copied into that space (which may be smaller than or
+equal to the preallocated size).
}
}
@@ -5028,8 +5035,8 @@ when you call a buffer operation,
the stack is at the same level
it was immediately after the previous buffer operation.
(The only exception to this rule is @Lid{luaL_addvalue}.)
-After calling @Lid{luaL_pushresult} the stack is back to its
-level when the buffer was initialized,
+After calling @Lid{luaL_pushresult},
+the stack is back to its level when the buffer was initialized,
plus the final string on its top.
}
@@ -7118,7 +7125,7 @@ empty string as a match immediately after another match.
As an example,
consider the results of the following code:
@verbatim{
-> string.gsub("abc", "()a*()", print)
+> string.gsub("abc", "()a*()", print);
--> 1 2
--> 3 3
--> 4 4
diff --git a/testes/api.lua b/testes/api.lua
index ed857fd055..6f35e13249 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -366,7 +366,7 @@ do
-- "argerror" without frames
assert(T.checkpanic("loadstring 4") ==
"bad argument #4 (string expected, got no value)")
-
+
-- memory error
T.totalmem(T.totalmem()+10000) -- set low memory limit (+10k)
@@ -987,12 +987,12 @@ do
local a, b = T.testC([[
call 0 1 # create resource
- pushint 34
+ pushnil
toclose -2 # mark call result to be closed
- toclose -1 # mark number to be closed (will be ignored)
+ toclose -1 # mark nil to be closed (will be ignored)
return 2
]], newresource)
- assert(a[1] == 11 and b == 34)
+ assert(a[1] == 11 and b == nil)
assert(#openresource == 0) -- was closed
-- repeat the test, but calling function in a 'multret' context
@@ -1005,7 +1005,7 @@ do
assert(#openresource == 0) -- was closed
-- error
- local a, b = pcall(T.testC, [[
+ local a, b = pcall(T.makeCfunc[[
call 0 1 # create resource
toclose -1 # mark it to be closed
error # resource is the error object
@@ -1038,6 +1038,13 @@ do
]], newresource, check)
assert(a == 3) -- no extra items left in the stack
+ -- non-closable value
+ local a, b = pcall(T.makeCfunc[[
+ pushint 32
+ toclose -1
+ ]])
+ assert(not a and string.find(b, "(C temporary)"))
+
end
@@ -1249,9 +1256,9 @@ do -- closing state with no extra memory
T.closestate(L)
T.alloccount()
end
-
+
do -- garbage collection with no extra memory
- local L = T.newstate()
+ local L = T.newstate()
T.loadlib(L)
local res = (T.doremote(L, [[
_ENV = require"_G"
diff --git a/testes/db.lua b/testes/db.lua
index 5b243c3993..976962b02f 100644
--- a/testes/db.lua
+++ b/testes/db.lua
@@ -214,14 +214,14 @@ local function foo (a, ...)
local t = table.pack(...)
for i = 1, t.n do
local n, v = debug.getlocal(1, -i)
- assert(n == "(*vararg)" and v == t[i])
+ assert(n == "(vararg)" and v == t[i])
end
assert(not debug.getlocal(1, -(t.n + 1)))
assert(not debug.setlocal(1, -(t.n + 1), 30))
if t.n > 0 then
(function (x)
- assert(debug.setlocal(2, -1, x) == "(*vararg)")
- assert(debug.setlocal(2, -t.n, x) == "(*vararg)")
+ assert(debug.setlocal(2, -1, x) == "(vararg)")
+ assert(debug.setlocal(2, -t.n, x) == "(vararg)")
end)(430)
assert(... == 430)
end
@@ -328,9 +328,9 @@ assert(a[f] and a[g] and a[assert] and a[debug.getlocal] and not a[print])
-- tests for manipulating non-registered locals (C and Lua temporaries)
local n, v = debug.getlocal(0, 1)
-assert(v == 0 and n == "(*temporary)")
+assert(v == 0 and n == "(C temporary)")
local n, v = debug.getlocal(0, 2)
-assert(v == 2 and n == "(*temporary)")
+assert(v == 2 and n == "(C temporary)")
assert(not debug.getlocal(0, 3))
assert(not debug.getlocal(0, 0))
@@ -607,7 +607,7 @@ co = load[[
local a = 0
-- 'A' should be visible to debugger only after its complete definition
debug.sethook(function (e, l)
- if l == 3 then a = a + 1; assert(debug.getlocal(2, 1) == "(*temporary)")
+ if l == 3 then a = a + 1; assert(debug.getlocal(2, 1) == "(temporary)")
elseif l == 4 then a = a + 1; assert(debug.getlocal(2, 1) == "A")
end
end, "l")
@@ -875,15 +875,15 @@ local debug = require'debug'
local a = 12 -- a local variable
local n, v = debug.getlocal(1, 1)
-assert(n == "(*temporary)" and v == debug) -- unkown name but known value
+assert(n == "(temporary)" and v == debug) -- unkown name but known value
n, v = debug.getlocal(1, 2)
-assert(n == "(*temporary)" and v == 12) -- unkown name but known value
+assert(n == "(temporary)" and v == 12) -- unkown name but known value
-- a function with an upvalue
local f = function () local x; return a end
n, v = debug.getupvalue(f, 1)
-assert(n == "(*no name)" and v == 12)
-assert(debug.setupvalue(f, 1, 13) == "(*no name)")
+assert(n == "(no name)" and v == 12)
+assert(debug.setupvalue(f, 1, 13) == "(no name)")
assert(a == 13)
local t = debug.getinfo(f)
diff --git a/testes/locals.lua b/testes/locals.lua
index 90a8b8456e..24681dd9eb 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -266,6 +266,27 @@ do -- errors in __close
end
+do
+
+ -- errors due to non-closable values
+ local function foo ()
+ local *toclose x = 34
+ end
+ local stat, msg = pcall(foo)
+ assert(not stat and string.find(msg, "variable 'x'"))
+
+
+ -- with other errors, non-closable values are ignored
+ local function foo ()
+ local *toclose x = 34
+ local *toclose y = function () error(32) end
+ end
+ local stat, msg = pcall(foo)
+ assert(not stat and msg == 32)
+
+end
+
+
if rawget(_G, "T") then
-- memory error inside closing function
From 28d829c86712ce5bc453feccc5129a32f78d80c0 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 4 Dec 2018 15:01:42 -0200
Subject: [PATCH 105/889] Calls cannot be tail in the scope of a to-be-closed
variable
A to-be-closed variable must be closed when a block ends, so even
a 'return foo()' cannot directly returns the results of 'foo'; the
function must close the scope before returning.
---
lparser.c | 5 ++++-
lvm.c | 2 +-
manual/manual.of | 17 ++++++++---------
testes/locals.lua | 13 ++++++++-----
4 files changed, 21 insertions(+), 16 deletions(-)
diff --git a/lparser.c b/lparser.c
index e525e57816..eed8bffd93 100644
--- a/lparser.c
+++ b/lparser.c
@@ -53,6 +53,7 @@ typedef struct BlockCnt {
lu_byte nactvar; /* # active locals outside the block */
lu_byte upval; /* true if some variable in the block is an upvalue */
lu_byte isloop; /* true if 'block' is a loop */
+ lu_byte insidetbc; /* true if inside the scope of a to-be-closed var. */
} BlockCnt;
@@ -510,6 +511,7 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) {
bl->firstlabel = fs->ls->dyd->label.n;
bl->firstgoto = fs->ls->dyd->gt.n;
bl->upval = 0;
+ bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc);
bl->previous = fs->bl;
fs->bl = bl;
lua_assert(fs->freereg == fs->nactvar);
@@ -1631,6 +1633,7 @@ static void tocloselocalstat (LexState *ls) {
checknext(ls, '=');
exp1(ls, 0);
markupval(fs, fs->nactvar);
+ fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */
adjustlocalvars(ls, 1);
luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0);
}
@@ -1701,7 +1704,7 @@ static void retstat (LexState *ls) {
nret = explist(ls, &e); /* optional return values */
if (hasmultret(e.k)) {
luaK_setmultret(fs, &e);
- if (e.k == VCALL && nret == 1) { /* tail call? */
+ if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) { /* tail call? */
SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL);
lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar);
}
diff --git a/lvm.c b/lvm.c
index 50d967c6b8..fc8722a84f 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1565,7 +1565,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
if (nparams1) /* vararg function? */
delta = ci->u.l.nextraargs + nparams1;
/* close upvalues from current call */
- ProtectNT(luaF_close(L, base, LUA_OK));
+ luaF_close(L, base, -1); /* (no to-be-closed vars. here) */
updatestack(ci);
}
if (!ttisfunction(s2v(ra))) { /* not a function? */
diff --git a/manual/manual.of b/manual/manual.of
index 3902f2f359..8b5e5d93aa 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1538,9 +1538,6 @@ except that its value is @emph{closed} whenever the variable
goes out of scope, including normal block termination,
exiting its block by @Rw{break}/@Rw{goto}/@Rw{return},
or exiting by an error.
-If a block ends in a tail call @see{functioncall},
-all variables of the caller function go out of scope
-before the start of the callee function.
To \emph{close} a value has the following meaning here:
If the value of the variable when it goes out of scope is a function,
@@ -2038,8 +2035,8 @@ A call of the form @T{f'@rep{string}'}
is syntactic sugar for @T{f('@rep{string}')};
that is, the argument list is a single literal string.
-A call of the form @T{return @rep{functioncall}} is called
-a @def{tail call}.
+A call of the form @T{return @rep{functioncall}} not in the
+scope of a to-be-closed variable is called a @def{tail call}.
Lua implements @def{proper tail calls}
(or @emph{proper tail recursion}):
in a tail call,
@@ -2049,13 +2046,15 @@ a program can execute.
However, a tail call erases any debug information about the
calling function.
Note that a tail call only happens with a particular syntax,
-where the @Rw{return} has one single function call as argument;
-this syntax makes the calling function return exactly
-the returns of the called function.
+where the @Rw{return} has one single function call as argument,
+and it is outside the scope of any to-be-closed variable.
+This syntax makes the calling function return exactly
+the returns of the called function,
+without any intervening action.
So, none of the following examples are tail calls:
@verbatim{
return (f(x)) -- results adjusted to 1
-return 2 * f(x)
+return 2 * f(x) -- result multiplied by 2
return x, f(x) -- additional results
f(x); return -- results discarded
return x or f(x) -- results adjusted to 1
diff --git a/testes/locals.lua b/testes/locals.lua
index 24681dd9eb..340af61c0e 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -225,21 +225,24 @@ end
do
- -- to-be-closed variables must be closed in tail calls
+ -- calls cannot be tail in the scope of to-be-closed variables
local X, Y
local function foo ()
local *toclose _ = function () Y = 10 end
- assert(X == 20 and Y == nil)
+ assert(X == true and Y == nil) -- 'X' not closed yet
return 1,2,3
end
local function bar ()
- local *toclose _ = function () X = 20 end
- return foo()
+ local *toclose _ = function () X = false end
+ X = true
+ do
+ return foo() -- not a tail call!
+ end
end
local a, b, c, d = bar()
- assert(a == 1 and b == 2 and c == 3 and X == 20 and Y == 10 and d == nil)
+ assert(a == 1 and b == 2 and c == 3 and X == false and Y == 10 and d == nil)
end
From 46beca5bed8a7700b18100fe48a78373be5055f9 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 10 Dec 2018 13:46:03 -0200
Subject: [PATCH 106/889] Better error messages for some polymorphic functions
New auxiliary functions/macros 'luaL_argexpected'/'luaL_typeerror'
ease the creation of error messages such as
bad argument #2 to 'setmetatable' (nil or table expected, got boolean)
(The novelty being the "got boolean" part...)
---
lauxlib.c | 6 +++---
lauxlib.h | 5 +++++
lbaselib.c | 7 +++----
lcorolib.c | 2 +-
ldblib.c | 3 +--
lstrlib.c | 4 ++--
manual/manual.of | 26 ++++++++++++++++++++++++++
7 files changed, 41 insertions(+), 12 deletions(-)
diff --git a/lauxlib.c b/lauxlib.c
index fd4acbd109..769586b691 100644
--- a/lauxlib.c
+++ b/lauxlib.c
@@ -185,7 +185,7 @@ LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) {
}
-static int typeerror (lua_State *L, int arg, const char *tname) {
+int luaL_typeerror (lua_State *L, int arg, const char *tname) {
const char *msg;
const char *typearg; /* name for the type of the actual argument */
if (luaL_getmetafield(L, arg, "__name") == LUA_TSTRING)
@@ -200,7 +200,7 @@ static int typeerror (lua_State *L, int arg, const char *tname) {
static void tag_error (lua_State *L, int arg, int tag) {
- typeerror(L, arg, lua_typename(L, tag));
+ luaL_typeerror(L, arg, lua_typename(L, tag));
}
@@ -339,7 +339,7 @@ LUALIB_API void *luaL_testudata (lua_State *L, int ud, const char *tname) {
LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) {
void *p = luaL_testudata(L, ud, tname);
- if (p == NULL) typeerror(L, ud, tname);
+ luaL_argexpected(L, p != NULL, ud, tname);
return p;
}
diff --git a/lauxlib.h b/lauxlib.h
index 9ec0f5317b..e5d378aebe 100644
--- a/lauxlib.h
+++ b/lauxlib.h
@@ -48,6 +48,7 @@ LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e);
LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e);
LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len);
LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg);
+LUALIB_API int (luaL_typeerror) (lua_State *L, int arg, const char *tname);
LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg,
size_t *l);
LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg,
@@ -126,6 +127,10 @@ LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname,
#define luaL_argcheck(L, cond,arg,extramsg) \
((void)((cond) || luaL_argerror(L, (arg), (extramsg))))
+
+#define luaL_argexpected(L,cond,arg,tname) \
+ ((void)((cond) || luaL_typeerror(L, (arg), (tname))))
+
#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL))
#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL))
diff --git a/lbaselib.c b/lbaselib.c
index e776c2a25b..201c93e325 100644
--- a/lbaselib.c
+++ b/lbaselib.c
@@ -125,8 +125,7 @@ static int luaB_getmetatable (lua_State *L) {
static int luaB_setmetatable (lua_State *L) {
int t = lua_type(L, 2);
luaL_checktype(L, 1, LUA_TTABLE);
- luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2,
- "nil or table expected");
+ luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table");
if (luaL_getmetafield(L, 1, "__metatable") != LUA_TNIL)
return luaL_error(L, "cannot change a protected metatable");
lua_settop(L, 2);
@@ -145,8 +144,8 @@ static int luaB_rawequal (lua_State *L) {
static int luaB_rawlen (lua_State *L) {
int t = lua_type(L, 1);
- luaL_argcheck(L, t == LUA_TTABLE || t == LUA_TSTRING, 1,
- "table or string expected");
+ luaL_argexpected(L, t == LUA_TTABLE || t == LUA_TSTRING, 1,
+ "table or string");
lua_pushinteger(L, lua_rawlen(L, 1));
return 1;
}
diff --git a/lcorolib.c b/lcorolib.c
index 9038f9fb5d..34462b5356 100644
--- a/lcorolib.c
+++ b/lcorolib.c
@@ -20,7 +20,7 @@
static lua_State *getco (lua_State *L) {
lua_State *co = lua_tothread(L, 1);
- luaL_argcheck(L, co, 1, "thread expected");
+ luaL_argexpected(L, co, 1, "thread");
return co;
}
diff --git a/ldblib.c b/ldblib.c
index 200108426e..ada35250c3 100644
--- a/ldblib.c
+++ b/ldblib.c
@@ -55,8 +55,7 @@ static int db_getmetatable (lua_State *L) {
static int db_setmetatable (lua_State *L) {
int t = lua_type(L, 2);
- luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2,
- "nil or table expected");
+ luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table");
lua_settop(L, 2);
lua_setmetatable(L, 1);
return 1; /* return 1st argument */
diff --git a/lstrlib.c b/lstrlib.c
index a635e9d4d6..e9c60c0f3e 100644
--- a/lstrlib.c
+++ b/lstrlib.c
@@ -857,9 +857,9 @@ static int str_gsub (lua_State *L) {
lua_Integer n = 0; /* replacement count */
MatchState ms;
luaL_Buffer b;
- luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING ||
+ luaL_argexpected(L, tr == LUA_TNUMBER || tr == LUA_TSTRING ||
tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3,
- "string/function/table expected");
+ "string/function/table");
luaL_buffinit(L, &b);
if (anchor) {
p++; lp--; /* skip anchor character */
diff --git a/manual/manual.of b/manual/manual.of
index 8b5e5d93aa..0e8e3d72db 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -4979,6 +4979,19 @@ This function never returns.
}
+@APIEntry{
+void luaL_argexpected (lua_State *L,
+ int cond,
+ int arg,
+ const char *tname);|
+@apii{0,0,v}
+
+Checks whether @id{cond} is true.
+If it is not, raises an error about the type of the argument @id{arg}
+with a standard message @seeF{luaL_typeerror}.
+
+}
+
@APIEntry{typedef struct luaL_Buffer luaL_Buffer;|
Type for a @def{string buffer}.
@@ -5713,6 +5726,19 @@ to start the traceback.
}
+@APIEntry{const char *luaL_typeerror (lua_State *L,
+ int arg,
+ const char *tname);|
+@apii{0,0,v}
+
+Raises a type error for argument @id{arg}
+of the @N{C function} that called it,
+using a standard message;
+@id{tname} is a @Q{name} for the expected type.
+This function never returns.
+
+}
+
@APIEntry{const char *luaL_typename (lua_State *L, int index);|
@apii{0,0,-}
From 51316f9df7aacb54633a3e9b910a070590ac6259 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 11 Dec 2018 11:34:47 -0200
Subject: [PATCH 107/889] 'math.rand()' uses higher bits to produce float value
The call 'math.rand()' converts the higher bits of the internal unsigned
integer random to a float, instead of its lower bits. That ensures that
Lua compiled with different float precisions always generates equal (up
to the available precision) random numbers when given the same seed.
---
lmathlib.c | 58 ++++++++++++++++++++++++++++++++-----------------
testes/math.lua | 14 +++++++-----
2 files changed, 46 insertions(+), 26 deletions(-)
diff --git a/lmathlib.c b/lmathlib.c
index e8e88e7a5b..f2bfff9b14 100644
--- a/lmathlib.c
+++ b/lmathlib.c
@@ -323,14 +323,18 @@ static Rand64 nextrand (Rand64 *state) {
/*
** Convert bits from a random integer into a float in the
-** interval [0,1).
+** interval [0,1), getting the higher FIG bits from the
+** random unsigned integer and converting that to a float.
*/
-#define maskFIG (~(~(Rand64)1 << (FIGS - 1))) /* use FIGS bits */
-#define shiftFIG \
- (l_mathop(0.5) / ((Rand64)1 << (FIGS - 1))) /* 2^(-FIGS) */
+
+/* must throw out the extra (64 - FIGS) bits */
+#define shift64_FIG (64 - FIGS)
+
+/* to scale to [0, 1), multiply by scaleFIG = 2^(-FIGS) */
+#define scaleFIG (l_mathop(0.5) / ((Rand64)1 << (FIGS - 1)))
static lua_Number I2d (Rand64 x) {
- return (lua_Number)(x & maskFIG) * shiftFIG;
+ return (lua_Number)(trim64(x) >> shift64_FIG) * scaleFIG;
}
/* convert a 'Rand64' to a 'lua_Unsigned' */
@@ -449,35 +453,49 @@ static Rand64 nextrand (Rand64 *state) {
/* an unsigned 1 with proper type */
#define UONE ((lu_int32)1)
+
#if FIGS <= 32
-#define maskHI 0 /* do not need bits from higher half */
-#define maskLOW (~(~UONE << (FIGS - 1))) /* use FIGS bits */
-#define shiftFIG (l_mathop(0.5) / (UONE << (FIGS - 1))) /* 2^(-FIGS) */
+/* 2^(-FIGS) */
+#define scaleFIG (l_mathop(0.5) / (UONE << (FIGS - 1)))
+
+/*
+** get up to 32 bits from higher half, shifting right to
+** throw out the extra bits.
+*/
+static lua_Number I2d (Rand64 x) {
+ lua_Number h = (lua_Number)(trim32(x.h) >> (32 - FIGS));
+ return h * scaleFIG;
+}
#else /* 32 < FIGS <= 64 */
/* must take care to not shift stuff by more than 31 slots */
-/* use FIGS - 32 bits from higher half */
-#define maskHI (~(~UONE << (FIGS - 33)))
+/* 2^(-FIGS) = 1.0 / 2^30 / 2^3 / 2^(FIGS-33) */
+#define scaleFIG \
+ ((lua_Number)1.0 / (UONE << 30) / 8.0 / (UONE << (FIGS - 33)))
-/* use 32 bits from lower half */
-#define maskLOW (~(~UONE << 31))
-
-/* 2^(-FIGS) == (1 / 2^33) / 2^(FIGS-33) */
-#define shiftFIG ((lua_Number)(1.0 / 8589934592.0) / (UONE << (FIGS - 33)))
+/*
+** use FIGS - 32 bits from lower half, throwing out the other
+** (32 - (FIGS - 32)) = (64 - FIGS) bits
+*/
+#define shiftLOW (64 - FIGS)
-#endif
+/*
+** higher 32 bits go after those (FIGS - 32) bits: shiftHI = 2^(FIGS - 32)
+*/
+#define shiftHI ((lua_Number)(UONE << (FIGS - 33)) * 2.0)
-#define twoto32 l_mathop(4294967296.0) /* 2^32 */
static lua_Number I2d (Rand64 x) {
- lua_Number h = (lua_Number)(x.h & maskHI);
- lua_Number l = (lua_Number)(x.l & maskLOW);
- return (h * twoto32 + l) * shiftFIG;
+ lua_Number h = (lua_Number)trim32(x.h) * shiftHI;
+ lua_Number l = (lua_Number)(trim32(x.l) >> shiftLOW);
+ return (h + l) * scaleFIG;
}
+#endif
+
/* convert a 'Rand64' to a 'lua_Unsigned' */
static lua_Unsigned I2UInt (Rand64 x) {
diff --git a/testes/math.lua b/testes/math.lua
index 7c780e592a..dc5b84f680 100644
--- a/testes/math.lua
+++ b/testes/math.lua
@@ -823,17 +823,19 @@ do
assert(random(0) == res)
math.randomseed(1007, 0)
- -- using lower bits to generate random floats; (the '% 2^32' converts
+ -- using higher bits to generate random floats; (the '% 2^32' converts
-- 32-bit integers to floats as unsigned)
local res
if floatbits <= 32 then
- -- get all bits from the lower half
- res = (l & ~(~0 << floatbits)) % 2^32
+ -- get all bits from the higher half
+ res = (h >> (32 - floatbits)) % 2^32
else
- -- get 32 bits from the lower half and the rest from the higher half
- res = ((h & ~(~0 << (floatbits - 32))) % 2^32) * 2^32 + (l % 2^32)
+ -- get 32 bits from the higher half and the rest from the lower half
+ res = (h % 2^32) * 2^(floatbits - 32) + ((l >> (64 - floatbits)) % 2^32)
end
- assert(random() * 2^floatbits == res)
+ local rand = random()
+ assert(eq(rand, 0x0.7a7040a5a323c9d6, 2^-floatbits))
+ assert(rand * 2^floatbits == res)
end
math.randomseed(0, os.time())
From 3b06f983ae0e57b90cdeb500c84bb524e5c3635b Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 11 Dec 2018 11:54:14 -0200
Subject: [PATCH 108/889] Details
- in 'luaB_tonumber', do not need to "checkany" when argument
is a number.
- in 'lua_resume', the call to 'luaD_rawrunprotected' cannot return
a status equal to -1.
---
lbaselib.c | 2 +-
ldo.c | 29 +++++++++++++----------------
2 files changed, 14 insertions(+), 17 deletions(-)
diff --git a/lbaselib.c b/lbaselib.c
index 201c93e325..7c0ebfecd3 100644
--- a/lbaselib.c
+++ b/lbaselib.c
@@ -68,7 +68,6 @@ static const char *b_str2int (const char *s, int base, lua_Integer *pn) {
static int luaB_tonumber (lua_State *L) {
if (lua_isnoneornil(L, 2)) { /* standard conversion? */
- luaL_checkany(L, 1);
if (lua_type(L, 1) == LUA_TNUMBER) { /* already a number? */
lua_settop(L, 1); /* yes; return it */
return 1;
@@ -79,6 +78,7 @@ static int luaB_tonumber (lua_State *L) {
if (s != NULL && lua_stringtonumber(L, s) == l + 1)
return 1; /* successful conversion to number */
/* else not a number */
+ luaL_checkany(L, 1); /* (but there must be some parameter) */
}
}
else {
diff --git a/ldo.c b/ldo.c
index 2762fefa0b..bdd7fb6db2 100644
--- a/ldo.c
+++ b/ldo.c
@@ -678,22 +678,19 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
L->nny = 0; /* allow yields */
api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs);
status = luaD_rawrunprotected(L, resume, &nargs);
- if (unlikely(status == -1)) /* error calling 'lua_resume'? */
- status = LUA_ERRRUN;
- else { /* continue running after recoverable errors */
- while (errorstatus(status) && recover(L, status)) {
- /* unroll continuation */
- status = luaD_rawrunprotected(L, unroll, &status);
- }
- if (likely(!errorstatus(status)))
- lua_assert(status == L->status); /* normal end or yield */
- else { /* unrecoverable error */
- status = luaF_close(L, L->stack, status); /* close all upvalues */
- L->status = cast_byte(status); /* mark thread as 'dead' */
- luaD_seterrorobj(L, status, L->stack + 1); /* push error message */
- L->ci = &L->base_ci; /* back to the original C level */
- L->ci->top = L->top;
- }
+ /* continue running after recoverable errors */
+ while (errorstatus(status) && recover(L, status)) {
+ /* unroll continuation */
+ status = luaD_rawrunprotected(L, unroll, &status);
+ }
+ if (likely(!errorstatus(status)))
+ lua_assert(status == L->status); /* normal end or yield */
+ else { /* unrecoverable error */
+ status = luaF_close(L, L->stack, status); /* close all upvalues */
+ L->status = cast_byte(status); /* mark thread as 'dead' */
+ luaD_seterrorobj(L, status, L->stack + 1); /* push error message */
+ L->ci = &L->base_ci; /* back to the original C level */
+ L->ci->top = L->top;
}
*nresults = (status == LUA_YIELD) ? L->ci->u2.nyield
: cast_int(L->top - (L->ci->func + 1));
From fdc25a1ebfe9968dcec390dd556375105aa0be40 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 13 Dec 2018 13:07:53 -0200
Subject: [PATCH 109/889] New functions 'lua_resetthread' and 'coroutine.kill'
New functions to reset/kill a thread/coroutine, mainly (only?) to
close any pending to-be-closed variable. ('lua_resetthread' also
allows a thread to be reused...)
---
lcorolib.c | 58 +++++++++++++++++++++++++++++++++++---------
ldo.c | 4 +++
lfunc.c | 18 ++++++++------
lfunc.h | 11 +++++++++
lstate.c | 27 +++++++++++++++++++--
ltests.c | 3 +++
lua.h | 1 +
lvm.c | 2 +-
manual/manual.of | 34 +++++++++++++++++++++++---
testes/coroutine.lua | 45 ++++++++++++++++++++++++++++++++++
testes/main.lua | 24 ++++++++++++++----
11 files changed, 195 insertions(+), 32 deletions(-)
diff --git a/lcorolib.c b/lcorolib.c
index 34462b5356..cdb5fedc93 100644
--- a/lcorolib.c
+++ b/lcorolib.c
@@ -107,29 +107,40 @@ static int luaB_yield (lua_State *L) {
}
-static int luaB_costatus (lua_State *L) {
- lua_State *co = getco(L);
- if (L == co) lua_pushliteral(L, "running");
+#define COS_RUN 0
+#define COS_DEAD 1
+#define COS_YIELD 2
+#define COS_NORM 3
+
+
+static const char *statname[] = {"running", "dead", "suspended", "normal"};
+
+
+static int auxstatus (lua_State *L, lua_State *co) {
+ if (L == co) return COS_RUN;
else {
switch (lua_status(co)) {
case LUA_YIELD:
- lua_pushliteral(L, "suspended");
- break;
+ return COS_YIELD;
case LUA_OK: {
lua_Debug ar;
- if (lua_getstack(co, 0, &ar) > 0) /* does it have frames? */
- lua_pushliteral(L, "normal"); /* it is running */
+ if (lua_getstack(co, 0, &ar)) /* does it have frames? */
+ return COS_NORM; /* it is running */
else if (lua_gettop(co) == 0)
- lua_pushliteral(L, "dead");
+ return COS_DEAD;
else
- lua_pushliteral(L, "suspended"); /* initial state */
- break;
+ return COS_YIELD; /* initial state */
}
default: /* some error occurred */
- lua_pushliteral(L, "dead");
- break;
+ return COS_DEAD;
}
}
+}
+
+
+static int luaB_costatus (lua_State *L) {
+ lua_State *co = getco(L);
+ lua_pushstring(L, statname[auxstatus(L, co)]);
return 1;
}
@@ -147,6 +158,28 @@ static int luaB_corunning (lua_State *L) {
}
+static int luaB_kill (lua_State *L) {
+ lua_State *co = getco(L);
+ int status = auxstatus(L, co);
+ switch (status) {
+ case COS_DEAD: case COS_YIELD: {
+ status = lua_resetthread(co);
+ if (status == LUA_OK) {
+ lua_pushboolean(L, 1);
+ return 1;
+ }
+ else {
+ lua_pushboolean(L, 0);
+ lua_xmove(co, L, 1); /* copy error message */
+ return 2;
+ }
+ }
+ default: /* normal or running coroutine */
+ return luaL_error(L, "cannot kill a %s coroutine", statname[status]);
+ }
+}
+
+
static const luaL_Reg co_funcs[] = {
{"create", luaB_cocreate},
{"resume", luaB_coresume},
@@ -155,6 +188,7 @@ static const luaL_Reg co_funcs[] = {
{"wrap", luaB_cowrap},
{"yield", luaB_yield},
{"isyieldable", luaB_yieldable},
+ {"kill", luaB_kill},
{NULL, NULL}
};
diff --git a/ldo.c b/ldo.c
index bdd7fb6db2..056fef0cae 100644
--- a/ldo.c
+++ b/ldo.c
@@ -98,6 +98,10 @@ void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {
setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling"));
break;
}
+ case CLOSEPROTECT: {
+ setnilvalue(s2v(oldtop)); /* no error message */
+ break;
+ }
default: {
setobjs2s(L, oldtop, L->top - 1); /* error message on current top */
break;
diff --git a/lfunc.c b/lfunc.c
index 11d2850f76..bdf3cd2598 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -127,17 +127,18 @@ static int prepclosingmethod (lua_State *L, TValue *obj, TValue *err) {
/*
-** Prepare and call a closing method. If status is OK, code is
-** still inside the original protected call, and so any error
-** will be handled there. Otherwise, a previous error already
-** activated original protected call, and so the call to the
-** closing method must be protected here.
+** Prepare and call a closing method. If status is OK, code is still
+** inside the original protected call, and so any error will be handled
+** there. Otherwise, a previous error already activated original
+** protected call, and so the call to the closing method must be
+** protected here. (A status = CLOSEPROTECT behaves like a previous
+** error, to also run the closing method in protected mode).
** If status is OK, the call to the closing method will be pushed
** at the top of the stack. Otherwise, values are pushed after
** the 'level' of the upvalue being closed, as everything after
** that won't be used again.
*/
-static int closeupval (lua_State *L, TValue *uv, StkId level, int status) {
+static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) {
if (likely(status == LUA_OK)) {
if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */
callclose(L, NULL); /* call closing method */
@@ -207,9 +208,10 @@ int luaF_close (lua_State *L, StkId level, int status) {
if (!iswhite(uv))
gray2black(uv); /* closed upvalues cannot be gray */
luaC_barrier(L, uv, slot);
- if (status >= 0 && uv->tt == LUA_TUPVALTBC) { /* must be closed? */
+ if (uv->tt == LUA_TUPVALTBC && status != NOCLOSINGMETH) {
+ /* must run closing method */
ptrdiff_t levelrel = savestack(L, level);
- status = closeupval(L, uv->v, upl, status); /* may realloc. the stack */
+ status = callclosemth(L, uv->v, upl, status); /* may change the stack */
level = restorestack(L, levelrel);
}
}
diff --git a/lfunc.h b/lfunc.h
index c9fe13148a..0ed79c48ab 100644
--- a/lfunc.h
+++ b/lfunc.h
@@ -42,6 +42,17 @@
#define MAXMISS 10
+/*
+** Special "status" for 'luaF_close'
+*/
+
+/* close upvalues without running their closing methods */
+#define NOCLOSINGMETH (-1)
+
+/* close upvalues running all closing methods in protected mode */
+#define CLOSEPROTECT (-2)
+
+
LUAI_FUNC Proto *luaF_newproto (lua_State *L);
LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems);
LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems);
diff --git a/lstate.c b/lstate.c
index 9d39995964..5ee024fcab 100644
--- a/lstate.c
+++ b/lstate.c
@@ -258,7 +258,7 @@ static void preinit_thread (lua_State *L, global_State *g) {
static void close_state (lua_State *L) {
global_State *g = G(L);
- luaF_close(L, L->stack, -1); /* close all upvalues for this thread */
+ luaF_close(L, L->stack, CLOSEPROTECT); /* close all upvalues */
luaC_freeallobjects(L); /* collect all objects */
if (ttisnil(&g->nilvalue)) /* closing a fully built state? */
luai_userstateclose(L);
@@ -301,7 +301,7 @@ LUA_API lua_State *lua_newthread (lua_State *L) {
void luaE_freethread (lua_State *L, lua_State *L1) {
LX *l = fromstate(L1);
- luaF_close(L1, L1->stack, -1); /* close all upvalues for this thread */
+ luaF_close(L1, L1->stack, NOCLOSINGMETH); /* close all upvalues */
lua_assert(L1->openupval == NULL);
luai_userstatefree(L, L1);
freestack(L1);
@@ -309,6 +309,29 @@ void luaE_freethread (lua_State *L, lua_State *L1) {
}
+int lua_resetthread (lua_State *L) {
+ CallInfo *ci;
+ int status;
+ lua_lock(L);
+ ci = &L->base_ci;
+ status = luaF_close(L, L->stack, CLOSEPROTECT);
+ setnilvalue(s2v(L->stack)); /* 'function' entry for basic 'ci' */
+ if (status != CLOSEPROTECT) /* real errors? */
+ luaD_seterrorobj(L, status, L->stack + 1);
+ else {
+ status = LUA_OK;
+ L->top = L->stack + 1;
+ }
+ ci->callstatus = CIST_C;
+ ci->func = L->stack;
+ ci->top = L->top + LUA_MINSTACK;
+ L->ci = ci;
+ L->status = status;
+ lua_unlock(L);
+ return status;
+}
+
+
LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
int i;
lua_State *L;
diff --git a/ltests.c b/ltests.c
index 63d423e08e..a38a8926f0 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1366,6 +1366,9 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) {
else if EQ("newthread") {
lua_newthread(L1);
}
+ else if EQ("resetthread") {
+ lua_pushinteger(L1, lua_resetthread(L1));
+ }
else if EQ("newuserdata") {
lua_newuserdata(L1, getnum);
}
diff --git a/lua.h b/lua.h
index 6aa184d18b..95ce5a2e9b 100644
--- a/lua.h
+++ b/lua.h
@@ -147,6 +147,7 @@ extern const char lua_ident[];
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
LUA_API void (lua_close) (lua_State *L);
LUA_API lua_State *(lua_newthread) (lua_State *L);
+LUA_API int (lua_resetthread) (lua_State *L);
LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf);
diff --git a/lvm.c b/lvm.c
index fc8722a84f..652095dcd0 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1565,7 +1565,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
if (nparams1) /* vararg function? */
delta = ci->u.l.nextraargs + nparams1;
/* close upvalues from current call */
- luaF_close(L, base, -1); /* (no to-be-closed vars. here) */
+ luaF_close(L, base, LUA_OK);
updatestack(ci);
}
if (!ttisfunction(s2v(ra))) { /* not a function? */
diff --git a/manual/manual.of b/manual/manual.of
index 0e8e3d72db..862d032bcd 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -3927,6 +3927,19 @@ and then pops the top element.
}
+@APIEntry{int lua_resetthread (lua_State *L);|
+@apii{0,?,-}
+
+Resets a thread, cleaning its call stack and closing all pending
+to-be-closed variables.
+Returns a status code:
+@Lid{LUA_OK} for no errors in closing methods,
+or an error status otherwise.
+In case of error,
+leave the error object on the stack,
+
+}
+
@APIEntry{int lua_resume (lua_State *L, lua_State *from, int nargs,
int *nresults);|
@apii{?,?,-}
@@ -3948,11 +3961,8 @@ or returned by the body function.
@Lid{LUA_OK} if the coroutine finishes its execution
without errors,
or an error code in case of errors @seeC{lua_pcall}.
-
In case of errors,
-the stack is not unwound,
-so you can use the debug API over it.
-The error object is on the top of the stack.
+the error object is on the top of the stack.
To resume a coroutine,
you remove all results from the last @Lid{lua_yield},
@@ -6285,6 +6295,17 @@ it is not inside a non-yieldable @N{C function}.
}
+@LibEntry{coroutine.kill(co)|
+
+Kills coroutine @id{co},
+closing all its pending to-be-closed variables
+and putting the coroutine in a dead state.
+In case of error closing some variable,
+returns @false plus the error object;
+otherwise returns @true.
+
+}
+
@LibEntry{coroutine.resume (co [, val1, @Cdots])|
Starts or continues the execution of coroutine @id{co}.
@@ -8648,6 +8669,11 @@ has been removed.
When needed, this metamethod must be explicitly defined.
}
+@item{
+When a coroutine finishes with an error,
+its stack is unwound (to run any pending closing methods).
+}
+
}
}
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index 7d42eadde0..5674a4dd14 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -119,6 +119,51 @@ end
assert(#a == 25 and a[#a] == 97)
x, a = nil
+
+-- coroutine kill
+do
+ -- ok to kill a dead coroutine
+ local co = coroutine.create(print)
+ assert(coroutine.resume(co, "testing 'coroutine.kill'"))
+ assert(coroutine.status(co) == "dead")
+ assert(coroutine.kill(co))
+
+ -- cannot kill the running coroutine
+ local st, msg = pcall(coroutine.kill, coroutine.running())
+ assert(not st and string.find(msg, "running"))
+
+ local main = coroutine.running()
+
+ -- cannot kill a "normal" coroutine
+ ;(coroutine.wrap(function ()
+ local st, msg = pcall(coroutine.kill, main)
+ assert(not st and string.find(msg, "normal"))
+ end))()
+
+ -- to-be-closed variables in coroutines
+ local X
+ co = coroutine.create(function ()
+ local *toclose x = function (err) assert(err == nil); X = false end
+ X = true
+ coroutine.yield()
+ end)
+ coroutine.resume(co)
+ assert(X)
+ assert(coroutine.kill(co))
+ assert(not X and coroutine.status(co) == "dead")
+
+ -- error killing a coroutine
+ co = coroutine.create(function()
+ local *toclose x = function (err) assert(err == nil); error(111) end
+ coroutine.yield()
+ end)
+ coroutine.resume(co)
+ local st, msg = coroutine.kill(co)
+ assert(not st and coroutine.status(co) == "dead" and msg == 111)
+
+end
+
+
-- yielding across C boundaries
co = coroutine.wrap(function()
diff --git a/testes/main.lua b/testes/main.lua
index c7bde0d962..b9dcab1c12 100644
--- a/testes/main.lua
+++ b/testes/main.lua
@@ -254,15 +254,15 @@ NoRun("error object is a table value", [[lua %s]], prog)
-- chunk broken in many lines
-s = [=[ --
-function f ( x )
+s = [=[ --
+function f ( x )
local a = [[
xuxu
]]
local b = "\
xuxu\n"
if x == 11 then return 1 + 12 , 2 + 20 end --[[ test multiple returns ]]
- return x + 1
+ return x + 1
--\\
end
return( f( 100 ) )
@@ -272,10 +272,10 @@ s = string.gsub(s, ' ', '\n\n') -- change all spaces for newlines
prepfile(s)
RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
checkprogout("101\n13\t22\n\n")
-
+
prepfile[[#comment in 1st line without \n at the end]]
RUN('lua %s', prog)
-
+
prepfile[[#test line number when file starts with comment line
debug = require"debug"
print(debug.getinfo(1).currentline)
@@ -306,6 +306,20 @@ NoRun("", "lua %s", prog) -- no message
prepfile("os.exit(false, true)")
NoRun("", "lua %s", prog) -- no message
+
+-- to-be-closed variables in main chunk
+prepfile[[
+ local *toclose x = function (err)
+ assert(err == 120)
+ print("Ok")
+ end
+ local *toclose e1 = function () error(120) end
+ os.exit(true, true)
+]]
+RUN('lua %s > %s', prog, out)
+checkprogout("Ok")
+
+
-- remove temporary files
assert(os.remove(prog))
assert(os.remove(otherprog))
From 57f5b81da9f1f23380d20f164012e10c5f4fef94 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 14 Dec 2018 13:12:01 -0200
Subject: [PATCH 110/889] Bug: Long brackets with a huge number of '=' causes
overflow
A long bracket with too many equal signs can overflow the 'int' used for
the counting and some arithmetic done on the value. Changing the counter
to 'size_t' avoids that. (Because what is counted goes to a buffer, an
overflow in the counter will first raise a buffer-overflow error.)
---
bugs | 19 +++++++++++++++++++
llex.c | 30 ++++++++++++++++--------------
2 files changed, 35 insertions(+), 14 deletions(-)
diff --git a/bugs b/bugs
index d7a717c38f..a965025b66 100644
--- a/bugs
+++ b/bugs
@@ -4017,6 +4017,25 @@ patch = [[
+--[=[
+Bug{
+what = [[Long brackets with a huge number of '=' overflow some
+internal buffer arithmetic]],
+report = [[Marco, 2018/12/12]],
+since = [[5.1]],
+fix = nil,
+example = [[
+local eqs = string.rep("=", 0x3ffffffe)
+local code = "return [" .. eqs .. "[a]" .. eqs .. "]"
+print(#assert(load(code))())
+]],
+patch = [[
+]]
+}
+]=]
+
+
+
--[=[
Bug{
diff --git a/llex.c b/llex.c
index 4a25607cf3..38c6d92d03 100644
--- a/llex.c
+++ b/llex.c
@@ -244,12 +244,12 @@ static int read_numeral (LexState *ls, SemInfo *seminfo) {
/*
-** skip a sequence '[=*[' or ']=*]'; if sequence is well formed, return
-** its number of '='s; otherwise, return a negative number (-1 iff there
-** are no '='s after initial bracket)
+** reads a sequence '[=*[' or ']=*]', leaving the last bracket.
+** If sequence is well formed, return its number of '='s + 2; otherwise,
+** return 1 if there is no '='s or 0 otherwise (an unfinished '[==...').
*/
-static int skip_sep (LexState *ls) {
- int count = 0;
+static size_t skip_sep (LexState *ls) {
+ size_t count = 0;
int s = ls->current;
lua_assert(s == '[' || s == ']');
save_and_next(ls);
@@ -257,11 +257,13 @@ static int skip_sep (LexState *ls) {
save_and_next(ls);
count++;
}
- return (ls->current == s) ? count : (-count) - 1;
+ return (ls->current == s) ? count + 2
+ : (count == 0) ? 1
+ : 0;
}
-static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) {
+static void read_long_string (LexState *ls, SemInfo *seminfo, size_t sep) {
int line = ls->linenumber; /* initial line (for error message) */
save_and_next(ls); /* skip 2nd '[' */
if (currIsNewline(ls)) /* string starts with a newline? */
@@ -295,8 +297,8 @@ static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) {
}
} endloop:
if (seminfo)
- seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + (2 + sep),
- luaZ_bufflen(ls->buff) - 2*(2 + sep));
+ seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + sep,
+ luaZ_bufflen(ls->buff) - 2 * sep);
}
@@ -444,9 +446,9 @@ static int llex (LexState *ls, SemInfo *seminfo) {
/* else is a comment */
next(ls);
if (ls->current == '[') { /* long comment? */
- int sep = skip_sep(ls);
+ size_t sep = skip_sep(ls);
luaZ_resetbuffer(ls->buff); /* 'skip_sep' may dirty the buffer */
- if (sep >= 0) {
+ if (sep >= 2) {
read_long_string(ls, NULL, sep); /* skip long comment */
luaZ_resetbuffer(ls->buff); /* previous call may dirty the buff. */
break;
@@ -458,12 +460,12 @@ static int llex (LexState *ls, SemInfo *seminfo) {
break;
}
case '[': { /* long string or simply '[' */
- int sep = skip_sep(ls);
- if (sep >= 0) {
+ size_t sep = skip_sep(ls);
+ if (sep >= 2) {
read_long_string(ls, seminfo, sep);
return TK_STRING;
}
- else if (sep != -1) /* '[=...' missing second bracket */
+ else if (sep == 0) /* '[=...' missing second bracket? */
lexerror(ls, "invalid long string delimiter", TK_STRING);
return '[';
}
From 2258f3133b27a6a3db703644311e0a59b6c7b0f6 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 14 Dec 2018 13:49:02 -0200
Subject: [PATCH 111/889] Added file 'testes/heavy.lua'
This file is not part of the regular tests. It tests error conditions
that demand too much memory or too much time to create:
* string with too many characters
* control structure with body too large
* chunk with too many lines
* identifier with too many characters
* chunks with too many instructions
* function with too many constants
* too many strings internalized
* table with too many entries
In machines with limited memory (less than 150 GB), many tests run up
to a "not enough memory" error. We need some memory (~256 GB) to
run all tests up to their intrinsic limits.
---
testes/heavy.lua | 173 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 173 insertions(+)
create mode 100644 testes/heavy.lua
diff --git a/testes/heavy.lua b/testes/heavy.lua
new file mode 100644
index 0000000000..4731c7472f
--- /dev/null
+++ b/testes/heavy.lua
@@ -0,0 +1,173 @@
+-- $Id: heavy.lua,v 1.7 2017/12/29 15:42:15 roberto Exp $
+-- See Copyright Notice in file all.lua
+
+local function teststring ()
+ print("creating a string too long")
+ do
+ local a = "x"
+ local st, msg = pcall(function ()
+ while true do
+ a = a .. a.. a.. a.. a.. a.. a.. a.. a.. a
+ .. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
+ .. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
+ .. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
+ .. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
+ .. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
+ .. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
+ .. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
+ .. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
+ .. a .. a.. a.. a.. a.. a.. a.. a.. a.. a
+ print(string.format("string with %d bytes", #a))
+ end
+ end)
+ assert(not st and
+ (string.find(msg, "string length overflow") or
+ string.find(msg, "not enough memory")))
+ print("string length overflow with " .. #a * 100)
+ end
+ print('+')
+end
+
+local function loadrep (x, what)
+ local p = 1<<20
+ local s = string.rep(x, p)
+ local count = 0
+ local function f()
+ count = count + p
+ if count % (0x80*p) == 0 then
+ io.stderr:write("(", count // 2^20, " M)")
+ end
+ return s
+ end
+ local st, msg = load(f, "=big")
+ print("\nmemory: ", collectgarbage'count' * 1024)
+ msg = string.match(msg, "^[^\n]+") -- get only first line
+ print(string.format("total: 0x%x %s ('%s')", count, what, msg))
+ return st, msg
+end
+
+
+function controlstruct ()
+ print("control structure too long")
+ local lim = ((1 << 24) - 2) // 3
+ local s = string.rep("a = a + 1\n", lim)
+ s = "while true do " .. s .. "end"
+ assert(load(s))
+ print("ok with " .. lim .. " lines")
+ lim = lim + 3
+ s = string.rep("a = a + 1\n", lim)
+ s = "while true do " .. s .. "end"
+ local st, msg = load(s)
+ assert(not st and string.find(msg, "too long"))
+ print(msg)
+end
+
+
+function manylines ()
+ print("loading chunk with too many lines")
+ local st, msg = loadrep("\n", "lines")
+ assert(not st and string.find(msg, "too many lines"))
+ print('+')
+end
+
+
+function hugeid ()
+ print("loading chunk with huge identifier")
+ local st, msg = loadrep("a", "chars")
+ assert(not st and
+ (string.find(msg, "lexical element too long") or
+ string.find(msg, "not enough memory")))
+ print('+')
+end
+
+function toomanyinst ()
+ print("loading chunk with too many instructions")
+ local st, msg = loadrep("a = 10; ", "instructions")
+ print('+')
+end
+
+
+local function loadrepfunc (prefix, f)
+ local count = -1
+ local function aux ()
+ count = count + 1
+ if count == 0 then
+ return prefix
+ else
+ if count % (0x100000) == 0 then
+ io.stderr:write("(", count // 2^20, " M)")
+ end
+ return f(count)
+ end
+ end
+ local st, msg = load(aux, "k")
+ print("\nmemory: ", collectgarbage'count' * 1024)
+ msg = string.match(msg, "^[^\n]+") -- get only first line
+ print("expected error: ", msg)
+end
+
+
+function toomanyconst ()
+ print("loading function with too many constants")
+ loadrepfunc("function foo () return {0,",
+ function (n)
+ -- convert 'n' to a string in the format [["...",]],
+ -- where '...' is a kind of number in base 128
+ -- (in a range that does not include either the double quote
+ -- and the escape.)
+ return string.char(34,
+ ((n // 128^0) & 127) + 128,
+ ((n // 128^1) & 127) + 128,
+ ((n // 128^2) & 127) + 128,
+ ((n // 128^3) & 127) + 128,
+ ((n // 128^4) & 127) + 128,
+ 34, 44)
+ end)
+end
+
+
+function toomanystr ()
+ local a = {}
+ local st, msg = pcall(function ()
+ for i = 1, math.huge do
+ if i % (0x100000) == 0 then
+ io.stderr:write("(", i // 2^20, " M)")
+ end
+ a[i] = string.pack("I", i)
+ end
+ end)
+ local size = #a
+ a = collectgarbage'count'
+ print("\nmemory:", a * 1024)
+ print("expected error:", msg)
+ print("size:", size)
+end
+
+
+function toomanyidx ()
+ local a = {}
+ local st, msg = pcall(function ()
+ for i = 1, math.huge do
+ if i % (0x100000) == 0 then
+ io.stderr:write("(", i // 2^20, " M)")
+ end
+ a[i] = i
+ end
+ end)
+ print("\nmemory: ", collectgarbage'count' * 1024)
+ print("expected error: ", msg)
+ print("size:", #a)
+end
+
+
+
+-- teststring()
+-- controlstruct()
+-- manylines()
+-- hugeid()
+-- toomanyinst()
+-- toomanyconst()
+-- toomanystr()
+toomanyidx()
+
+print "OK"
From af6d9f31165a13c34c0601f37ca5a67c365d1d01 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 17 Dec 2018 13:56:22 -0200
Subject: [PATCH 112/889] Details
A few details in the makefile and in the manual. (In particular,
it updates the dependency lists in the makefile.)
---
ltests.c | 3 ++-
makefile | 23 ++++++++++++-----------
manual/manual.of | 16 ++++++++--------
3 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/ltests.c b/ltests.c
index a38a8926f0..0b5ed90b22 100644
--- a/ltests.c
+++ b/ltests.c
@@ -156,7 +156,8 @@ void *debug_realloc (void *ud, void *b, size_t oldsize, size_t size) {
size_t realsize = sizeof(Header) + size + MARKSIZE;
if (realsize < size) return NULL; /* arithmetic overflow! */
newblock = cast(Header *, malloc(realsize)); /* alloc a new block */
- if (newblock == NULL) return NULL; /* really out of memory? */
+ if (newblock == NULL)
+ return NULL; /* really out of memory? */
if (block) {
memcpy(newblock + 1, block + 1, commonsize); /* copy old contents */
freeblock(mc, block); /* erase (and check) old copy */
diff --git a/makefile b/makefile
index 1263f7fdcf..02a0814fb7 100644
--- a/makefile
+++ b/makefile
@@ -14,10 +14,11 @@ CWARNSCPP= \
-Wredundant-decls \
-Wdisabled-optimization \
-Wdouble-promotion \
- -Wstrict-aliasing=3 \
- -Wno-aggressive-loop-optimizations \
- -Wlogical-op \
- -Werror \
+ #-Wno-aggressive-loop-optimizations \
+ #-Wlogical-op \
+ #-Wfatal-errors \
+ #-Wstrict-aliasing=3 \
+ # -Werror \
# -pedantic # warns if we use jump tables \
# the next warnings generate too much noise, so they are disabled
# -Wconversion -Wno-sign-conversion \
@@ -39,6 +40,7 @@ CWARNS= $(CWARNSCPP) $(CWARNSC)
# -DEXTERNMEMCHECK -DHARDSTACKTESTS -DHARDMEMTESTS -DTRACEMEM='"tempmem"'
+# -DMAXINDEXRK=1
# -g -DLUA_USER_H='"ltests.h"'
# -pg -malign-double
# -DLUA_USE_CTYPE -DLUA_USE_APICHECK
@@ -50,7 +52,6 @@ TESTS= -DLUA_USER_H='"ltests.h"' -O0
# LOCAL = $(TESTS) $(CWARNS) -g
-
# enable Linux goodies
MYCFLAGS= $(LOCAL) -std=c99 -DLUA_USE_LINUX -DLUA_USE_READLINE
MYLDFLAGS= $(LOCAL) -Wl,-E
@@ -58,7 +59,7 @@ MYLIBS= -ldl -lreadline
CC= gcc
-CFLAGS= -Wall -O2 $(MYCFLAGS) -Wfatal-errors -fno-stack-protector -fno-common
+CFLAGS= -Wall -O2 $(MYCFLAGS) -fno-stack-protector -fno-common -march=native
AR= ar rc
RANLIB= ranlib
RM= rm -f
@@ -122,7 +123,7 @@ echo:
@echo "MYLIBS = $(MYLIBS)"
@echo "DL = $(DL)"
-$(ALL_O): makefile
+$(ALL_O): makefile ltests.h
# DO NOT EDIT
# automatically made with 'gcc -MM l*.c'
@@ -146,8 +147,8 @@ ldo.o: ldo.c lprefix.h lua.h luaconf.h lapi.h llimits.h lstate.h \
lparser.h lstring.h ltable.h lundump.h lvm.h
ldump.o: ldump.c lprefix.h lua.h luaconf.h lobject.h llimits.h lstate.h \
ltm.h lzio.h lmem.h lundump.h
-lfunc.o: lfunc.c lprefix.h lua.h luaconf.h lfunc.h lobject.h llimits.h \
- lgc.h lstate.h ltm.h lzio.h lmem.h
+lfunc.o: lfunc.c lprefix.h lua.h luaconf.h ldebug.h lstate.h lobject.h \
+ llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h
lgc.o: lgc.c lprefix.h lua.h luaconf.h ldebug.h lstate.h lobject.h \
llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h lstring.h ltable.h
linit.o: linit.c lprefix.h lua.h luaconf.h lualib.h lauxlib.h
@@ -178,8 +179,8 @@ ltable.o: ltable.c lprefix.h lua.h luaconf.h ldebug.h lstate.h lobject.h \
ltablib.o: ltablib.c lprefix.h lua.h luaconf.h lauxlib.h lualib.h
ltests.o: ltests.c lprefix.h lua.h luaconf.h lapi.h llimits.h lstate.h \
lobject.h ltm.h lzio.h lmem.h lauxlib.h lcode.h llex.h lopcodes.h \
- lparser.h lctype.h ldebug.h ldo.h lfunc.h lstring.h lgc.h ltable.h \
- lualib.h
+ lparser.h lctype.h ldebug.h ldo.h lfunc.h lopnames.h lstring.h lgc.h \
+ ltable.h lualib.h
ltm.o: ltm.c lprefix.h lua.h luaconf.h ldebug.h lstate.h lobject.h \
llimits.h ltm.h lzio.h lmem.h ldo.h lgc.h lstring.h ltable.h lvm.h
lua.o: lua.c lprefix.h lua.h luaconf.h lauxlib.h lualib.h
diff --git a/manual/manual.of b/manual/manual.of
index 862d032bcd..044bd09c90 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1027,8 +1027,7 @@ Any kind of end-of-line sequence
or newline followed by carriage return)
is converted to a simple newline.
-For convenience,
-when the opening long bracket is immediately followed by a newline,
+When the opening long bracket is immediately followed by a newline,
the newline is not included in the string.
As an example, in a system using ASCII
(in which @Char{a} is coded @N{as 97},
@@ -1048,7 +1047,7 @@ alo
Any byte in a literal string not
explicitly affected by the previous rules represents itself.
However, Lua opens files for parsing in text mode,
-and the system file functions may have problems with
+and the system's file functions may have problems with
some control characters.
So, it is safer to represent
non-text data as a quoted literal with
@@ -1782,19 +1781,19 @@ These operators always result in @false or @true.
Equality (@T{==}) first compares the type of its operands.
If the types are different, then the result is @false.
Otherwise, the values of the operands are compared.
-Strings are compared in the obvious way.
+Strings are equal if they have the same content.
Numbers are equal if they denote the same mathematical value.
Tables, userdata, and threads
are compared by reference:
two objects are considered equal only if they are the same object.
Every time you create a new object
-(a table, userdata, or thread),
+(a table, a userdata, or a thread),
this new object is different from any previously existing object.
-A closure is always equal to itself.
-Closures with any detectable difference
+A function is always equal to itself.
+Functions with any detectable difference
(different behavior, different definition) are always different.
-Closures created at different times but with no detectable differences
+Functions created at different times but with no detectable differences
may be classified as equal or not
(depending on internal cashing details).
@@ -4324,6 +4323,7 @@ The unsigned version of @Lid{lua_Integer}.
Returns the pseudo-index that represents the @id{i}-th upvalue of
the running function @see{c-closure}.
+@id{i} must be in the range @M{[1,256]}.
}
From 5d7dec552044303abc0d469d430c1fbec3c7b635 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 17 Dec 2018 15:19:56 -0200
Subject: [PATCH 113/889] Added directory 'testes/libs/P1' to the repository
This directory is used for some tests. As standard Lua has no command
to create directories, it must be present before running tests.
---
testes/libs/P1/dummy | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 testes/libs/P1/dummy
diff --git a/testes/libs/P1/dummy b/testes/libs/P1/dummy
new file mode 100644
index 0000000000..b0468a0a3f
--- /dev/null
+++ b/testes/libs/P1/dummy
@@ -0,0 +1,2 @@
+# This is a dummy file just to make git keep the otherwise empty
+# directory 'P1' in the repository.
From 662506476b3b9bf651de064884a00c8dbce6e281 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 19 Dec 2018 13:15:14 -0200
Subject: [PATCH 114/889] 'all' script automatically 'make's everything
The script 'all', to run all tests, automatically ensures that the
Lua interpreter and the test C libraries (in 'testes/libs/') are
updated with any changes in 'luaconf.h'.
---
all | 4 +++-
makefile | 1 +
testes/libs/makefile | 11 ++++++-----
3 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/all b/all
index 12acaf3650..8f78ee4d5b 100755
--- a/all
+++ b/all
@@ -1,4 +1,6 @@
-cd testes
+make -s -j
+cd testes/libs; make -s
+cd .. # back to directory 'testes'
ulimit -S -s 2000
if { ../lua all.lua; } then
echo -e "\n\n final OK!!!!\n\n"
diff --git a/makefile b/makefile
index 02a0814fb7..3bd319bea9 100644
--- a/makefile
+++ b/makefile
@@ -90,6 +90,7 @@ ALL_O= $(CORE_O) $(LUA_O) $(LUAC_O) $(AUX_O) $(LIB_O)
ALL_A= $(CORE_T)
all: $(ALL_T)
+ touch all
o: $(ALL_O)
diff --git a/testes/libs/makefile b/testes/libs/makefile
index 9925fb005b..acff4848c2 100644
--- a/testes/libs/makefile
+++ b/testes/libs/makefile
@@ -9,18 +9,19 @@ CFLAGS = -Wall -std=gnu99 -O2 -I$(LUA_DIR) -fPIC -shared
# libraries used by the tests
all: lib1.so lib11.so lib2.so lib21.so lib2-v2.so
+ touch all
-lib1.so: lib1.c
+lib1.so: lib1.c $(LUA_DIR)/luaconf.h $(LUA_DIR)/ltests.h
$(CC) $(CFLAGS) -o lib1.so lib1.c
-lib11.so: lib11.c
+lib11.so: lib11.c $(LUA_DIR)/luaconf.h $(LUA_DIR)/ltests.h
$(CC) $(CFLAGS) -o lib11.so lib11.c
-lib2.so: lib2.c
+lib2.so: lib2.c $(LUA_DIR)/luaconf.h $(LUA_DIR)/ltests.h
$(CC) $(CFLAGS) -o lib2.so lib2.c
-lib21.so: lib21.c
+lib21.so: lib21.c $(LUA_DIR)/luaconf.h $(LUA_DIR)/ltests.h
$(CC) $(CFLAGS) -o lib21.so lib21.c
lib2-v2.so: lib2.so
- mv lib2.so ./lib2-v2.so
+ cp lib2.so ./lib2-v2.so
From da37ac9c7894186a0e2e0e6f1f5f00b825fd1555 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 27 Dec 2018 14:19:53 -0200
Subject: [PATCH 115/889] Detail
Slightly better error message for invalid conversions in 'string.format'.
---
lstrlib.c | 3 +--
testes/strings.lua | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/lstrlib.c b/lstrlib.c
index e9c60c0f3e..dde868c082 100644
--- a/lstrlib.c
+++ b/lstrlib.c
@@ -1180,8 +1180,7 @@ static int str_format (lua_State *L) {
break;
}
default: { /* also treat cases 'pnLlh' */
- return luaL_error(L, "invalid option '%%%c' to 'format'",
- *(strfrmt - 1));
+ return luaL_error(L, "invalid conversion '%s' to 'format'", form);
}
}
lua_assert(nb < MAX_ITEM);
diff --git a/testes/strings.lua b/testes/strings.lua
index 1260dbcc26..587a0e06d3 100644
--- a/testes/strings.lua
+++ b/testes/strings.lua
@@ -302,7 +302,7 @@ check("%100.3d", "too long")
check("%1"..aux..".3d", "too long")
check("%1.100d", "too long")
check("%10.1"..aux.."004d", "too long")
-check("%t", "invalid option")
+check("%t", "invalid conversion")
check("%"..aux.."d", "repeated flags")
check("%d %d", "no value")
From ba7da13ec5938f978c37d63aa40a3e340b301f79 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 27 Dec 2018 14:32:29 -0200
Subject: [PATCH 116/889] Changes in the control of C-stack overflow
* unification of the 'nny' and 'nCcalls' counters;
* external C functions ('lua_CFunction') count more "slots" in
the C stack (to allow for their possible use of buffers)
* added a new test script specific for C-stack overflows. (Most
of those tests were already present, but concentrating them
in a single script easies the task of checking whether
'LUAI_MAXCCALLS' is adequate in a system.)
---
lapi.c | 4 +--
ldo.c | 40 ++++++++++++----------------
llimits.h | 6 +++--
lparser.c | 8 +++---
lstate.c | 49 ++++++++++++++++++++++------------
lstate.h | 52 ++++++++++++++++++++++++++++++-------
ltests.h | 2 +-
luaconf.h | 4 +--
testes/all.lua | 1 +
testes/coroutine.lua | 4 +--
testes/cstack.lua | 62 ++++++++++++++++++++++++++++++++++++++++++++
testes/pm.lua | 12 ---------
12 files changed, 170 insertions(+), 74 deletions(-)
create mode 100644 testes/cstack.lua
diff --git a/lapi.c b/lapi.c
index 9cabe7ca4e..2d10bb73f7 100644
--- a/lapi.c
+++ b/lapi.c
@@ -956,7 +956,7 @@ LUA_API void lua_callk (lua_State *L, int nargs, int nresults,
api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread");
checkresults(L, nargs, nresults);
func = L->top - (nargs+1);
- if (k != NULL && L->nny == 0) { /* need to prepare continuation? */
+ if (k != NULL && yieldable(L)) { /* need to prepare continuation? */
L->ci->u.c.k = k; /* save continuation */
L->ci->u.c.ctx = ctx; /* save context */
luaD_call(L, func, nresults); /* do the call */
@@ -1004,7 +1004,7 @@ LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc,
func = savestack(L, o);
}
c.func = L->top - (nargs+1); /* function to be called */
- if (k == NULL || L->nny > 0) { /* no continuation or no yieldable? */
+ if (k == NULL || !yieldable(L)) { /* no continuation or no yieldable? */
c.nresults = nresults; /* do a 'conventional' protected call */
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
}
diff --git a/ldo.c b/ldo.c
index 056fef0cae..f8d8f11cfb 100644
--- a/ldo.c
+++ b/ldo.c
@@ -138,7 +138,7 @@ l_noret luaD_throw (lua_State *L, int errcode) {
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
- unsigned short oldnCcalls = L->nCcalls - L->nci;
+ l_uint32 oldnCcalls = L->nCcalls - L->nci;
struct lua_longjmp lj;
lua_assert(L->nCcalls >= L->nci);
lj.status = LUA_OK;
@@ -513,12 +513,17 @@ void luaD_call (lua_State *L, StkId func, int nresults) {
/*
-** Similar to 'luaD_call', but does not allow yields during the call
+** Similar to 'luaD_call', but does not allow yields during the call.
+** If there is a stack overflow, freeing all CI structures will
+** force the subsequent call to invoke 'luaE_extendCI', which then
+** will raise any errors.
*/
void luaD_callnoyield (lua_State *L, StkId func, int nResults) {
- L->nny++;
+ incXCcalls(L);
+ if (getCcalls(L) >= LUAI_MAXCCALLS) /* possible stack overflow? */
+ luaE_freeCI(L);
luaD_call(L, func, nResults);
- L->nny--;
+ decXCcalls(L);
}
@@ -530,7 +535,7 @@ static void finishCcall (lua_State *L, int status) {
CallInfo *ci = L->ci;
int n;
/* must have a continuation and must be able to call it */
- lua_assert(ci->u.c.k != NULL && L->nny == 0);
+ lua_assert(ci->u.c.k != NULL && yieldable(L));
/* error status can only happen in a protected call */
lua_assert((ci->callstatus & CIST_YPCALL) || status == LUA_YIELD);
if (ci->callstatus & CIST_YPCALL) { /* was inside a pcall? */
@@ -601,7 +606,6 @@ static int recover (lua_State *L, int status) {
luaD_seterrorobj(L, status, oldtop);
L->ci = ci;
L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */
- L->nny = 0; /* should be zero to be yieldable */
luaD_shrinkstack(L);
L->errfunc = ci->u.c.old_errfunc;
return 1; /* continue running the coroutine */
@@ -622,13 +626,6 @@ static int resume_error (lua_State *L, const char *msg, int narg) {
}
-/*
-** "Cost" in the C stack for a coroutine invocation.
-*/
-#if !defined(LUAL_COROCSTK)
-#define LUAL_COROCSTK 3
-#endif
-
/*
** Do the work for 'lua_resume' in protected mode. Most of the work
** depends on the status of the coroutine: initial state, suspended
@@ -664,7 +661,6 @@ static void resume (lua_State *L, void *ud) {
LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
int *nresults) {
int status;
- unsigned short oldnny = L->nny; /* save "number of non-yieldable" calls */
lua_lock(L);
if (L->status == LUA_OK) { /* may be starting a coroutine */
if (L->ci != &L->base_ci) /* not in base level? */
@@ -675,11 +671,10 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
if (from == NULL)
L->nCcalls = 1;
else /* correct 'nCcalls' for this thread */
- L->nCcalls = from->nCcalls - from->nci + L->nci + LUAL_COROCSTK;
+ L->nCcalls = getCcalls(from) - from->nci + L->nci + CSTACKCF;
if (L->nCcalls >= LUAI_MAXCCALLS)
return resume_error(L, "C stack overflow", nargs);
luai_userstateresume(L, nargs);
- L->nny = 0; /* allow yields */
api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs);
status = luaD_rawrunprotected(L, resume, &nargs);
/* continue running after recoverable errors */
@@ -698,14 +693,13 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
}
*nresults = (status == LUA_YIELD) ? L->ci->u2.nyield
: cast_int(L->top - (L->ci->func + 1));
- L->nny = oldnny; /* restore 'nny' */
lua_unlock(L);
return status;
}
LUA_API int lua_isyieldable (lua_State *L) {
- return (L->nny == 0);
+ return yieldable(L);
}
@@ -715,7 +709,7 @@ LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx,
luai_userstateyield(L, nresults);
lua_lock(L);
api_checknelems(L, nresults);
- if (unlikely(L->nny > 0)) {
+ if (unlikely(!yieldable(L))) {
if (L != G(L)->mainthread)
luaG_runerror(L, "attempt to yield across a C-call boundary");
else
@@ -741,7 +735,7 @@ LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx,
/*
** Call the C function 'func' in protected mode, restoring basic
-** thread information ('allowhook', 'nny', etc.) and in particular
+** thread information ('allowhook', etc.) and in particular
** its stack level in case of errors.
*/
int luaD_pcall (lua_State *L, Pfunc func, void *u,
@@ -749,7 +743,6 @@ int luaD_pcall (lua_State *L, Pfunc func, void *u,
int status;
CallInfo *old_ci = L->ci;
lu_byte old_allowhooks = L->allowhook;
- unsigned short old_nny = L->nny;
ptrdiff_t old_errfunc = L->errfunc;
L->errfunc = ef;
status = luaD_rawrunprotected(L, func, u);
@@ -757,7 +750,6 @@ int luaD_pcall (lua_State *L, Pfunc func, void *u,
StkId oldtop = restorestack(L, old_top);
L->ci = old_ci;
L->allowhook = old_allowhooks;
- L->nny = old_nny;
status = luaF_close(L, oldtop, status);
oldtop = restorestack(L, old_top); /* previous call may change stack */
luaD_seterrorobj(L, status, oldtop);
@@ -811,7 +803,7 @@ int luaD_protectedparser (lua_State *L, ZIO *z, const char *name,
const char *mode) {
struct SParser p;
int status;
- L->nny++; /* cannot yield during parsing */
+ incnny(L); /* cannot yield during parsing */
p.z = z; p.name = name; p.mode = mode;
p.dyd.actvar.arr = NULL; p.dyd.actvar.size = 0;
p.dyd.gt.arr = NULL; p.dyd.gt.size = 0;
@@ -822,7 +814,7 @@ int luaD_protectedparser (lua_State *L, ZIO *z, const char *name,
luaM_freearray(L, p.dyd.actvar.arr, p.dyd.actvar.size);
luaM_freearray(L, p.dyd.gt.arr, p.dyd.gt.size);
luaM_freearray(L, p.dyd.label.arr, p.dyd.label.size);
- L->nny--;
+ decnny(L);
return status;
}
diff --git a/llimits.h b/llimits.h
index 8ab58b5ce9..9d35d1c774 100644
--- a/llimits.h
+++ b/llimits.h
@@ -184,11 +184,13 @@ typedef LUAI_UACINT l_uacInt;
** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h)
*/
#if LUAI_BITSINT >= 32
-typedef unsigned int Instruction;
+typedef unsigned int l_uint32;
#else
-typedef unsigned long Instruction;
+typedef unsigned long l_uint32;
#endif
+typedef l_uint32 Instruction;
+
/*
diff --git a/lparser.c b/lparser.c
index eed8bffd93..3887958ef7 100644
--- a/lparser.c
+++ b/lparser.c
@@ -367,10 +367,12 @@ static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) {
}
-#define enterlevel(ls) luaE_incCcalls((ls)->L)
-
+/*
+** Macros to limit the maximum recursion depth while parsing
+*/
+#define enterlevel(ls) luaE_enterCcall((ls)->L)
-#define leavelevel(ls) ((ls)->L->nCcalls--)
+#define leavelevel(ls) luaE_exitCcall((ls)->L)
/*
diff --git a/lstate.c b/lstate.c
index 5ee024fcab..04a9e0649e 100644
--- a/lstate.c
+++ b/lstate.c
@@ -99,24 +99,42 @@ void luaE_setdebt (global_State *g, l_mem debt) {
/*
** Increment count of "C calls" and check for overflows. In case of
** a stack overflow, check appropriate error ("regular" overflow or
-** overflow while handling stack overflow). If 'nCalls' is larger than
-** LUAI_MAXCCALLS (which means it is handling a "regular" overflow) but
-** smaller than 9/8 of LUAI_MAXCCALLS, does not report an error (to
-** allow overflow handling to work)
+** overflow while handling stack overflow).
+** If 'nCcalls' is larger than LUAI_MAXCCALLS but smaller than
+** LUAI_MAXCCALLS + CSTACKCF (plus 2 to avoid by-one errors), it means
+** it has just entered the "overflow zone", so the function raises an
+** overflow error.
+** If 'nCcalls' is larger than LUAI_MAXCCALLS + CSTACKCF + 2
+** (which means it is already handling an overflow) but smaller than
+** 9/8 of LUAI_MAXCCALLS, does not report an error (to allow message
+** handling to work).
+** Otherwise, report a stack overflow while handling a stack overflow
+** (probably caused by a repeating error in the message handling
+** function).
*/
-void luaE_incCcalls (lua_State *L) {
- if (++L->nCcalls >= LUAI_MAXCCALLS) {
- if (L->nCcalls == LUAI_MAXCCALLS)
- luaG_runerror(L, "C stack overflow");
- else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3)))
- luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
+void luaE_enterCcall (lua_State *L) {
+ int ncalls = getCcalls(L);
+ L->nCcalls++;
+ if (ncalls >= LUAI_MAXCCALLS) { /* possible overflow? */
+ luaE_freeCI(L); /* release unused CIs */
+ ncalls = getCcalls(L); /* update call count */
+ if (ncalls >= LUAI_MAXCCALLS) { /* still overflow? */
+ if (ncalls <= LUAI_MAXCCALLS + CSTACKCF + 2) {
+ /* no error before increments; raise the error now */
+ L->nCcalls += (CSTACKCF + 4); /* avoid raising it again */
+ luaG_runerror(L, "C stack overflow");
+ }
+ else if (ncalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3)))
+ luaD_throw(L, LUA_ERRERR); /* error while handling stack error */
+ }
}
}
CallInfo *luaE_extendCI (lua_State *L) {
CallInfo *ci;
- luaE_incCcalls(L);
+ lua_assert(L->ci->next == NULL);
+ luaE_enterCcall(L);
ci = luaM_new(L, CallInfo);
lua_assert(L->ci->next == NULL);
L->ci->next = ci;
@@ -135,13 +153,13 @@ void luaE_freeCI (lua_State *L) {
CallInfo *ci = L->ci;
CallInfo *next = ci->next;
ci->next = NULL;
- L->nCcalls -= L->nci; /* to subtract removed elements from 'nCcalls' */
+ L->nCcalls -= L->nci; /* subtract removed elements from 'nCcalls' */
while ((ci = next) != NULL) {
next = ci->next;
luaM_free(L, ci);
L->nci--;
}
- L->nCcalls += L->nci; /* to subtract removed elements from 'nCcalls' */
+ L->nCcalls += L->nci; /* adjust result */
}
@@ -151,7 +169,7 @@ void luaE_freeCI (lua_State *L) {
void luaE_shrinkCI (lua_State *L) {
CallInfo *ci = L->ci;
CallInfo *next2; /* next's next */
- L->nCcalls -= L->nci; /* to subtract removed elements from 'nCcalls' */
+ L->nCcalls -= L->nci; /* subtract removed elements from 'nCcalls' */
/* while there are two nexts */
while (ci->next != NULL && (next2 = ci->next->next) != NULL) {
luaM_free(L, ci->next); /* free next */
@@ -160,7 +178,7 @@ void luaE_shrinkCI (lua_State *L) {
next2->previous = ci;
ci = next2; /* keep next's next */
}
- L->nCcalls += L->nci; /* to subtract removed elements from 'nCcalls' */
+ L->nCcalls += L->nci; /* adjust result */
}
@@ -250,7 +268,6 @@ static void preinit_thread (lua_State *L, global_State *g) {
L->allowhook = 1;
resethookcount(L);
L->openupval = NULL;
- L->nny = 1;
L->status = LUA_OK;
L->errfunc = 0;
}
diff --git a/lstate.h b/lstate.h
index ce33770748..b069b3901a 100644
--- a/lstate.h
+++ b/lstate.h
@@ -15,7 +15,6 @@
/*
-
** Some notes about garbage-collected objects: All objects in Lua must
** be kept somehow accessible until being freed, so all objects always
** belong to one (and only one) of these lists, using field 'next' of
@@ -43,26 +42,58 @@
** 'weak': tables with weak values to be cleared;
** 'ephemeron': ephemeron tables with white->white entries;
** 'allweak': tables with weak keys and/or weak values to be cleared.
-
*/
-/*
+/*
** About 'nCcalls': each thread in Lua (a lua_State) keeps a count of
-** how many "C calls" it has in the C stack, to avoid C-stack overflow.
+** how many "C calls" it can do in the C stack, to avoid C-stack overflow.
** This count is very rough approximation; it considers only recursive
** functions inside the interpreter, as non-recursive calls can be
** considered using a fixed (although unknown) amount of stack space.
**
+** The count itself has two parts: the lower part is the count itself;
+** the higher part counts the number of non-yieldable calls in the stack.
+**
+** Because calls to external C functions can use of unkown amount
+** of space (e.g., functions using an auxiliary buffer), calls
+** to these functions add more than one to the count.
+**
** The proper count also includes the number of CallInfo structures
** allocated by Lua, as a kind of "potential" calls. So, when Lua
** calls a function (and "consumes" one CallInfo), it needs neither to
** increment nor to check 'nCcalls', as its use of C stack is already
** accounted for.
-
*/
+/* number of "C stack slots" used by an external C function */
+#define CSTACKCF 10
+
+/* true if this thread does not have non-yieldable calls in the stack */
+#define yieldable(L) (((L)->nCcalls & 0xffff0000) == 0)
+
+/* real number of C calls */
+#define getCcalls(L) ((L)->nCcalls & 0xffff)
+
+
+/* Increment the number of non-yieldable calls */
+#define incnny(L) ((L)->nCcalls += 0x10000)
+
+/* Decrement the number of non-yieldable calls */
+#define decnny(L) ((L)->nCcalls -= 0x10000)
+
+/* Increment the number of non-yieldable calls and nCcalls */
+#define incXCcalls(L) ((L)->nCcalls += 0x10000 + CSTACKCF)
+
+/* Decrement the number of non-yieldable calls and nCcalls */
+#define decXCcalls(L) ((L)->nCcalls -= 0x10000 + CSTACKCF)
+
+
+
+
+
+
struct lua_longjmp; /* defined in ldo.c */
@@ -208,8 +239,9 @@ typedef struct global_State {
*/
struct lua_State {
CommonHeader;
- unsigned short nci; /* number of items in 'ci' list */
lu_byte status;
+ lu_byte allowhook;
+ unsigned short nci; /* number of items in 'ci' list */
StkId top; /* first free slot in the stack */
global_State *l_G;
CallInfo *ci; /* call info for current function */
@@ -223,13 +255,11 @@ struct lua_State {
CallInfo base_ci; /* CallInfo for first level (C calling Lua) */
volatile lua_Hook hook;
ptrdiff_t errfunc; /* current error handling function (stack index) */
+ l_uint32 nCcalls; /* number of allowed nested C calls - 'nci' */
int stacksize;
int basehookcount;
int hookcount;
- unsigned short nny; /* number of non-yieldable calls in stack */
- unsigned short nCcalls; /* number of nested C calls + 'nny' */
l_signalT hookmask;
- lu_byte allowhook;
};
@@ -283,8 +313,10 @@ LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1);
LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L);
LUAI_FUNC void luaE_freeCI (lua_State *L);
LUAI_FUNC void luaE_shrinkCI (lua_State *L);
-LUAI_FUNC void luaE_incCcalls (lua_State *L);
+LUAI_FUNC void luaE_enterCcall (lua_State *L);
+
+#define luaE_exitCcall(L) ((L)->nCcalls--)
#endif
diff --git a/ltests.h b/ltests.h
index 9d409c8d43..997e1c4b08 100644
--- a/ltests.h
+++ b/ltests.h
@@ -31,7 +31,7 @@
/* compiled with -O0, Lua uses a lot of C stack space... */
#undef LUAI_MAXCCALLS
-#define LUAI_MAXCCALLS 200
+#define LUAI_MAXCCALLS 400
/* to avoid warnings, and to make sure value is really unused */
#define UNUSED(x) (x=0, (void)(x))
diff --git a/luaconf.h b/luaconf.h
index ff7085131a..0fc161a4b3 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -695,14 +695,14 @@
/*
@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system.
** CHANGE it if it uses too much C-stack space. (For long double,
-** 'string.format("%.99f", -1e4932)' needs 5034 bytes, so a
+** 'string.format("%.99f", -1e4932)' needs 5052 bytes, so a
** smaller buffer would force a memory allocation for each call to
** 'string.format'.)
*/
#if LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE
#define LUAL_BUFFERSIZE 8192
#else
-#define LUAL_BUFFERSIZE ((int)(0x80 * sizeof(void*) * sizeof(lua_Integer)))
+#define LUAL_BUFFERSIZE ((int)(16 * sizeof(void*) * sizeof(lua_Number)))
#endif
/*
diff --git a/testes/all.lua b/testes/all.lua
index 26d2497659..84ba80a625 100644
--- a/testes/all.lua
+++ b/testes/all.lua
@@ -172,6 +172,7 @@ if not _G._soft then
assert(f() == 'b')
assert(f() == 'a')
end
+dofile('cstack.lua')
dofile('nextvar.lua')
dofile('pm.lua')
dofile('utf8.lua')
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index 5674a4dd14..ca30011ff5 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -107,7 +107,7 @@ function filter (p, g)
end)
end
-local x = gen(100)
+local x = gen(80)
local a = {}
while 1 do
local n = x()
@@ -116,7 +116,7 @@ while 1 do
x = filter(n, x)
end
-assert(#a == 25 and a[#a] == 97)
+assert(#a == 22 and a[#a] == 79)
x, a = nil
diff --git a/testes/cstack.lua b/testes/cstack.lua
new file mode 100644
index 0000000000..9e5bbaefd7
--- /dev/null
+++ b/testes/cstack.lua
@@ -0,0 +1,62 @@
+-- $Id: testes/cstack.lua $
+-- See Copyright Notice in file all.lua
+
+print"testing C-stack overflow detection"
+
+-- Segmentation faults in these tests probably result from a C-stack
+-- overflow. To avoid these errors, recompile Lua with a smaller
+-- value for the constant 'LUAI_MAXCCALLS' or else ensure a larger
+-- stack for the program.
+
+local function checkerror (msg, f, ...)
+ local s, err = pcall(f, ...)
+ assert(not s and string.find(err, msg))
+end
+
+
+do -- simple recursion
+ local count = 0
+ local function foo ()
+ count = count + 1
+ foo()
+ end
+ checkerror("stack overflow", foo)
+ print(" maximum recursion: " .. count)
+end
+
+
+-- bug since 2.5 (C-stack overflow in recursion inside pattern matching)
+do
+ local function f (size)
+ local s = string.rep("a", size)
+ local p = string.rep(".?", size)
+ return string.match(s, p)
+ end
+ local m = f(80)
+ assert(#m == 80)
+ checkerror("too complex", f, 200000)
+end
+
+
+-- testing stack-overflow in recursive 'gsub'
+do
+ local count = 0
+ local function foo ()
+ count = count + 1
+ string.gsub("a", ".", foo)
+ end
+ checkerror("stack overflow", foo)
+ print(" maximum 'gsub' nest (calls): " .. count)
+
+ -- can be done with metamethods, too
+ count = 0
+ local t = setmetatable({}, {__index = foo})
+ foo = function ()
+ count = count + 1
+ string.gsub("a", ".", t)
+ end
+ checkerror("stack overflow", foo)
+ print(" maximum 'gsub' nest (metamethods): " .. count)
+end
+
+print'OK'
diff --git a/testes/pm.lua b/testes/pm.lua
index cdcf3becb8..1afaccf694 100644
--- a/testes/pm.lua
+++ b/testes/pm.lua
@@ -237,18 +237,6 @@ checkerror("invalid capture index %%0", string.gsub, "alo", "(%0)", "a")
checkerror("invalid capture index %%1", string.gsub, "alo", "(%1)", "a")
checkerror("invalid use of '%%'", string.gsub, "alo", ".", "%x")
--- bug since 2.5 (C-stack overflow)
-do
- local function f (size)
- local s = string.rep("a", size)
- local p = string.rep(".?", size)
- return pcall(string.match, s, p)
- end
- local r, m = f(80)
- assert(r and #m == 80)
- r, m = f(200000)
- assert(not r and string.find(m, "too complex"))
-end
if not _soft then
print("big strings")
From 437a5b07d415e1a74160ddfd804017171d6cc5cb Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 28 Dec 2018 15:42:34 -0200
Subject: [PATCH 117/889] Added a warning system to Lua
The warning system is just a way for Lua to emit warnings, messages
to the programmer that do not interfere with the running program.
---
lapi.c | 18 ++++++++++++++++
lauxlib.c | 28 +++++++++++++++++++++++-
lbaselib.c | 8 +++++++
lstate.c | 2 ++
lstate.h | 2 ++
ltests.c | 33 +++++++++++++++++++++++++++-
lua.h | 14 ++++++++++++
manual/manual.of | 56 +++++++++++++++++++++++++++++++++++++++++-------
testes/all.lua | 13 ++++++-----
testes/api.lua | 14 ++++++++++++
10 files changed, 173 insertions(+), 15 deletions(-)
diff --git a/lapi.c b/lapi.c
index 2d10bb73f7..0f0166e58a 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1267,6 +1267,24 @@ LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) {
}
+void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud) {
+ lua_lock(L);
+ G(L)->ud_warn = ud;
+ G(L)->warnf = f;
+ lua_unlock(L);
+}
+
+
+void lua_warning (lua_State *L, const char *msg) {
+ lua_WarnFunction wf = G(L)->warnf;
+ lua_lock(L);
+ if (wf != NULL)
+ wf(&G(L)->ud_warn, msg);
+ lua_unlock(L);
+}
+
+
+
LUA_API void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue) {
Udata *u;
lua_lock(L);
diff --git a/lauxlib.c b/lauxlib.c
index 769586b691..abf923f80a 100644
--- a/lauxlib.c
+++ b/lauxlib.c
@@ -986,9 +986,35 @@ static int panic (lua_State *L) {
}
+/*
+** checks whether 'message' ends with end-of-line
+** (and therefore is the last part of a warning)
+*/
+static int islast (const char *message) {
+ size_t len = strlen(message);
+ return (len > 0 && message[len - 1] == '\n');
+}
+
+
+/*
+** Emit a warning. If '*pud' is NULL, previous message was to be
+** continued by the current one.
+*/
+static void warnf (void **pud, const char *message) {
+ if (*pud == NULL) /* previous message was not the last? */
+ lua_writestringerror("%s", message);
+ else /* start a new warning */
+ lua_writestringerror("Lua warning: %s", message);
+ *pud = (islast(message)) ? pud : NULL;
+}
+
+
LUALIB_API lua_State *luaL_newstate (void) {
lua_State *L = lua_newstate(l_alloc, NULL);
- if (L) lua_atpanic(L, &panic);
+ if (L) {
+ lua_atpanic(L, &panic);
+ lua_setwarnf(L, warnf, L);
+ }
return L;
}
diff --git a/lbaselib.c b/lbaselib.c
index 7c0ebfecd3..26683a1dcb 100644
--- a/lbaselib.c
+++ b/lbaselib.c
@@ -43,6 +43,13 @@ static int luaB_print (lua_State *L) {
}
+static int luaB_warn (lua_State *L) {
+ const char *msg = luaL_checkstring(L, 1);
+ lua_warning(L, msg);
+ return 0;
+}
+
+
#define SPACECHARS " \f\n\r\t\v"
static const char *b_str2int (const char *s, int base, lua_Integer *pn) {
@@ -482,6 +489,7 @@ static const luaL_Reg base_funcs[] = {
{"pairs", luaB_pairs},
{"pcall", luaB_pcall},
{"print", luaB_print},
+ {"warn", luaB_warn},
{"rawequal", luaB_rawequal},
{"rawlen", luaB_rawlen},
{"rawget", luaB_rawget},
diff --git a/lstate.c b/lstate.c
index 04a9e0649e..b3e9ec6016 100644
--- a/lstate.c
+++ b/lstate.c
@@ -365,6 +365,8 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
L->next = NULL;
g->frealloc = f;
g->ud = ud;
+ g->warnf = NULL;
+ g->ud_warn = NULL;
g->mainthread = L;
g->seed = luai_makeseed(L);
g->gcrunning = 0; /* no GC while building state */
diff --git a/lstate.h b/lstate.h
index b069b3901a..f379325640 100644
--- a/lstate.h
+++ b/lstate.h
@@ -231,6 +231,8 @@ typedef struct global_State {
TString *tmname[TM_N]; /* array with tag-method names */
struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */
TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */
+ lua_WarnFunction warnf; /* warning function */
+ void *ud_warn; /* auxiliary data to 'warnf' */
} global_State;
diff --git a/ltests.c b/ltests.c
index 0b5ed90b22..5ea8b0804c 100644
--- a/ltests.c
+++ b/ltests.c
@@ -63,10 +63,36 @@ static void pushobject (lua_State *L, const TValue *o) {
}
+static void badexit (void) {
+ /* avoid assertion failures when exiting */
+ l_memcontrol.numblocks = l_memcontrol.total = 0;
+ exit(EXIT_FAILURE);
+}
+
+
static int tpanic (lua_State *L) {
fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n",
lua_tostring(L, -1));
- return (exit(EXIT_FAILURE), 0); /* do not return to Lua */
+ return (badexit(), 0); /* do not return to Lua */
+}
+
+
+static int islast (const char *message) {
+ size_t len = strlen(message);
+ return (len > 0 && message[len - 1] == '\n');
+}
+
+
+static void warnf (void **pud, const char *msg) {
+ if (*pud == NULL) /* continuation line? */
+ printf("%s", msg); /* print it */
+ else if (msg[0] == '*') /* expected warning? */
+ printf("Expected Lua warning: %s", msg + 1); /* print without the star */
+ else { /* a real warning; should not happen during tests */
+ fprintf(stderr, "Warning in test mode (%s), aborting...\n", msg);
+ badexit();
+ }
+ *pud = islast(msg) ? pud : NULL;
}
@@ -1405,6 +1431,10 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) {
const char *msg = getstring;
printf("%s\n", msg);
}
+ else if EQ("warning") {
+ const char *msg = getstring;
+ lua_warning(L1, msg);
+ }
else if EQ("pushbool") {
lua_pushboolean(L1, getnum);
}
@@ -1743,6 +1773,7 @@ static void checkfinalmem (void) {
int luaB_opentests (lua_State *L) {
void *ud;
lua_atpanic(L, &tpanic);
+ lua_setwarnf(L, &warnf, L);
atexit(checkfinalmem);
lua_assert(lua_getallocf(L, &ud) == debug_realloc);
lua_assert(ud == cast_voidp(&l_memcontrol));
diff --git a/lua.h b/lua.h
index 95ce5a2e9b..a6f8268b8e 100644
--- a/lua.h
+++ b/lua.h
@@ -126,6 +126,13 @@ typedef int (*lua_Writer) (lua_State *L, const void *p, size_t sz, void *ud);
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
+/*
+** Type for warning functions
+*/
+typedef void (*lua_WarnFunction) (void **pud, const char *msg);
+
+
+
/*
** generic extra include file
@@ -299,6 +306,13 @@ LUA_API int (lua_isyieldable) (lua_State *L);
#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL)
+/*
+** Warning-related functions
+*/
+LUA_API void (lua_setwarnf) (lua_State *L, lua_WarnFunction f, void *ud);
+LUA_API void (lua_warning) (lua_State *L, const char *msg);
+
+
/*
** garbage-collection function and options
*/
diff --git a/manual/manual.of b/manual/manual.of
index 044bd09c90..196ea1efe4 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1795,7 +1795,7 @@ Functions with any detectable difference
(different behavior, different definition) are always different.
Functions created at different times but with no detectable differences
may be classified as equal or not
-(depending on internal cashing details).
+(depending on internal caching details).
You can change the way that Lua compares tables and userdata
by using the @idx{__eq} metamethod @see{metatable}.
@@ -4033,6 +4033,16 @@ for the @Q{newindex} event @see{metatable}.
}
+@APIEntry{int lua_setiuservalue (lua_State *L, int index, int n);|
+@apii{1,0,-}
+
+Pops a value from the stack and sets it as
+the new @id{n}-th user value associated to the
+full userdata at the given index.
+Returns 0 if the userdata does not have that value.
+
+}
+
@APIEntry{void lua_setmetatable (lua_State *L, int index);|
@apii{1,0,-}
@@ -4066,13 +4076,13 @@ If @id{index} @N{is 0}, then all stack elements are removed.
}
-@APIEntry{int lua_setiuservalue (lua_State *L, int index, int n);|
-@apii{1,0,-}
+@APIEntry{void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud);|
+@apii{0,0,-}
-Pops a value from the stack and sets it as
-the new @id{n}-th user value associated to the
-full userdata at the given index.
-Returns 0 if the userdata does not have that value.
+Sets the @x{warning function} to be used by Lua to emit warnings
+@see{lua_WarnFunction}.
+The @id{ud} parameter initializes the slot @id{pud} passed to
+the warning function.
}
@@ -4334,6 +4344,30 @@ Returns the version number of this core.
}
+@APIEntry{
+typedef void (*lua_WarnFunction) (void **pud, const char *msg);|
+
+The type of @x{warning function}s, called by Lua to emit warnings.
+The first parameter is the address of a writable slot,
+constant for a given Lua state and
+initialized by @Lid{lua_setwarnf}.
+The second parameter is the warning message.
+This function should assume that
+a message not ending with an end-of-line will be
+continued by the message in the next call.
+
+}
+
+@APIEntry{
+void lua_warning (lua_State *L, const char *msg);|
+@apii{0,0,-}
+
+Emits a warning with the given message.
+A message not ending with an end-of-line should be
+continued in another call to this function.
+
+}
+
@APIEntry{
typedef int (*lua_Writer) (lua_State *L,
const void* p,
@@ -4345,7 +4379,7 @@ Every time it produces another piece of chunk,
@Lid{lua_dump} calls the writer,
passing along the buffer to be written (@id{p}),
its size (@id{sz}),
-and the @id{data} parameter supplied to @Lid{lua_dump}.
+and the @id{ud} parameter supplied to @Lid{lua_dump}.
The writer returns an error code:
@N{0 means} no errors;
@@ -6261,6 +6295,12 @@ The current value of this variable is @St{Lua 5.4}.
}
+@LibEntry{warn (message)|
+
+Emits a warning with the given message.
+
+}
+
@LibEntry{xpcall (f, msgh [, arg1, @Cdots])|
This function is similar to @Lid{pcall},
diff --git a/testes/all.lua b/testes/all.lua
index 84ba80a625..bde4195e62 100644
--- a/testes/all.lua
+++ b/testes/all.lua
@@ -5,8 +5,8 @@
local version = "Lua 5.4"
if _VERSION ~= version then
- io.stderr:write("\nThis test suite is for ", version, ", not for ", _VERSION,
- "\nExiting tests\n")
+ warn(string.format(
+ "This test suite is for %s, not for %s\nExiting tests\n", version, _VERSION))
return
end
@@ -190,11 +190,10 @@ assert(dofile('verybig.lua', true) == 10); collectgarbage()
dofile('files.lua')
if #msgs > 0 then
- print("\ntests not performed:")
+ warn("*tests not performed:\n ")
for i=1,#msgs do
- print(msgs[i])
+ warn(msgs[i]); warn("\n ")
end
- print()
end
-- no test module should define 'debug'
@@ -220,6 +219,10 @@ local _G, showmem, print, format, clock, time, difftime, assert, open =
local fname = T and "time-debug.txt" or "time.txt"
local lasttime
+
+warn("*This is "); warn("an expected"); warn(" warning\n")
+warn("*This is"); warn(" another one\n")
+
if not usertests then
-- open file with time of last performed test
local f = io.open(fname)
diff --git a/testes/api.lua b/testes/api.lua
index 6f35e13249..b4d6386602 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -111,6 +111,20 @@ do -- testing 'rotate'
tcheck(t, {10, 20, 30, 40})
end
+
+-- testing warnings
+T.testC([[
+ warning "*This "
+ warning "warning "
+ warning "should be in a"
+ warning " single line
+"
+ warning "*This should be "
+ warning "another warning
+"
+]])
+
+
-- testing message handlers
do
local f = T.makeCfunc[[
From c6f7181e910b6b2ff1346b5486a31be87b1da5af Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 1 Jan 2019 12:14:56 -0200
Subject: [PATCH 118/889] No more LUA_ERRGCMM errors
Errors in finalizers (__gc metamethods) are never propagated.
Instead, they generate a warning.
---
lapi.c | 4 +--
lgc.c | 28 ++++++++--------
lstate.c | 7 ++++
lstate.h | 1 +
ltests.c | 61 +++++++++++++++++++++++++--------
lua.h | 3 +-
manual/manual.of | 26 +++++----------
testes/all.lua | 11 +++---
testes/api.lua | 28 +++++-----------
testes/gc.lua | 87 ++++++++++++++++++++++++++++--------------------
10 files changed, 145 insertions(+), 111 deletions(-)
diff --git a/lapi.c b/lapi.c
index 0f0166e58a..8ff7bfbd30 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1276,10 +1276,8 @@ void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud) {
void lua_warning (lua_State *L, const char *msg) {
- lua_WarnFunction wf = G(L)->warnf;
lua_lock(L);
- if (wf != NULL)
- wf(&G(L)->ud_warn, msg);
+ luaE_warning(L, msg);
lua_unlock(L);
}
diff --git a/lgc.c b/lgc.c
index 95a8ad5b97..0c3386e745 100644
--- a/lgc.c
+++ b/lgc.c
@@ -824,7 +824,7 @@ static void dothecall (lua_State *L, void *ud) {
}
-static void GCTM (lua_State *L, int propagateerrors) {
+static void GCTM (lua_State *L) {
global_State *g = G(L);
const TValue *tm;
TValue v;
@@ -845,15 +845,13 @@ static void GCTM (lua_State *L, int propagateerrors) {
L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */
L->allowhook = oldah; /* restore hooks */
g->gcrunning = running; /* restore state */
- if (status != LUA_OK && propagateerrors) { /* error while running __gc? */
- if (status == LUA_ERRRUN) { /* is there an error object? */
- const char *msg = (ttisstring(s2v(L->top - 1)))
- ? svalue(s2v(L->top - 1))
- : "no message";
- luaO_pushfstring(L, "error in __gc metamethod (%s)", msg);
- status = LUA_ERRGCMM; /* error in __gc metamethod */
- }
- luaD_throw(L, status); /* re-throw error */
+ if (status != LUA_OK) { /* error while running __gc? */
+ const char *msg = (ttisstring(s2v(L->top - 1)))
+ ? svalue(s2v(L->top - 1))
+ : "error object is not a string";
+ luaE_warning(L, "error in __gc metamethod (");
+ luaE_warning(L, msg);
+ luaE_warning(L, ")\n");
}
}
}
@@ -866,7 +864,7 @@ static int runafewfinalizers (lua_State *L, int n) {
global_State *g = G(L);
int i;
for (i = 0; i < n && g->tobefnz; i++)
- GCTM(L, 1); /* call one finalizer */
+ GCTM(L); /* call one finalizer */
return i;
}
@@ -874,10 +872,10 @@ static int runafewfinalizers (lua_State *L, int n) {
/*
** call all pending finalizers
*/
-static void callallpendingfinalizers (lua_State *L, int propagateerrors) {
+static void callallpendingfinalizers (lua_State *L) {
global_State *g = G(L);
while (g->tobefnz)
- GCTM(L, propagateerrors);
+ GCTM(L);
}
@@ -1124,7 +1122,7 @@ static void finishgencycle (lua_State *L, global_State *g) {
checkSizes(L, g);
g->gcstate = GCSpropagate; /* skip restart */
if (!g->gcemergency)
- callallpendingfinalizers(L, 1);
+ callallpendingfinalizers(L);
}
@@ -1334,7 +1332,7 @@ void luaC_freeallobjects (lua_State *L) {
luaC_changemode(L, KGC_INC);
separatetobefnz(g, 1); /* separate all objects with finalizers */
lua_assert(g->finobj == NULL);
- callallpendingfinalizers(L, 0);
+ callallpendingfinalizers(L);
deletelist(L, g->allgc, obj2gco(g->mainthread));
deletelist(L, g->finobj, NULL);
deletelist(L, g->fixedgc, NULL); /* collect fixed objects */
diff --git a/lstate.c b/lstate.c
index b3e9ec6016..7f6475a881 100644
--- a/lstate.c
+++ b/lstate.c
@@ -409,3 +409,10 @@ LUA_API void lua_close (lua_State *L) {
}
+void luaE_warning (lua_State *L, const char *msg) {
+ lua_WarnFunction wf = G(L)->warnf;
+ if (wf != NULL)
+ wf(&G(L)->ud_warn, msg);
+}
+
+
diff --git a/lstate.h b/lstate.h
index f379325640..05a74dda85 100644
--- a/lstate.h
+++ b/lstate.h
@@ -316,6 +316,7 @@ LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L);
LUAI_FUNC void luaE_freeCI (lua_State *L);
LUAI_FUNC void luaE_shrinkCI (lua_State *L);
LUAI_FUNC void luaE_enterCcall (lua_State *L);
+LUAI_FUNC void luaE_warning (lua_State *L, const char *msg);
#define luaE_exitCcall(L) ((L)->nCcalls--)
diff --git a/ltests.c b/ltests.c
index 5ea8b0804c..0cb6d3a7b4 100644
--- a/ltests.c
+++ b/ltests.c
@@ -63,7 +63,11 @@ static void pushobject (lua_State *L, const TValue *o) {
}
-static void badexit (void) {
+static void badexit (const char *fmt, ...) {
+ va_list argp;
+ va_start(argp, fmt);
+ vfprintf(stderr, fmt, argp);
+ va_end(argp);
/* avoid assertion failures when exiting */
l_memcontrol.numblocks = l_memcontrol.total = 0;
exit(EXIT_FAILURE);
@@ -71,9 +75,9 @@ static void badexit (void) {
static int tpanic (lua_State *L) {
- fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n",
- lua_tostring(L, -1));
- return (badexit(), 0); /* do not return to Lua */
+ return (badexit("PANIC: unprotected error in call to Lua API (%s)\n",
+ lua_tostring(L, -1)),
+ 0); /* do not return to Lua */
}
@@ -83,16 +87,47 @@ static int islast (const char *message) {
}
+/*
+** Warning function for tests. Fist, it concatenates all parts of
+** a warning in buffer 'buff'. Then:
+** messages starting with '#' are shown on standard output (used to
+** test explicit warnings);
+** messages containing '@' are stored in global '_WARN' (used to test
+** errors that generate warnings);
+** other messages abort the tests (they represent real warning conditions;
+** the standard tests should not generate these conditions unexpectedly).
+*/
static void warnf (void **pud, const char *msg) {
- if (*pud == NULL) /* continuation line? */
- printf("%s", msg); /* print it */
- else if (msg[0] == '*') /* expected warning? */
- printf("Expected Lua warning: %s", msg + 1); /* print without the star */
- else { /* a real warning; should not happen during tests */
- fprintf(stderr, "Warning in test mode (%s), aborting...\n", msg);
- badexit();
- }
- *pud = islast(msg) ? pud : NULL;
+ static char buff[200]; /* should be enough for tests... */
+ static int cont = 0; /* message to be continued */
+ if (cont) { /* continuation? */
+ if (strlen(msg) >= sizeof(buff) - strlen(buff))
+ badexit("warnf-buffer overflow");
+ strcat(buff, msg); /* add new message to current warning */
+ }
+ else { /* new warning */
+ if (strlen(msg) >= sizeof(buff))
+ badexit("warnf-buffer overflow");
+ strcpy(buff, msg); /* start a new warning */
+ }
+ if (!islast(msg)) /* message not finished yet? */
+ cont = 1; /* wait for more */
+ else { /* handle message */
+ cont = 0; /* prepare for next message */
+ if (buff[0] == '#') /* expected warning? */
+ printf("Expected Lua warning: %s", buff); /* print it */
+ else if (strchr(buff, '@') != NULL) { /* warning for test purposes? */
+ lua_State *L = cast(lua_State *, *pud);
+ lua_unlock(L);
+ lua_pushstring(L, buff);
+ lua_setglobal(L, "_WARN"); /* assign message to global '_WARN' */
+ lua_lock(L);
+ return;
+ }
+ else { /* a real warning; should not happen during tests */
+ badexit("Unexpected warning in test mode: %s\naborting...\n", buff);
+ }
+ }
}
diff --git a/lua.h b/lua.h
index a6f8268b8e..b777624e69 100644
--- a/lua.h
+++ b/lua.h
@@ -51,8 +51,7 @@
#define LUA_ERRRUN 2
#define LUA_ERRSYNTAX 3
#define LUA_ERRMEM 4
-#define LUA_ERRGCMM 5
-#define LUA_ERRERR 6
+#define LUA_ERRERR 5
typedef struct lua_State lua_State;
diff --git a/manual/manual.of b/manual/manual.of
index 196ea1efe4..d64f0f1a43 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -722,8 +722,6 @@ Lua calls the finalizers of all objects marked for finalization,
following the reverse order that they were marked.
If any finalizer marks objects for collection during that phase,
these marks have no effect.
-If any finalizer raises an error during that phase,
-its execution is interrupted but the error is ignored.
Finalizers cannot yield.
@@ -2645,8 +2643,7 @@ by looking only at its arguments
The third field, @T{x},
tells whether the function may raise errors:
@Char{-} means the function never raises any error;
-@Char{m} means the function may raise out-of-memory errors
-and errors running a finalizer;
+@Char{m} means the function may raise only out-of-memory errors;
@Char{v} means the function may raise the errors explained in the text;
@Char{e} means the function can run arbitrary Lua code,
either directly or through metamethods,
@@ -3364,12 +3361,6 @@ syntax error during precompilation;}
@item{@Lid{LUA_ERRMEM}|
@x{memory allocation (out-of-memory) error};}
-@item{@Lid{LUA_ERRGCMM}|
-error while running a @idx{__gc} metamethod.
-(This error has no relation with the chunk being loaded.
-It is generated by the garbage collector.)
-}
-
}
The @id{lua_load} function uses a user-supplied @id{reader} function
@@ -3564,13 +3555,6 @@ For such errors, Lua does not call the @x{message handler}.
error while running the @x{message handler}.
}
-@item{@defid{LUA_ERRGCMM}|
-error while running a @idx{__gc} metamethod.
-For such errors, Lua does not call the @x{message handler}
-(as this kind of error typically has no relation
-with the function being called).
-}
-
}
}
@@ -6298,6 +6282,8 @@ The current value of this variable is @St{Lua 5.4}.
@LibEntry{warn (message)|
Emits a warning with the given message.
+Note that messages not ending with an end-of-line
+are assumed to be continued by the message in the next call.
}
@@ -8773,6 +8759,12 @@ so there is no need to check whether they are using the same
address space.)
}
+@item{
+The constant @Lid{LUA_ERRGCMM} was removed.
+Errors in finalizers are never propagated;
+instead, they generate a warning.
+}
+
}
}
diff --git a/testes/all.lua b/testes/all.lua
index bde4195e62..506afad287 100644
--- a/testes/all.lua
+++ b/testes/all.lua
@@ -190,12 +190,17 @@ assert(dofile('verybig.lua', true) == 10); collectgarbage()
dofile('files.lua')
if #msgs > 0 then
- warn("*tests not performed:\n ")
+ warn("#tests not performed:\n ")
for i=1,#msgs do
warn(msgs[i]); warn("\n ")
end
+ warn("\n")
end
+print("(there should be two warnings now)")
+warn("#This is "); warn("an expected"); warn(" warning\n")
+warn("#This is"); warn(" another one\n")
+
-- no test module should define 'debug'
assert(debug == nil)
@@ -219,10 +224,6 @@ local _G, showmem, print, format, clock, time, difftime, assert, open =
local fname = T and "time-debug.txt" or "time.txt"
local lasttime
-
-warn("*This is "); warn("an expected"); warn(" warning\n")
-warn("*This is"); warn(" another one\n")
-
if not usertests then
-- open file with time of last performed test
local f = io.open(fname)
diff --git a/testes/api.lua b/testes/api.lua
index b4d6386602..893a36cb75 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -114,13 +114,12 @@ end
-- testing warnings
T.testC([[
- warning "*This "
- warning "warning "
- warning "should be in a"
- warning " single line
+ warning "#This shold be a"
+ warning " single "
+ warning "warning
"
- warning "*This should be "
- warning "another warning
+ warning "#This should be "
+ warning "another one
"
]])
@@ -896,24 +895,15 @@ do -- testing errors during GC
a[i] = T.newuserdata(i) -- creates several udata
end
for i=1,20,2 do -- mark half of them to raise errors during GC
- debug.setmetatable(a[i], {__gc = function (x) error("error inside gc") end})
+ debug.setmetatable(a[i],
+ {__gc = function (x) error("@expected error in gc") end})
end
for i=2,20,2 do -- mark the other half to count and to create more garbage
debug.setmetatable(a[i], {__gc = function (x) load("A=A+1")() end})
end
+ a = nil
_G.A = 0
- a = 0
- while 1 do
- local stat, msg = pcall(collectgarbage)
- if stat then
- break -- stop when no more errors
- else
- a = a + 1
- assert(string.find(msg, "__gc"))
- end
- end
- assert(a == 10) -- number of errors
-
+ collectgarbage()
assert(A == 10) -- number of normal collections
collectgarbage("restart")
end
diff --git a/testes/gc.lua b/testes/gc.lua
index 8b9179c858..84e8ffb700 100644
--- a/testes/gc.lua
+++ b/testes/gc.lua
@@ -353,40 +353,36 @@ GC()
-- testing errors during GC
-do
-collectgarbage("stop") -- stop collection
-local u = {}
-local s = {}; setmetatable(s, {__mode = 'k'})
-setmetatable(u, {__gc = function (o)
- local i = s[o]
- s[i] = true
- assert(not s[i - 1]) -- check proper finalization order
- if i == 8 then error("here") end -- error during GC
-end})
-
-for i = 6, 10 do
- local n = setmetatable({}, getmetatable(u))
- s[n] = i
-end
-
-assert(not pcall(collectgarbage))
-for i = 8, 10 do assert(s[i]) end
-
-for i = 1, 5 do
- local n = setmetatable({}, getmetatable(u))
- s[n] = i
-end
+if T then
+ collectgarbage("stop") -- stop collection
+ local u = {}
+ local s = {}; setmetatable(s, {__mode = 'k'})
+ setmetatable(u, {__gc = function (o)
+ local i = s[o]
+ s[i] = true
+ assert(not s[i - 1]) -- check proper finalization order
+ if i == 8 then error("@expected@") end -- error during GC
+ end})
+
+ for i = 6, 10 do
+ local n = setmetatable({}, getmetatable(u))
+ s[n] = i
+ end
-collectgarbage()
-for i = 1, 10 do assert(s[i]) end
+ collectgarbage()
+ assert(string.find(_WARN, "error in __gc metamethod"))
+ assert(string.match(_WARN, "@(.-)@") == "expected")
+ for i = 8, 10 do assert(s[i]) end
-getmetatable(u).__gc = false
+ for i = 1, 5 do
+ local n = setmetatable({}, getmetatable(u))
+ s[n] = i
+ end
+ collectgarbage()
+ for i = 1, 10 do assert(s[i]) end
--- __gc errors with non-string messages
-setmetatable({}, {__gc = function () error{} end})
-local a, b = pcall(collectgarbage)
-assert(not a and type(b) == "string" and string.find(b, "error in __gc"))
+ getmetatable(u).__gc = false
end
print '+'
@@ -478,9 +474,11 @@ end
-- errors during collection
-u = setmetatable({}, {__gc = function () error "!!!" end})
-u = nil
-assert(not pcall(collectgarbage))
+if T then
+ u = setmetatable({}, {__gc = function () error "@expected error" end})
+ u = nil
+ collectgarbage()
+end
if not _soft then
@@ -645,11 +643,26 @@ do
end
-- create several objects to raise errors when collected while closing state
-do
- local mt = {__gc = function (o) return o + 1 end}
- for i = 1,10 do
+if T then
+ local error, assert, warn, find = error, assert, warn, string.find
+ local n = 0
+ local lastmsg
+ local mt = {__gc = function (o)
+ n = n + 1
+ assert(n == o[1])
+ if n == 1 then
+ _WARN = nil
+ elseif n == 2 then
+ assert(find(_WARN, "@expected warning"))
+ lastmsg = _WARN -- get message from previous error (first 'o')
+ else
+ assert(lastmsg == _WARN) -- subsequent error messages are equal
+ end
+ error"@expected warning"
+ end}
+ for i = 10, 1, -1 do
-- create object and preserve it until the end
- table.insert(___Glob, setmetatable({}, mt))
+ table.insert(___Glob, setmetatable({i}, mt))
end
end
From 4ace93ca6502dd1da38d5c06fa099d229e791ba8 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 4 Jan 2019 13:09:47 -0200
Subject: [PATCH 119/889] No more to-be-closed functions
To-be-closed variables must contain objects with '__toclose'
metamethods (or nil). Functions were removed for several reasons:
* Functions interact badly with sandboxes. If a sandbox raises
an error to interrupt a script, a to-be-closed function still
can hijack control and continue running arbitrary sandboxed code.
* Functions interact badly with coroutines. If a coroutine yields
and is never resumed again, its to-be-closed functions will never
run. To-be-closed objects, on the other hand, will still be closed,
provided they have appropriate finalizers.
* If you really need a function, it is easy to create a dummy
object to run that function in its '__toclose' metamethod.
This comit also adds closing of variables in case of panic.
---
ldo.c | 1 +
lfunc.c | 24 ++++++++------------
ltests.c | 18 +++++++++------
manual/manual.of | 34 ++++++++++++----------------
testes/api.lua | 19 +++++++++++++++-
testes/coroutine.lua | 13 +++++++++--
testes/goto.lua | 2 +-
testes/locals.lua | 54 +++++++++++++++++++++++++-------------------
8 files changed, 97 insertions(+), 68 deletions(-)
diff --git a/ldo.c b/ldo.c
index f8d8f11cfb..077109c4ab 100644
--- a/ldo.c
+++ b/ldo.c
@@ -118,6 +118,7 @@ l_noret luaD_throw (lua_State *L, int errcode) {
}
else { /* thread has no error handler */
global_State *g = G(L);
+ errcode = luaF_close(L, L->stack, errcode); /* close all upvalues */
L->status = cast_byte(errcode); /* mark it as dead */
if (g->mainthread->errorJmp) { /* main thread has a handler? */
setobjs2s(L, g->mainthread->top++, L->top - 1); /* copy error obj. */
diff --git a/lfunc.c b/lfunc.c
index bdf3cd2598..362b798cba 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -100,28 +100,23 @@ UpVal *luaF_findupval (lua_State *L, StkId level) {
static void callclose (lua_State *L, void *ud) {
UNUSED(ud);
- luaD_callnoyield(L, L->top - 2, 0);
+ luaD_callnoyield(L, L->top - 3, 0);
}
/*
-** Prepare closing method plus its argument for object 'obj' with
+** Prepare closing method plus its arguments for object 'obj' with
** error message 'err'. (This function assumes EXTRA_STACK.)
*/
static int prepclosingmethod (lua_State *L, TValue *obj, TValue *err) {
StkId top = L->top;
- if (ttisfunction(obj)) { /* object to-be-closed is a function? */
- setobj2s(L, top, obj); /* push function */
- setobj2s(L, top + 1, err); /* push error msg. as argument */
- }
- else { /* try '__close' metamethod */
- const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE);
- if (ttisnil(tm)) /* no metamethod? */
- return 0; /* nothing to call */
- setobj2s(L, top, tm); /* will call metamethod... */
- setobj2s(L, top + 1, obj); /* with 'self' as the argument */
- }
- L->top = top + 2; /* add function and argument */
+ const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE);
+ if (ttisnil(tm)) /* no metamethod? */
+ return 0; /* nothing to call */
+ setobj2s(L, top, tm); /* will call metamethod... */
+ setobj2s(L, top + 1, obj); /* with 'self' as the 1st argument */
+ setobj2s(L, top + 2, err); /* and error msg. as 2nd argument */
+ L->top = top + 3; /* add function and arguments */
return 1;
}
@@ -156,6 +151,7 @@ static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) {
if (newstatus != LUA_OK) /* another error when closing? */
status = newstatus; /* this will be the new error */
}
+ /* else no metamethod; ignore this case and keep original error */
}
return status;
}
diff --git a/ltests.c b/ltests.c
index 0cb6d3a7b4..36a974aeb6 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1201,8 +1201,8 @@ static const char *const delimits = " \t\n,;";
static void skip (const char **pc) {
for (;;) {
if (**pc != '\0' && strchr(delimits, **pc)) (*pc)++;
- else if (**pc == '#') {
- while (**pc != '\n' && **pc != '\0') (*pc)++;
+ else if (**pc == '#') { /* comment? */
+ while (**pc != '\n' && **pc != '\0') (*pc)++; /* until end-of-line */
}
else break;
}
@@ -1544,18 +1544,22 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) {
}
else if EQ("setfield") {
int t = getindex;
- lua_setfield(L1, t, getstring);
+ const char *s = getstring;
+ lua_setfield(L1, t, s);
}
else if EQ("setglobal") {
- lua_setglobal(L1, getstring);
+ const char *s = getstring;
+ lua_setglobal(L1, s);
}
else if EQ("sethook") {
int mask = getnum;
int count = getnum;
- sethookaux(L1, mask, count, getstring);
+ const char *s = getstring;
+ sethookaux(L1, mask, count, s);
}
else if EQ("setmetatable") {
- lua_setmetatable(L1, getindex);
+ int idx = getindex;
+ lua_setmetatable(L1, idx);
}
else if EQ("settable") {
lua_settable(L1, getindex);
@@ -1620,7 +1624,7 @@ static struct X { int x; } x;
return lua_yieldk(L1, nres, i, Cfunck);
}
else if EQ("toclose") {
- lua_toclose(L, getnum);
+ lua_toclose(L1, getnum);
}
else luaL_error(L, "unknown instruction %s", buff);
}
diff --git a/manual/manual.of b/manual/manual.of
index d64f0f1a43..b9ab1ebe52 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1536,33 +1536,29 @@ goes out of scope, including normal block termination,
exiting its block by @Rw{break}/@Rw{goto}/@Rw{return},
or exiting by an error.
-To \emph{close} a value has the following meaning here:
-If the value of the variable when it goes out of scope is a function,
-that function is called;
-otherwise, if the value has a @idx{__close} metamethod,
-that metamethod is called;
-otherwise, if the value is @nil, nothing is done;
-otherwise, an error is raised.
-In the function case,
-if the scope is being closed by an error,
-the error object is passed as an argument to the function;
-if there is no error, the function gets @nil.
-In the metamethod case,
-the value itself always is passed as an argument to the metamethod.
+Here, to \emph{close} a value means
+to call its @idx{__close} metamethod.
+If the value is @nil, it is ignored;
+otherwise,
+if it does not have a @idx{__close} metamethod,
+an error is raised.
+When calling the metamethod,
+the value itself is passed as the first argument
+and the error object (if any) is passed as a second argument;
+if there was no error, the second argument is @nil.
If several to-be-closed variables go out of scope at the same event,
they are closed in the reverse order that they were declared.
-If there is any error while running a closing function,
+If there is any error while running a closing method,
that error is handled like an error in the regular code
where the variable was defined;
in particular,
-the other pending closing functions will still be called.
+the other pending closing methods will still be called.
If a coroutine yields inside a block and is never resumed again,
the variables visible at that block will never go out of scope,
and therefore they will not be closed.
-Similarly, if a script is interrupted by an unprotected error,
-its to-be-closed variables will not be closed.
+(You should use finalizers to handle this case.)
}
@@ -3002,7 +2998,7 @@ and therefore never returns
}
@APIEntry{int lua_gc (lua_State *L, int what, int data);|
-@apii{0,0,v}
+@apii{0,0,-}
Controls the garbage collector.
@@ -3056,8 +3052,6 @@ returns a boolean that tells whether the collector is running
For more details about these options,
see @Lid{collectgarbage}.
-This function may raise errors when calling finalizers.
-
}
@APIEntry{lua_Alloc lua_getallocf (lua_State *L, void **ud);|
diff --git a/testes/api.lua b/testes/api.lua
index 893a36cb75..9904dadfcc 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -396,6 +396,23 @@ do
assert(string.find(msg, "stack overflow"))
end
+ -- exit in panic still close to-be-closed variables
+ assert(T.checkpanic([[
+ pushstring "return {__close = function () Y = 'ho'; end}"
+ newtable
+ loadstring -2
+ call 0 1
+ setmetatable -2
+ toclose -1
+ pushstring "hi"
+ error
+ ]],
+ [[
+ getglobal Y
+ concat 2 # concat original error with global Y
+ ]]) == "hiho")
+
+
end
-- testing deep C stack
@@ -1115,7 +1132,7 @@ end)
testamem("to-be-closed variables", function()
local flag
do
- local *toclose x = function () flag = true end
+ local *toclose x = setmetatable({}, {__close = function () flag = true end})
flag = false
local x = {}
end
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index ca30011ff5..a4321bedaf 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -142,8 +142,15 @@ do
-- to-be-closed variables in coroutines
local X
+
+ local function func2close (f)
+ return setmetatable({}, {__close = f})
+ end
+
co = coroutine.create(function ()
- local *toclose x = function (err) assert(err == nil); X = false end
+ local *toclose x = func2close(function (self, err)
+ assert(err == nil); X = false
+ end)
X = true
coroutine.yield()
end)
@@ -154,7 +161,9 @@ do
-- error killing a coroutine
co = coroutine.create(function()
- local *toclose x = function (err) assert(err == nil); error(111) end
+ local *toclose x = func2close(function (self, err)
+ assert(err == nil); error(111)
+ end)
coroutine.yield()
end)
coroutine.resume(co)
diff --git a/testes/goto.lua b/testes/goto.lua
index 5d863a428d..f3dcfd4a3e 100644
--- a/testes/goto.lua
+++ b/testes/goto.lua
@@ -258,7 +258,7 @@ do
::L2:: goto L3
::L1:: do
- local *toclose a = function () X = true end
+ local *toclose a = setmetatable({}, {__close = function () X = true end})
assert(X == nil)
if a then goto L2 end -- jumping back out of scope of 'a'
end
diff --git a/testes/locals.lua b/testes/locals.lua
index 340af61c0e..de47ae3182 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -177,13 +177,19 @@ print"testing to-be-closed variables"
local function stack(n) n = ((n == 0) or stack(n - 1)) end
+local function func2close (f)
+ return setmetatable({}, {__close = f})
+end
+
do
local a = {}
do
local *toclose x = setmetatable({"x"}, {__close = function (self)
a[#a + 1] = self[1] end})
- local *toclose y = function (x) assert(x == nil); a[#a + 1] = "y" end
+ local *toclose y = func2close(function (self, err)
+ assert(err == nil); a[#a + 1] = "y"
+ end)
a[#a + 1] = "in"
end
a[#a + 1] = "out"
@@ -193,7 +199,7 @@ end
do
local X = false
- local function closescope () stack(10); X = true end
+ local closescope = func2close(function () stack(10); X = true end)
-- closing functions do not corrupt returning values
local function foo (x)
@@ -228,13 +234,13 @@ do
-- calls cannot be tail in the scope of to-be-closed variables
local X, Y
local function foo ()
- local *toclose _ = function () Y = 10 end
+ local *toclose _ = func2close(function () Y = 10 end)
assert(X == true and Y == nil) -- 'X' not closed yet
return 1,2,3
end
local function bar ()
- local *toclose _ = function () X = false end
+ local *toclose _ = func2close(function () X = false end)
X = true
do
return foo() -- not a tail call!
@@ -249,11 +255,15 @@ end
do -- errors in __close
local log = {}
local function foo (err)
- local *toclose x = function (msg) log[#log + 1] = msg; error(1) end
- local *toclose x1 = function (msg) log[#log + 1] = msg; end
- local *toclose gc = function () collectgarbage() end
- local *toclose y = function (msg) log[#log + 1] = msg; error(2) end
- local *toclose z = function (msg) log[#log + 1] = msg or 10; error(3) end
+ local *toclose x =
+ func2close(function (self, msg) log[#log + 1] = msg; error(1) end)
+ local *toclose x1 =
+ func2close(function (self, msg) log[#log + 1] = msg; end)
+ local *toclose gc = func2close(function () collectgarbage() end)
+ local *toclose y =
+ func2close(function (self, msg) log[#log + 1] = msg; error(2) end)
+ local *toclose z =
+ func2close(function (self, msg) log[#log + 1] = msg or 10; error(3) end)
if err then error(4) end
end
local stat, msg = pcall(foo, false)
@@ -282,7 +292,7 @@ do
-- with other errors, non-closable values are ignored
local function foo ()
local *toclose x = 34
- local *toclose y = function () error(32) end
+ local *toclose y = func2close(function () error(32) end)
end
local stat, msg = pcall(foo)
assert(not stat and msg == 32)
@@ -294,7 +304,7 @@ if rawget(_G, "T") then
-- memory error inside closing function
local function foo ()
- local *toclose y = function () T.alloccount() end
+ local *toclose y = func2close(function () T.alloccount() end)
local *toclose x = setmetatable({}, {__close = function ()
T.alloccount(0); local x = {} -- force a memory error
end})
@@ -308,12 +318,12 @@ if rawget(_G, "T") then
local _, msg = pcall(foo)
assert(msg == "not enough memory")
- local function close (msg)
+ local close = func2close(function (self, msg)
T.alloccount()
assert(msg == "not enough memory")
- end
+ end)
- -- set a memory limit and return a closing function to remove the limit
+ -- set a memory limit and return a closing object to remove the limit
local function enter (count)
stack(10) -- reserve some stack space
T.alloccount(count)
@@ -336,13 +346,13 @@ if rawget(_G, "T") then
-- repeat test with extra closing upvalues
local function test ()
- local *toclose xxx = function (msg)
+ local *toclose xxx = func2close(function (self, msg)
assert(msg == "not enough memory");
error(1000) -- raise another error
- end
- local *toclose xx = function (msg)
+ end)
+ local *toclose xx = func2close(function (self, msg)
assert(msg == "not enough memory");
- end
+ end)
local *toclose x = enter(0) -- set a memory limit
-- creation of previous upvalue will raise a memory error
os.exit(false) -- should not run
@@ -413,9 +423,9 @@ do
local x = false
local y = false
local co = coroutine.create(function ()
- local *toclose xv = function () x = true end
+ local *toclose xv = func2close(function () x = true end)
do
- local *toclose yv = function () y = true end
+ local *toclose yv = func2close(function () y = true end)
coroutine.yield(100) -- yield doesn't close variable
end
coroutine.yield(200) -- yield doesn't close variable
@@ -453,9 +463,7 @@ do
end,
nil, -- state
nil, -- control variable
- function () -- closing function
- numopen = numopen - 1
- end
+ func2close(function () numopen = numopen - 1 end) -- closing function
end
local s = 0
From 264659bd53e92969a1e17d65c0266597cde24b5d Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 8 Jan 2019 14:22:32 -0200
Subject: [PATCH 120/889] Optional 'init' argument to 'string.gmatch'
The function 'string.gmatch' now has an optional 'init' argument,
similar to 'string.find' and 'string.match'. Moreover, there was
some reorganization in the manipulation of indices in the string
library.
This commit also includes small janitorial work in the manual
and in comments in the interpreter loop.
---
lstrlib.c | 76 +++++++++++++++++++++++++++++++---------------
lvm.c | 14 ++++-----
manual/manual.of | 42 +++++++++++++------------
testes/pm.lua | 29 ++++++++++++++++++
testes/strings.lua | 5 +++
testes/tpack.lua | 2 --
6 files changed, 116 insertions(+), 52 deletions(-)
diff --git a/lstrlib.c b/lstrlib.c
index dde868c082..41ebc52371 100644
--- a/lstrlib.c
+++ b/lstrlib.c
@@ -60,23 +60,50 @@ static int str_len (lua_State *L) {
}
-/* translate a relative string position: negative means back from end */
-static lua_Integer posrelat (lua_Integer pos, size_t len) {
- if (pos >= 0) return pos;
- else if (0u - (size_t)pos > len) return 0;
- else return (lua_Integer)len + pos + 1;
+/*
+** translate a relative initial string position
+** (negative means back from end): clip result to [1, inf).
+** The length of any string in Lua must fit in a lua_Integer,
+** so there are no overflows in the casts.
+** The inverted comparison avoids a possible overflow
+** computing '-pos'.
+*/
+static size_t posrelatI (lua_Integer pos, size_t len) {
+ if (pos > 0)
+ return (size_t)pos;
+ else if (pos == 0)
+ return 1;
+ else if (pos < -(lua_Integer)len) /* inverted comparison */
+ return 1; /* clip to 1 */
+ else return len + (size_t)pos + 1;
+}
+
+
+/*
+** Gets an optional ending string position from argument 'arg',
+** with default value 'def'.
+** Negative means back from end: clip result to [0, len]
+*/
+static size_t getendpos (lua_State *L, int arg, lua_Integer def,
+ size_t len) {
+ lua_Integer pos = luaL_optinteger(L, arg, def);
+ if (pos > (lua_Integer)len)
+ return len;
+ else if (pos >= 0)
+ return (size_t)pos;
+ else if (pos < -(lua_Integer)len)
+ return 0;
+ else return len + (size_t)pos + 1;
}
static int str_sub (lua_State *L) {
size_t l;
const char *s = luaL_checklstring(L, 1, &l);
- lua_Integer start = posrelat(luaL_checkinteger(L, 2), l);
- lua_Integer end = posrelat(luaL_optinteger(L, 3, -1), l);
- if (start < 1) start = 1;
- if (end > (lua_Integer)l) end = l;
+ size_t start = posrelatI(luaL_checkinteger(L, 2), l);
+ size_t end = getendpos(L, 3, -1, l);
if (start <= end)
- lua_pushlstring(L, s + start - 1, (size_t)(end - start) + 1);
+ lua_pushlstring(L, s + start - 1, (end - start) + 1);
else lua_pushliteral(L, "");
return 1;
}
@@ -149,11 +176,10 @@ static int str_rep (lua_State *L) {
static int str_byte (lua_State *L) {
size_t l;
const char *s = luaL_checklstring(L, 1, &l);
- lua_Integer posi = posrelat(luaL_optinteger(L, 2, 1), l);
- lua_Integer pose = posrelat(luaL_optinteger(L, 3, posi), l);
+ lua_Integer pi = luaL_optinteger(L, 2, 1);
+ size_t posi = posrelatI(pi, l);
+ size_t pose = getendpos(L, 3, pi, l);
int n, i;
- if (posi < 1) posi = 1;
- if (pose > (lua_Integer)l) pose = l;
if (posi > pose) return 0; /* empty interval; return no values */
if (pose - posi >= INT_MAX) /* arithmetic overflow? */
return luaL_error(L, "string slice too long");
@@ -171,8 +197,8 @@ static int str_char (lua_State *L) {
luaL_Buffer b;
char *p = luaL_buffinitsize(L, &b, n);
for (i=1; i<=n; i++) {
- lua_Integer c = luaL_checkinteger(L, i);
- luaL_argcheck(L, uchar(c) == c, i, "value out of range");
+ lua_Unsigned c = (lua_Unsigned)luaL_checkinteger(L, i);
+ luaL_argcheck(L, c <= (lua_Unsigned)UCHAR_MAX, i, "value out of range");
p[i - 1] = uchar(c);
}
luaL_pushresultsize(&b, n);
@@ -695,16 +721,15 @@ static int str_find_aux (lua_State *L, int find) {
size_t ls, lp;
const char *s = luaL_checklstring(L, 1, &ls);
const char *p = luaL_checklstring(L, 2, &lp);
- lua_Integer init = posrelat(luaL_optinteger(L, 3, 1), ls);
- if (init < 1) init = 1;
- else if (init > (lua_Integer)ls + 1) { /* start after string's end? */
+ size_t init = posrelatI(luaL_optinteger(L, 3, 1), ls) - 1;
+ if (init > ls) { /* start after string's end? */
lua_pushnil(L); /* cannot find anything */
return 1;
}
/* explicit request or no special characters? */
if (find && (lua_toboolean(L, 4) || nospecials(p, lp))) {
/* do a plain search */
- const char *s2 = lmemfind(s + init - 1, ls - (size_t)init + 1, p, lp);
+ const char *s2 = lmemfind(s + init, ls - init, p, lp);
if (s2) {
lua_pushinteger(L, (s2 - s) + 1);
lua_pushinteger(L, (s2 - s) + lp);
@@ -713,7 +738,7 @@ static int str_find_aux (lua_State *L, int find) {
}
else {
MatchState ms;
- const char *s1 = s + init - 1;
+ const char *s1 = s + init;
int anchor = (*p == '^');
if (anchor) {
p++; lp--; /* skip anchor character */
@@ -777,11 +802,14 @@ static int gmatch (lua_State *L) {
size_t ls, lp;
const char *s = luaL_checklstring(L, 1, &ls);
const char *p = luaL_checklstring(L, 2, &lp);
+ size_t init = posrelatI(luaL_optinteger(L, 3, 1), ls) - 1;
GMatchState *gm;
- lua_settop(L, 2); /* keep them on closure to avoid being collected */
+ lua_settop(L, 2); /* keep strings on closure to avoid being collected */
gm = (GMatchState *)lua_newuserdatauv(L, sizeof(GMatchState), 0);
+ if (init > ls) /* start after string's end? */
+ init = ls + 1; /* avoid overflows in 's + init' */
prepstate(&gm->ms, L, s, ls, p, lp);
- gm->src = s; gm->p = p; gm->lastmatch = NULL;
+ gm->src = s + init; gm->p = p; gm->lastmatch = NULL;
lua_pushcclosure(L, gmatch_aux, 3);
return 1;
}
@@ -1572,7 +1600,7 @@ static int str_unpack (lua_State *L) {
const char *fmt = luaL_checkstring(L, 1);
size_t ld;
const char *data = luaL_checklstring(L, 2, &ld);
- size_t pos = (size_t)posrelat(luaL_optinteger(L, 3, 1), ld) - 1;
+ size_t pos = posrelatI(luaL_optinteger(L, 3, 1), ld) - 1;
int n = 0; /* number of results */
luaL_argcheck(L, pos <= ld, 3, "initial position out of string");
initheader(L, &h);
diff --git a/lvm.c b/lvm.c
index 652095dcd0..23e7ff700c 100644
--- a/lvm.c
+++ b/lvm.c
@@ -991,7 +991,8 @@ void luaV_finishOp (lua_State *L) {
/*
** Protect code that will finish the loop (returns) or can only raise
-** errors.
+** errors. (That is, it will not return to the interpreter main loop
+** after changing the stack or hooks.)
*/
#define halfProtect(exp) (savepc(L), (exp))
@@ -1607,7 +1608,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
L->top = ra;
halfProtect(luaD_poscall(L, ci, 0)); /* no hurry... */
}
- else {
+ else { /* do the 'poscall' here */
int nres = ci->nresults;
L->ci = ci->previous; /* back to caller */
L->top = base - 1;
@@ -1621,7 +1622,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
L->top = ra + 1;
halfProtect(luaD_poscall(L, ci, 1)); /* no hurry... */
}
- else {
+ else { /* do the 'poscall' here */
int nres = ci->nresults;
L->ci = ci->previous; /* back to caller */
if (nres == 0)
@@ -1652,8 +1653,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
lua_Integer ilimit, initv;
int stopnow;
if (unlikely(!forlimit(plimit, &ilimit, 1, &stopnow))) {
- savestate(L, ci); /* for the error message */
- luaG_forerror(L, plimit, "limit");
+ savestate(L, ci); /* for the error message */
+ luaG_forerror(L, plimit, "limit");
}
initv = (stopnow ? 0 : ivalue(init));
setivalue(plimit, ilimit);
@@ -1717,8 +1718,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_TFORPREP) {
- /* is 'toclose' not nil? */
- if (!ttisnil(s2v(ra + 3))) {
+ if (!ttisnil(s2v(ra + 3))) { /* is 'toclose' not nil? */
/* create to-be-closed upvalue for it */
halfProtect(luaF_newtbcupval(L, ra + 3));
}
diff --git a/manual/manual.of b/manual/manual.of
index b9ab1ebe52..421d04de87 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -83,25 +83,10 @@ it usually represents the absence of a useful value.
The type @emph{boolean} has two values, @false and @true.
Both @nil and @false make a condition false;
any other value makes it true.
-The type @emph{number} represents both
-integer numbers and real (floating-point) numbers.
-The type @emph{string} represents immutable sequences of bytes.
-@index{eight-bit clean}
-Lua is 8-bit clean:
-strings can contain any 8-bit value,
-including @x{embedded zeros} (@Char{\0}).
-Lua is also encoding-agnostic;
-it makes no assumptions about the contents of a string.
-The type @emph{number} uses two internal representations,
-or two @x{subtypes},
-one called @def{integer} and the other called @def{float}.
-Lua has explicit rules about when each representation is used,
-but it also converts between them automatically as needed @see{coercion}.
-Therefore,
-the programmer may choose to mostly ignore the difference
-between integers and floats
-or to assume complete control over the representation of each number.
+The type @emph{number} represents both
+integer numbers and real (floating-point) numbers,
+using two @x{subtypes}: @def{integer} and @def{float}.
Standard Lua uses 64-bit integers and double-precision (64-bit) floats,
but you can also compile Lua so that it
uses 32-bit integers and/or single-precision (32-bit) floats.
@@ -110,6 +95,22 @@ is particularly attractive
for small machines and embedded systems.
(See macro @id{LUA_32BITS} in file @id{luaconf.h}.)
+Lua has explicit rules about when each subtype is used,
+but it also converts between them automatically as needed @see{coercion}.
+Therefore,
+the programmer may choose to mostly ignore the difference
+between integers and floats
+or to assume complete control over the representation of each number.
+
+The type @emph{string} represents immutable sequences of bytes.
+@index{eight-bit clean}
+Lua is 8-bit clean:
+strings can contain any 8-bit value,
+including @x{embedded zeros} (@Char{\0}).
+Lua is also encoding-agnostic;
+it makes no assumptions about the contents of a string.
+The length of any string in Lua must fit in a Lua integer.
+
Lua can call (and manipulate) functions written in Lua and
functions written in C @see{functioncall}.
Both are represented by the type @emph{function}.
@@ -6788,13 +6789,16 @@ the string argument should not contain @x{embedded zeros}.
}
-@LibEntry{string.gmatch (s, pattern)|
+@LibEntry{string.gmatch (s, pattern [, init])|
Returns an iterator function that,
each time it is called,
returns the next captures from @id{pattern} @see{pm}
over the string @id{s}.
If @id{pattern} specifies no captures,
then the whole match is produced in each call.
+A third, optional numeric argument @id{init} specifies
+where to start the search;
+its default value @N{is 1} and can be negative.
As an example, the following loop
will iterate over all the words from string @id{s},
diff --git a/testes/pm.lua b/testes/pm.lua
index 1afaccf694..8cc8772e87 100644
--- a/testes/pm.lua
+++ b/testes/pm.lua
@@ -297,6 +297,35 @@ for k,v in pairs(t) do assert(k+1 == v+0); a=a+1 end
assert(a == 3)
+do -- init parameter in gmatch
+ local s = 0
+ for k in string.gmatch("10 20 30", "%d+", 3) do
+ s = s + tonumber(k)
+ end
+ assert(s == 50)
+
+ s = 0
+ for k in string.gmatch("11 21 31", "%d+", -4) do
+ s = s + tonumber(k)
+ end
+ assert(s == 32)
+
+ -- there is an empty string at the end of the subject
+ s = 0
+ for k in string.gmatch("11 21 31", "%w*", 9) do
+ s = s + 1
+ end
+ assert(s == 1)
+
+ -- there are no empty strings after the end of the subject
+ s = 0
+ for k in string.gmatch("11 21 31", "%w*", 10) do
+ s = s + 1
+ end
+ assert(s == 0)
+end
+
+
-- tests for `%f' (`frontiers')
assert(string.gsub("aaa aa a aaa a", "%f[%w]a", "x") == "xaa xa x xaa x")
diff --git a/testes/strings.lua b/testes/strings.lua
index 587a0e06d3..884809249a 100644
--- a/testes/strings.lua
+++ b/testes/strings.lua
@@ -94,6 +94,11 @@ assert(string.char(string.byte("\xe4l\0
assert(string.char(string.byte("\xe4l\0u", 1, 0)) == "")
assert(string.char(string.byte("\xe4l\0u", -10, 100)) == "\xe4l\0u")
+checkerror("out of range", string.char, 256)
+checkerror("out of range", string.char, -1)
+checkerror("out of range", string.char, math.maxinteger)
+checkerror("out of range", string.char, math.mininteger)
+
assert(string.upper("ab\0c") == "AB\0C")
assert(string.lower("\0ABCc%$") == "\0abcc%$")
assert(string.rep('teste', 0) == '')
diff --git a/testes/tpack.lua b/testes/tpack.lua
index 4c5fc7f74d..2b9953f831 100644
--- a/testes/tpack.lua
+++ b/testes/tpack.lua
@@ -314,9 +314,7 @@ do -- testing initial position
for i = 1, #x + 1 do
assert(unpack("c0", x, i) == "")
end
- checkerror("out of string", unpack, "c0", x, 0)
checkerror("out of string", unpack, "c0", x, #x + 2)
- checkerror("out of string", unpack, "c0", x, -(#x + 1))
end
From 2c32bff60987d38a60a58d4f0123f3783da60a63 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 30 Jan 2019 11:44:42 -0200
Subject: [PATCH 121/889] After a "bad collections", avoid switching back back
to generational
After a major bad collection (one that collects too few objects),
next collection will be major again. In that case, avoid switching
back to generational mode (as it will have to switch again to
incremental to do next major collection).
---
lapi.c | 6 +-
lgc.c | 158 ++++++++++++++++++++++++++++++++++++--------------
lgc.h | 9 ++-
lstate.c | 4 +-
lstate.h | 1 +
testes/gc.lua | 6 ++
6 files changed, 134 insertions(+), 50 deletions(-)
diff --git a/lapi.c b/lapi.c
index 8ff7bfbd30..4026497e00 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1141,22 +1141,21 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
break;
}
case LUA_GCGEN: {
- int oldmode = g->gckind;
int minormul = va_arg(argp, int);
int majormul = va_arg(argp, int);
+ res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC;
if (minormul != 0)
g->genminormul = minormul;
if (majormul != 0)
setgcparam(g->genmajormul, majormul);
luaC_changemode(L, KGC_GEN);
- res = (oldmode == KGC_GEN) ? LUA_GCGEN : LUA_GCINC;
break;
}
case LUA_GCINC: {
- int oldmode = g->gckind;
int pause = va_arg(argp, int);
int stepmul = va_arg(argp, int);
int stepsize = va_arg(argp, int);
+ res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC;
if (pause != 0)
setgcparam(g->gcpause, pause);
if (stepmul != 0)
@@ -1164,7 +1163,6 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
if (stepsize != 0)
g->gcstepsize = stepsize;
luaC_changemode(L, KGC_INC);
- res = (oldmode == KGC_GEN) ? LUA_GCGEN : LUA_GCINC;
break;
}
default: res = -1; /* invalid option */
diff --git a/lgc.c b/lgc.c
index 0c3386e745..bf0460d549 100644
--- a/lgc.c
+++ b/lgc.c
@@ -101,6 +101,7 @@
static void reallymarkobject (global_State *g, GCObject *o);
static lu_mem atomic (lua_State *L);
+static void entersweep (lua_State *L);
/*
@@ -1162,15 +1163,7 @@ static void youngcollection (lua_State *L, global_State *g) {
}
-/*
-** Enter generational mode. Must go until the end of an atomic cycle
-** to ensure that all threads are in the gray list. Then, turn all
-** objects into old and finishes the collection.
-*/
-static void entergen (lua_State *L, global_State *g) {
- luaC_runtilstate(L, bitmask(GCSpause)); /* prepare to start a new cycle */
- luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */
- atomic(L);
+static void atomic2gen (lua_State *L, global_State *g) {
/* sweep all elements making them old */
sweep2old(L, &g->allgc);
/* everything alive now is old */
@@ -1183,15 +1176,31 @@ static void entergen (lua_State *L, global_State *g) {
sweep2old(L, &g->tobefnz);
g->gckind = KGC_GEN;
+ g->lastatomic = 0;
g->GCestimate = gettotalbytes(g); /* base for memory control */
finishgencycle(L, g);
}
+/*
+** Enter generational mode. Must go until the end of an atomic cycle
+** to ensure that all threads and weak tables are in the gray lists.
+** Then, turn all objects into old and finishes the collection.
+*/
+static lu_mem entergen (lua_State *L, global_State *g) {
+ lu_mem numobjs;
+ luaC_runtilstate(L, bitmask(GCSpause)); /* prepare to start a new cycle */
+ luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */
+ numobjs = atomic(L); /* propagates all and then do the atomic stuff */
+ atomic2gen(L, g);
+ return numobjs;
+}
+
+
/*
** Enter incremental mode. Turn all objects white, make all
** intermediate lists point to NULL (to avoid invalid pointers),
-** and go to pause state.
+** and go to the pause state.
*/
static void enterinc (global_State *g) {
whitelist(g, g->allgc);
@@ -1201,6 +1210,7 @@ static void enterinc (global_State *g) {
g->finobjrold = g->finobjold = g->finobjsur = NULL;
g->gcstate = GCSpause;
g->gckind = KGC_INC;
+ g->lastatomic = 0;
}
@@ -1215,54 +1225,114 @@ void luaC_changemode (lua_State *L, int newmode) {
else
enterinc(g); /* entering incremental mode */
}
+ g->lastatomic = 0;
}
/*
** Does a full collection in generational mode.
*/
-static void fullgen (lua_State *L, global_State *g) {
+static lu_mem fullgen (lua_State *L, global_State *g) {
enterinc(g);
- entergen(L, g);
+ return entergen(L, g);
+}
+
+
+/*
+** Set debt for the next minor collection, which will happen when
+** memory grows 'genminormul'%.
+*/
+static void setminordebt (global_State *g) {
+ luaE_setdebt(g, -(cast(l_mem, (gettotalbytes(g) / 100)) * g->genminormul));
}
/*
-** Does a generational "step". If memory grows 'genmajormul'% larger
-** than last major collection (kept in 'g->GCestimate'), does a major
-** collection. Otherwise, does a minor collection and set debt to make
-** another collection when memory grows 'genminormul'% larger.
-** When it does a major collection, it then checks whether it could
-** reclaim at least ?? memory. If not, it sets a long pause for the
-** next collection. (Therefore, the next collection will be a major
-** one, too.)
+** Does a major collection after last collection was a "bad collection".
+**
+** When the program is building a big struture, it allocates lots of
+** memory but generates very little garbage. In those scenarios,
+** the generational mode just wastes time doing small collections, and
+** major collections are frequently what we call a "bad collection", a
+** collection that frees too few objects. To avoid the cost of switching
+** between generational mode and the incremental mode needed for full
+** (major) collections, the collector tries to stay in incremental mode
+** after a bad collection, and to switch back to generational mode only
+** after a "good" collection (one that traverses less than 9/8 objects
+** of the previous one).
+** The collector must choose whether to stay in incremental mode or to
+** switch back to generational mode before sweeping. At this point, it
+** does not know the real memory in use, so it cannot use memory to
+** decide whether to return to generational mode. Instead, it uses the
+** number of objects traversed (returned by 'atomic') as a proxy. The
+** field 'g->lastatomic' keeps this count from the last collection.
+** ('g->lastatomic != 0' also means that the last collection was bad.)
+*/
+static void stepgenfull (lua_State *L, global_State *g) {
+ lu_mem newatomic; /* count of traversed objects */
+ lu_mem lastatomic = g->lastatomic; /* count from last collection */
+ if (g->gckind == KGC_GEN) /* still in generational mode? */
+ enterinc(g); /* enter incremental mode */
+ luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */
+ newatomic = atomic(L); /* mark everybody */
+ if (newatomic < lastatomic + (lastatomic >> 3)) { /* good collection? */
+ atomic2gen(L, g); /* return to generational mode */
+ setminordebt(g);
+ }
+ else { /* another bad collection; stay in incremental mode */
+ g->GCestimate = gettotalbytes(g); /* first estimate */;
+ entersweep(L);
+ luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */
+ setpause(g);
+ g->lastatomic = newatomic;
+ }
+}
+
+
+/*
+** Does a generational "step".
+** Usually, this means doing a minor collection and setting the debt to
+** make another collection when memory grows 'genminormul'% larger.
+**
+** However, there are exceptions. If memory grows 'genmajormul'%
+** larger than it was at the end of the last major collection (kept
+** in 'g->GCestimate'), the function does a major collection. At the
+** end, it checks whether the major collection was able to free a
+** decent amount of memory (at least half the growth in memory since
+** previous major collection). If so, the collector keeps its state,
+** and the next collection will probably be minor again. Otherwise,
+** we have what we call a "bad collection". In that case, set the field
+** 'g->lastatomic' to signal that fact, so that the next collection will
+** go to 'stepgenfull'.
+**
** 'GCdebt <= 0' means an explicit call to GC step with "size" zero;
-** in that case, always do a minor collection.
+** in that case, do a minor collection.
*/
static void genstep (lua_State *L, global_State *g) {
- lu_mem majorbase = g->GCestimate; /* memory after last major collection */
- lu_mem majorinc = (majorbase / 100) * getgcparam(g->genmajormul);
- lu_mem memnew = gettotalbytes(g);
- if (g->GCdebt > 0 && memnew > majorbase + majorinc) {
- fullgen(L, g);
- memnew = gettotalbytes(g);
- if (memnew < majorbase + (majorinc / 2)) {
- /* collected at least half of memory growth since last major
- collection; go back to minor collections */
- luaE_setdebt(g, -(cast(l_mem, (memnew / 100)) * g->genminormul));
+ if (g->lastatomic != 0) /* last collection was a bad one? */
+ stepgenfull(L, g); /* do a full step */
+ else {
+ lu_mem majorbase = g->GCestimate; /* memory after last major collection */
+ lu_mem majorinc = (majorbase / 100) * getgcparam(g->genmajormul);
+ if (g->GCdebt > 0 && gettotalbytes(g) > majorbase + majorinc) {
+ lu_mem numobjs = fullgen(L, g); /* do a major collection */
+ if (gettotalbytes(g) < majorbase + (majorinc / 2)) {
+ /* collected at least half of memory growth since last major
+ collection; keep doing minor collections */
+ setminordebt(g);
+ }
+ else { /* bad collection */
+ g->lastatomic = numobjs; /* signal that last collection was bad */
+ setpause(g); /* do a long wait for next (major) collection */
+ }
}
- else {
- /* memory seems to be growing; do a long wait for next (major)
- collection */
- setpause(g);
+ else { /* regular case; do a minor collection */
+ youngcollection(L, g);
+ setminordebt(g);
+ g->GCestimate = majorbase; /* preserve base value */
}
}
- else {
- youngcollection(L, g);
- memnew = gettotalbytes(g);
- luaE_setdebt(g, -(cast(l_mem, (memnew / 100)) * g->genminormul));
- g->GCestimate = majorbase; /* preserve base value */
- }
+ lua_assert(isdecGCmodegen(g));
}
/* }====================================================== */
@@ -1493,10 +1563,10 @@ static void incstep (lua_State *L, global_State *g) {
void luaC_step (lua_State *L) {
global_State *g = G(L);
if (g->gcrunning) { /* running? */
- if (g->gckind == KGC_INC)
- incstep(L, g);
- else
+ if(isdecGCmodegen(g))
genstep(L, g);
+ else
+ incstep(L, g);
}
}
diff --git a/lgc.h b/lgc.h
index 6b1c286153..9ba7ecb050 100644
--- a/lgc.h
+++ b/lgc.h
@@ -123,7 +123,7 @@
#define LUAI_GENMINORMUL 20
/* wait memory to double before starting new cycle */
-#define LUAI_GCPAUSE 200 /* 200% */
+#define LUAI_GCPAUSE 200
/*
** some gc parameters are stored divided by 4 to allow a maximum value
@@ -138,6 +138,13 @@
#define LUAI_GCSTEPSIZE 13 /* 8 KB */
+/*
+** Check whether the declared GC mode is generational. While in
+** generational mode, the collector can go temporarily to incremental
+** mode to improve performance. This is signaled by 'g->lastatomic != 0'.
+*/
+#define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0)
+
/*
** Does one step of collection when debt becomes positive. 'pre'/'pos'
** allows some adjustments to be done only when needed. macro
diff --git a/lstate.c b/lstate.c
index 7f6475a881..6c35ea1a6c 100644
--- a/lstate.c
+++ b/lstate.c
@@ -233,7 +233,8 @@ static void init_registry (lua_State *L, global_State *g) {
/*
** open parts of the state that may cause memory-allocation errors.
-** ('ttisnil(&g->nilvalue)'' flags that the state was completely build)
+** ('g->nilvalue' being a nil value flags that the state was completely
+** build.)
*/
static void f_luaopen (lua_State *L, void *ud) {
global_State *g = G(L);
@@ -386,6 +387,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
g->twups = NULL;
g->totalbytes = sizeof(LG);
g->GCdebt = 0;
+ g->lastatomic = 0;
setivalue(&g->nilvalue, 0); /* to signal that state is not yet built */
setgcparam(g->gcpause, LUAI_GCPAUSE);
setgcparam(g->gcstepmul, LUAI_GCMUL);
diff --git a/lstate.h b/lstate.h
index 05a74dda85..11bf18fde2 100644
--- a/lstate.h
+++ b/lstate.h
@@ -193,6 +193,7 @@ typedef struct global_State {
l_mem totalbytes; /* number of bytes currently allocated - GCdebt */
l_mem GCdebt; /* bytes allocated not yet compensated by the collector */
lu_mem GCestimate; /* an estimate of the non-garbage memory in use */
+ lu_mem lastatomic; /* see function 'genstep' in file 'lgc.c' */
stringtable strt; /* hash table for strings */
TValue l_registry;
TValue nilvalue; /* a nil value */
diff --git a/testes/gc.lua b/testes/gc.lua
index 84e8ffb700..05bf564e5c 100644
--- a/testes/gc.lua
+++ b/testes/gc.lua
@@ -11,6 +11,12 @@ collectgarbage()
local oldmode = collectgarbage("incremental")
+-- changing modes should return previous mode
+assert(collectgarbage("generational") == "incremental")
+assert(collectgarbage("generational") == "generational")
+assert(collectgarbage("incremental") == "generational")
+assert(collectgarbage("incremental") == "incremental")
+
local function gcinfo ()
return collectgarbage"count" * 1024
From cf71a5ddc742692fad813f89f1c9ef53e1ffde0f Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 13 Mar 2019 13:16:53 -0300
Subject: [PATCH 122/889] Details
Several small improvements (code style, warnings, comments, more tests),
in particular:
- 'lua_topointer' extended to handle strings
- raises an error in 'string.format("%10q")' ('%q' with modifiers)
- in the manual for 'string.format', the term "option" replaced by
"conversion specifier" (the term used by the C standard)
---
lapi.c | 31 ++++++++++++++++++--------
lfunc.c | 3 ++-
lopcodes.h | 15 ++++++-------
lstrlib.c | 6 ++++--
ltests.c | 7 ++++--
manual/manual.of | 54 +++++++++++++++++++++++++++-------------------
testes/api.lua | 21 ++++++++++++------
testes/strings.lua | 1 +
8 files changed, 87 insertions(+), 51 deletions(-)
diff --git a/lapi.c b/lapi.c
index 4026497e00..66d7564945 100644
--- a/lapi.c
+++ b/lapi.c
@@ -414,8 +414,7 @@ LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) {
}
-LUA_API void *lua_touserdata (lua_State *L, int idx) {
- const TValue *o = index2value(L, idx);
+static void *touserdata (const TValue *o) {
switch (ttype(o)) {
case LUA_TUSERDATA: return getudatamem(uvalue(o));
case LUA_TLIGHTUSERDATA: return pvalue(o);
@@ -424,23 +423,37 @@ LUA_API void *lua_touserdata (lua_State *L, int idx) {
}
+LUA_API void *lua_touserdata (lua_State *L, int idx) {
+ const TValue *o = index2value(L, idx);
+ return touserdata(o);
+}
+
+
LUA_API lua_State *lua_tothread (lua_State *L, int idx) {
const TValue *o = index2value(L, idx);
return (!ttisthread(o)) ? NULL : thvalue(o);
}
+/*
+** Returns a pointer to the internal representation of an object.
+** Note that ANSI C does not allow the conversion of a pointer to
+** function to a 'void*', so the conversion here goes through
+** a 'size_t'. (As the returned pointer is only informative, this
+** conversion should not be a problem.)
+*/
LUA_API const void *lua_topointer (lua_State *L, int idx) {
const TValue *o = index2value(L, idx);
switch (ttypetag(o)) {
- case LUA_TTABLE: return hvalue(o);
- case LUA_TLCL: return clLvalue(o);
- case LUA_TCCL: return clCvalue(o);
case LUA_TLCF: return cast_voidp(cast_sizet(fvalue(o)));
- case LUA_TTHREAD: return thvalue(o);
- case LUA_TUSERDATA: return getudatamem(uvalue(o));
- case LUA_TLIGHTUSERDATA: return pvalue(o);
- default: return NULL;
+ case LUA_TUSERDATA: case LUA_TLIGHTUSERDATA:
+ return touserdata(o);
+ default: {
+ if (iscollectable(o))
+ return gcvalue(o);
+ else
+ return NULL;
+ }
}
}
diff --git a/lfunc.c b/lfunc.c
index 362b798cba..3e044b65b2 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -138,7 +138,8 @@ static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) {
if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */
callclose(L, NULL); /* call closing method */
else if (!ttisnil(uv)) { /* non-closable non-nil value? */
- const char *vname = luaG_findlocal(L, L->ci, level - L->ci->func, NULL);
+ int idx = cast_int(level - L->ci->func);
+ const char *vname = luaG_findlocal(L, L->ci, idx, NULL);
if (vname == NULL) vname = "?";
luaG_runerror(L, "attempt to close non-closable variable '%s'", vname);
}
diff --git a/lopcodes.h b/lopcodes.h
index d7403caf4b..3e100259b7 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -90,7 +90,6 @@ enum OpMode {iABC, iABx, iAsBx, iAx, isJ}; /* basic instruction formats */
#define MAXARG_B ((1<> 1)
-#define MAXARG_Cx ((1<<(SIZE_C + 1))-1)
/* creates a mask with 'n' 1 bits at position 'p' */
@@ -233,8 +232,8 @@ OP_BANDK,/* A B C R(A) := R(B) & K(C):integer */
OP_BORK,/* A B C R(A) := R(B) | K(C):integer */
OP_BXORK,/* A B C R(A) := R(B) ~ K(C):integer */
-OP_SHRI,/* A B C R(A) := R(B) >> C */
-OP_SHLI,/* A B C R(A) := C << R(B) */
+OP_SHRI,/* A B sC R(A) := R(B) >> C */
+OP_SHLI,/* A B sC R(A) := C << R(B) */
OP_ADD,/* A B C R(A) := R(B) + R(C) */
OP_SUB,/* A B C R(A) := R(B) - R(C) */
@@ -272,7 +271,7 @@ OP_GTI,/* A sB if ((R(A) > sB) ~= k) then pc++ */
OP_GEI,/* A sB if ((R(A) >= sB) ~= k) then pc++ */
OP_TEST,/* A if (not R(A) == k) then pc++ */
-OP_TESTSET,/* A B if (not R(B) == k) then R(A) := R(B) else pc++ */
+OP_TESTSET,/* A B if (not R(B) == k) then pc++ else R(A) := R(B) */
OP_CALL,/* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */
OP_TAILCALL,/* A B C return R(A)(R(A+1), ... ,R(A+B-1)) */
@@ -305,15 +304,15 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */
} OpCode;
-#define NUM_OPCODES (cast_int(OP_EXTRAARG) + 1)
+#define NUM_OPCODES ((int)(OP_EXTRAARG) + 1)
/*===========================================================================
Notes:
- (*) In OP_CALL, if (B == 0) then B = top. If (C == 0), then 'top' is
- set to last_result+1, so next open instruction (OP_CALL, OP_RETURN*,
- OP_SETLIST) may use 'top'.
+ (*) In OP_CALL, if (B == 0) then B = top - A. If (C == 0), then
+ 'top' is set to last_result+1, so next open instruction (OP_CALL,
+ OP_RETURN*, OP_SETLIST) may use 'top'.
(*) In OP_VARARG, if (C == 0) then use actual number of varargs and
set top (like in OP_CALL with C == 0).
diff --git a/lstrlib.c b/lstrlib.c
index 41ebc52371..ab4258e5b1 100644
--- a/lstrlib.c
+++ b/lstrlib.c
@@ -181,7 +181,7 @@ static int str_byte (lua_State *L) {
size_t pose = getendpos(L, 3, pi, l);
int n, i;
if (posi > pose) return 0; /* empty interval; return no values */
- if (pose - posi >= INT_MAX) /* arithmetic overflow? */
+ if (pose - posi >= (size_t)INT_MAX) /* arithmetic overflow? */
return luaL_error(L, "string slice too long");
n = (int)(pose - posi) + 1;
luaL_checkstack(L, n, "string slice too long");
@@ -1159,7 +1159,7 @@ static int str_format (lua_State *L) {
char *buff = luaL_prepbuffsize(&b, MAX_ITEM); /* to put formatted item */
int nb = 0; /* number of bytes in added item */
if (++arg > top)
- luaL_argerror(L, arg, "no value");
+ return luaL_argerror(L, arg, "no value");
strfrmt = scanformat(L, strfrmt, form);
switch (*strfrmt++) {
case 'c': {
@@ -1186,6 +1186,8 @@ static int str_format (lua_State *L) {
break;
}
case 'q': {
+ if (form[2] != '\0') /* modifiers? */
+ return luaL_error(L, "specifier '%%q' cannot have modifiers");
addliteral(L, &b, arg);
break;
}
diff --git a/ltests.c b/ltests.c
index 36a974aeb6..233753820d 100644
--- a/ltests.c
+++ b/ltests.c
@@ -164,7 +164,7 @@ typedef union Header {
Memcontrol l_memcontrol =
- {0L, 0L, 0L, 0L, (~0L), {0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L}};
+ {0UL, 0UL, 0UL, 0UL, (~0UL), {0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL}};
static void freeblock (Memcontrol *mc, Header *block) {
@@ -1596,7 +1596,10 @@ static struct X { int x; } x;
lua_pushnumber(L1, lua_tonumber(L1, getindex));
}
else if EQ("topointer") {
- lua_pushnumber(L1, cast_sizet(lua_topointer(L1, getindex)));
+ lua_pushlightuserdata(L1, cast_voidp(lua_topointer(L1, getindex)));
+ }
+ else if EQ("touserdata") {
+ lua_pushlightuserdata(L1, lua_touserdata(L1, getindex));
}
else if EQ("tostring") {
const char *s = lua_tostring(L1, getindex);
diff --git a/manual/manual.of b/manual/manual.of
index 421d04de87..9c8ab033ec 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -143,7 +143,7 @@ that is, @x{arrays} that can have as indices not only numbers,
but any Lua value except @nil and @x{NaN}.
(@emphx{Not a Number} is a special floating-point value
used by the @x{IEEE 754} standard to represent
-undefined or unrepresentable numerical results, such as @T{0/0}.)
+undefined numerical results, such as @T{0/0}.)
Tables can be @emph{heterogeneous};
that is, they can contain values of all types (except @nil).
Any key with value @nil is not considered part of the table.
@@ -670,8 +670,8 @@ are called when the garbage collector detects that the
corresponding table or userdata is unreachable.
Finalizers allow you to coordinate Lua's garbage collection
with external resource management
-(such as closing files, network or database connections,
-or freeing your own memory).
+such as closing files, network or database connections,
+or freeing your own memory.
For an object (table or userdata) to be finalized when collected,
you must @emph{mark} it for finalization.
@@ -1323,11 +1323,12 @@ labels in Lua are considered statements too:
}
A label is visible in the entire block where it is defined,
-except
-inside nested blocks where a label with the same name is defined and
-inside nested functions.
+except inside nested functions.
A goto may jump to any visible label as long as it does not
enter into the scope of a local variable.
+A label should not be declared
+where a label with the same name is visible,
+even if this other label has been declared in an enclosing block.
Labels and empty statements are called @def{void statements},
as they perform no actions.
@@ -1537,7 +1538,7 @@ goes out of scope, including normal block termination,
exiting its block by @Rw{break}/@Rw{goto}/@Rw{return},
or exiting by an error.
-Here, to \emph{close} a value means
+Here, to @emph{close} a value means
to call its @idx{__close} metamethod.
If the value is @nil, it is ignored;
otherwise,
@@ -4236,7 +4237,7 @@ indicates whether the operation succeeded.
Converts the value at the given index to a generic
@N{C pointer} (@T{void*}).
-The value can be a userdata, a table, a thread, or a function;
+The value can be a userdata, a table, a thread, a string, or a function;
otherwise, @id{lua_topointer} returns @id{NULL}.
Different objects will give different pointers.
There is no way to convert the pointer back to its original value.
@@ -6712,8 +6713,10 @@ to save space.
Functions with upvalues have only their number of upvalues saved.
When (re)loaded,
-those upvalues receive fresh instances containing @nil.
-(You can use the debug library to serialize
+those upvalues receive fresh instances.
+(See the @Lid{load} function for details about
+how these upvalues are initialized.
+You can use the debug library to serialize
and reload the upvalues of a function
in a way adequate to your needs.)
@@ -6747,12 +6750,12 @@ after the two indices.
Returns a formatted version of its variable number of arguments
following the description given in its first argument (which must be a string).
The format string follows the same rules as the @ANSI{sprintf}.
-The only differences are that the options/modifiers
+The only differences are that the conversion specifiers and modifiers
@T{*}, @id{h}, @id{L}, @id{l}, @id{n},
and @id{p} are not supported
-and that there is an extra option, @id{q}.
+and that there is an extra specifier, @id{q}.
-The @id{q} option formats booleans, nil, numbers, and strings
+The specifier @id{q} formats booleans, nil, numbers, and strings
in a way that the result is a valid constant in Lua source code.
Booleans and nil are written in the obvious way
(@id{true}, @id{false}, @id{nil}).
@@ -6770,22 +6773,23 @@ may produce the string:
"a string with \"quotes\" and \
new line"
}
+This specifier does not support modifiers (flags, width, length).
-Options
+The conversion specifiers
@id{A}, @id{a}, @id{E}, @id{e}, @id{f},
@id{G}, and @id{g} all expect a number as argument.
-Options @id{c}, @id{d},
+The specifiers @id{c}, @id{d},
@id{i}, @id{o}, @id{u}, @id{X}, and @id{x}
expect an integer.
When Lua is compiled with a C89 compiler,
-options @id{A} and @id{a} (hexadecimal floats)
-do not support any modifier (flags, width, length).
+the specifiers @id{A} and @id{a} (hexadecimal floats)
+do not support modifiers.
-Option @id{s} expects a string;
+The specifier @id{s} expects a string;
if its argument is not a string,
it is converted to one following the same rules of @Lid{tostring}.
-If the option has any modifier (flags, width, length),
-the string argument should not contain @x{embedded zeros}.
+If the specifier has any modifier,
+the corresponding string argument should not contain @x{embedded zeros}.
}
@@ -8009,8 +8013,8 @@ or there is any input from some special files
}
}
-For the last two cases, @id{size}
-specifies the size of the buffer, in bytes.
+For the last two cases,
+@id{size} is a hint for the size of the buffer, in bytes.
The default is an appropriate size.
}
@@ -8698,6 +8702,12 @@ When a coroutine finishes with an error,
its stack is unwound (to run any pending closing methods).
}
+@item{
+A label for a @Rw{goto} cannot be declared where a label with the same
+name is visible, even if this other label is declared in an enclosing
+block.
+}
+
}
}
diff --git a/testes/api.lua b/testes/api.lua
index 9904dadfcc..d034ea80ab 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -332,6 +332,7 @@ function to (s, x, n)
return T.testC(string.format("%s %d; return 1", s, n), x)
end
+local null = T.pushuserdata(0)
local hfunc = string.gmatch("", "") -- a "heavy C function" (with upvalues)
assert(debug.getupvalue(hfunc, 1))
assert(to("tostring", {}) == nil)
@@ -349,13 +350,19 @@ assert(to("tonumber", {}) == 0)
assert(to("tonumber", "12") == 12)
assert(to("tonumber", "s2") == 0)
assert(to("tonumber", 1, 20) == 0)
-assert(to("topointer", 10) == 0)
-assert(to("topointer", true) == 0)
-assert(to("topointer", T.pushuserdata(20)) == 20)
-assert(to("topointer", io.read) ~= 0) -- light C function
-assert(to("topointer", hfunc) ~= 0) -- "heavy" C function
-assert(to("topointer", function () end) ~= 0) -- Lua function
-assert(to("topointer", io.stdin) ~= 0) -- full userdata
+assert(to("topointer", 10) == null)
+assert(to("topointer", true) == null)
+assert(to("topointer", nil) == null)
+assert(to("topointer", "abc") ~= null)
+assert(to("topointer", string.rep("x", 10)) ==
+ to("topointer", string.rep("x", 10))) -- short strings
+assert(to("topointer", string.rep("x", 300)) ~=
+ to("topointer", string.rep("x", 300))) -- long strings
+assert(to("topointer", T.pushuserdata(20)) ~= null)
+assert(to("topointer", io.read) ~= null) -- light C function
+assert(to("topointer", hfunc) ~= null) -- "heavy" C function
+assert(to("topointer", function () end) ~= null) -- Lua function
+assert(to("topointer", io.stdin) ~= null) -- full userdata
assert(to("func2num", 20) == 0)
assert(to("func2num", T.pushuserdata(10)) == 0)
assert(to("func2num", io.read) ~= 0) -- light C function
diff --git a/testes/strings.lua b/testes/strings.lua
index 884809249a..da53a87e7b 100644
--- a/testes/strings.lua
+++ b/testes/strings.lua
@@ -199,6 +199,7 @@ end
assert(string.format("\0%s\0", "\0\0\1") == "\0\0\0\1\0")
checkerror("contains zeros", string.format, "%10s", "\0")
+checkerror("cannot have modifiers", string.format, "%10q", "1")
-- format x tostring
assert(string.format("%s %s", nil, true) == "nil true")
From dfebe439db76b5c9fd9b4e3877857e2449c8ad87 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 13 Mar 2019 14:04:01 -0300
Subject: [PATCH 123/889] New conversion specifier '%p' for 'string.format'
The call 'string.format("%p", val)' gives a Lua equivalent to the
C API function 'lua_topointer'.
---
lstrlib.c | 5 +++++
manual/manual.of | 13 ++++++++++---
testes/strings.lua | 16 ++++++++++++++++
3 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/lstrlib.c b/lstrlib.c
index ab4258e5b1..6230cd0c00 100644
--- a/lstrlib.c
+++ b/lstrlib.c
@@ -1185,6 +1185,11 @@ static int str_format (lua_State *L) {
nb = l_sprintf(buff, MAX_ITEM, form, (LUAI_UACNUMBER)n);
break;
}
+ case 'p': {
+ const void *p = lua_topointer(L, arg);
+ nb = l_sprintf(buff, MAX_ITEM, form, p);
+ break;
+ }
case 'q': {
if (form[2] != '\0') /* modifiers? */
return luaL_error(L, "specifier '%%q' cannot have modifiers");
diff --git a/manual/manual.of b/manual/manual.of
index 9c8ab033ec..2eb8773f5b 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -6751,8 +6751,7 @@ Returns a formatted version of its variable number of arguments
following the description given in its first argument (which must be a string).
The format string follows the same rules as the @ANSI{sprintf}.
The only differences are that the conversion specifiers and modifiers
-@T{*}, @id{h}, @id{L}, @id{l}, @id{n},
-and @id{p} are not supported
+@T{*}, @id{h}, @id{L}, @id{l}, and @id{n} are not supported
and that there is an extra specifier, @id{q}.
The specifier @id{q} formats booleans, nil, numbers, and strings
@@ -6791,6 +6790,14 @@ it is converted to one following the same rules of @Lid{tostring}.
If the specifier has any modifier,
the corresponding string argument should not contain @x{embedded zeros}.
+The specifier @id{p} formats the pointer
+returned by @Lid{lua_topointer}.
+That gives a unique string identifier for tables, userdata,
+threads, strings, and functions.
+For other values (numbers, nil, booleans),
+this specifier results in a string representing
+the pointer @id{NULL}.
+
}
@LibEntry{string.gmatch (s, pattern [, init])|
@@ -8768,7 +8775,7 @@ address space.)
}
@item{
-The constant @Lid{LUA_ERRGCMM} was removed.
+The constant @id{LUA_ERRGCMM} was removed.
Errors in finalizers are never propagated;
instead, they generate a warning.
}
diff --git a/testes/strings.lua b/testes/strings.lua
index da53a87e7b..8bcbb39156 100644
--- a/testes/strings.lua
+++ b/testes/strings.lua
@@ -153,6 +153,22 @@ else -- compatible coercion
assert(tostring(-1203 + 0.0) == "-1203")
end
+do -- tests for '%p' format
+ -- not much to test, as C does not specify what '%p' does.
+ -- ("The value of the pointer is converted to a sequence of printing
+ -- characters, in an implementation-defined manner.")
+ local null = string.format("%p", nil)
+ assert(string.format("%p", {}) ~= null)
+ assert(string.format("%p", 4) == null)
+ assert(string.format("%p", print) ~= null)
+ assert(string.format("%p", coroutine.running()) ~= null)
+ assert(string.format("%p", {}) ~= string.format("%p", {}))
+ assert(string.format("%p", string.rep("a", 10)) ==
+ string.format("%p", string.rep("a", 10))) -- short strings
+ assert(string.format("%p", string.rep("a", 300)) ~=
+ string.format("%p", string.rep("a", 300))) -- long strings
+ assert(#string.format("%90p", {}) == 90)
+end
x = '"lo"\n\\'
assert(string.format('%q%s', x, x) == '"\\"lo\\"\\\n\\\\""lo"\n\\')
From c5feac2b5edf86aa9bdc4b8acd5380f2fe9e197f Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 13 Mar 2019 14:14:40 -0300
Subject: [PATCH 124/889] Strings inside Lua are not fully aligned
Removed code to ensure that strings inside Lua (as returned by
'lua_tolstring') always start in fully aligned addresses.
Since version 5.3 the documentation does not ensure that.
---
lobject.h | 10 +---------
lstring.h | 2 +-
2 files changed, 2 insertions(+), 10 deletions(-)
diff --git a/lobject.h b/lobject.h
index df31a1a0d1..53e67932a0 100644
--- a/lobject.h
+++ b/lobject.h
@@ -350,21 +350,13 @@ typedef struct TString {
} TString;
-/*
-** Ensures that address after this type is always fully aligned.
-*/
-typedef union UTString {
- LUAI_MAXALIGN; /* ensures maximum alignment for strings */
- TString tsv;
-} UTString;
-
/*
** Get the actual string (array of bytes) from a 'TString'.
** (Access to 'extra' ensures that value is really a 'TString'.)
*/
#define getstr(ts) \
- check_exp(sizeof((ts)->extra), cast_charp((ts)) + sizeof(UTString))
+ check_exp(sizeof((ts)->extra), cast_charp((ts)) + sizeof(TString))
/* get the actual string (array of bytes) from a Lua value */
diff --git a/lstring.h b/lstring.h
index e7e4aedc48..b25502187b 100644
--- a/lstring.h
+++ b/lstring.h
@@ -19,7 +19,7 @@
#define MEMERRMSG "not enough memory"
-#define sizelstring(l) (sizeof(union UTString) + ((l) + 1) * sizeof(char))
+#define sizelstring(l) (sizeof(TString) + ((l) + 1) * sizeof(char))
#define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, \
(sizeof(s)/sizeof(char))-1))
From 9eca305e75010e30342486a4139846faf1b3eccb Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 13 Mar 2019 14:47:48 -0300
Subject: [PATCH 125/889] 'math.randomseed()' sets a somewhat random seed
When called with no arguments, 'math.randomseed' uses time and ASLR
to generate a somewhat random seed. the initial seed when Lua starts
is generated this way.
---
lmathlib.c | 31 +++++++++++++++++++++----------
manual/manual.of | 23 ++++++++++++++---------
testes/math.lua | 2 +-
3 files changed, 36 insertions(+), 20 deletions(-)
diff --git a/lmathlib.c b/lmathlib.c
index f2bfff9b14..e3ccc3ee58 100644
--- a/lmathlib.c
+++ b/lmathlib.c
@@ -301,7 +301,7 @@ static int math_type (lua_State *L) {
/* rotate left 'x' by 'n' bits */
static Rand64 rotl (Rand64 x, int n) {
- return (x << n) | (trim64(x) >> (64 - n));
+ return (x << n) | (trim64(x) >> (64 - n));
}
static Rand64 nextrand (Rand64 *state) {
@@ -597,11 +597,27 @@ static void setseed (Rand64 *state, lua_Unsigned n1, lua_Unsigned n2) {
}
+/*
+** Set a "random" seed. To get some randomness, use the current time
+** and the address of 'L' (in case the machine does address space layout
+** randomization).
+*/
+static void randseed (lua_State *L, RanState *state) {
+ lua_Unsigned seed1 = (lua_Unsigned)time(NULL);
+ lua_Unsigned seed2 = (lua_Unsigned)(size_t)L;
+ setseed(state->s, seed1, seed2);
+}
+
+
static int math_randomseed (lua_State *L) {
RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1));
- lua_Integer n1 = luaL_checkinteger(L, 1);
- lua_Integer n2 = luaL_optinteger(L, 2, 0);
- setseed(state->s, n1, n2);
+ if (lua_isnone(L, 1))
+ randseed(L, state);
+ else {
+ lua_Integer n1 = luaL_checkinteger(L, 1);
+ lua_Integer n2 = luaL_optinteger(L, 2, 0);
+ setseed(state->s, n1, n2);
+ }
return 0;
}
@@ -615,15 +631,10 @@ static const luaL_Reg randfuncs[] = {
/*
** Register the random functions and initialize their state.
-** To give some "randomness" to the initial seed, use the current time
-** and the address of 'L' (in case the machine does address space layout
-** randomization).
*/
static void setrandfunc (lua_State *L) {
RanState *state = (RanState *)lua_newuserdatauv(L, sizeof(RanState), 0);
- lua_Unsigned seed1 = (lua_Unsigned)time(NULL);
- lua_Unsigned seed2 = (lua_Unsigned)(size_t)L;
- setseed(state->s, seed1, seed2);
+ randseed(L, state); /* initialize with a "random" seed */
luaL_setfuncs(L, randfuncs, 1);
}
diff --git a/manual/manual.of b/manual/manual.of
index 2eb8773f5b..b47fd86565 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -7643,14 +7643,10 @@ is equivalent to @T{math.random(1,n)}.
The call @T{math.random(0)} produces an integer with
all bits (pseudo)random.
-Lua initializes its pseudo-random generator with
-a weak attempt for ``randomness'',
+Lua initializes its pseudo-random generator with the equivalent of
+a call to @Lid{math.randomseed} with no arguments,
so that @id{math.random} should generate
different sequences of results each time the program runs.
-To ensure a required level of randomness to the initial state
-(or contrarily, to have a deterministic sequence,
-for instance when debugging a program),
-you should call @Lid{math.randomseed} explicitly.
The results from this function have good statistical qualities,
but they are not cryptographically secure.
@@ -7660,14 +7656,23 @@ some number of previous results.)
}
-@LibEntry{math.randomseed (x [, y])|
+@LibEntry{math.randomseed ([x [, y]])|
-The integer parameters @id{x} and @id{y} are
-concatenated into a 128-bit @Q{seed} that
+When called with at least one argument,
+the integer parameters @id{x} and @id{y} are
+concatenated into a 128-bit @emphx{seed} that
is used to reinitialize the pseudo-random generator;
equal seeds produce equal sequences of numbers.
The default for @id{y} is zero.
+When called with no arguments,
+Lua generates a seed with
+a weak attempt for randomness.
+To ensure a required level of randomness to the initial state
+(or contrarily, to have a deterministic sequence,
+for instance when debugging a program),
+you should call @Lid{math.randomseed} with explicit arguments.
+
}
@LibEntry{math.sin (x)|
diff --git a/testes/math.lua b/testes/math.lua
index dc5b84f680..b010ff6c3a 100644
--- a/testes/math.lua
+++ b/testes/math.lua
@@ -838,7 +838,7 @@ do
assert(rand * 2^floatbits == res)
end
-math.randomseed(0, os.time())
+math.randomseed()
do -- test random for floats
local randbits = math.min(floatbits, 64) -- at most 64 random bits
From b56d4e570a60a8e84df8288c3122eb5bb5c20af6 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 14 Mar 2019 15:30:54 -0300
Subject: [PATCH 126/889] Changes in the warning system
- The warning functions get an extra parameter that tells whether
message is to be continued (instead of using end-of-lines as a signal).
- The user data for the warning function is a regular value, instead
of a writable slot inside the Lua state.
---
lapi.c | 4 ++--
lauxlib.c | 34 ++++++++++++---------------
lbaselib.c | 2 +-
lgc.c | 8 +++----
lstate.c | 4 ++--
lstate.h | 2 +-
ltests.c | 60 ++++++++++++++++++------------------------------
lua.h | 4 ++--
manual/manual.of | 26 ++++++++++-----------
testes/all.lua | 10 ++++----
testes/api.lua | 12 ++++------
testes/gc.lua | 11 +++++----
12 files changed, 79 insertions(+), 98 deletions(-)
diff --git a/lapi.c b/lapi.c
index 66d7564945..06396ad3bc 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1286,9 +1286,9 @@ void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud) {
}
-void lua_warning (lua_State *L, const char *msg) {
+void lua_warning (lua_State *L, const char *msg, int tocont) {
lua_lock(L);
- luaE_warning(L, msg);
+ luaE_warning(L, msg, tocont);
lua_unlock(L);
}
diff --git a/lauxlib.c b/lauxlib.c
index abf923f80a..e9c02d3660 100644
--- a/lauxlib.c
+++ b/lauxlib.c
@@ -987,33 +987,29 @@ static int panic (lua_State *L) {
/*
-** checks whether 'message' ends with end-of-line
-** (and therefore is the last part of a warning)
+** Emit a warning. '*previoustocont' signals whether previous message
+** was to be continued by the current one.
*/
-static int islast (const char *message) {
- size_t len = strlen(message);
- return (len > 0 && message[len - 1] == '\n');
-}
-
-
-/*
-** Emit a warning. If '*pud' is NULL, previous message was to be
-** continued by the current one.
-*/
-static void warnf (void **pud, const char *message) {
- if (*pud == NULL) /* previous message was not the last? */
- lua_writestringerror("%s", message);
- else /* start a new warning */
- lua_writestringerror("Lua warning: %s", message);
- *pud = (islast(message)) ? pud : NULL;
+static void warnf (void *ud, const char *message, int tocont) {
+ int *previoustocont = (int *)ud;
+ if (!*previoustocont) /* previous message was the last? */
+ lua_writestringerror("%s", "Lua warning: "); /* start a new warning */
+ lua_writestringerror("%s", message); /* write message */
+ if (!tocont) /* is this the last part? */
+ lua_writestringerror("%s", "\n"); /* finish message with end-of-line */
+ *previoustocont = tocont;
}
LUALIB_API lua_State *luaL_newstate (void) {
lua_State *L = lua_newstate(l_alloc, NULL);
if (L) {
+ int *previoustocont; /* space for warning state */
lua_atpanic(L, &panic);
- lua_setwarnf(L, warnf, L);
+ previoustocont = (int *)lua_newuserdatauv(L, sizeof(int), 0);
+ luaL_ref(L, LUA_REGISTRYINDEX); /* make sure it won't be collected */
+ *previoustocont = 0; /* next message starts a new warning */
+ lua_setwarnf(L, warnf, previoustocont);
}
return L;
}
diff --git a/lbaselib.c b/lbaselib.c
index 26683a1dcb..d4b619a530 100644
--- a/lbaselib.c
+++ b/lbaselib.c
@@ -45,7 +45,7 @@ static int luaB_print (lua_State *L) {
static int luaB_warn (lua_State *L) {
const char *msg = luaL_checkstring(L, 1);
- lua_warning(L, msg);
+ lua_warning(L, msg, lua_toboolean(L, 2));
return 0;
}
diff --git a/lgc.c b/lgc.c
index bf0460d549..c834319bb8 100644
--- a/lgc.c
+++ b/lgc.c
@@ -832,7 +832,7 @@ static void GCTM (lua_State *L) {
lua_assert(!g->gcemergency);
setgcovalue(L, &v, udata2finalize(g));
tm = luaT_gettmbyobj(L, &v, TM_GC);
- if (tm != NULL && ttisfunction(tm)) { /* is there a finalizer? */
+ if (ttisfunction(tm)) { /* is the finalizer a function? */
int status;
lu_byte oldah = L->allowhook;
int running = g->gcrunning;
@@ -850,9 +850,9 @@ static void GCTM (lua_State *L) {
const char *msg = (ttisstring(s2v(L->top - 1)))
? svalue(s2v(L->top - 1))
: "error object is not a string";
- luaE_warning(L, "error in __gc metamethod (");
- luaE_warning(L, msg);
- luaE_warning(L, ")\n");
+ luaE_warning(L, "error in __gc metamethod (", 1);
+ luaE_warning(L, msg, 1);
+ luaE_warning(L, ")", 0);
}
}
}
diff --git a/lstate.c b/lstate.c
index 6c35ea1a6c..f5579a6613 100644
--- a/lstate.c
+++ b/lstate.c
@@ -411,10 +411,10 @@ LUA_API void lua_close (lua_State *L) {
}
-void luaE_warning (lua_State *L, const char *msg) {
+void luaE_warning (lua_State *L, const char *msg, int tocont) {
lua_WarnFunction wf = G(L)->warnf;
if (wf != NULL)
- wf(&G(L)->ud_warn, msg);
+ wf(G(L)->ud_warn, msg, tocont);
}
diff --git a/lstate.h b/lstate.h
index 11bf18fde2..e35f89625b 100644
--- a/lstate.h
+++ b/lstate.h
@@ -317,7 +317,7 @@ LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L);
LUAI_FUNC void luaE_freeCI (lua_State *L);
LUAI_FUNC void luaE_shrinkCI (lua_State *L);
LUAI_FUNC void luaE_enterCcall (lua_State *L);
-LUAI_FUNC void luaE_warning (lua_State *L, const char *msg);
+LUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont);
#define luaE_exitCcall(L) ((L)->nCcalls--)
diff --git a/ltests.c b/ltests.c
index 233753820d..40de22927b 100644
--- a/ltests.c
+++ b/ltests.c
@@ -63,11 +63,8 @@ static void pushobject (lua_State *L, const TValue *o) {
}
-static void badexit (const char *fmt, ...) {
- va_list argp;
- va_start(argp, fmt);
- vfprintf(stderr, fmt, argp);
- va_end(argp);
+static void badexit (const char *fmt, const char *s) {
+ fprintf(stderr, fmt, s);
/* avoid assertion failures when exiting */
l_memcontrol.numblocks = l_memcontrol.total = 0;
exit(EXIT_FAILURE);
@@ -81,52 +78,35 @@ static int tpanic (lua_State *L) {
}
-static int islast (const char *message) {
- size_t len = strlen(message);
- return (len > 0 && message[len - 1] == '\n');
-}
-
-
/*
** Warning function for tests. Fist, it concatenates all parts of
** a warning in buffer 'buff'. Then:
-** messages starting with '#' are shown on standard output (used to
+** - messages starting with '#' are shown on standard output (used to
** test explicit warnings);
-** messages containing '@' are stored in global '_WARN' (used to test
+** - messages containing '@' are stored in global '_WARN' (used to test
** errors that generate warnings);
-** other messages abort the tests (they represent real warning conditions;
-** the standard tests should not generate these conditions unexpectedly).
+** - other messages abort the tests (they represent real warning
+** conditions; the standard tests should not generate these conditions
+** unexpectedly).
*/
-static void warnf (void **pud, const char *msg) {
- static char buff[200]; /* should be enough for tests... */
- static int cont = 0; /* message to be continued */
- if (cont) { /* continuation? */
- if (strlen(msg) >= sizeof(buff) - strlen(buff))
- badexit("warnf-buffer overflow");
- strcat(buff, msg); /* add new message to current warning */
- }
- else { /* new warning */
- if (strlen(msg) >= sizeof(buff))
- badexit("warnf-buffer overflow");
- strcpy(buff, msg); /* start a new warning */
- }
- if (!islast(msg)) /* message not finished yet? */
- cont = 1; /* wait for more */
- else { /* handle message */
- cont = 0; /* prepare for next message */
+static void warnf (void *ud, const char *msg, int tocont) {
+ static char buff[200] = ""; /* should be enough for tests... */
+ if (strlen(msg) >= sizeof(buff) - strlen(buff))
+ badexit("%s", "warnf-buffer overflow");
+ strcat(buff, msg); /* add new message to current warning */
+ if (!tocont) { /* message finished? */
if (buff[0] == '#') /* expected warning? */
- printf("Expected Lua warning: %s", buff); /* print it */
+ printf("Expected Lua warning: %s\n", buff); /* print it */
else if (strchr(buff, '@') != NULL) { /* warning for test purposes? */
- lua_State *L = cast(lua_State *, *pud);
+ lua_State *L = cast(lua_State *, ud);
lua_unlock(L);
lua_pushstring(L, buff);
lua_setglobal(L, "_WARN"); /* assign message to global '_WARN' */
lua_lock(L);
- return;
}
- else { /* a real warning; should not happen during tests */
+ else /* a real warning; should not happen during tests */
badexit("Unexpected warning in test mode: %s\naborting...\n", buff);
- }
+ buff[0] = '\0'; /* prepare buffer for next warning */
}
}
@@ -1466,9 +1446,13 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) {
const char *msg = getstring;
printf("%s\n", msg);
}
+ else if EQ("warningC") {
+ const char *msg = getstring;
+ lua_warning(L1, msg, 1);
+ }
else if EQ("warning") {
const char *msg = getstring;
- lua_warning(L1, msg);
+ lua_warning(L1, msg, 0);
}
else if EQ("pushbool") {
lua_pushboolean(L1, getnum);
diff --git a/lua.h b/lua.h
index b777624e69..09611db574 100644
--- a/lua.h
+++ b/lua.h
@@ -128,7 +128,7 @@ typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
/*
** Type for warning functions
*/
-typedef void (*lua_WarnFunction) (void **pud, const char *msg);
+typedef void (*lua_WarnFunction) (void *ud, const char *msg, int tocont);
@@ -309,7 +309,7 @@ LUA_API int (lua_isyieldable) (lua_State *L);
** Warning-related functions
*/
LUA_API void (lua_setwarnf) (lua_State *L, lua_WarnFunction f, void *ud);
-LUA_API void (lua_warning) (lua_State *L, const char *msg);
+LUA_API void (lua_warning) (lua_State *L, const char *msg, int tocont);
/*
diff --git a/manual/manual.of b/manual/manual.of
index b47fd86565..63e78f9592 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -4061,7 +4061,7 @@ If @id{index} @N{is 0}, then all stack elements are removed.
Sets the @x{warning function} to be used by Lua to emit warnings
@see{lua_WarnFunction}.
-The @id{ud} parameter initializes the slot @id{pud} passed to
+The @id{ud} parameter sets the value @id{ud} passed to
the warning function.
}
@@ -4325,25 +4325,24 @@ Returns the version number of this core.
}
@APIEntry{
-typedef void (*lua_WarnFunction) (void **pud, const char *msg);|
+typedef void (*lua_WarnFunction) (void *ud, const char *msg, int tocont);|
The type of @x{warning function}s, called by Lua to emit warnings.
-The first parameter is the address of a writable slot,
-constant for a given Lua state and
-initialized by @Lid{lua_setwarnf}.
+The first parameter is an opaque pointer
+set by @Lid{lua_setwarnf}.
The second parameter is the warning message.
-This function should assume that
-a message not ending with an end-of-line will be
-continued by the message in the next call.
+The third parameter is a boolean that
+indicates whether the message is
+to be continued by the message in the next call.
}
@APIEntry{
-void lua_warning (lua_State *L, const char *msg);|
+void lua_warning (lua_State *L, const char *msg, int tocont);|
@apii{0,0,-}
Emits a warning with the given message.
-A message not ending with an end-of-line should be
+A message in a call with @id{tocont} true should be
continued in another call to this function.
}
@@ -6275,11 +6274,12 @@ The current value of this variable is @St{Lua 5.4}.
}
-@LibEntry{warn (message)|
+@LibEntry{warn (message [, tocont])|
Emits a warning with the given message.
-Note that messages not ending with an end-of-line
-are assumed to be continued by the message in the next call.
+A message in a call with @id{tocont} true should be
+continued in another call to this function.
+The default for @id{tocont} is false.
}
diff --git a/testes/all.lua b/testes/all.lua
index 506afad287..8d727b6b21 100644
--- a/testes/all.lua
+++ b/testes/all.lua
@@ -6,7 +6,7 @@
local version = "Lua 5.4"
if _VERSION ~= version then
warn(string.format(
- "This test suite is for %s, not for %s\nExiting tests\n", version, _VERSION))
+ "This test suite is for %s, not for %s\nExiting tests", version, _VERSION))
return
end
@@ -190,16 +190,16 @@ assert(dofile('verybig.lua', true) == 10); collectgarbage()
dofile('files.lua')
if #msgs > 0 then
- warn("#tests not performed:\n ")
+ warn("#tests not performed:", true)
for i=1,#msgs do
- warn(msgs[i]); warn("\n ")
+ warn("\n ", true); warn(msgs[i], true)
end
warn("\n")
end
print("(there should be two warnings now)")
-warn("#This is "); warn("an expected"); warn(" warning\n")
-warn("#This is"); warn(" another one\n")
+warn("#This is ", true); warn("an expected", true); warn(" warning")
+warn("#This is", true); warn(" another one")
-- no test module should define 'debug'
assert(debug == nil)
diff --git a/testes/api.lua b/testes/api.lua
index d034ea80ab..08672e8abe 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -114,13 +114,11 @@ end
-- testing warnings
T.testC([[
- warning "#This shold be a"
- warning " single "
- warning "warning
-"
- warning "#This should be "
- warning "another one
-"
+ warningC "#This shold be a"
+ warningC " single "
+ warning "warning"
+ warningC "#This should be "
+ warning "another one"
]])
diff --git a/testes/gc.lua b/testes/gc.lua
index 05bf564e5c..91e78a4822 100644
--- a/testes/gc.lua
+++ b/testes/gc.lua
@@ -18,6 +18,8 @@ assert(collectgarbage("incremental") == "generational")
assert(collectgarbage("incremental") == "incremental")
+local function nop () end
+
local function gcinfo ()
return collectgarbage"count" * 1024
end
@@ -388,7 +390,7 @@ if T then
collectgarbage()
for i = 1, 10 do assert(s[i]) end
- getmetatable(u).__gc = false
+ getmetatable(u).__gc = nil
end
print '+'
@@ -604,8 +606,8 @@ if T then
collectgarbage("stop")
local x = T.newuserdata(0)
local y = T.newuserdata(0)
- debug.setmetatable(y, {__gc = true}) -- bless the new udata before...
- debug.setmetatable(x, {__gc = true}) -- ...the old one
+ debug.setmetatable(y, {__gc = nop}) -- bless the new udata before...
+ debug.setmetatable(x, {__gc = nop}) -- ...the old one
assert(T.gccolor(y) == "white")
T.checkmemory()
collectgarbage("restart")
@@ -631,6 +633,7 @@ if T then
assert(T.totalmem("thread") == t + 1)
end
+
-- create an object to be collected when state is closed
do
local setmetatable,assert,type,print,getmetatable =
@@ -650,7 +653,7 @@ end
-- create several objects to raise errors when collected while closing state
if T then
- local error, assert, warn, find = error, assert, warn, string.find
+ local error, assert, find = error, assert, string.find
local n = 0
local lastmsg
local mt = {__gc = function (o)
From 8fa4f1380b9a203bfdf002c2e9e9e13ebb8384c1 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 14 Mar 2019 15:53:42 -0300
Subject: [PATCH 127/889] Finalizers must be callable
Non-function __gc metamethods are not ignored; if present, the
metamethod will be called even if it is not a function.
---
lgc.c | 2 +-
manual/manual.of | 23 +++++++++++++++++++----
2 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/lgc.c b/lgc.c
index c834319bb8..8cb3e9fad5 100644
--- a/lgc.c
+++ b/lgc.c
@@ -832,7 +832,7 @@ static void GCTM (lua_State *L) {
lua_assert(!g->gcemergency);
setgcovalue(L, &v, udata2finalize(g));
tm = luaT_gettmbyobj(L, &v, TM_GC);
- if (ttisfunction(tm)) { /* is the finalizer a function? */
+ if (!notm(tm)) { /* is there a finalizer? */
int status;
lu_byte oldah = L->allowhook;
int running = g->gcrunning;
diff --git a/manual/manual.of b/manual/manual.of
index 63e78f9592..1e4ca8575e 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -689,10 +689,8 @@ After the collection,
Lua goes through that list.
For each object in the list,
it checks the object's @idx{__gc} metamethod:
-If it is a function,
-Lua calls it with the object as its single argument;
-if the metamethod is not a function,
-Lua simply ignores it.
+If it is present,
+Lua calls it with the object as its single argument.
At the end of each garbage-collection cycle,
the finalizers for objects are called in
@@ -726,6 +724,9 @@ these marks have no effect.
Finalizers cannot yield.
+Any error while running a finalizer generates a warning;
+it is not propagated.
+
}
@sect3{weak-table| @title{Weak Tables}
@@ -7279,6 +7280,11 @@ that is,
with maximum alignment of 1 (no alignment)
and native endianness.
+Native endianness assumes that the whole system is
+either big or little endian.
+The packing functions will not emulate correctly the behavior
+of mixed-endian formats.
+
Alignment works as follows:
For each option,
the format gets extra padding until the data starts
@@ -7288,6 +7294,7 @@ this minimum must be a power of 2.
Options @St{c} and @St{z} are not aligned;
option @St{s} follows the alignment of its starting integer.
+
All padding is filled with zeros by @Lid{string.pack}
(and ignored by @Lid{string.unpack}).
@@ -8720,6 +8727,14 @@ name is visible, even if this other label is declared in an enclosing
block.
}
+@item{
+When finalizing an object,
+Lua does not ignore @idx{__gc} metamethods that are not functions.
+Any value will be called, if present.
+(Non-callable values will generate a warning,
+like any other error when calling a finalizer.)
+}
+
}
}
From 1e0c73d5b643707335b06abd2546a83d9439d14c Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 15 Mar 2019 13:14:17 -0300
Subject: [PATCH 128/889] Changes in the validation of UTF-8
All UTF-8 encoding functionality (including the escape
sequence '\u') accepts all values from the original UTF-8
specification (with sequences of up to six bytes).
By default, the decoding functions in the UTF-8 library do not
accept invalid Unicode code points, such as surrogates. A new
parameter 'nonstrict' makes them accept all code points up to
(2^31)-1, as in the original UTF-8 specification.
---
llex.c | 2 +-
lobject.c | 6 +--
lutf8lib.c | 76 ++++++++++++++++++++++++-------------
manual/manual.of | 43 +++++++++++++++++++--
testes/literals.lua | 17 ++++++---
testes/utf8.lua | 92 +++++++++++++++++++++++++++++----------------
6 files changed, 164 insertions(+), 72 deletions(-)
diff --git a/llex.c b/llex.c
index 38c6d92d03..1539f5258c 100644
--- a/llex.c
+++ b/llex.c
@@ -335,7 +335,7 @@ static unsigned long readutf8esc (LexState *ls) {
while ((save_and_next(ls), lisxdigit(ls->current))) {
i++;
r = (r << 4) + luaO_hexavalue(ls->current);
- esccheck(ls, r <= 0x10FFFF, "UTF-8 value too large");
+ esccheck(ls, r <= 0x7FFFFFFFu, "UTF-8 value too large");
}
esccheck(ls, ls->current == '}', "missing '}'");
next(ls); /* skip '}' */
diff --git a/lobject.c b/lobject.c
index 3ce052c29a..5d340de65e 100644
--- a/lobject.c
+++ b/lobject.c
@@ -343,7 +343,7 @@ size_t luaO_str2num (const char *s, TValue *o) {
int luaO_utf8esc (char *buff, unsigned long x) {
int n = 1; /* number of bytes put in buffer (backwards) */
- lua_assert(x <= 0x10FFFF);
+ lua_assert(x <= 0x7FFFFFFFu);
if (x < 0x80) /* ascii? */
buff[UTF8BUFFSZ - 1] = cast_char(x);
else { /* need continuation bytes */
@@ -435,9 +435,9 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {
pushstr(L, buff, l);
break;
}
- case 'U': { /* an 'int' as a UTF-8 sequence */
+ case 'U': { /* a 'long' as a UTF-8 sequence */
char buff[UTF8BUFFSZ];
- int l = luaO_utf8esc(buff, cast(long, va_arg(argp, long)));
+ int l = luaO_utf8esc(buff, va_arg(argp, long));
pushstr(L, buff + UTF8BUFFSZ - l, l);
break;
}
diff --git a/lutf8lib.c b/lutf8lib.c
index dc95b285c9..ec711c9a10 100644
--- a/lutf8lib.c
+++ b/lutf8lib.c
@@ -21,12 +21,14 @@
#include "lualib.h"
-#define MAXUNICODE 0x10FFFF
+#define MAXUNICODE 0x10FFFFu
+
+#define MAXUTF 0x7FFFFFFFu
/*
-** Integer type for decoded UTF-8 values; MAXUNICODE needs 21 bits.
+** Integer type for decoded UTF-8 values; MAXUTF needs 31 bits.
*/
-#if LUAI_BITSINT >= 21
+#if LUAI_BITSINT >= 31
typedef unsigned int utfint;
#else
typedef unsigned long utfint;
@@ -46,38 +48,46 @@ static lua_Integer u_posrelat (lua_Integer pos, size_t len) {
/*
-** Decode one UTF-8 sequence, returning NULL if byte sequence is invalid.
+** Decode one UTF-8 sequence, returning NULL if byte sequence is
+** invalid. The array 'limits' stores the minimum value for each
+** sequence length, to check for overlong representations. Its first
+** entry forces an error for non-ascii bytes with no continuation
+** bytes (count == 0).
*/
-static const char *utf8_decode (const char *o, utfint *val) {
- static const unsigned int limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFF};
- const unsigned char *s = (const unsigned char *)o;
- unsigned int c = s[0];
+static const char *utf8_decode (const char *s, utfint *val, int strict) {
+ static const utfint limits[] =
+ {~(utfint)0, 0x80, 0x800, 0x10000u, 0x200000u, 0x4000000u};
+ unsigned int c = (unsigned char)s[0];
utfint res = 0; /* final result */
if (c < 0x80) /* ascii? */
res = c;
else {
int count = 0; /* to count number of continuation bytes */
- while (c & 0x40) { /* still have continuation bytes? */
- int cc = s[++count]; /* read next byte */
+ for (; c & 0x40; c <<= 1) { /* while it needs continuation bytes... */
+ unsigned int cc = (unsigned char)s[++count]; /* read next byte */
if ((cc & 0xC0) != 0x80) /* not a continuation byte? */
return NULL; /* invalid byte sequence */
res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */
- c <<= 1; /* to test next bit */
}
res |= ((utfint)(c & 0x7F) << (count * 5)); /* add first byte */
- if (count > 3 || res > MAXUNICODE || res <= limits[count])
+ if (count > 5 || res > MAXUTF || res < limits[count])
return NULL; /* invalid byte sequence */
s += count; /* skip continuation bytes read */
}
+ if (strict) {
+ /* check for invalid code points; too large or surrogates */
+ if (res > MAXUNICODE || (0xD800u <= res && res <= 0xDFFFu))
+ return NULL;
+ }
if (val) *val = res;
- return (const char *)s + 1; /* +1 to include first byte */
+ return s + 1; /* +1 to include first byte */
}
/*
-** utf8len(s [, i [, j]]) --> number of characters that start in the
-** range [i,j], or nil + current position if 's' is not well formed in
-** that interval
+** utf8len(s [, i [, j [, nonstrict]]]) --> number of characters that
+** start in the range [i,j], or nil + current position if 's' is not
+** well formed in that interval
*/
static int utflen (lua_State *L) {
lua_Integer n = 0; /* counter for the number of characters */
@@ -85,12 +95,13 @@ static int utflen (lua_State *L) {
const char *s = luaL_checklstring(L, 1, &len);
lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len);
lua_Integer posj = u_posrelat(luaL_optinteger(L, 3, -1), len);
+ int nonstrict = lua_toboolean(L, 4);
luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 2,
"initial position out of string");
luaL_argcheck(L, --posj < (lua_Integer)len, 3,
"final position out of string");
while (posi <= posj) {
- const char *s1 = utf8_decode(s + posi, NULL);
+ const char *s1 = utf8_decode(s + posi, NULL, !nonstrict);
if (s1 == NULL) { /* conversion error? */
lua_pushnil(L); /* return nil ... */
lua_pushinteger(L, posi + 1); /* ... and current position */
@@ -105,14 +116,15 @@ static int utflen (lua_State *L) {
/*
-** codepoint(s, [i, [j]]) -> returns codepoints for all characters
-** that start in the range [i,j]
+** codepoint(s, [i, [j [, nonstrict]]]) -> returns codepoints for all
+** characters that start in the range [i,j]
*/
static int codepoint (lua_State *L) {
size_t len;
const char *s = luaL_checklstring(L, 1, &len);
lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len);
lua_Integer pose = u_posrelat(luaL_optinteger(L, 3, posi), len);
+ int nonstrict = lua_toboolean(L, 4);
int n;
const char *se;
luaL_argcheck(L, posi >= 1, 2, "out of range");
@@ -126,7 +138,7 @@ static int codepoint (lua_State *L) {
se = s + pose; /* string end */
for (s += posi - 1; s < se;) {
utfint code;
- s = utf8_decode(s, &code);
+ s = utf8_decode(s, &code, !nonstrict);
if (s == NULL)
return luaL_error(L, "invalid UTF-8 code");
lua_pushinteger(L, code);
@@ -137,8 +149,8 @@ static int codepoint (lua_State *L) {
static void pushutfchar (lua_State *L, int arg) {
- lua_Integer code = luaL_checkinteger(L, arg);
- luaL_argcheck(L, 0 <= code && code <= MAXUNICODE, arg, "value out of range");
+ lua_Unsigned code = (lua_Unsigned)luaL_checkinteger(L, arg);
+ luaL_argcheck(L, code <= MAXUTF, arg, "value out of range");
lua_pushfstring(L, "%U", (long)code);
}
@@ -209,7 +221,7 @@ static int byteoffset (lua_State *L) {
}
-static int iter_aux (lua_State *L) {
+static int iter_aux (lua_State *L, int strict) {
size_t len;
const char *s = luaL_checklstring(L, 1, &len);
lua_Integer n = lua_tointeger(L, 2) - 1;
@@ -223,8 +235,8 @@ static int iter_aux (lua_State *L) {
return 0; /* no more codepoints */
else {
utfint code;
- const char *next = utf8_decode(s + n, &code);
- if (next == NULL || iscont(next))
+ const char *next = utf8_decode(s + n, &code, strict);
+ if (next == NULL)
return luaL_error(L, "invalid UTF-8 code");
lua_pushinteger(L, n + 1);
lua_pushinteger(L, code);
@@ -233,9 +245,19 @@ static int iter_aux (lua_State *L) {
}
+static int iter_auxstrict (lua_State *L) {
+ return iter_aux(L, 1);
+}
+
+static int iter_auxnostrict (lua_State *L) {
+ return iter_aux(L, 0);
+}
+
+
static int iter_codes (lua_State *L) {
+ int nonstrict = lua_toboolean(L, 2);
luaL_checkstring(L, 1);
- lua_pushcfunction(L, iter_aux);
+ lua_pushcfunction(L, nonstrict ? iter_auxnostrict : iter_auxstrict);
lua_pushvalue(L, 1);
lua_pushinteger(L, 0);
return 3;
@@ -243,7 +265,7 @@ static int iter_codes (lua_State *L) {
/* pattern to match a single UTF-8 character */
-#define UTF8PATT "[\0-\x7F\xC2-\xF4][\x80-\xBF]*"
+#define UTF8PATT "[\0-\x7F\xC2-\xFD][\x80-\xBF]*"
static const luaL_Reg funcs[] = {
diff --git a/manual/manual.of b/manual/manual.of
index 1e4ca8575e..8a8ebad5df 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1004,6 +1004,8 @@ the escape sequence @T{\u{@rep{XXX}}}
(note the mandatory enclosing brackets),
where @rep{XXX} is a sequence of one or more hexadecimal digits
representing the character code point.
+This code point can be any value smaller than @M{2@sp{31}}.
+(Lua uses the original UTF-8 specification here.)
Literal strings can also be defined using a long format
enclosed by @def{long brackets}.
@@ -6899,6 +6901,7 @@ x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
}
@LibEntry{string.len (s)|
+
Receives a string and returns its length.
The empty string @T{""} has length 0.
Embedded zeros are counted,
@@ -6907,6 +6910,7 @@ so @T{"a\000bc\000"} has length 5.
}
@LibEntry{string.lower (s)|
+
Receives a string and returns a copy of this string with all
uppercase letters changed to lowercase.
All other characters are left unchanged.
@@ -6915,6 +6919,7 @@ The definition of what an uppercase letter is depends on the current locale.
}
@LibEntry{string.match (s, pattern [, init])|
+
Looks for the first @emph{match} of
@id{pattern} @see{pm} in the string @id{s}.
If it finds one, then @id{match} returns
@@ -6946,6 +6951,7 @@ The format string cannot have the variable-length options
}
@LibEntry{string.rep (s, n [, sep])|
+
Returns a string that is the concatenation of @id{n} copies of
the string @id{s} separated by the string @id{sep}.
The default value for @id{sep} is the empty string
@@ -6958,11 +6964,13 @@ with a single call to this function.)
}
@LibEntry{string.reverse (s)|
+
Returns a string that is the string @id{s} reversed.
}
@LibEntry{string.sub (s, i [, j])|
+
Returns the substring of @id{s} that
starts at @id{i} and continues until @id{j};
@id{i} and @id{j} can be negative.
@@ -6998,6 +7006,7 @@ this function also returns the index of the first unread byte in @id{s}.
}
@LibEntry{string.upper (s)|
+
Receives a string and returns a copy of this string with all
lowercase letters changed to uppercase.
All other characters are left unchanged.
@@ -7318,8 +7327,24 @@ or one plus the length of the subject string.
As in the string library,
negative indices count from the end of the string.
+Functions that create byte sequences
+accept all values up to @T{0x7FFFFFFF},
+as defined in the original UTF-8 specification;
+that implies byte sequences of up to six bytes.
+
+Functions that interpret byte sequences only accept
+valid sequences (well formed and not overlong).
+By default, they only accept byte sequences
+that result in valid Unicode code points,
+rejecting values larger than @T{10FFFF} and surrogates.
+A boolean argument @id{nonstrict}, when available,
+lifts these checks,
+so that all values up to @T{0x7FFFFFFF} are accepted.
+(Not well formed and overlong sequences are still rejected.)
+
@LibEntry{utf8.char (@Cdots)|
+
Receives zero or more integers,
converts each one to its corresponding UTF-8 byte sequence
and returns a string with the concatenation of all these sequences.
@@ -7327,14 +7352,15 @@ and returns a string with the concatenation of all these sequences.
}
@LibEntry{utf8.charpattern|
-The pattern (a string, not a function) @St{[\0-\x7F\xC2-\xF4][\x80-\xBF]*}
+
+The pattern (a string, not a function) @St{[\0-\x7F\xC2-\xFD][\x80-\xBF]*}
@see{pm},
which matches exactly one UTF-8 byte sequence,
assuming that the subject is a valid UTF-8 string.
}
-@LibEntry{utf8.codes (s)|
+@LibEntry{utf8.codes (s [, nonstrict])|
Returns values so that the construction
@verbatim{
@@ -7347,7 +7373,8 @@ It raises an error if it meets any invalid byte sequence.
}
-@LibEntry{utf8.codepoint (s [, i [, j]])|
+@LibEntry{utf8.codepoint (s [, i [, j [, nonstrict]]])|
+
Returns the codepoints (as integers) from all characters in @id{s}
that start between byte position @id{i} and @id{j} (both included).
The default for @id{i} is 1 and for @id{j} is @id{i}.
@@ -7355,7 +7382,8 @@ It raises an error if it meets any invalid byte sequence.
}
-@LibEntry{utf8.len (s [, i [, j]])|
+@LibEntry{utf8.len (s [, i [, j [, nonstrict]]])|
+
Returns the number of UTF-8 characters in string @id{s}
that start between positions @id{i} and @id{j} (both inclusive).
The default for @id{i} is @num{1} and for @id{j} is @num{-1}.
@@ -7365,6 +7393,7 @@ returns a false value plus the position of the first invalid byte.
}
@LibEntry{utf8.offset (s, n [, i])|
+
Returns the position (in bytes) where the encoding of the
@id{n}-th character of @id{s}
(counting from position @id{i}) starts.
@@ -8755,6 +8784,12 @@ You can enclose the call in parentheses if you need to
discard these extra results.
}
+@item{
+By default, the decoding functions in the @Lid{utf8} library
+do not accept surrogates as valid code points.
+An extra parameter in these functions makes them more permissive.
+}
+
}
}
diff --git a/testes/literals.lua b/testes/literals.lua
index 76c08f12b6..fc45d4adf4 100644
--- a/testes/literals.lua
+++ b/testes/literals.lua
@@ -56,16 +56,23 @@ assert("abc\z
assert("\u{0}\u{00000000}\x00\0" == string.char(0, 0, 0, 0))
-- limits for 1-byte sequences
-assert("\u{0}\u{7F}" == "\x00\z\x7F")
+assert("\u{0}\u{7F}" == "\x00\x7F")
-- limits for 2-byte sequences
-assert("\u{80}\u{7FF}" == "\xC2\x80\z\xDF\xBF")
+assert("\u{80}\u{7FF}" == "\xC2\x80\xDF\xBF")
-- limits for 3-byte sequences
-assert("\u{800}\u{FFFF}" == "\xE0\xA0\x80\z\xEF\xBF\xBF")
+assert("\u{800}\u{FFFF}" == "\xE0\xA0\x80\xEF\xBF\xBF")
-- limits for 4-byte sequences
-assert("\u{10000}\u{10FFFF}" == "\xF0\x90\x80\x80\z\xF4\x8F\xBF\xBF")
+assert("\u{10000}\u{1FFFFF}" == "\xF0\x90\x80\x80\xF7\xBF\xBF\xBF")
+
+-- limits for 5-byte sequences
+assert("\u{200000}\u{3FFFFFF}" == "\xF8\x88\x80\x80\x80\xFB\xBF\xBF\xBF\xBF")
+
+-- limits for 6-byte sequences
+assert("\u{4000000}\u{7FFFFFFF}" ==
+ "\xFC\x84\x80\x80\x80\x80\xFD\xBF\xBF\xBF\xBF\xBF")
-- Error in escape sequences
@@ -94,7 +101,7 @@ lexerror([["xyz\300"]], [[\300"]])
lexerror([[" \256"]], [[\256"]])
-- errors in UTF-8 sequences
-lexerror([["abc\u{110000}"]], [[abc\u{110000]]) -- too large
+lexerror([["abc\u{100000000}"]], [[abc\u{100000000]]) -- too large
lexerror([["abc\u11r"]], [[abc\u1]]) -- missing '{'
lexerror([["abc\u"]], [[abc\u"]]) -- missing '{'
lexerror([["abc\u{11r"]], [[abc\u{11r]]) -- missing '}'
diff --git a/testes/utf8.lua b/testes/utf8.lua
index 4b6a57fd49..86ec1b00f1 100644
--- a/testes/utf8.lua
+++ b/testes/utf8.lua
@@ -21,62 +21,59 @@ local justone = "^" .. utf8.charpattern .. "$"
-- 't' is the list of codepoints of 's'
local function checksyntax (s, t)
+ -- creates a string "return '\u{t[1]}...\u{t[n]}'"
local ts = {"return '"}
for i = 1, #t do ts[i + 1] = string.format("\\u{%x}", t[i]) end
ts[#t + 2] = "'"
ts = table.concat(ts)
+ -- its execution should result in 's'
assert(assert(load(ts))() == s)
end
assert(utf8.offset("alo", 5) == nil)
assert(utf8.offset("alo", -4) == nil)
--- 't' is the list of codepoints of 's'
-local function check (s, t)
- local l = utf8.len(s)
+-- 'check' makes several tests over the validity of string 's'.
+-- 't' is the list of codepoints of 's'.
+local function check (s, t, nonstrict)
+ local l = utf8.len(s, 1, -1, nonstrict)
assert(#t == l and len(s) == l)
- assert(utf8.char(table.unpack(t)) == s)
+ assert(utf8.char(table.unpack(t)) == s) -- 't' and 's' are equivalent
assert(utf8.offset(s, 0) == 1)
checksyntax(s, t)
- local t1 = {utf8.codepoint(s, 1, -1)}
+ -- creates new table with all codepoints of 's'
+ local t1 = {utf8.codepoint(s, 1, -1, nonstrict)}
assert(#t == #t1)
- for i = 1, #t do assert(t[i] == t1[i]) end
+ for i = 1, #t do assert(t[i] == t1[i]) end -- 't' is equal to 't1'
- for i = 1, l do
+ for i = 1, l do -- for all codepoints
local pi = utf8.offset(s, i) -- position of i-th char
local pi1 = utf8.offset(s, 2, pi) -- position of next char
assert(string.find(string.sub(s, pi, pi1 - 1), justone))
assert(utf8.offset(s, -1, pi1) == pi)
assert(utf8.offset(s, i - l - 1) == pi)
- assert(pi1 - pi == #utf8.char(utf8.codepoint(s, pi)))
- for j = pi, pi1 - 1 do
+ assert(pi1 - pi == #utf8.char(utf8.codepoint(s, pi, pi, nonstrict)))
+ for j = pi, pi1 - 1 do
assert(utf8.offset(s, 0, j) == pi)
end
for j = pi + 1, pi1 - 1 do
assert(not utf8.len(s, j))
end
- assert(utf8.len(s, pi, pi) == 1)
- assert(utf8.len(s, pi, pi1 - 1) == 1)
- assert(utf8.len(s, pi) == l - i + 1)
- assert(utf8.len(s, pi1) == l - i)
- assert(utf8.len(s, 1, pi) == i)
+ assert(utf8.len(s, pi, pi, nonstrict) == 1)
+ assert(utf8.len(s, pi, pi1 - 1, nonstrict) == 1)
+ assert(utf8.len(s, pi, -1, nonstrict) == l - i + 1)
+ assert(utf8.len(s, pi1, -1, nonstrict) == l - i)
+ assert(utf8.len(s, 1, pi, -1, nonstrict) == i)
end
local i = 0
- for p, c in utf8.codes(s) do
+ for p, c in utf8.codes(s, nonstrict) do
i = i + 1
assert(c == t[i] and p == utf8.offset(s, i))
- assert(utf8.codepoint(s, p) == c)
- end
- assert(i == #t)
-
- i = 0
- for p, c in utf8.codes(s) do
- i = i + 1
- assert(c == t[i] and p == utf8.offset(s, i))
+ assert(utf8.codepoint(s, p, p, nonstrict) == c)
end
assert(i == #t)
@@ -105,13 +102,17 @@ do -- error indication in utf8.len
check("\xF4\x9F\xBF\xBF", 1)
end
--- error in utf8.codes
-checkerror("invalid UTF%-8 code",
- function ()
- local s = "ab\xff"
- for c in utf8.codes(s) do assert(c) end
- end)
-
+-- errors in utf8.codes
+do
+ local function errorcodes (s)
+ checkerror("invalid UTF%-8 code",
+ function ()
+ for c in utf8.codes(s) do assert(c) end
+ end)
+ end
+ errorcodes("ab\xff")
+ errorcodes("\u{110000}")
+end
-- error in initial position for offset
checkerror("position out of range", utf8.offset, "abc", 1, 5)
@@ -141,14 +142,22 @@ do
assert(#t == 0)
checkerror("out of range", utf8.codepoint, s, -(#s + 1), 1)
checkerror("out of range", utf8.codepoint, s, 1, #s + 1)
+ -- surrogates
+ assert(utf8.codepoint("\u{D7FF}") == 0xD800 - 1)
+ assert(utf8.codepoint("\u{E000}") == 0xDFFF + 1)
+ assert(utf8.codepoint("\u{D800}", 1, 1, true) == 0xD800)
+ assert(utf8.codepoint("\u{DFFF}", 1, 1, true) == 0xDFFF)
+ assert(utf8.codepoint("\u{7FFFFFFF}", 1, 1, true) == 0x7FFFFFFF)
end
assert(utf8.char() == "")
-assert(utf8.char(97, 98, 99) == "abc")
+assert(utf8.char(0, 97, 98, 99, 1) == "\0abc\1")
assert(utf8.codepoint(utf8.char(0x10FFFF)) == 0x10FFFF)
+assert(utf8.codepoint(utf8.char(0x7FFFFFFF), 1, 1, true) == (1<<31) - 1)
-checkerror("value out of range", utf8.char, 0x10FFFF + 1)
+checkerror("value out of range", utf8.char, 0x7FFFFFFF + 1)
+checkerror("value out of range", utf8.char, -1)
local function invalid (s)
checkerror("invalid UTF%-8 code", utf8.codepoint, s)
@@ -158,6 +167,10 @@ end
-- UTF-8 representation for 0x11ffff (value out of valid range)
invalid("\xF4\x9F\xBF\xBF")
+-- surrogates
+invalid("\u{D800}")
+invalid("\u{DFFF}")
+
-- overlong sequences
invalid("\xC0\x80") -- zero
invalid("\xC1\xBF") -- 0x7F (should be coded in 1 byte)
@@ -183,6 +196,21 @@ s = "\0 \x7F\z
s = string.gsub(s, " ", "")
check(s, {0,0x7F, 0x80,0x7FF, 0x800,0xFFFF, 0x10000,0x10FFFF})
+do
+ -- original UTF-8 values
+ local s = "\u{4000000}\u{7FFFFFFF}"
+ assert(#s == 12)
+ check(s, {0x4000000, 0x7FFFFFFF}, true)
+
+ s = "\u{200000}\u{3FFFFFF}"
+ assert(#s == 10)
+ check(s, {0x200000, 0x3FFFFFF}, true)
+
+ s = "\u{10000}\u{1fffff}"
+ assert(#s == 8)
+ check(s, {0x10000, 0x1FFFFF}, true)
+end
+
x = "日本語a-4\0éó"
check(x, {26085, 26412, 35486, 97, 45, 52, 0, 233, 243})
From 9b37a4695ebf50b37b5b4fb279ae948f23b5b6a0 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 19 Mar 2019 10:53:18 -0300
Subject: [PATCH 129/889] New semantics for the integer 'for' loop
The numerical 'for' loop over integers now uses a precomputed counter
to control its number of iteractions. This change eliminates several
weird cases caused by overflows (wrap-around) in the control variable.
(It also ensures that every integer loop halts.)
Also, the special opcodes for the usual case of step==1 were removed.
(The new code is already somewhat complex for the usual case,
but efficient.)
---
ljumptab.h | 2 -
lopcodes.c | 2 -
lopcodes.h | 4 --
lopnames.h | 2 -
lparser.c | 42 +++++--------
lvm.c | 147 +++++++++++++++++++++++----------------------
manual/manual.of | 124 +++++++++++++++++---------------------
testes/code.lua | 4 +-
testes/db.lua | 2 +-
testes/nextvar.lua | 73 ++++++++++++++++++++--
10 files changed, 215 insertions(+), 187 deletions(-)
diff --git a/ljumptab.h b/ljumptab.h
index 9fa72a73c8..fa4277cc86 100644
--- a/ljumptab.h
+++ b/ljumptab.h
@@ -99,8 +99,6 @@ static void *disptab[NUM_OPCODES] = {
&&L_OP_RETURN,
&&L_OP_RETURN0,
&&L_OP_RETURN1,
-&&L_OP_FORLOOP1,
-&&L_OP_FORPREP1,
&&L_OP_FORLOOP,
&&L_OP_FORPREP,
&&L_OP_TFORPREP,
diff --git a/lopcodes.c b/lopcodes.c
index 3f0d551a99..c35a0aaf4c 100644
--- a/lopcodes.c
+++ b/lopcodes.c
@@ -93,8 +93,6 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
,opmode(0, 1, 0, 0, iABC) /* OP_RETURN */
,opmode(0, 0, 0, 0, iABC) /* OP_RETURN0 */
,opmode(0, 0, 0, 0, iABC) /* OP_RETURN1 */
- ,opmode(0, 0, 0, 1, iABx) /* OP_FORLOOP1 */
- ,opmode(0, 0, 0, 1, iABx) /* OP_FORPREP1 */
,opmode(0, 0, 0, 1, iABx) /* OP_FORLOOP */
,opmode(0, 0, 0, 1, iABx) /* OP_FORPREP */
,opmode(0, 0, 0, 0, iABx) /* OP_TFORPREP */
diff --git a/lopcodes.h b/lopcodes.h
index 3e100259b7..f867a01bb3 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -280,10 +280,6 @@ OP_RETURN,/* A B C return R(A), ... ,R(A+B-2) (see note) */
OP_RETURN0,/* return */
OP_RETURN1,/* A return R(A) */
-OP_FORLOOP1,/* A Bx R(A)++;
- if R(A) <= R(A+1) then { pc-=Bx; R(A+3)=R(A) } */
-OP_FORPREP1,/* A Bx R(A)--; pc+=Bx */
-
OP_FORLOOP,/* A Bx R(A)+=R(A+2);
if R(A) = R(A+1) then { pc-=Bx; R(A+3)=R(A) } */
OP_FORPREP,/* A Bx R(A)-=R(A+2); pc+=Bx */
diff --git a/lopnames.h b/lopnames.h
index 96d901ac68..dfca34d1f9 100644
--- a/lopnames.h
+++ b/lopnames.h
@@ -84,8 +84,6 @@ static const char *const opnames[] = {
"RETURN",
"RETURN0",
"RETURN1",
- "FORLOOP1",
- "FORPREP1",
"FORLOOP",
"FORPREP",
"TFORPREP",
diff --git a/lparser.c b/lparser.c
index 3887958ef7..8ffd97423a 100644
--- a/lparser.c
+++ b/lparser.c
@@ -1371,18 +1371,14 @@ static void repeatstat (LexState *ls, int line) {
/*
** Read an expression and generate code to put its results in next
-** stack slot. Return true if expression is a constant integer and,
-** if 'i' is not-zero, its value is equal to 'i'.
+** stack slot.
**
*/
-static int exp1 (LexState *ls, int i) {
+static void exp1 (LexState *ls) {
expdesc e;
- int res;
expr(ls, &e);
- res = luaK_isKint(&e) && (i == 0 || i == e.u.ival);
luaK_exp2nextreg(ls->fs, &e);
lua_assert(e.k == VNONRELOC);
- return res;
}
@@ -1403,31 +1399,29 @@ static void fixforjump (FuncState *fs, int pc, int dest, int back) {
/*
-** Generate code for a 'for' loop. 'kind' can be zero (a common for
-** loop), one (a basic for loop, with integer values and increment of
-** 1), or two (a generic for loop).
+** Generate code for a 'for' loop.
*/
-static void forbody (LexState *ls, int base, int line, int nvars, int kind) {
+static void forbody (LexState *ls, int base, int line, int nvars, int isgen) {
/* forbody -> DO block */
- static OpCode forprep[3] = {OP_FORPREP, OP_FORPREP1, OP_TFORPREP};
- static OpCode forloop[3] = {OP_FORLOOP, OP_FORLOOP1, OP_TFORLOOP};
+ static OpCode forprep[2] = {OP_FORPREP, OP_TFORPREP};
+ static OpCode forloop[2] = {OP_FORLOOP, OP_TFORLOOP};
BlockCnt bl;
FuncState *fs = ls->fs;
int prep, endfor;
checknext(ls, TK_DO);
- prep = luaK_codeABx(fs, forprep[kind], base, 0);
+ prep = luaK_codeABx(fs, forprep[isgen], base, 0);
enterblock(fs, &bl, 0); /* scope for declared variables */
adjustlocalvars(ls, nvars);
luaK_reserveregs(fs, nvars);
block(ls);
leaveblock(fs); /* end of scope for declared variables */
fixforjump(fs, prep, luaK_getlabel(fs), 0);
- if (kind == 2) { /* generic for? */
+ if (isgen) { /* generic for? */
luaK_codeABC(fs, OP_TFORCALL, base, 0, nvars);
luaK_fixline(fs, line);
base += 2; /* base for 'OP_TFORLOOP' (skips function and state) */
}
- endfor = luaK_codeABx(fs, forloop[kind], base, 0);
+ endfor = luaK_codeABx(fs, forloop[isgen], base, 0);
fixforjump(fs, endfor, prep + 1, 1);
luaK_fixline(fs, line);
}
@@ -1437,26 +1431,22 @@ static void fornum (LexState *ls, TString *varname, int line) {
/* fornum -> NAME = exp,exp[,exp] forbody */
FuncState *fs = ls->fs;
int base = fs->freereg;
- int basicfor = 1; /* true if it is a "basic" 'for' (integer + 1) */
new_localvarliteral(ls, "(for index)");
new_localvarliteral(ls, "(for limit)");
new_localvarliteral(ls, "(for step)");
new_localvar(ls, varname);
checknext(ls, '=');
- if (!exp1(ls, 0)) /* initial value not an integer? */
- basicfor = 0; /* not a basic 'for' */
+ exp1(ls); /* initial value */
checknext(ls, ',');
- exp1(ls, 0); /* limit */
- if (testnext(ls, ',')) {
- if (!exp1(ls, 1)) /* optional step not 1? */
- basicfor = 0; /* not a basic 'for' */
- }
+ exp1(ls); /* limit */
+ if (testnext(ls, ','))
+ exp1(ls); /* optional step */
else { /* default step = 1 */
luaK_int(fs, fs->freereg, 1);
luaK_reserveregs(fs, 1);
}
adjustlocalvars(ls, 3); /* control variables */
- forbody(ls, base, line, 1, basicfor);
+ forbody(ls, base, line, 1, 0);
}
@@ -1484,7 +1474,7 @@ static void forlist (LexState *ls, TString *indexname) {
adjust_assign(ls, 4, explist(ls, &e), &e);
adjustlocalvars(ls, 4); /* control variables */
luaK_checkstack(fs, 3); /* extra space to call generator */
- forbody(ls, base, line, nvars - 4, 2);
+ forbody(ls, base, line, nvars - 4, 1);
}
@@ -1633,7 +1623,7 @@ static void tocloselocalstat (LexState *ls) {
luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr)));
new_localvar(ls, str_checkname(ls));
checknext(ls, '=');
- exp1(ls, 0);
+ exp1(ls);
markupval(fs, fs->nactvar);
fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */
adjustlocalvars(ls, 1);
diff --git a/lvm.c b/lvm.c
index 23e7ff700c..75b05f003c 100644
--- a/lvm.c
+++ b/lvm.c
@@ -148,35 +148,34 @@ int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode) {
/*
** Try to convert a 'for' limit to an integer, preserving the semantics
-** of the loop. (The following explanation assumes a non-negative step;
+** of the loop. (The following explanation assumes a positive step;
** it is valid for negative steps mutatis mutandis.)
** If the limit is an integer or can be converted to an integer,
** rounding down, that is it.
-** Otherwise, check whether the limit can be converted to a float. If
-** the number is too large, it is OK to set the limit as LUA_MAXINTEGER,
-** which means no limit. If the number is too negative, the loop
-** should not run, because any initial integer value is larger than the
-** limit. So, it sets the limit to LUA_MININTEGER. 'stopnow' corrects
-** the extreme case when the initial value is LUA_MININTEGER, in which
-** case the LUA_MININTEGER limit would still run the loop once.
+** Otherwise, check whether the limit can be converted to a float. If
+** the float is too large, clip it to LUA_MAXINTEGER. If the float
+** is too negative, the loop should not run, because any initial
+** integer value is greater than such limit; so, it sets 'stopnow'.
+** (For this latter case, no integer limit would be correct; even a
+** limit of LUA_MININTEGER would run the loop once for an initial
+** value equal to LUA_MININTEGER.)
*/
-static int forlimit (const TValue *obj, lua_Integer *p, lua_Integer step,
+static int forlimit (const TValue *lim, lua_Integer *p, lua_Integer step,
int *stopnow) {
*stopnow = 0; /* usually, let loops run */
- if (ttisinteger(obj))
- *p = ivalue(obj);
- else if (!luaV_tointeger(obj, p, (step < 0 ? 2 : 1))) {
+ if (!luaV_tointeger(lim, p, (step < 0 ? 2 : 1))) {
/* not coercible to in integer */
- lua_Number n; /* try to convert to float */
- if (!tonumber(obj, &n)) /* cannot convert to float? */
+ lua_Number flim; /* try to convert to float */
+ if (!tonumber(lim, &flim)) /* cannot convert to float? */
return 0; /* not a number */
- if (luai_numlt(0, n)) { /* if true, float is larger than max integer */
- *p = LUA_MAXINTEGER;
- if (step < 0) *stopnow = 1;
+ /* 'flim' is a float out of integer bounds */
+ if (luai_numlt(0, flim)) { /* if it is positive, it is too large */
+ *p = LUA_MAXINTEGER; /* truncate */
+ if (step < 0) *stopnow = 1; /* initial value must be less than it */
}
- else { /* float is less than min integer */
- *p = LUA_MININTEGER;
- if (step >= 0) *stopnow = 1;
+ else { /* it is less than min integer */
+ *p = LUA_MININTEGER; /* truncate */
+ if (step > 0) *stopnow = 1; /* initial value must be greater than it */
}
}
return 1;
@@ -1636,85 +1635,87 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
}
return;
}
- vmcase(OP_FORLOOP1) {
- lua_Integer idx = intop(+, ivalue(s2v(ra)), 1); /* increment index */
- lua_Integer limit = ivalue(s2v(ra + 1));
- if (idx <= limit) {
- pc -= GETARG_Bx(i); /* jump back */
- chgivalue(s2v(ra), idx); /* update internal index... */
- setivalue(s2v(ra + 3), idx); /* ...and external index */
- }
- updatetrap(ci);
- vmbreak;
- }
- vmcase(OP_FORPREP1) {
- TValue *init = s2v(ra);
- TValue *plimit = s2v(ra + 1);
- lua_Integer ilimit, initv;
- int stopnow;
- if (unlikely(!forlimit(plimit, &ilimit, 1, &stopnow))) {
- savestate(L, ci); /* for the error message */
- luaG_forerror(L, plimit, "limit");
- }
- initv = (stopnow ? 0 : ivalue(init));
- setivalue(plimit, ilimit);
- setivalue(init, intop(-, initv, 1));
- pc += GETARG_Bx(i);
- vmbreak;
- }
vmcase(OP_FORLOOP) {
- if (ttisinteger(s2v(ra))) { /* integer loop? */
- lua_Integer step = ivalue(s2v(ra + 2));
- lua_Integer idx = intop(+, ivalue(s2v(ra)), step); /* new index */
- lua_Integer limit = ivalue(s2v(ra + 1));
- if ((0 < step) ? (idx <= limit) : (limit <= idx)) {
+ if (ttisinteger(s2v(ra + 2))) { /* integer loop? */
+ lua_Unsigned count = l_castS2U(ivalue(s2v(ra)));
+ if (count > 0) { /* still more iterations? */
+ lua_Integer step = ivalue(s2v(ra + 2));
+ lua_Integer idx = ivalue(s2v(ra + 3));
+ idx = intop(+, idx, step); /* add step to index */
+ chgivalue(s2v(ra), count - 1); /* update counter... */
+ setivalue(s2v(ra + 3), idx); /* ...and index */
pc -= GETARG_Bx(i); /* jump back */
- chgivalue(s2v(ra), idx); /* update internal index... */
- setivalue(s2v(ra + 3), idx); /* ...and external index */
}
}
else { /* floating loop */
lua_Number step = fltvalue(s2v(ra + 2));
lua_Number limit = fltvalue(s2v(ra + 1));
- lua_Number idx = fltvalue(s2v(ra));
+ lua_Number idx = fltvalue(s2v(ra + 3));
idx = luai_numadd(L, idx, step); /* inc. index */
if (luai_numlt(0, step) ? luai_numle(idx, limit)
: luai_numle(limit, idx)) {
+ setfltvalue(s2v(ra + 3), idx); /* update index */
pc -= GETARG_Bx(i); /* jump back */
- chgfltvalue(s2v(ra), idx); /* update internal index... */
- setfltvalue(s2v(ra + 3), idx); /* ...and external index */
}
}
- updatetrap(ci);
+ updatetrap(ci); /* allows a signal to break the loop */
vmbreak;
}
vmcase(OP_FORPREP) {
- TValue *init = s2v(ra);
+ TValue *pinit = s2v(ra);
TValue *plimit = s2v(ra + 1);
TValue *pstep = s2v(ra + 2);
lua_Integer ilimit;
int stopnow;
- if (ttisinteger(init) && ttisinteger(pstep) &&
+ savestate(L, ci); /* in case of errors */
+ if (ttisinteger(pinit) && ttisinteger(pstep) &&
forlimit(plimit, &ilimit, ivalue(pstep), &stopnow)) {
- /* all values are integer */
- lua_Integer initv = (stopnow ? 0 : ivalue(init));
- setivalue(plimit, ilimit);
- setivalue(init, intop(-, initv, ivalue(pstep)));
+ /* integer loop */
+ lua_Integer init = ivalue(pinit);
+ lua_Integer step = ivalue(pstep);
+ setivalue(s2v(ra + 3), init); /* control variable */
+ if (step == 0)
+ luaG_runerror(L, "'for' step is zero");
+ else if (stopnow)
+ pc += GETARG_Bx(i) + 1; /* skip the loop */
+ else if (step > 0) { /* ascending loop? */
+ if (init > ilimit)
+ pc += GETARG_Bx(i) + 1; /* skip the loop */
+ else {
+ lua_Unsigned count = l_castS2U(ilimit) - l_castS2U(init);
+ if (step != 1) /* avoid division in the too common case */
+ count /= l_castS2U(step);
+ setivalue(s2v(ra), count);
+ }
+ }
+ else { /* descending loop */
+ if (init < ilimit)
+ pc += GETARG_Bx(i) + 1; /* skip the loop */
+ else {
+ lua_Unsigned count = l_castS2U(init) - l_castS2U(ilimit);
+ count /= -l_castS2U(step);
+ setivalue(s2v(ra), count);
+ }
+ }
}
else { /* try making all values floats */
- lua_Number ninit; lua_Number nlimit; lua_Number nstep;
- savestate(L, ci); /* in case of errors */
- if (unlikely(!tonumber(plimit, &nlimit)))
+ lua_Number init; lua_Number flimit; lua_Number step;
+ if (unlikely(!tonumber(plimit, &flimit)))
luaG_forerror(L, plimit, "limit");
- setfltvalue(plimit, nlimit);
- if (unlikely(!tonumber(pstep, &nstep)))
+ setfltvalue(plimit, flimit);
+ if (unlikely(!tonumber(pstep, &step)))
luaG_forerror(L, pstep, "step");
- setfltvalue(pstep, nstep);
- if (unlikely(!tonumber(init, &ninit)))
- luaG_forerror(L, init, "initial value");
- setfltvalue(init, luai_numsub(L, ninit, nstep));
+ setfltvalue(pstep, step);
+ if (unlikely(!tonumber(pinit, &init)))
+ luaG_forerror(L, pinit, "initial value");
+ if (step == 0)
+ luaG_runerror(L, "'for' step is zero");
+ if (luai_numlt(0, step) ? luai_numlt(flimit, init)
+ : luai_numlt(init, flimit))
+ pc += GETARG_Bx(i) + 1; /* skip the loop */
+ else
+ setfltvalue(s2v(ra + 3), init); /* control variable */
}
- pc += GETARG_Bx(i);
vmbreak;
}
vmcase(OP_TFORPREP) {
diff --git a/manual/manual.of b/manual/manual.of
index 8a8ebad5df..b7ced443e9 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -594,7 +594,7 @@ controls how long the collector waits before starting a new cycle.
The collector starts a new cycle when the use of memory
hits @M{n%} of the use after the previous collection.
Larger values make the collector less aggressive.
-Values smaller than 100 mean the collector will not wait to
+Values less than 100 mean the collector will not wait to
start a new cycle.
A value of 200 means that the collector waits for the total memory in use
to double before starting a new cycle.
@@ -608,7 +608,7 @@ how many elements it marks or sweeps for each
kilobyte of memory allocated.
Larger values make the collector more aggressive but also increase
the size of each incremental step.
-You should not use values smaller than 100,
+You should not use values less than 100,
because they make the collector too slow and
can result in the collector never finishing a cycle.
The default value is 100; the maximum value is 1000.
@@ -1004,7 +1004,7 @@ the escape sequence @T{\u{@rep{XXX}}}
(note the mandatory enclosing brackets),
where @rep{XXX} is a sequence of one or more hexadecimal digits
representing the character code point.
-This code point can be any value smaller than @M{2@sp{31}}.
+This code point can be any value less than @M{2@sp{31}}.
(Lua uses the original UTF-8 specification here.)
Literal strings can also be defined using a long format
@@ -1370,73 +1370,49 @@ because now @Rw{return} is the last statement in its (inner) block.
The @Rw{for} statement has two forms:
one numerical and one generic.
+@sect4{@title{The numerical @Rw{for} loop}
+
The numerical @Rw{for} loop repeats a block of code while a
-control variable runs through an arithmetic progression.
+control variable goes through an arithmetic progression.
It has the following syntax:
@Produc{
@producname{stat}@producbody{@Rw{for} @bnfNter{Name} @bnfter{=}
exp @bnfter{,} exp @bnfopt{@bnfter{,} exp} @Rw{do} block @Rw{end}}
}
-The @emph{block} is repeated for @emph{name} starting at the value of
-the first @emph{exp}, until it passes the second @emph{exp} by steps of the
-third @emph{exp}.
-More precisely, a @Rw{for} statement like
-@verbatim{
-for v = @rep{e1}, @rep{e2}, @rep{e3} do @rep{block} end
-}
-is equivalent to the code:
-@verbatim{
-do
- local @rep{var}, @rep{limit}, @rep{step} = tonumber(@rep{e1}), tonumber(@rep{e2}), tonumber(@rep{e3})
- if not (@rep{var} and @rep{limit} and @rep{step}) then error() end
- @rep{var} = @rep{var} - @rep{step}
- while true do
- @rep{var} = @rep{var} + @rep{step}
- if (@rep{step} >= 0 and @rep{var} > @rep{limit}) or (@rep{step} < 0 and @rep{var} < @rep{limit}) then
- break
- end
- local v = @rep{var}
- @rep{block}
- end
-end
-}
-
-Note the following:
-@itemize{
-
-@item{
-All three control expressions are evaluated only once,
-before the loop starts.
-They must all result in numbers.
-}
-
-@item{
-@T{@rep{var}}, @T{@rep{limit}}, and @T{@rep{step}} are invisible variables.
-The names shown here are for explanatory purposes only.
-}
-
-@item{
-If the third expression (the step) is absent,
-then a step @N{of 1} is used.
-}
-
-@item{
-You can use @Rw{break} and @Rw{goto} to exit a @Rw{for} loop.
-}
-
-@item{
-The loop variable @T{v} is local to the loop body.
+The given identifier (@bnfNter{Name}) defines the control variable,
+which is local to the loop body (@emph{block}).
+
+The loop starts by evaluating once the three control expressions;
+they must all result in numbers.
+Their values are called respectively
+the @emph{initial value}, the @emph{limit}, and the @emph{step}.
+If the step is absent, it defaults @N{to 1}.
+Then the loop body is repeated with the value of the control variable
+going through an arithmetic progression,
+starting at the initial value,
+with a common difference given by the step,
+until that value passes the limit.
+A negative step makes a decreasing sequence;
+a step equal to zero raises an error.
+If the initial value is already greater than the limit
+(or less than, if the step is negative), the body is not executed.
+
+If both the initial value and the step are integers,
+the loop is done with integers;
+in this case, the range of the control variable is limited
+by the range of integers.
+Otherwise, the loop is done with floats.
+(Beware of floating-point accuracy in this case.)
+
+You should not change the value of the control variable
+during the loop.
If you need its value after the loop,
assign it to another variable before exiting the loop.
-}
-@item{
-The values in @rep{var}, @rep{limit}, and @rep{step}
-can be integers or floats.
-All operations on them respect the usual rules in Lua.
}
-}
+@sect4{@title{The generic @Rw{for} loop}
+
The generic @Rw{for} statement works over functions,
called @def{iterators}.
@@ -1499,6 +1475,8 @@ then assign them to other variables before breaking or exiting the loop.
}
+}
+
@sect3{funcstat| @title{Function Calls as Statements}
To allow possible side-effects,
function calls can be executed as statements:
@@ -1819,7 +1797,7 @@ A comparison @T{a > b} is translated to @T{b < a}
and @T{a >= b} is translated to @T{b <= a}.
Following the @x{IEEE 754} standard,
-@x{NaN} is considered neither smaller than,
+@x{NaN} is considered neither less than,
nor equal to, nor greater than any value (including itself).
}
@@ -2171,7 +2149,7 @@ then the function returns with no results.
@index{multiple return}
There is a system-dependent limit on the number of values
that a function may return.
-This limit is guaranteed to be larger than 1000.
+This limit is guaranteed to be greater than 1000.
The @emphx{colon} syntax
is used for defining @def{methods},
@@ -2367,7 +2345,7 @@ but it also can be any positive index after the stack top
within the space allocated for the stack,
that is, indices up to the stack size.
(Note that 0 is never an acceptable index.)
-Indices to upvalues @see{c-closure} larger than the real number
+Indices to upvalues @see{c-closure} greater than the real number
of upvalues in the current @N{C function} are also acceptable (but invalid).
Except when noted otherwise,
functions in the API work with acceptable indices.
@@ -2879,7 +2857,7 @@ Ensures that the stack has space for at least @id{n} extra slots
(that is, that you can safely push up to @id{n} values into it).
It returns false if it cannot fulfill the request,
either because it would cause the stack
-to be larger than a fixed maximum size
+to be greater than a fixed maximum size
(typically at least several thousand elements) or
because it cannot allocate memory for the extra space.
This function never shrinks the stack;
@@ -4053,7 +4031,7 @@ for the @Q{newindex} event @see{metatable}.
Accepts any index, @N{or 0},
and sets the stack top to this index.
-If the new top is larger than the old one,
+If the new top is greater than the old one,
then the new elements are filled with @nil.
If @id{index} @N{is 0}, then all stack elements are removed.
@@ -5056,7 +5034,7 @@ size @id{sz} with a call @T{luaL_buffinitsize(L, &b, sz)}.}
@item{
Finish by calling @T{luaL_pushresultsize(&b, sz)},
where @id{sz} is the total size of the resulting string
-copied into that space (which may be smaller than or
+copied into that space (which may be less than or
equal to the preallocated size).
}
@@ -7336,7 +7314,7 @@ Functions that interpret byte sequences only accept
valid sequences (well formed and not overlong).
By default, they only accept byte sequences
that result in valid Unicode code points,
-rejecting values larger than @T{10FFFF} and surrogates.
+rejecting values greater than @T{10FFFF} and surrogates.
A boolean argument @id{nonstrict}, when available,
lifts these checks,
so that all values up to @T{0x7FFFFFFF} are accepted.
@@ -7572,7 +7550,7 @@ returns the arc tangent of @id{y}.
@LibEntry{math.ceil (x)|
-Returns the smallest integral value larger than or equal to @id{x}.
+Returns the smallest integral value greater than or equal to @id{x}.
}
@@ -7597,7 +7575,7 @@ Returns the value @M{e@sp{x}}
@LibEntry{math.floor (x)|
-Returns the largest integral value smaller than or equal to @id{x}.
+Returns the largest integral value less than or equal to @id{x}.
}
@@ -7611,7 +7589,7 @@ that rounds the quotient towards zero. (integer/float)
@LibEntry{math.huge|
The float value @idx{HUGE_VAL},
-a value larger than any other numeric value.
+a value greater than any other numeric value.
}
@@ -8352,7 +8330,7 @@ of the given thread:
@N{level 1} is the function that called @id{getinfo}
(except for tail calls, which do not count on the stack);
and so on.
-If @id{f} is a number larger than the number of active functions,
+If @id{f} is a number greater than the number of active functions,
then @id{getinfo} returns @nil.
The returned table can contain all the fields returned by @Lid{lua_getinfo},
@@ -8745,6 +8723,12 @@ has been removed.
When needed, this metamethod must be explicitly defined.
}
+@item{
+The semantics of the numerical @Rw{for} loop
+over integers changed in some details.
+In particular, the control variable never wraps around.
+}
+
@item{
When a coroutine finishes with an error,
its stack is unwound (to run any pending closing methods).
diff --git a/testes/code.lua b/testes/code.lua
index 834ff5e23d..128ca2cb8b 100644
--- a/testes/code.lua
+++ b/testes/code.lua
@@ -303,9 +303,9 @@ check(function (x) return x & 2.0 end, 'LOADF', 'BAND', 'RETURN1')
-- basic 'for' loops
check(function () for i = -10, 10.5 do end end,
-'LOADI', 'LOADK', 'LOADI', 'FORPREP1', 'FORLOOP1', 'RETURN0')
+'LOADI', 'LOADK', 'LOADI', 'FORPREP', 'FORLOOP', 'RETURN0')
check(function () for i = 0xfffffff, 10.0, 1 do end end,
-'LOADK', 'LOADF', 'LOADI', 'FORPREP1', 'FORLOOP1', 'RETURN0')
+'LOADK', 'LOADF', 'LOADI', 'FORPREP', 'FORLOOP', 'RETURN0')
-- bug in constant folding for 5.1
check(function () return -nil end, 'LOADNIL', 'UNM', 'RETURN1')
diff --git a/testes/db.lua b/testes/db.lua
index 976962b02f..0858dd2016 100644
--- a/testes/db.lua
+++ b/testes/db.lua
@@ -162,7 +162,7 @@ test([[for i,v in pairs{'a','b'} do
end
]], {1,2,1,2,1,3})
-test([[for i=1,4 do a=1 end]], {1,1,1,1,1})
+test([[for i=1,4 do a=1 end]], {1,1,1,1}, true)
do -- testing line info/trace with large gaps in source
diff --git a/testes/nextvar.lua b/testes/nextvar.lua
index d2306ed1ff..e769ccdd46 100644
--- a/testes/nextvar.lua
+++ b/testes/nextvar.lua
@@ -76,7 +76,7 @@ while a < lim do
a = math.ceil(a*1.3)
end
-
+
local function check (t, na, nh)
local a, h = T.querytab(t)
if a ~= na or h ~= nh then
@@ -100,7 +100,7 @@ local s = 'return {'
for i=1,lim do
s = s..i..','
local s = s
- for k=0,lim do
+ for k=0,lim do
local t = load(s..'}', '')()
assert(#t == i)
check(t, fb(i), mp2(k))
@@ -279,7 +279,7 @@ do -- clear global table
end
---
+--
local function checknext (a)
local b = {}
@@ -500,7 +500,7 @@ else --[
mt.__newindex = nil
mt.__len = nil
local tab2 = {}
- local u2 = T.newuserdata(0)
+ local u2 = T.newuserdata(0)
debug.setmetatable(u2, {__newindex = function (_, k, v) tab2[k] = v end})
table.move(u, 1, 4, 1, u2)
assert(#tab2 == 4 and tab2[1] == tab[1] and tab2[4] == tab[4])
@@ -601,6 +601,69 @@ do -- checking types
end
+
+do -- testing other strange cases for numeric 'for'
+
+ local function checkfor (from, to, step, t)
+ local c = 0
+ for i = from, to, step do
+ c = c + 1
+ assert(i == t[c])
+ end
+ assert(c == #t)
+ end
+
+ local maxi = math.maxinteger
+ local mini = math.mininteger
+
+ checkfor(mini, maxi, maxi, {mini, -1, maxi - 1})
+
+ checkfor(mini, math.huge, maxi, {mini, -1, maxi - 1})
+
+ checkfor(maxi, mini, mini, {maxi, -1})
+
+ checkfor(maxi, mini, -maxi, {maxi, 0, -maxi})
+
+ checkfor(maxi, -math.huge, mini, {maxi, -1})
+
+ checkfor(maxi, mini, 1, {})
+ checkfor(mini, maxi, -1, {})
+
+ checkfor(maxi - 6, maxi, 3, {maxi - 6, maxi - 3, maxi})
+ checkfor(mini + 4, mini, -2, {mini + 4, mini + 2, mini})
+
+ local step = maxi // 10
+ local c = mini
+ for i = mini, maxi, step do
+ assert(i == c)
+ c = c + step
+ end
+
+ c = maxi
+ for i = maxi, mini, -step do
+ assert(i == c)
+ c = c - step
+ end
+
+ checkfor(maxi, maxi, maxi, {maxi})
+ checkfor(maxi, maxi, mini, {maxi})
+ checkfor(mini, mini, maxi, {mini})
+ checkfor(mini, mini, mini, {mini})
+end
+
+
+checkerror("'for' step is zero", function ()
+ for i = 1, 10, 0 do end
+end)
+
+checkerror("'for' step is zero", function ()
+ for i = 1, -10, 0 do end
+end)
+
+checkerror("'for' step is zero", function ()
+ for i = 1.0, -10, 0.0 do end
+end)
+
collectgarbage()
@@ -657,7 +720,7 @@ a[3] = 30
-- testing ipairs with metamethods
a = {n=10}
setmetatable(a, { __index = function (t,k)
- if k <= t.n then return k * 10 end
+ if k <= t.n then return k * 10 end
end})
i = 0
for k,v in ipairs(a) do
From 39bb3cf2422603d854ee12529cc3419dc735802a Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 19 Mar 2019 11:15:49 -0300
Subject: [PATCH 130/889] Name 'nonstrict' in the UTF-8 library changed to
'lax'
It is not a good idea to use negative words to describe boolean
values. (When we negate that boolean we create a double negative...)
---
lutf8lib.c | 18 +++++++++---------
manual/manual.of | 8 ++++----
2 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/lutf8lib.c b/lutf8lib.c
index ec711c9a10..16cd68ad68 100644
--- a/lutf8lib.c
+++ b/lutf8lib.c
@@ -85,7 +85,7 @@ static const char *utf8_decode (const char *s, utfint *val, int strict) {
/*
-** utf8len(s [, i [, j [, nonstrict]]]) --> number of characters that
+** utf8len(s [, i [, j [, lax]]]) --> number of characters that
** start in the range [i,j], or nil + current position if 's' is not
** well formed in that interval
*/
@@ -95,13 +95,13 @@ static int utflen (lua_State *L) {
const char *s = luaL_checklstring(L, 1, &len);
lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len);
lua_Integer posj = u_posrelat(luaL_optinteger(L, 3, -1), len);
- int nonstrict = lua_toboolean(L, 4);
+ int lax = lua_toboolean(L, 4);
luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 2,
"initial position out of string");
luaL_argcheck(L, --posj < (lua_Integer)len, 3,
"final position out of string");
while (posi <= posj) {
- const char *s1 = utf8_decode(s + posi, NULL, !nonstrict);
+ const char *s1 = utf8_decode(s + posi, NULL, !lax);
if (s1 == NULL) { /* conversion error? */
lua_pushnil(L); /* return nil ... */
lua_pushinteger(L, posi + 1); /* ... and current position */
@@ -116,7 +116,7 @@ static int utflen (lua_State *L) {
/*
-** codepoint(s, [i, [j [, nonstrict]]]) -> returns codepoints for all
+** codepoint(s, [i, [j [, lax]]]) -> returns codepoints for all
** characters that start in the range [i,j]
*/
static int codepoint (lua_State *L) {
@@ -124,7 +124,7 @@ static int codepoint (lua_State *L) {
const char *s = luaL_checklstring(L, 1, &len);
lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len);
lua_Integer pose = u_posrelat(luaL_optinteger(L, 3, posi), len);
- int nonstrict = lua_toboolean(L, 4);
+ int lax = lua_toboolean(L, 4);
int n;
const char *se;
luaL_argcheck(L, posi >= 1, 2, "out of range");
@@ -138,7 +138,7 @@ static int codepoint (lua_State *L) {
se = s + pose; /* string end */
for (s += posi - 1; s < se;) {
utfint code;
- s = utf8_decode(s, &code, !nonstrict);
+ s = utf8_decode(s, &code, !lax);
if (s == NULL)
return luaL_error(L, "invalid UTF-8 code");
lua_pushinteger(L, code);
@@ -249,15 +249,15 @@ static int iter_auxstrict (lua_State *L) {
return iter_aux(L, 1);
}
-static int iter_auxnostrict (lua_State *L) {
+static int iter_auxlax (lua_State *L) {
return iter_aux(L, 0);
}
static int iter_codes (lua_State *L) {
- int nonstrict = lua_toboolean(L, 2);
+ int lax = lua_toboolean(L, 2);
luaL_checkstring(L, 1);
- lua_pushcfunction(L, nonstrict ? iter_auxnostrict : iter_auxstrict);
+ lua_pushcfunction(L, lax ? iter_auxlax : iter_auxstrict);
lua_pushvalue(L, 1);
lua_pushinteger(L, 0);
return 3;
diff --git a/manual/manual.of b/manual/manual.of
index b7ced443e9..fc2550e084 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -7315,7 +7315,7 @@ valid sequences (well formed and not overlong).
By default, they only accept byte sequences
that result in valid Unicode code points,
rejecting values greater than @T{10FFFF} and surrogates.
-A boolean argument @id{nonstrict}, when available,
+A boolean argument @id{lax}, when available,
lifts these checks,
so that all values up to @T{0x7FFFFFFF} are accepted.
(Not well formed and overlong sequences are still rejected.)
@@ -7338,7 +7338,7 @@ assuming that the subject is a valid UTF-8 string.
}
-@LibEntry{utf8.codes (s [, nonstrict])|
+@LibEntry{utf8.codes (s [, lax])|
Returns values so that the construction
@verbatim{
@@ -7351,7 +7351,7 @@ It raises an error if it meets any invalid byte sequence.
}
-@LibEntry{utf8.codepoint (s [, i [, j [, nonstrict]]])|
+@LibEntry{utf8.codepoint (s [, i [, j [, lax]]])|
Returns the codepoints (as integers) from all characters in @id{s}
that start between byte position @id{i} and @id{j} (both included).
@@ -7360,7 +7360,7 @@ It raises an error if it meets any invalid byte sequence.
}
-@LibEntry{utf8.len (s [, i [, j [, nonstrict]]])|
+@LibEntry{utf8.len (s [, i [, j [, lax]]])|
Returns the number of UTF-8 characters in string @id{s}
that start between positions @id{i} and @id{j} (both inclusive).
From f53eabeed855081fa38e9af5cf7c977915f5213f Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 19 Mar 2019 15:31:08 -0300
Subject: [PATCH 131/889] Small changes in the header of binary files
- LUAC_VERSION is equal to LUA_VERSION_NUM, and it is stored
as an int.
- 'sizeof(int)' and 'sizeof(size_t)' removed from the header, as
the binary format does not depend on these sizes. (It uses its
own serialization for unsigned integer values.)
---
ldump.c | 4 +---
lundump.c | 38 +++++++++++++++++++++++---------------
lundump.h | 3 +--
testes/calls.lua | 20 +++++++++-----------
4 files changed, 34 insertions(+), 31 deletions(-)
diff --git a/ldump.c b/ldump.c
index aca6245bf4..c447557682 100644
--- a/ldump.c
+++ b/ldump.c
@@ -198,11 +198,9 @@ static void DumpFunction (const Proto *f, TString *psource, DumpState *D) {
static void DumpHeader (DumpState *D) {
DumpLiteral(LUA_SIGNATURE, D);
- DumpByte(LUAC_VERSION, D);
+ DumpInt(LUAC_VERSION, D);
DumpByte(LUAC_FORMAT, D);
DumpLiteral(LUAC_DATA, D);
- DumpByte(sizeof(int), D);
- DumpByte(sizeof(size_t), D);
DumpByte(sizeof(Instruction), D);
DumpByte(sizeof(lua_Integer), D);
DumpByte(sizeof(lua_Number), D);
diff --git a/lundump.c b/lundump.c
index 00ff6deffb..a600493367 100644
--- a/lundump.c
+++ b/lundump.c
@@ -10,6 +10,7 @@
#include "lprefix.h"
+#include
#include
#include "lua.h"
@@ -37,7 +38,7 @@ typedef struct {
static l_noret error (LoadState *S, const char *why) {
- luaO_pushfstring(S->L, "%s: %s precompiled chunk", S->name, why);
+ luaO_pushfstring(S->L, "%s: bad binary format (%s)", S->name, why);
luaD_throw(S->L, LUA_ERRSYNTAX);
}
@@ -50,7 +51,7 @@ static l_noret error (LoadState *S, const char *why) {
static void LoadBlock (LoadState *S, void *b, size_t size) {
if (luaZ_read(S->Z, b, size) != 0)
- error(S, "truncated");
+ error(S, "truncated chunk");
}
@@ -60,24 +61,32 @@ static void LoadBlock (LoadState *S, void *b, size_t size) {
static lu_byte LoadByte (LoadState *S) {
int b = zgetc(S->Z);
if (b == EOZ)
- error(S, "truncated");
+ error(S, "truncated chunk");
return cast_byte(b);
}
-static size_t LoadSize (LoadState *S) {
+static size_t LoadUnsigned (LoadState *S, size_t limit) {
size_t x = 0;
int b;
+ limit >>= 7;
do {
b = LoadByte(S);
+ if (x >= limit)
+ error(S, "integer overflow");
x = (x << 7) | (b & 0x7f);
} while ((b & 0x80) == 0);
return x;
}
+static size_t LoadSize (LoadState *S) {
+ return LoadUnsigned(S, ~(size_t)0);
+}
+
+
static int LoadInt (LoadState *S) {
- return cast_int(LoadSize(S));
+ return cast_int(LoadUnsigned(S, INT_MAX));
}
@@ -255,28 +264,27 @@ static void checkliteral (LoadState *S, const char *s, const char *msg) {
static void fchecksize (LoadState *S, size_t size, const char *tname) {
if (LoadByte(S) != size)
- error(S, luaO_pushfstring(S->L, "%s size mismatch in", tname));
+ error(S, luaO_pushfstring(S->L, "%s size mismatch", tname));
}
#define checksize(S,t) fchecksize(S,sizeof(t),#t)
static void checkHeader (LoadState *S) {
- checkliteral(S, LUA_SIGNATURE + 1, "not a"); /* 1st char already checked */
- if (LoadByte(S) != LUAC_VERSION)
- error(S, "version mismatch in");
+ /* 1st char already checked */
+ checkliteral(S, LUA_SIGNATURE + 1, "not a binary chunk");
+ if (LoadInt(S) != LUAC_VERSION)
+ error(S, "version mismatch");
if (LoadByte(S) != LUAC_FORMAT)
- error(S, "format mismatch in");
- checkliteral(S, LUAC_DATA, "corrupted");
- checksize(S, int);
- checksize(S, size_t);
+ error(S, "format mismatch");
+ checkliteral(S, LUAC_DATA, "corrupted chunk");
checksize(S, Instruction);
checksize(S, lua_Integer);
checksize(S, lua_Number);
if (LoadInteger(S) != LUAC_INT)
- error(S, "endianness mismatch in");
+ error(S, "integer format mismatch");
if (LoadNumber(S) != LUAC_NUM)
- error(S, "float format mismatch in");
+ error(S, "float format mismatch");
}
diff --git a/lundump.h b/lundump.h
index 739c7fcde0..5b05fed471 100644
--- a/lundump.h
+++ b/lundump.h
@@ -18,8 +18,7 @@
#define LUAC_INT 0x5678
#define LUAC_NUM cast_num(370.5)
-#define MYINT(s) (s[0]-'0')
-#define LUAC_VERSION (MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR))
+#define LUAC_VERSION LUA_VERSION_NUM
#define LUAC_FORMAT 0 /* this is the official format */
/* load one chunk; from lundump.c */
diff --git a/testes/calls.lua b/testes/calls.lua
index f5108a470e..941493b131 100644
--- a/testes/calls.lua
+++ b/testes/calls.lua
@@ -397,17 +397,15 @@ assert((function (a) return a end)() == nil)
print("testing binary chunks")
do
- local header = string.pack("c4BBc6BBBBBj",
- "\27Lua", -- signature
- 5*16 + 4, -- version 5.4
- 0, -- format
- "\x19\x93\r\n\x1a\n", -- data
- string.packsize("i"), -- sizeof(int)
- string.packsize("T"), -- sizeof(size_t)
- 4, -- size of instruction
- string.packsize("j"), -- sizeof(lua integer)
- string.packsize("n"), -- sizeof(lua number)
- 0x5678 -- LUAC_INT
+ local header = string.pack("c4BBBc6BBBj",
+ "\27Lua", -- signature
+ (504 >> 7) & 0x7f, (504 & 0x7f) | 0x80, -- version 5.4 (504)
+ 0, -- format
+ "\x19\x93\r\n\x1a\n", -- data
+ 4, -- size of instruction
+ string.packsize("j"), -- sizeof(lua integer)
+ string.packsize("n"), -- sizeof(lua number)
+ 0x5678 -- LUAC_INT
-- LUAC_NUM may not have a unique binary representation (padding...)
)
local c = string.dump(function () local a = 1; local b = 3; return a+b*3 end)
From 682054920ddc434fd4a7f8cc78027dbb03f47f00 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 21 Mar 2019 16:01:55 -0300
Subject: [PATCH 132/889] Details in the implementation of the integer 'for'
loop
Changed some implementation details; in particular, it is back using
an internal variable to keep the index, with the control variable
being only a copy of that internal variable. (The direct use of
the control variable demands a check of its type for each access,
which offsets the gains from the use of a single variable.)
---
lvm.c | 87 +++++++++++++++++++++++-----------------------
testes/nextvar.lua | 6 ++++
2 files changed, 49 insertions(+), 44 deletions(-)
diff --git a/lvm.c b/lvm.c
index 75b05f003c..31b4dc36fc 100644
--- a/lvm.c
+++ b/lvm.c
@@ -150,35 +150,36 @@ int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode) {
** Try to convert a 'for' limit to an integer, preserving the semantics
** of the loop. (The following explanation assumes a positive step;
** it is valid for negative steps mutatis mutandis.)
+** Return true if the loop must not run.
** If the limit is an integer or can be converted to an integer,
-** rounding down, that is it.
+** rounding down, that is the limit.
** Otherwise, check whether the limit can be converted to a float. If
** the float is too large, clip it to LUA_MAXINTEGER. If the float
** is too negative, the loop should not run, because any initial
-** integer value is greater than such limit; so, it sets 'stopnow'.
+** integer value is greater than such limit; so, it returns true to
+** signal that.
** (For this latter case, no integer limit would be correct; even a
** limit of LUA_MININTEGER would run the loop once for an initial
** value equal to LUA_MININTEGER.)
*/
-static int forlimit (const TValue *lim, lua_Integer *p, lua_Integer step,
- int *stopnow) {
- *stopnow = 0; /* usually, let loops run */
+static int forlimit (lua_State *L, lua_Integer init, const TValue *lim,
+ lua_Integer *p, lua_Integer step) {
if (!luaV_tointeger(lim, p, (step < 0 ? 2 : 1))) {
/* not coercible to in integer */
lua_Number flim; /* try to convert to float */
if (!tonumber(lim, &flim)) /* cannot convert to float? */
- return 0; /* not a number */
- /* 'flim' is a float out of integer bounds */
+ luaG_forerror(L, lim, "limit");
+ /* else 'flim' is a float out of integer bounds */
if (luai_numlt(0, flim)) { /* if it is positive, it is too large */
+ if (step < 0) return 1; /* initial value must be less than it */
*p = LUA_MAXINTEGER; /* truncate */
- if (step < 0) *stopnow = 1; /* initial value must be less than it */
}
else { /* it is less than min integer */
+ if (step > 0) return 1; /* initial value must be greater than it */
*p = LUA_MININTEGER; /* truncate */
- if (step > 0) *stopnow = 1; /* initial value must be greater than it */
}
}
- return 1;
+ return (step > 0 ? init > *p : init < *p); /* not to run? */
}
@@ -1637,24 +1638,26 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
}
vmcase(OP_FORLOOP) {
if (ttisinteger(s2v(ra + 2))) { /* integer loop? */
- lua_Unsigned count = l_castS2U(ivalue(s2v(ra)));
+ lua_Unsigned count = l_castS2U(ivalue(s2v(ra + 1)));
if (count > 0) { /* still more iterations? */
lua_Integer step = ivalue(s2v(ra + 2));
- lua_Integer idx = ivalue(s2v(ra + 3));
+ lua_Integer idx = ivalue(s2v(ra)); /* internal index */
+ chgivalue(s2v(ra + 1), count - 1); /* update counter */
idx = intop(+, idx, step); /* add step to index */
- chgivalue(s2v(ra), count - 1); /* update counter... */
- setivalue(s2v(ra + 3), idx); /* ...and index */
+ chgivalue(s2v(ra), idx); /* update internal index */
+ setivalue(s2v(ra + 3), idx); /* and control variable */
pc -= GETARG_Bx(i); /* jump back */
}
}
else { /* floating loop */
lua_Number step = fltvalue(s2v(ra + 2));
lua_Number limit = fltvalue(s2v(ra + 1));
- lua_Number idx = fltvalue(s2v(ra + 3));
- idx = luai_numadd(L, idx, step); /* inc. index */
+ lua_Number idx = fltvalue(s2v(ra));
+ idx = luai_numadd(L, idx, step); /* increment index */
if (luai_numlt(0, step) ? luai_numle(idx, limit)
: luai_numle(limit, idx)) {
- setfltvalue(s2v(ra + 3), idx); /* update index */
+ chgfltvalue(s2v(ra), idx); /* update internal index */
+ setfltvalue(s2v(ra + 3), idx); /* and control variable */
pc -= GETARG_Bx(i); /* jump back */
}
}
@@ -1665,56 +1668,52 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
TValue *pinit = s2v(ra);
TValue *plimit = s2v(ra + 1);
TValue *pstep = s2v(ra + 2);
- lua_Integer ilimit;
- int stopnow;
savestate(L, ci); /* in case of errors */
- if (ttisinteger(pinit) && ttisinteger(pstep) &&
- forlimit(plimit, &ilimit, ivalue(pstep), &stopnow)) {
- /* integer loop */
+ if (ttisinteger(pinit) && ttisinteger(pstep)) { /* integer loop? */
lua_Integer init = ivalue(pinit);
lua_Integer step = ivalue(pstep);
- setivalue(s2v(ra + 3), init); /* control variable */
+ lua_Integer limit;
if (step == 0)
luaG_runerror(L, "'for' step is zero");
- else if (stopnow)
+ setivalue(s2v(ra + 3), init); /* control variable */
+ if (forlimit(L, init, plimit, &limit, step))
pc += GETARG_Bx(i) + 1; /* skip the loop */
- else if (step > 0) { /* ascending loop? */
- if (init > ilimit)
- pc += GETARG_Bx(i) + 1; /* skip the loop */
- else {
- lua_Unsigned count = l_castS2U(ilimit) - l_castS2U(init);
+ else { /* prepare loop counter */
+ lua_Unsigned count;
+ if (step > 0) { /* ascending loop? */
+ count = l_castS2U(limit) - l_castS2U(init);
if (step != 1) /* avoid division in the too common case */
count /= l_castS2U(step);
- setivalue(s2v(ra), count);
}
- }
- else { /* descending loop */
- if (init < ilimit)
- pc += GETARG_Bx(i) + 1; /* skip the loop */
- else {
- lua_Unsigned count = l_castS2U(init) - l_castS2U(ilimit);
+ else { /* step < 0; descending loop */
+ count = l_castS2U(init) - l_castS2U(limit);
count /= -l_castS2U(step);
- setivalue(s2v(ra), count);
}
+ /* store the counter in place of the limit (which won't be
+ needed anymore */
+ setivalue(plimit, l_castU2S(count));
}
}
else { /* try making all values floats */
- lua_Number init; lua_Number flimit; lua_Number step;
- if (unlikely(!tonumber(plimit, &flimit)))
+ lua_Number init; lua_Number limit; lua_Number step;
+ if (unlikely(!tonumber(plimit, &limit)))
luaG_forerror(L, plimit, "limit");
- setfltvalue(plimit, flimit);
if (unlikely(!tonumber(pstep, &step)))
luaG_forerror(L, pstep, "step");
- setfltvalue(pstep, step);
if (unlikely(!tonumber(pinit, &init)))
luaG_forerror(L, pinit, "initial value");
if (step == 0)
luaG_runerror(L, "'for' step is zero");
- if (luai_numlt(0, step) ? luai_numlt(flimit, init)
- : luai_numlt(init, flimit))
+ if (luai_numlt(0, step) ? luai_numlt(limit, init)
+ : luai_numlt(init, limit))
pc += GETARG_Bx(i) + 1; /* skip the loop */
- else
+ else {
+ /* make sure internal values are all float */
+ setfltvalue(plimit, limit);
+ setfltvalue(pstep, step);
+ setfltvalue(s2v(ra), init); /* internal index */
setfltvalue(s2v(ra + 3), init); /* control variable */
+ }
}
vmbreak;
}
diff --git a/testes/nextvar.lua b/testes/nextvar.lua
index e769ccdd46..87a6bfa81c 100644
--- a/testes/nextvar.lua
+++ b/testes/nextvar.lua
@@ -544,6 +544,12 @@ do
a = 0; for i=1.0, 0.99999, -1 do a=a+1 end; assert(a==1)
end
+do -- changing the control variable
+ local a
+ a = 0; for i = 1, 10 do a = a + 1; i = "x" end; assert(a == 10)
+ a = 0; for i = 10.0, 1, -1 do a = a + 1; i = "x" end; assert(a == 10)
+end
+
-- conversion
a = 0; for i="10","1","-2" do a=a+1 end; assert(a==5)
From 23e6bac8a0bbb9e5df43cbc0b7634b6d1395b0ff Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 22 Mar 2019 13:37:17 -0300
Subject: [PATCH 133/889] Keep correct type for immediate operands in
comparisons
When calling metamethods for things like 'a < 3.0', which generates
the opcode OP_LTI, the C register tells that the operand was
converted to an integer, so that it can be corrected to float when
calling a metamethod.
This commit also includes some other stuff:
- file 'onelua.c' added to the project
- opcode OP_PREPVARARG renamed to OP_VARARGPREP
- comparison opcodes rewritten through macros
---
lcode.c | 42 ++++++++++--------
ljumptab.h | 2 +-
llimits.h | 2 +
lopcodes.c | 2 +-
lopcodes.h | 5 ++-
lopnames.h | 2 +-
lparser.c | 2 +-
ltm.c | 8 +++-
ltm.h | 2 +-
lvm.c | 96 +++++++++++++++++++----------------------
onelua.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++
testes/db.lua | 2 +-
testes/events.lua | 99 +++++++++++++++++++-----------------------
13 files changed, 237 insertions(+), 134 deletions(-)
create mode 100644 onelua.c
diff --git a/lcode.c b/lcode.c
index 1872ede2ba..1e36e5842a 100644
--- a/lcode.c
+++ b/lcode.c
@@ -179,8 +179,8 @@ void luaK_ret (FuncState *fs, int first, int nret) {
** Code a "conditional jump", that is, a test or comparison opcode
** followed by a jump. Return jump position.
*/
-static int condjump (FuncState *fs, OpCode op, int A, int B, int k) {
- luaK_codeABCk(fs, op, A, B, 0, k);
+static int condjump (FuncState *fs, OpCode op, int A, int B, int C, int k) {
+ luaK_codeABCk(fs, op, A, B, C, k);
return luaK_jump(fs);
}
@@ -799,7 +799,7 @@ static int need_value (FuncState *fs, int list) {
/*
** Ensures final expression result (which includes results from its
-** jump ** lists) is in register 'reg'.
+** jump lists) is in register 'reg'.
** If expression has jumps, need to patch these jumps either to
** its final position or to "load" instructions (for those tests
** that do not produce values).
@@ -814,8 +814,9 @@ static void exp2reg (FuncState *fs, expdesc *e, int reg) {
int p_t = NO_JUMP; /* position of an eventual LOAD true */
if (need_value(fs, e->t) || need_value(fs, e->f)) {
int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs);
- p_f = code_loadbool(fs, reg, 0, 1);
- p_t = code_loadbool(fs, reg, 1, 0);
+ p_f = code_loadbool(fs, reg, 0, 1); /* load false and skip next i. */
+ p_t = code_loadbool(fs, reg, 1, 0); /* load true */
+ /* jump around these booleans if 'e' is not a test */
luaK_patchtohere(fs, fj);
}
final = luaK_getlabel(fs);
@@ -1005,13 +1006,13 @@ static int jumponcond (FuncState *fs, expdesc *e, int cond) {
Instruction ie = getinstruction(fs, e);
if (GET_OPCODE(ie) == OP_NOT) {
removelastinstruction(fs); /* remove previous OP_NOT */
- return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond);
+ return condjump(fs, OP_TEST, GETARG_B(ie), 0, 0, !cond);
}
/* else go through */
}
discharge2anyreg(fs, e);
freeexp(fs, e);
- return condjump(fs, OP_TESTSET, NO_REG, e->u.info, cond);
+ return condjump(fs, OP_TESTSET, NO_REG, e->u.info, 0, cond);
}
@@ -1139,13 +1140,15 @@ static int isSCint (expdesc *e) {
/*
** Check whether expression 'e' is a literal integer or float in
-** proper range to fit in register sC
+** proper range to fit in a register (sB or sC).
*/
-static int isSCnumber (expdesc *e, lua_Integer *i) {
+static int isSCnumber (expdesc *e, lua_Integer *i, int *isfloat) {
if (e->k == VKINT)
*i = e->u.ival;
else if (!(e->k == VKFLT && floatI(e->u.nval, i)))
return 0; /* not a number */
+ else
+ *isfloat = 1;
if (!hasjumps(e) && fitsC(*i)) {
*i += OFFSET_sC;
return 1;
@@ -1372,21 +1375,20 @@ static void codeshift (FuncState *fs, OpCode op,
/*
-** Emit code for order comparisons.
-** When the first operand A is an integral value in the proper range,
-** change (A < B) to (B > A) and (A <= B) to (B >= A) so that
-** it can use an immediate operand.
+** Emit code for order comparisons. When using an immediate operand,
+** 'isfloat' tells whether the original value was a float.
*/
static void codeorder (FuncState *fs, OpCode op, expdesc *e1, expdesc *e2) {
int r1, r2;
lua_Integer im;
- if (isSCnumber(e2, &im)) {
+ int isfloat = 0;
+ if (isSCnumber(e2, &im, &isfloat)) {
/* use immediate operand */
r1 = luaK_exp2anyreg(fs, e1);
r2 = cast_int(im);
op = cast(OpCode, (op - OP_LT) + OP_LTI);
}
- else if (isSCnumber(e1, &im)) {
+ else if (isSCnumber(e1, &im, &isfloat)) {
/* transform (A < B) to (B > A) and (A <= B) to (B >= A) */
r1 = luaK_exp2anyreg(fs, e2);
r2 = cast_int(im);
@@ -1397,7 +1399,7 @@ static void codeorder (FuncState *fs, OpCode op, expdesc *e1, expdesc *e2) {
r2 = luaK_exp2anyreg(fs, e2);
}
freeexps(fs, e1, e2);
- e1->u.info = condjump(fs, op, r1, r2, 1);
+ e1->u.info = condjump(fs, op, r1, r2, isfloat, 1);
e1->k = VJMP;
}
@@ -1409,13 +1411,14 @@ static void codeorder (FuncState *fs, OpCode op, expdesc *e1, expdesc *e2) {
static void codeeq (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) {
int r1, r2;
lua_Integer im;
+ int isfloat = 0; /* not needed here, but kept for symmetry */
OpCode op;
if (e1->k != VNONRELOC) {
lua_assert(e1->k == VK || e1->k == VKINT || e1->k == VKFLT);
swapexps(e1, e2);
}
r1 = luaK_exp2anyreg(fs, e1); /* 1nd expression must be in register */
- if (isSCnumber(e2, &im)) {
+ if (isSCnumber(e2, &im, &isfloat)) {
op = OP_EQI;
r2 = cast_int(im); /* immediate operand */
}
@@ -1428,7 +1431,7 @@ static void codeeq (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) {
r2 = luaK_exp2anyreg(fs, e2);
}
freeexps(fs, e1, e2);
- e1->u.info = condjump(fs, op, r1, r2, (opr == OPR_EQ));
+ e1->u.info = condjump(fs, op, r1, r2, isfloat, (opr == OPR_EQ));
e1->k = VJMP;
}
@@ -1489,7 +1492,8 @@ void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) {
case OPR_LT: case OPR_LE:
case OPR_GT: case OPR_GE: {
lua_Integer dummy;
- if (!isSCnumber(v, &dummy))
+ int dummy2;
+ if (!isSCnumber(v, &dummy, &dummy2))
luaK_exp2anyreg(fs, v);
/* else keep numeral, which may be an immediate operand */
break;
diff --git a/ljumptab.h b/ljumptab.h
index fa4277cc86..2d4cf28bf6 100644
--- a/ljumptab.h
+++ b/ljumptab.h
@@ -107,7 +107,7 @@ static void *disptab[NUM_OPCODES] = {
&&L_OP_SETLIST,
&&L_OP_CLOSURE,
&&L_OP_VARARG,
-&&L_OP_PREPVARARG,
+&&L_OP_VARARGPREP,
&&L_OP_EXTRAARG
};
diff --git a/llimits.h b/llimits.h
index 9d35d1c774..155bb16081 100644
--- a/llimits.h
+++ b/llimits.h
@@ -325,6 +325,8 @@ typedef l_uint32 Instruction;
#define luai_numeq(a,b) ((a)==(b))
#define luai_numlt(a,b) ((a)<(b))
#define luai_numle(a,b) ((a)<=(b))
+#define luai_numgt(a,b) ((a)>(b))
+#define luai_numge(a,b) ((a)>=(b))
#define luai_numisnan(a) (!luai_numeq((a), (a)))
#endif
diff --git a/lopcodes.c b/lopcodes.c
index c35a0aaf4c..23c3a6e451 100644
--- a/lopcodes.c
+++ b/lopcodes.c
@@ -101,7 +101,7 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
,opmode(0, 1, 0, 0, iABC) /* OP_SETLIST */
,opmode(0, 0, 0, 1, iABx) /* OP_CLOSURE */
,opmode(1, 0, 0, 1, iABC) /* OP_VARARG */
- ,opmode(0, 0, 0, 1, iABC) /* OP_PREPVARARG */
+ ,opmode(0, 0, 0, 1, iABC) /* OP_VARARGPREP */
,opmode(0, 0, 0, 0, iAx) /* OP_EXTRAARG */
};
diff --git a/lopcodes.h b/lopcodes.h
index f867a01bb3..bbdd68978b 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -294,7 +294,7 @@ OP_CLOSURE,/* A Bx R(A) := closure(KPROTO[Bx]) */
OP_VARARG,/* A C R(A), R(A+1), ..., R(A+C-2) = vararg */
-OP_PREPVARARG,/*A (adjust vararg parameters) */
+OP_VARARGPREP,/*A (adjust vararg parameters) */
OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */
} OpCode;
@@ -331,6 +331,9 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */
C > 0 means the function is vararg and (C - 1) is its number of
fixed parameters.
+ (*) In comparisons with an immediate operand, C signals whether the
+ original operand was a float.
+
===========================================================================*/
diff --git a/lopnames.h b/lopnames.h
index dfca34d1f9..28535fe226 100644
--- a/lopnames.h
+++ b/lopnames.h
@@ -92,7 +92,7 @@ static const char *const opnames[] = {
"SETLIST",
"CLOSURE",
"VARARG",
- "PREPVARARG",
+ "VARARGPREP",
"EXTRAARG",
NULL
};
diff --git a/lparser.c b/lparser.c
index 8ffd97423a..4c2ddbfe50 100644
--- a/lparser.c
+++ b/lparser.c
@@ -817,7 +817,7 @@ static void constructor (LexState *ls, expdesc *t) {
static void setvararg (FuncState *fs, int nparams) {
fs->f->is_vararg = 1;
- luaK_codeABC(fs, OP_PREPVARARG, nparams, 0, 0);
+ luaK_codeABC(fs, OP_VARARGPREP, nparams, 0, 0);
}
diff --git a/ltm.c b/ltm.c
index 23a97a62cc..c4fd762bc1 100644
--- a/ltm.c
+++ b/ltm.c
@@ -205,9 +205,13 @@ int luaT_callorderTM (lua_State *L, const TValue *p1, const TValue *p2,
int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2,
- int inv, TMS event) {
+ int inv, int isfloat, TMS event) {
TValue aux; const TValue *p2;
- setivalue(&aux, v2);
+ if (isfloat) {
+ setfltvalue(&aux, cast_num(v2));
+ }
+ else
+ setivalue(&aux, v2);
if (inv) { /* arguments were exchanged? */
p2 = p1; p1 = &aux; /* correct them */
}
diff --git a/ltm.h b/ltm.h
index fad47842a1..e308fb80e0 100644
--- a/ltm.h
+++ b/ltm.h
@@ -82,7 +82,7 @@ LUAI_FUNC void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2,
LUAI_FUNC int luaT_callorderTM (lua_State *L, const TValue *p1,
const TValue *p2, TMS event);
LUAI_FUNC int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2,
- int inv, TMS event);
+ int inv, int isfloat, TMS event);
LUAI_FUNC void luaT_adjustvarargs (lua_State *L, int nfixparams,
struct CallInfo *ci, const Proto *p);
diff --git a/lvm.c b/lvm.c
index 31b4dc36fc..47bc67c94d 100644
--- a/lvm.c
+++ b/lvm.c
@@ -772,11 +772,10 @@ void luaV_finishOp (lua_State *L) {
/*
** {==================================================================
-** Macros for arithmetic/bitwise opcodes in 'luaV_execute'
+** Macros for arithmetic/bitwise/comparison opcodes in 'luaV_execute'
** ===================================================================
*/
-
#define l_addi(L,a,b) intop(+, a, b)
#define l_subi(L,a,b) intop(-, a, b)
#define l_muli(L,a,b) intop(*, a, b)
@@ -784,6 +783,11 @@ void luaV_finishOp (lua_State *L) {
#define l_bor(L,a,b) intop(|, a, b)
#define l_bxor(L,a,b) intop(^, a, b)
+#define l_lti(a,b) (a < b)
+#define l_lei(a,b) (a <= b)
+#define l_gti(a,b) (a > b)
+#define l_gei(a,b) (a >= b)
+
/*
** Auxiliary macro for arithmetic operations over floats and others
@@ -916,6 +920,36 @@ void luaV_finishOp (lua_State *L) {
else \
Protect(luaT_trybinTM(L, v1, v2, ra, tm)); }
+
+/*
+** Order operations with register operands.
+*/
+#define op_order(L,opi,opf,other) { \
+ TValue *rb = vRB(i); \
+ if (ttisinteger(s2v(ra)) && ttisinteger(rb)) \
+ cond = opi(ivalue(s2v(ra)), ivalue(rb)); \
+ else if (ttisnumber(s2v(ra)) && ttisnumber(rb)) \
+ cond = opf(s2v(ra), rb); \
+ else \
+ Protect(cond = other(L, s2v(ra), rb)); \
+ docondjump(); }
+
+
+/*
+** Order operations with immediate operand.
+*/
+#define op_orderI(L,opi,opf,inv,tm) { \
+ int im = GETARG_sB(i); \
+ if (ttisinteger(s2v(ra))) \
+ cond = opi(ivalue(s2v(ra)), im); \
+ else if (ttisfloat(s2v(ra))) \
+ cond = opf(fltvalue(s2v(ra)), cast_num(im)); \
+ else { \
+ int isf = GETARG_C(i); \
+ Protect(cond = luaT_callorderiTM(L, s2v(ra), im, inv, isf, tm)); \
+ } \
+ docondjump(); }
+
/* }================================================================== */
@@ -1034,7 +1068,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
pc = ci->u.l.savedpc;
if (trap) {
if (cl->p->is_vararg)
- trap = 0; /* hooks will start after PREPVARARG instruction */
+ trap = 0; /* hooks will start after VARARGPREP instruction */
else if (pc == cl->p->code) /* first instruction (not resuming)? */
luaD_hookcall(L, ci);
ci->u.l.trap = 1; /* there may be other hooks */
@@ -1447,25 +1481,11 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_LT) {
- TValue *rb = vRB(i);
- if (ttisinteger(s2v(ra)) && ttisinteger(rb))
- cond = (ivalue(s2v(ra)) < ivalue(rb));
- else if (ttisnumber(s2v(ra)) && ttisnumber(rb))
- cond = LTnum(s2v(ra), rb);
- else
- Protect(cond = lessthanothers(L, s2v(ra), rb));
- docondjump();
+ op_order(L, l_lti, LTnum, lessthanothers);
vmbreak;
}
vmcase(OP_LE) {
- TValue *rb = vRB(i);
- if (ttisinteger(s2v(ra)) && ttisinteger(rb))
- cond = (ivalue(s2v(ra)) <= ivalue(rb));
- else if (ttisnumber(s2v(ra)) && ttisnumber(rb))
- cond = LEnum(s2v(ra), rb);
- else
- Protect(cond = lessequalothers(L, s2v(ra), rb));
- docondjump();
+ op_order(L, l_lei, LEnum, lessequalothers);
vmbreak;
}
vmcase(OP_EQK) {
@@ -1487,47 +1507,19 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_LTI) {
- int im = GETARG_sB(i);
- if (ttisinteger(s2v(ra)))
- cond = (ivalue(s2v(ra)) < im);
- else if (ttisfloat(s2v(ra)))
- cond = luai_numlt(fltvalue(s2v(ra)), cast_num(im));
- else
- Protect(cond = luaT_callorderiTM(L, s2v(ra), im, 0, TM_LT));
- docondjump();
+ op_orderI(L, l_lti, luai_numlt, 0, TM_LT);
vmbreak;
}
vmcase(OP_LEI) {
- int im = GETARG_sB(i);
- if (ttisinteger(s2v(ra)))
- cond = (ivalue(s2v(ra)) <= im);
- else if (ttisfloat(s2v(ra)))
- cond = luai_numle(fltvalue(s2v(ra)), cast_num(im));
- else
- Protect(cond = luaT_callorderiTM(L, s2v(ra), im, 0, TM_LE));
- docondjump();
+ op_orderI(L, l_lei, luai_numle, 0, TM_LE);
vmbreak;
}
vmcase(OP_GTI) {
- int im = GETARG_sB(i);
- if (ttisinteger(s2v(ra)))
- cond = (im < ivalue(s2v(ra)));
- else if (ttisfloat(s2v(ra)))
- cond = luai_numlt(cast_num(im), fltvalue(s2v(ra)));
- else
- Protect(cond = luaT_callorderiTM(L, s2v(ra), im, 1, TM_LT));
- docondjump();
+ op_orderI(L, l_gti, luai_numgt, 1, TM_LT);
vmbreak;
}
vmcase(OP_GEI) {
- int im = GETARG_sB(i);
- if (ttisinteger(s2v(ra)))
- cond = (im <= ivalue(s2v(ra)));
- else if (ttisfloat(s2v(ra)))
- cond = luai_numle(cast_num(im), fltvalue(s2v(ra)));
- else
- Protect(cond = luaT_callorderiTM(L, s2v(ra), im, 1, TM_LE));
- docondjump();
+ op_orderI(L, l_gei, luai_numge, 1, TM_LE);
vmbreak;
}
vmcase(OP_TEST) {
@@ -1787,7 +1779,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
Protect(luaT_getvarargs(L, ci, ra, n));
vmbreak;
}
- vmcase(OP_PREPVARARG) {
+ vmcase(OP_VARARGPREP) {
luaT_adjustvarargs(L, GETARG_A(i), ci, cl->p);
updatetrap(ci);
if (trap) {
diff --git a/onelua.c b/onelua.c
new file mode 100644
index 0000000000..3c605981f0
--- /dev/null
+++ b/onelua.c
@@ -0,0 +1,107 @@
+/*
+* one.c -- Lua core, libraries, and interpreter in a single file
+*/
+
+/* default is to build the full interpreter */
+#ifndef MAKE_LIB
+#ifndef MAKE_LUAC
+#ifndef MAKE_LUA
+#define MAKE_LUA
+#endif
+#endif
+#endif
+
+/* choose suitable platform-specific features */
+/* some of these may need extra libraries such as -ldl -lreadline -lncurses */
+#if 0
+#define LUA_USE_LINUX
+#define LUA_USE_MACOSX
+#define LUA_USE_POSIX
+#define LUA_ANSI
+#endif
+
+/* no need to change anything below this line ----------------------------- */
+
+#include "lprefix.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/* setup for luaconf.h */
+#define LUA_CORE
+#define LUA_LIB
+#define ltable_c
+#define lvm_c
+#include "luaconf.h"
+
+/* do not export internal symbols */
+#undef LUAI_FUNC
+#undef LUAI_DDEC
+#undef LUAI_DDEF
+#define LUAI_FUNC static
+#define LUAI_DDEC(def) /* empty */
+#define LUAI_DDEF static
+
+/* core -- used by all */
+#include "lzio.c"
+#include "lctype.c"
+#include "lopcodes.c"
+#include "lmem.c"
+#include "lundump.c"
+#include "ldump.c"
+#include "lstate.c"
+#include "lgc.c"
+#include "llex.c"
+#include "lcode.c"
+#include "lparser.c"
+#include "ldebug.c"
+#include "lfunc.c"
+#include "lobject.c"
+#include "ltm.c"
+#include "lstring.c"
+#include "ltable.c"
+#include "ldo.c"
+#include "lvm.c"
+#include "lapi.c"
+
+/* auxiliary library -- used by all */
+#include "lauxlib.c"
+
+/* standard library -- not used by luac */
+#ifndef MAKE_LUAC
+#include "lbaselib.c"
+#include "lcorolib.c"
+#include "ldblib.c"
+#include "liolib.c"
+#include "lmathlib.c"
+#include "loadlib.c"
+#include "loslib.c"
+#include "lstrlib.c"
+#include "ltablib.c"
+#include "lutf8lib.c"
+#include "linit.c"
+#endif
+
+/* lua */
+#ifdef MAKE_LUA
+#include "lua.c"
+#endif
+
+/* luac */
+#ifdef MAKE_LUAC
+#include "luac.c"
+#endif
diff --git a/testes/db.lua b/testes/db.lua
index 0858dd2016..95275fb41d 100644
--- a/testes/db.lua
+++ b/testes/db.lua
@@ -162,7 +162,7 @@ test([[for i,v in pairs{'a','b'} do
end
]], {1,2,1,2,1,3})
-test([[for i=1,4 do a=1 end]], {1,1,1,1}, true)
+test([[for i=1,4 do a=1 end]], {1,1,1,1})
do -- testing line info/trace with large gaps in source
diff --git a/testes/events.lua b/testes/events.lua
index ac630d895c..cf68d1e99c 100644
--- a/testes/events.lua
+++ b/testes/events.lua
@@ -138,64 +138,55 @@ t.__bxor = f("bxor")
t.__shl = f("shl")
t.__shr = f("shr")
t.__bnot = f("bnot")
+t.__lt = f("lt")
+t.__le = f("le")
+
+
+local function checkcap (t)
+ assert(#cap + 1 == #t)
+ for i = 1, #t do
+ assert(cap[i - 1] == t[i])
+ assert(math.type(cap[i - 1]) == math.type(t[i]))
+ end
+end
-- Some tests are done inside small anonymous functions to ensure
-- that constants go to constant table even in debug compilation,
-- when the constant table is very small.
-assert(b+5 == b)
-assert(cap[0] == "add" and cap[1] == b and cap[2] == 5 and cap[3]==undef)
-assert(5.2 + b == 5.2)
-assert(cap[0] == "add" and cap[1] == 5.2 and cap[2] == b and cap[3]==undef)
-assert(b+'5' == b)
-assert(cap[0] == "add" and cap[1] == b and cap[2] == '5' and cap[3]==undef)
-assert(5+b == 5)
-assert(cap[0] == "add" and cap[1] == 5 and cap[2] == b and cap[3]==undef)
-assert('5'+b == '5')
-assert(cap[0] == "add" and cap[1] == '5' and cap[2] == b and cap[3]==undef)
-b=b-3; assert(getmetatable(b) == t)
-assert(cap[0] == "sub" and cap[1] == b and cap[2] == 3 and cap[3]==undef)
-assert(5-a == 5)
-assert(cap[0] == "sub" and cap[1] == 5 and cap[2] == a and cap[3]==undef)
-assert('5'-a == '5')
-assert(cap[0] == "sub" and cap[1] == '5' and cap[2] == a and cap[3]==undef)
-assert(a*a == a)
-assert(cap[0] == "mul" and cap[1] == a and cap[2] == a and cap[3]==undef)
-assert(a/0 == a)
-assert(cap[0] == "div" and cap[1] == a and cap[2] == 0 and cap[3]==undef)
-assert(a%2 == a)
-assert(cap[0] == "mod" and cap[1] == a and cap[2] == 2 and cap[3]==undef)
-assert(a // (1/0) == a)
-assert(cap[0] == "idiv" and cap[1] == a and cap[2] == 1/0 and cap[3]==undef)
-;(function () assert(a & "hi" == a) end)()
-assert(cap[0] == "band" and cap[1] == a and cap[2] == "hi" and cap[3]==undef)
-;(function () assert(10 & a == 10) end)()
-assert(cap[0] == "band" and cap[1] == 10 and cap[2] == a and cap[3]==undef)
-;(function () assert(a | 10 == a) end)()
-assert(cap[0] == "bor" and cap[1] == a and cap[2] == 10 and cap[3]==undef)
-assert(a | "hi" == a)
-assert(cap[0] == "bor" and cap[1] == a and cap[2] == "hi" and cap[3]==undef)
-assert("hi" ~ a == "hi")
-assert(cap[0] == "bxor" and cap[1] == "hi" and cap[2] == a and cap[3]==undef)
-;(function () assert(10 ~ a == 10) end)()
-assert(cap[0] == "bxor" and cap[1] == 10 and cap[2] == a and cap[3]==undef)
-assert(-a == a)
-assert(cap[0] == "unm" and cap[1] == a)
-assert(a^4 == a)
-assert(cap[0] == "pow" and cap[1] == a and cap[2] == 4 and cap[3]==undef)
-assert(a^'4' == a)
-assert(cap[0] == "pow" and cap[1] == a and cap[2] == '4' and cap[3]==undef)
-assert(4^a == 4)
-assert(cap[0] == "pow" and cap[1] == 4 and cap[2] == a and cap[3]==undef)
-assert('4'^a == '4')
-assert(cap[0] == "pow" and cap[1] == '4' and cap[2] == a and cap[3]==undef)
-assert(#a == a)
-assert(cap[0] == "len" and cap[1] == a)
-assert(~a == a)
-assert(cap[0] == "bnot" and cap[1] == a)
-assert(a << 3 == a)
-assert(cap[0] == "shl" and cap[1] == a and cap[2] == 3)
-assert(1.5 >> a == 1.5)
-assert(cap[0] == "shr" and cap[1] == 1.5 and cap[2] == a)
+assert(b+5 == b); checkcap{"add", b, 5}
+assert(5.2 + b == 5.2); checkcap{"add", 5.2, b}
+assert(b+'5' == b); checkcap{"add", b, '5'}
+assert(5+b == 5); checkcap{"add", 5, b}
+assert('5'+b == '5'); checkcap{"add", '5', b}
+b=b-3; assert(getmetatable(b) == t); checkcap{"sub", b, 3}
+assert(5-a == 5); checkcap{"sub", 5, a}
+assert('5'-a == '5'); checkcap{"sub", '5', a}
+assert(a*a == a); checkcap{"mul", a, a}
+assert(a/0 == a); checkcap{"div", a, 0}
+assert(a/0.0 == a); checkcap{"div", a, 0.0}
+assert(a%2 == a); checkcap{"mod", a, 2}
+assert(a // (1/0) == a); checkcap{"idiv", a, 1/0}
+;(function () assert(a & "hi" == a) end)(); checkcap{"band", a, "hi"}
+;(function () assert(10 & a == 10) end)(); checkcap{"band", 10, a}
+;(function () assert(a | 10 == a) end)(); checkcap{"bor", a, 10}
+assert(a | "hi" == a); checkcap{"bor", a, "hi"}
+assert("hi" ~ a == "hi"); checkcap{"bxor", "hi", a}
+;(function () assert(10 ~ a == 10) end)(); checkcap{"bxor", 10, a}
+assert(-a == a); checkcap{"unm", a, a}
+assert(a^4.0 == a); checkcap{"pow", a, 4.0}
+assert(a^'4' == a); checkcap{"pow", a, '4'}
+assert(4^a == 4); checkcap{"pow", 4, a}
+assert('4'^a == '4'); checkcap{"pow", '4', a}
+assert(#a == a); checkcap{"len", a, a}
+assert(~a == a); checkcap{"bnot", a, a}
+assert(a << 3 == a); checkcap{"shl", a, 3}
+assert(1.5 >> a == 1.5); checkcap{"shr", 1.5, a}
+
+-- for comparsion operators, all results are true
+assert(5.0 > a); checkcap{"lt", a, 5.0}
+assert(a >= 10); checkcap{"le", 10, a}
+assert(a <= -10.0); checkcap{"le", a, -10.0}
+assert(a < -10); checkcap{"lt", a, -10}
-- test for rawlen
From 7ceb2154ed69170f3e47f7a5a840e543c7c6ed3d Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 25 Mar 2019 10:38:56 -0300
Subject: [PATCH 134/889] Fixed small bugs/issues
- In 'readutf8esc' (llex.c), the overflow check must be done before
shifting the accumulator. It was working because tests were using
64-bit longs. Failed with 32-bit longs.
- In OP_FORPREP (lvm.c), avoid negating an unsigned value. Visual
Studio gives a warning for that operation, despite being well
defined in ISO C.
- In 'luaV_execute' (lvm.c), 'cond' can be defined only when needed,
like all other variables.
---
llex.c | 2 +-
lvm.c | 12 ++++++++----
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/llex.c b/llex.c
index 1539f5258c..b0bab37736 100644
--- a/llex.c
+++ b/llex.c
@@ -334,8 +334,8 @@ static unsigned long readutf8esc (LexState *ls) {
r = gethexa(ls); /* must have at least one digit */
while ((save_and_next(ls), lisxdigit(ls->current))) {
i++;
+ esccheck(ls, r <= (0x7FFFFFFFu >> 4), "UTF-8 value too large");
r = (r << 4) + luaO_hexavalue(ls->current);
- esccheck(ls, r <= 0x7FFFFFFFu, "UTF-8 value too large");
}
esccheck(ls, ls->current == '}', "missing '}'");
next(ls); /* skip '}' */
diff --git a/lvm.c b/lvm.c
index 47bc67c94d..d0358143e0 100644
--- a/lvm.c
+++ b/lvm.c
@@ -925,6 +925,7 @@ void luaV_finishOp (lua_State *L) {
** Order operations with register operands.
*/
#define op_order(L,opi,opf,other) { \
+ int cond; \
TValue *rb = vRB(i); \
if (ttisinteger(s2v(ra)) && ttisinteger(rb)) \
cond = opi(ivalue(s2v(ra)), ivalue(rb)); \
@@ -939,6 +940,7 @@ void luaV_finishOp (lua_State *L) {
** Order operations with immediate operand.
*/
#define op_orderI(L,opi,opf,inv,tm) { \
+ int cond; \
int im = GETARG_sB(i); \
if (ttisinteger(s2v(ra))) \
cond = opi(ivalue(s2v(ra)), im); \
@@ -1076,7 +1078,6 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
base = ci->func + 1;
/* main loop of interpreter */
for (;;) {
- int cond; /* flag for conditional jumps */
Instruction i; /* instruction being executed */
StkId ra; /* instruction's A register */
vmfetch();
@@ -1475,6 +1476,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_EQ) {
+ int cond;
TValue *rb = vRB(i);
Protect(cond = luaV_equalobj(L, s2v(ra), rb));
docondjump();
@@ -1491,11 +1493,12 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmcase(OP_EQK) {
TValue *rb = KB(i);
/* basic types do not use '__eq'; we can use raw equality */
- cond = luaV_equalobj(NULL, s2v(ra), rb);
+ int cond = luaV_equalobj(NULL, s2v(ra), rb);
docondjump();
vmbreak;
}
vmcase(OP_EQI) {
+ int cond;
int im = GETARG_sB(i);
if (ttisinteger(s2v(ra)))
cond = (ivalue(s2v(ra)) == im);
@@ -1523,7 +1526,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak;
}
vmcase(OP_TEST) {
- cond = !l_isfalse(s2v(ra));
+ int cond = !l_isfalse(s2v(ra));
docondjump();
vmbreak;
}
@@ -1679,7 +1682,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
}
else { /* step < 0; descending loop */
count = l_castS2U(init) - l_castS2U(limit);
- count /= -l_castS2U(step);
+ /* 'step+1' avoids negating 'mininteger' */
+ count /= l_castS2U(-(step + 1)) + 1u;
}
/* store the counter in place of the limit (which won't be
needed anymore */
From f9b0cf0e2ee35c5444959f77e95f3f07376b9e3e Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 25 Mar 2019 14:00:09 -0300
Subject: [PATCH 135/889] Year in copyright notice updated to 2019
---
lua.h | 4 ++--
manual/2html | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lua.h b/lua.h
index 09611db574..7c4a13bceb 100644
--- a/lua.h
+++ b/lua.h
@@ -25,7 +25,7 @@
#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR
#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE
-#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2018 Lua.org, PUC-Rio"
+#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2019 Lua.org, PUC-Rio"
#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes"
@@ -487,7 +487,7 @@ struct lua_Debug {
/******************************************************************************
-* Copyright (C) 1994-2018 Lua.org, PUC-Rio.
+* Copyright (C) 1994-2019 Lua.org, PUC-Rio.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
diff --git a/manual/2html b/manual/2html
index 04b2c61ed0..605c6e59f5 100755
--- a/manual/2html
+++ b/manual/2html
@@ -30,7 +30,7 @@ by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes
From 0443ad9e288825b6e4441eb11104bcdb4ff4593a Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 25 Mar 2019 14:12:06 -0300
Subject: [PATCH 136/889] LUAI_MAXCCALLS renamed LUAI_MAXCSTACK
The limit LUAI_MAXCCALLS was renamed LUAI_MAXCSTACK, which better
represents its meaning. Moreover, its definition was moved to
'luaconf.h', given its importance now that Lua does not use
a "stackless" implementation.
---
ldo.c | 4 ++--
llimits.h | 9 ---------
lstate.c | 16 ++++++++--------
ltests.h | 4 ++--
luaconf.h | 15 +++++++++++++++
5 files changed, 27 insertions(+), 21 deletions(-)
diff --git a/ldo.c b/ldo.c
index 077109c4ab..2a98c3977a 100644
--- a/ldo.c
+++ b/ldo.c
@@ -521,7 +521,7 @@ void luaD_call (lua_State *L, StkId func, int nresults) {
*/
void luaD_callnoyield (lua_State *L, StkId func, int nResults) {
incXCcalls(L);
- if (getCcalls(L) >= LUAI_MAXCCALLS) /* possible stack overflow? */
+ if (getCcalls(L) >= LUAI_MAXCSTACK) /* possible stack overflow? */
luaE_freeCI(L);
luaD_call(L, func, nResults);
decXCcalls(L);
@@ -673,7 +673,7 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
L->nCcalls = 1;
else /* correct 'nCcalls' for this thread */
L->nCcalls = getCcalls(from) - from->nci + L->nci + CSTACKCF;
- if (L->nCcalls >= LUAI_MAXCCALLS)
+ if (L->nCcalls >= LUAI_MAXCSTACK)
return resume_error(L, "C stack overflow", nargs);
luai_userstateresume(L, nargs);
api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs);
diff --git a/llimits.h b/llimits.h
index 155bb16081..3df873dbc2 100644
--- a/llimits.h
+++ b/llimits.h
@@ -168,15 +168,6 @@ typedef LUAI_UACINT l_uacInt;
#endif
-/*
-** maximum depth for nested C calls and syntactical nested non-terminals
-** in a program. (Value must fit in an unsigned short int. It must also
-** be compatible with the size of the C stack.)
-*/
-#if !defined(LUAI_MAXCCALLS)
-#define LUAI_MAXCCALLS 2200
-#endif
-
/*
diff --git a/lstate.c b/lstate.c
index f5579a6613..463a47d2bf 100644
--- a/lstate.c
+++ b/lstate.c
@@ -100,13 +100,13 @@ void luaE_setdebt (global_State *g, l_mem debt) {
** Increment count of "C calls" and check for overflows. In case of
** a stack overflow, check appropriate error ("regular" overflow or
** overflow while handling stack overflow).
-** If 'nCcalls' is larger than LUAI_MAXCCALLS but smaller than
-** LUAI_MAXCCALLS + CSTACKCF (plus 2 to avoid by-one errors), it means
+** If 'nCcalls' is larger than LUAI_MAXCSTACK but smaller than
+** LUAI_MAXCSTACK + CSTACKCF (plus 2 to avoid by-one errors), it means
** it has just entered the "overflow zone", so the function raises an
** overflow error.
-** If 'nCcalls' is larger than LUAI_MAXCCALLS + CSTACKCF + 2
+** If 'nCcalls' is larger than LUAI_MAXCSTACK + CSTACKCF + 2
** (which means it is already handling an overflow) but smaller than
-** 9/8 of LUAI_MAXCCALLS, does not report an error (to allow message
+** 9/8 of LUAI_MAXCSTACK, does not report an error (to allow message
** handling to work).
** Otherwise, report a stack overflow while handling a stack overflow
** (probably caused by a repeating error in the message handling
@@ -115,16 +115,16 @@ void luaE_setdebt (global_State *g, l_mem debt) {
void luaE_enterCcall (lua_State *L) {
int ncalls = getCcalls(L);
L->nCcalls++;
- if (ncalls >= LUAI_MAXCCALLS) { /* possible overflow? */
+ if (ncalls >= LUAI_MAXCSTACK) { /* possible overflow? */
luaE_freeCI(L); /* release unused CIs */
ncalls = getCcalls(L); /* update call count */
- if (ncalls >= LUAI_MAXCCALLS) { /* still overflow? */
- if (ncalls <= LUAI_MAXCCALLS + CSTACKCF + 2) {
+ if (ncalls >= LUAI_MAXCSTACK) { /* still overflow? */
+ if (ncalls <= LUAI_MAXCSTACK + CSTACKCF + 2) {
/* no error before increments; raise the error now */
L->nCcalls += (CSTACKCF + 4); /* avoid raising it again */
luaG_runerror(L, "C stack overflow");
}
- else if (ncalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3)))
+ else if (ncalls >= (LUAI_MAXCSTACK + (LUAI_MAXCSTACK >> 3)))
luaD_throw(L, LUA_ERRERR); /* error while handling stack error */
}
}
diff --git a/ltests.h b/ltests.h
index 997e1c4b08..a22c98e17f 100644
--- a/ltests.h
+++ b/ltests.h
@@ -30,8 +30,8 @@
/* compiled with -O0, Lua uses a lot of C stack space... */
-#undef LUAI_MAXCCALLS
-#define LUAI_MAXCCALLS 400
+#undef LUAI_MAXCSTACK
+#define LUAI_MAXCSTACK 400
/* to avoid warnings, and to make sure value is really unused */
#define UNUSED(x) (x=0, (void)(x))
diff --git a/luaconf.h b/luaconf.h
index 0fc161a4b3..5c714d4e53 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -27,6 +27,21 @@
** =====================================================================
*/
+/*
+@@ LUAI_MAXCSTACK defines the maximum depth for nested calls and
+** also limits the maximum depth of other recursive algorithms in
+** the implementation, such as syntactic analysis. A value too
+** large may allow the interpreter to crash (C-stack overflow).
+** The default value seems ok for regular machines, but may be
+** too high for restricted hardware.
+** The test file 'cstack.lua' may help finding a good limit.
+** (It will crash with a limit too high.)
+*/
+#if !defined(LUAI_MAXCSTACK)
+#define LUAI_MAXCSTACK 2200
+#endif
+
+
/*
@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. You
** can also define LUA_32BITS in the make file, but changing here you
From d12262068d689eacc452a459a021df0ad8f6d46c Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 27 Mar 2019 14:56:10 -0300
Subject: [PATCH 137/889] Small optimizations in range checks
Checks of the form '1 <= x && x <= M' were rewritten in the form
'(unsigned)x - 1 < (unsigned)M', which is usually more efficient.
(Other similar checks have similar translations.) Although
some compilers do these optimizations, that does not happen
for all compilers or all cases.
---
lapi.c | 10 ++++++----
ltable.c | 10 +++++-----
ltablib.c | 8 ++++++--
testes/utf8.lua | 3 +++
4 files changed, 20 insertions(+), 11 deletions(-)
diff --git a/lapi.c b/lapi.c
index 06396ad3bc..661fdb145f 100644
--- a/lapi.c
+++ b/lapi.c
@@ -936,8 +936,8 @@ LUA_API int lua_setiuservalue (lua_State *L, int idx, int n) {
api_checknelems(L, 1);
o = index2value(L, idx);
api_check(L, ttisfulluserdata(o), "full userdata expected");
- if (!(0 < n && n <= uvalue(o)->nuvalue))
- res = 0;
+ if (!(cast_uint(n) - 1u < cast_uint(uvalue(o)->nuvalue)))
+ res = 0; /* 'n' not in [1, uvalue(o)->nuvalue] */
else {
setobj(L, &uvalue(o)->uv[n - 1].uv, s2v(L->top - 1));
luaC_barrierback(L, gcvalue(o), s2v(L->top - 1));
@@ -1313,7 +1313,8 @@ static const char *aux_upvalue (TValue *fi, int n, TValue **val,
switch (ttypetag(fi)) {
case LUA_TCCL: { /* C closure */
CClosure *f = clCvalue(fi);
- if (!(1 <= n && n <= f->nupvalues)) return NULL;
+ if (!(cast_uint(n) - 1u < cast_uint(f->nupvalues)))
+ return NULL; /* 'n' not in [1, f->nupvalues] */
*val = &f->upvalue[n-1];
if (owner) *owner = obj2gco(f);
return "";
@@ -1322,7 +1323,8 @@ static const char *aux_upvalue (TValue *fi, int n, TValue **val,
LClosure *f = clLvalue(fi);
TString *name;
Proto *p = f->p;
- if (!(1 <= n && n <= p->sizeupvalues)) return NULL;
+ if (!(cast_uint(n) - 1u < cast_uint(p->sizeupvalues)))
+ return NULL; /* 'n' not in [1, p->sizeupvalues] */
*val = f->upvals[n-1]->v;
if (owner) *owner = obj2gco(f->upvals[n - 1]);
name = p->upvalues[n-1].name;
diff --git a/ltable.c b/ltable.c
index e12381b2d8..628c640c2b 100644
--- a/ltable.c
+++ b/ltable.c
@@ -48,8 +48,8 @@
/*
** MAXASIZE is the maximum size of the array part. It is the minimum
-** between 2^MAXABITS and the maximum size such that, measured in bytes,
-** it fits in a 'size_t'.
+** between 2^MAXABITS and the maximum size that, measured in bytes,
+** fits in a 'size_t'.
*/
#define MAXASIZE luaM_limitN(1u << MAXABITS, TValue)
@@ -269,7 +269,7 @@ static const TValue *getgeneric (Table *t, const TValue *key) {
** the array part of a table, 0 otherwise.
*/
static unsigned int arrayindex (lua_Integer k) {
- if (0 < k && l_castS2U(k) <= MAXASIZE)
+ if (l_castS2U(k) - 1u < MAXASIZE) /* 'k' in [1, MAXASIZE]? */
return cast_uint(k); /* 'key' is an appropriate array index */
else
return 0;
@@ -286,7 +286,7 @@ static unsigned int findindex (lua_State *L, Table *t, TValue *key,
unsigned int i;
if (ttisnil(key)) return 0; /* first iteration */
i = ttisinteger(key) ? arrayindex(ivalue(key)) : 0;
- if (i != 0 && i <= asize) /* is 'key' inside array part? */
+ if (i - 1u < asize) /* is 'key' inside array part? */
return i; /* yes; that's the index */
else {
const TValue *n = getgeneric(t, key);
@@ -678,7 +678,7 @@ TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
** changing the real size of the array).
*/
const TValue *luaH_getint (Table *t, lua_Integer key) {
- if (l_castS2U(key) - 1u < t->alimit) /* (1 <= key && key <= t->alimit)? */
+ if (l_castS2U(key) - 1u < t->alimit) /* 'key' in [1, t->alimit]? */
return &t->array[key - 1];
else if (!limitequalsasize(t) && /* key still may be in the array part? */
(l_castS2U(key) == t->alimit + 1 ||
diff --git a/ltablib.c b/ltablib.c
index 29c53e9483..a9169f9e3e 100644
--- a/ltablib.c
+++ b/ltablib.c
@@ -69,7 +69,9 @@ static int tinsert (lua_State *L) {
case 3: {
lua_Integer i;
pos = luaL_checkinteger(L, 2); /* 2nd argument is the position */
- luaL_argcheck(L, 1 <= pos && pos <= e, 2, "position out of bounds");
+ /* check whether 'pos' is in [1, e] */
+ luaL_argcheck(L, (lua_Unsigned)pos - 1u < (lua_Unsigned)e, 2,
+ "position out of bounds");
for (i = e; i > pos; i--) { /* move up elements */
lua_geti(L, 1, i - 1);
lua_seti(L, 1, i); /* t[i] = t[i - 1] */
@@ -89,7 +91,9 @@ static int tremove (lua_State *L) {
lua_Integer size = aux_getn(L, 1, TAB_RW);
lua_Integer pos = luaL_optinteger(L, 2, size);
if (pos != size) /* validate 'pos' if given */
- luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, "position out of bounds");
+ /* check whether 'pos' is in [1, size + 1] */
+ luaL_argcheck(L, (lua_Unsigned)pos - 1u <= (lua_Unsigned)size, 1,
+ "position out of bounds");
lua_geti(L, 1, pos); /* result = t[pos] */
for ( ; pos < size; pos++) {
lua_geti(L, 1, pos + 1);
diff --git a/testes/utf8.lua b/testes/utf8.lua
index 86ec1b00f1..b3b7687fb2 100644
--- a/testes/utf8.lua
+++ b/testes/utf8.lua
@@ -123,6 +123,9 @@ checkerror("continuation byte", utf8.offset, "𦧺", 1, 2)
checkerror("continuation byte", utf8.offset, "𦧺", 1, 2)
checkerror("continuation byte", utf8.offset, "\x80", 1)
+-- error in indices for len
+checkerror("out of string", utf8.len, "abc", 0, 2)
+checkerror("out of string", utf8.len, "abc", 1, 4)
local s = "hello World"
From 38425e069243fe6d991f2e99b4bba192af3563c7 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 1 Apr 2019 14:22:07 -0300
Subject: [PATCH 138/889] Avoid moving the collector while in 'GCSenteratomic'
state
The 'GCSenteratomic' is just an auxiliary state for transitioning
to 'GCSatomic'. All GC traversals should be done either on the
'GCSpropagate' state or the 'GCSatomic' state.
---
lgc.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lgc.c b/lgc.c
index 8cb3e9fad5..e9189ac31f 100644
--- a/lgc.c
+++ b/lgc.c
@@ -1423,6 +1423,7 @@ static lu_mem atomic (lua_State *L) {
/* registry and global metatables may be changed by API */
markvalue(g, &g->l_registry);
markmt(g); /* mark global metatables */
+ work += propagateall(g); /* empties 'gray' list */
/* remark occasional upvalues of (maybe) dead threads */
work += remarkupvals(g);
work += propagateall(g); /* propagate changes */
@@ -1486,8 +1487,7 @@ static lu_mem singlestep (lua_State *L) {
return propagatemark(g); /* traverse one gray object */
}
case GCSenteratomic: {
- lu_mem work = propagateall(g); /* make sure gray list is empty */
- work += atomic(L); /* work is what was traversed by 'atomic' */
+ lu_mem work = atomic(L); /* work is what was traversed by 'atomic' */
entersweep(L);
g->GCestimate = gettotalbytes(g); /* first estimate */;
return work;
From 5ca1075b714e825006e8ba4f8e7ea5544879bb41 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 4 Apr 2019 11:45:26 -0300
Subject: [PATCH 139/889] Added field 'srclen' to structure 'lua_Debug'
This new field gets the length of 'source' in the same structure.
Unlike the other strings in that structure, 'source' can be
relatively large, and Lua already has its length readily available.
---
ldblib.c | 3 ++-
ldebug.c | 14 +++++++++++---
llimits.h | 4 ++++
lobject.c | 27 ++++++++++++---------------
lobject.h | 2 +-
lua.h | 1 +
6 files changed, 31 insertions(+), 20 deletions(-)
diff --git a/ldblib.c b/ldblib.c
index ada35250c3..d045a82e22 100644
--- a/ldblib.c
+++ b/ldblib.c
@@ -167,7 +167,8 @@ static int db_getinfo (lua_State *L) {
return luaL_argerror(L, arg+2, "invalid option");
lua_newtable(L); /* table to collect results */
if (strchr(options, 'S')) {
- settabss(L, "source", ar.source);
+ lua_pushlstring(L, ar.source, ar.srclen);
+ lua_setfield(L, -2, "source");
settabss(L, "short_src", ar.short_src);
settabsi(L, "linedefined", ar.linedefined);
settabsi(L, "lastlinedefined", ar.lastlinedefined);
diff --git a/ldebug.c b/ldebug.c
index bd471e0c08..6cd4e071e8 100644
--- a/ldebug.c
+++ b/ldebug.c
@@ -262,18 +262,26 @@ LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) {
static void funcinfo (lua_Debug *ar, Closure *cl) {
if (noLuaClosure(cl)) {
ar->source = "=[C]";
+ ar->srclen = LL("=[C]");
ar->linedefined = -1;
ar->lastlinedefined = -1;
ar->what = "C";
}
else {
const Proto *p = cl->l.p;
- ar->source = p->source ? getstr(p->source) : "=?";
+ if (p->source) {
+ ar->source = getstr(p->source);
+ ar->srclen = tsslen(p->source);
+ }
+ else {
+ ar->source = "=?";
+ ar->srclen = LL("=?");
+ }
ar->linedefined = p->linedefined;
ar->lastlinedefined = p->lastlinedefined;
ar->what = (ar->linedefined == 0) ? "main" : "Lua";
}
- luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE);
+ luaO_chunkid(ar->short_src, ar->source, ar->srclen);
}
@@ -750,7 +758,7 @@ const char *luaG_addinfo (lua_State *L, const char *msg, TString *src,
int line) {
char buff[LUA_IDSIZE];
if (src)
- luaO_chunkid(buff, getstr(src), LUA_IDSIZE);
+ luaO_chunkid(buff, getstr(src), tsslen(src));
else { /* no source available; use "?" instead */
buff[0] = '?'; buff[1] = '\0';
}
diff --git a/llimits.h b/llimits.h
index 3df873dbc2..cc983972ef 100644
--- a/llimits.h
+++ b/llimits.h
@@ -65,6 +65,10 @@ typedef signed char ls_byte;
#define ispow2(x) (((x) & ((x) - 1)) == 0)
+/* number of chars of a literal string without the ending \0 */
+#define LL(x) (sizeof(x)/sizeof(char) - 1)
+
+
/*
** conversion of pointer to unsigned integer:
** this is for hashing only; there is no problem if the integer
diff --git a/lobject.c b/lobject.c
index 5d340de65e..67c37124f3 100644
--- a/lobject.c
+++ b/lobject.c
@@ -473,45 +473,42 @@ const char *luaO_pushfstring (lua_State *L, const char *fmt, ...) {
}
-/* number of chars of a literal string without the ending \0 */
-#define LL(x) (sizeof(x)/sizeof(char) - 1)
-
#define RETS "..."
#define PRE "[string \""
#define POS "\"]"
#define addstr(a,b,l) ( memcpy(a,b,(l) * sizeof(char)), a += (l) )
-void luaO_chunkid (char *out, const char *source, size_t bufflen) {
- size_t l = strlen(source);
+void luaO_chunkid (char *out, const char *source, size_t srclen) {
+ size_t bufflen = LUA_IDSIZE; /* free space in buffer */
if (*source == '=') { /* 'literal' source */
- if (l <= bufflen) /* small enough? */
- memcpy(out, source + 1, l * sizeof(char));
+ if (srclen <= bufflen) /* small enough? */
+ memcpy(out, source + 1, srclen * sizeof(char));
else { /* truncate it */
addstr(out, source + 1, bufflen - 1);
*out = '\0';
}
}
else if (*source == '@') { /* file name */
- if (l <= bufflen) /* small enough? */
- memcpy(out, source + 1, l * sizeof(char));
+ if (srclen <= bufflen) /* small enough? */
+ memcpy(out, source + 1, srclen * sizeof(char));
else { /* add '...' before rest of name */
addstr(out, RETS, LL(RETS));
bufflen -= LL(RETS);
- memcpy(out, source + 1 + l - bufflen, bufflen * sizeof(char));
+ memcpy(out, source + 1 + srclen - bufflen, bufflen * sizeof(char));
}
}
else { /* string; format as [string "source"] */
const char *nl = strchr(source, '\n'); /* find first new line (if any) */
addstr(out, PRE, LL(PRE)); /* add prefix */
bufflen -= LL(PRE RETS POS) + 1; /* save space for prefix+suffix+'\0' */
- if (l < bufflen && nl == NULL) { /* small one-line source? */
- addstr(out, source, l); /* keep it */
+ if (srclen < bufflen && nl == NULL) { /* small one-line source? */
+ addstr(out, source, srclen); /* keep it */
}
else {
- if (nl != NULL) l = nl - source; /* stop at first newline */
- if (l > bufflen) l = bufflen;
- addstr(out, source, l);
+ if (nl != NULL) srclen = nl - source; /* stop at first newline */
+ if (srclen > bufflen) srclen = bufflen;
+ addstr(out, source, srclen);
addstr(out, RETS, LL(RETS));
}
memcpy(out, POS, (LL(POS) + 1) * sizeof(char));
diff --git a/lobject.h b/lobject.h
index 53e67932a0..403b6047ba 100644
--- a/lobject.h
+++ b/lobject.h
@@ -747,7 +747,7 @@ LUAI_FUNC void luaO_tostring (lua_State *L, TValue *obj);
LUAI_FUNC const char *luaO_pushvfstring (lua_State *L, const char *fmt,
va_list argp);
LUAI_FUNC const char *luaO_pushfstring (lua_State *L, const char *fmt, ...);
-LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t len);
+LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t srclen);
#endif
diff --git a/lua.h b/lua.h
index 7c4a13bceb..ec31c7819f 100644
--- a/lua.h
+++ b/lua.h
@@ -469,6 +469,7 @@ struct lua_Debug {
const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */
const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */
const char *source; /* (S) */
+ size_t srclen; /* (S) */
int currentline; /* (l) */
int linedefined; /* (S) */
int lastlinedefined; /* (S) */
From 8004798b0374744208b102bb4cbcf12f904ea120 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 4 Apr 2019 16:31:24 -0300
Subject: [PATCH 140/889] Fixed wrong error message in 'return math.seed(0)'
Bug introduced in commit 28d829c8: OP_TAILCALL might raise an
error without saving 'pc'. (This commit also fixes a detail in
'testes/uf8.lua'.)
---
lvm.c | 10 ++++++----
testes/errors.lua | 4 ++++
testes/utf8.lua | 2 +-
3 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/lvm.c b/lvm.c
index d0358143e0..45788a010e 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1556,20 +1556,22 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
L->top = ra + b;
else /* previous instruction set top */
b = cast_int(L->top - ra);
+ savepc(ci); /* some calls here can raise errors */
if (TESTARG_k(i)) {
int nparams1 = GETARG_C(i);
if (nparams1) /* vararg function? */
delta = ci->u.l.nextraargs + nparams1;
- /* close upvalues from current call */
- luaF_close(L, base, LUA_OK);
- updatestack(ci);
+ /* close upvalues from current call; the compiler ensures
+ that there are no to-be-closed variables here */
+ luaF_close(L, base, NOCLOSINGMETH);
}
if (!ttisfunction(s2v(ra))) { /* not a function? */
luaD_tryfuncTM(L, ra); /* try '__call' metamethod */
b++; /* there is now one extra argument */
}
if (!ttisLclosure(s2v(ra))) { /* C function? */
- ProtectNT(luaD_call(L, ra, LUA_MULTRET)); /* call it */
+ luaD_call(L, ra, LUA_MULTRET); /* call it */
+ updatetrap(ci);
updatestack(ci); /* stack may have been relocated */
ci->func -= delta;
luaD_poscall(L, ci, cast_int(L->top - ra));
diff --git a/testes/errors.lua b/testes/errors.lua
index 74975e3143..0b12410eda 100644
--- a/testes/errors.lua
+++ b/testes/errors.lua
@@ -99,6 +99,10 @@ assert(not string.find(doit"a={13}; local bbbb=1; a[bbbb](3)", "'bbbb'"))
checkmessage("a={13}; local bbbb=1; a[bbbb](3)", "number")
checkmessage("a=(1)..{}", "a table value")
+-- tail calls
+checkmessage("local a={}; return a.bbbb(3)", "field 'bbbb'")
+checkmessage("a={}; do local a=1 end; return a:bbbb(3)", "method 'bbbb'")
+
checkmessage("a = #print", "length of a function value")
checkmessage("a = #3", "length of a number value")
diff --git a/testes/utf8.lua b/testes/utf8.lua
index b3b7687fb2..acbb181d24 100644
--- a/testes/utf8.lua
+++ b/testes/utf8.lua
@@ -66,7 +66,7 @@ local function check (s, t, nonstrict)
assert(utf8.len(s, pi, pi1 - 1, nonstrict) == 1)
assert(utf8.len(s, pi, -1, nonstrict) == l - i + 1)
assert(utf8.len(s, pi1, -1, nonstrict) == l - i)
- assert(utf8.len(s, 1, pi, -1, nonstrict) == i)
+ assert(utf8.len(s, 1, pi, nonstrict) == i)
end
local i = 0
From 65d1aa7a779b30bf5b0e7b968b3980b702b08b2c Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 9 Apr 2019 18:40:39 -0300
Subject: [PATCH 141/889] Syntax should not allow numbers touching identifiers
Code like 'a = 1print()' should not be accepted.
---
llex.c | 2 ++
testes/literals.lua | 9 +++++++++
2 files changed, 11 insertions(+)
diff --git a/llex.c b/llex.c
index b0bab37736..bb81cec430 100644
--- a/llex.c
+++ b/llex.c
@@ -228,6 +228,8 @@ static int read_numeral (LexState *ls, SemInfo *seminfo) {
save_and_next(ls);
else break;
}
+ if (lislalnum(ls->current)) /* is numeral touching an alpha num? */
+ save_and_next(ls); /* force an error */
save(ls, '\0');
if (luaO_str2num(luaZ_buffer(ls->buff), &obj) == 0) /* format error? */
lexerror(ls, "malformed number", TK_FLT);
diff --git a/testes/literals.lua b/testes/literals.lua
index fc45d4adf4..27f9377df1 100644
--- a/testes/literals.lua
+++ b/testes/literals.lua
@@ -306,4 +306,13 @@ assert(not load"a = 'non-ending string\n'")
assert(not load"a = '\\345'")
assert(not load"a = [=x]")
+local function malformednum (n, exp)
+ local s, msg = load("return " .. n)
+ assert(not s and string.find(msg, exp))
+end
+
+malformednum("0xe-", "near ")
+malformednum("0xep-p", "malformed number")
+malformednum("1print()", "malformed number")
+
print('OK')
From 0f028b9008097f00aa3953a3425c72e7ae2b4c98 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 9 Apr 2019 18:44:13 -0300
Subject: [PATCH 142/889] Corrected tests around non-portable 'isdst' in dates
The field 'isdst' in date tables may not be present; portable tests
should not assume it is.
---
testes/files.lua | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/testes/files.lua b/testes/files.lua
index 34fcf85134..0a05cf6046 100644
--- a/testes/files.lua
+++ b/testes/files.lua
@@ -777,7 +777,7 @@ local t = os.time()
D = os.date("*t", t)
load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and
D.hour==%H and D.min==%M and D.sec==%S and
- D.wday==%w+1 and D.yday==%j and type(D.isdst) == 'boolean')]], t))()
+ D.wday==%w+1 and D.yday==%j)]], t))()
checkerr("invalid conversion specifier", os.date, "%")
checkerr("invalid conversion specifier", os.date, "%9")
@@ -827,12 +827,16 @@ end
D = os.date("!*t", t)
load(os.date([[!assert(D.year==%Y and D.month==%m and D.day==%d and
D.hour==%H and D.min==%M and D.sec==%S and
- D.wday==%w+1 and D.yday==%j and type(D.isdst) == 'boolean')]], t))()
+ D.wday==%w+1 and D.yday==%j)]], t))()
do
local D = os.date("*t")
local t = os.time(D)
- assert(type(D.isdst) == 'boolean')
+ if not D.isdst then
+ print("no daylight saving information")
+ else
+ assert(type(D.isdst) == 'boolean')
+ end
D.isdst = nil
local t1 = os.time(D)
assert(t == t1) -- if isdst is absent uses correct default
From 979ad95eb114d8d43f928b184f051ac0cfacedf7 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 10 Apr 2019 12:41:56 -0300
Subject: [PATCH 143/889] Thorough revision of the reference manual
---
manual/manual.of | 692 ++++++++++++++++++++++++-----------------------
1 file changed, 351 insertions(+), 341 deletions(-)
diff --git a/manual/manual.of b/manual/manual.of
index fc2550e084..9f1ef631c5 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -15,7 +15,7 @@ Lua is dynamically typed,
runs by interpreting bytecode with a register-based
virtual machine,
and has automatic memory management with
-incremental garbage collection,
+a generational garbage collection,
making it ideal for configuration, scripting,
and rapid prototyping.
@@ -79,7 +79,7 @@ There are eight @x{basic types} in Lua:
@def{thread}, and @def{table}.
The type @emph{nil} has one single value, @nil,
whose main property is to be different from any other value;
-it usually represents the absence of a useful value.
+it often represents the absence of a useful value.
The type @emph{boolean} has two values, @false and @true.
Both @nil and @false make a condition false;
any other value makes it true.
@@ -130,7 +130,8 @@ the programmer can define operations for full userdata values
@see{metatable}.
Userdata values cannot be created or modified in Lua,
only through the @N{C API}.
-This guarantees the integrity of data owned by the host program.
+This guarantees the integrity of data owned by
+the host program and @N{C libraries}.
The type @def{thread} represents independent threads of execution
and it is used to implement coroutines @see{coroutine}.
@@ -146,7 +147,7 @@ used by the @x{IEEE 754} standard to represent
undefined numerical results, such as @T{0/0}.)
Tables can be @emph{heterogeneous};
that is, they can contain values of all types (except @nil).
-Any key with value @nil is not considered part of the table.
+Any key associated to the value @nil is not considered part of the table.
Conversely, any key that is not part of a table has
an associated value @nil.
@@ -176,14 +177,10 @@ In particular, floats with integral values
are equal to their respective integers
(e.g., @T{1.0 == 1}).
To avoid ambiguities,
-any float with integral value used as a key
-is converted to its respective integer.
+any float used as a key that is equal to an integer
+is converted to that integer.
For instance, if you write @T{a[2.0] = true},
-the actual key inserted into the table will be the
-integer @T{2}.
-(On the other hand,
-2 and @St{2} are different Lua values and therefore
-denote different table entries.)
+the actual key inserted into the table will be the integer @T{2}.
Tables, functions, threads, and (full) userdata values are @emph{objects}:
@@ -194,13 +191,13 @@ always manipulate references to such values;
these operations do not imply any kind of copy.
The library function @Lid{type} returns a string describing the type
-of a given value @see{predefined}.
+of a given value @seeF{type}.
}
@sect2{globalenv| @title{Environments and the Global Environment}
-As will be discussed in @refsec{variables} and @refsec{assignment},
+As we will discuss further in @refsec{variables} and @refsec{assignment},
any reference to a free name
(that is, a name not bound to any declaration) @id{var}
is syntactically translated to @T{_ENV.var}.
@@ -222,14 +219,15 @@ Any table used as the value of @id{_ENV} is called an @def{environment}.
Lua keeps a distinguished environment called the @def{global environment}.
This value is kept at a special index in the C registry @see{registry}.
In Lua, the global variable @Lid{_G} is initialized with this same value.
-(@Lid{_G} is never used internally.)
+(@Lid{_G} is never used internally,
+so changing its value will affect only your own code.)
When Lua loads a chunk,
the default value for its @id{_ENV} upvalue
is the global environment @seeF{load}.
Therefore, by default,
free names in Lua code refer to entries in the global environment
-(and, therefore, they are also called @def{global variables}).
+and, therefore, they are also called @def{global variables}.
Moreover, all standard libraries are loaded in the global environment
and some functions there operate on that environment.
You can use @Lid{load} (or @Lid{loadfile})
@@ -284,6 +282,12 @@ Lua breaks it and returns an appropriate message.
It is not called for memory-allocation errors
nor for errors while running finalizers.)
+Lua also offers a system of @emph{warnings} @seeF{warn}.
+Unlike errors, warnings do not interfere
+in any way with program execution.
+They typically only generate a message to the user,
+although this behavior can be adapted from C @see{lua_setwarnf}.
+
}
@sect2{metatable| @title{Metatables and Metamethods}
@@ -310,20 +314,14 @@ metamethods should be function values.
You can query the metatable of any value
using the @Lid{getmetatable} function.
Lua queries metamethods in metatables using a raw access @seeF{rawget}.
-So, to retrieve the metamethod for event @id{ev} in object @id{o},
-Lua does the equivalent to the following code:
-@verbatim{
-rawget(getmetatable(@rep{o}) or {}, "__@rep{ev}")
-}
You can replace the metatable of tables
using the @Lid{setmetatable} function.
-You cannot change the metatable of other types from Lua code
-(except by using the @link{debuglib|debug library});
-you should use the @N{C API} for that.
+You cannot change the metatable of other types from Lua code,
+except by using the @link{debuglib|debug library}.
-Tables and full userdata have individual metatables
-(although multiple tables and userdata can share their metatables).
+Tables and full userdata have individual metatables,
+although multiple tables and userdata can share their metatables.
Values of all other types share one single metatable per type;
that is, there is one single metatable for all numbers,
one for all strings, etc.
@@ -351,8 +349,7 @@ Each operation is identified by its corresponding key.
@item{@idx{__add}|
the addition (@T{+}) operation.
-If any operand for an addition is not a number
-(nor a string coercible to a number),
+If any operand for an addition is not a number,
Lua will try to call a metamethod.
First, Lua will check the first operand (even if it is valid).
If that operand does not define a metamethod for @idx{__add},
@@ -406,7 +403,7 @@ the bitwise AND (@T{&}) operation.
Behavior similar to the addition operation,
except that Lua will try a metamethod
if any operand is neither an integer
-nor a value coercible to an integer @see{coercion}.
+nor a float coercible to an integer @see{coercion}.
}
@item{@idx{__bor}|
@@ -493,8 +490,8 @@ and the result of the call
is the result of the operation.
If it is a table,
the final result is the result of indexing this table with @id{key}.
-(This indexing is regular, not raw,
-and therefore can trigger another metamethod.)
+This indexing is regular, not raw,
+and therefore can trigger another metamethod.
}
@item{@idx{__newindex}|
@@ -510,8 +507,8 @@ If it is a function,
it is called with @id{table}, @id{key}, and @id{value} as arguments.
If it is a table,
Lua does an indexing assignment to this table with the same key and value.
-(This assignment is regular, not raw,
-and therefore can trigger another metamethod.)
+This assignment is regular, not raw,
+and therefore can trigger another metamethod.
Whenever there is a @idx{__newindex} metamethod,
Lua does not perform the primitive assignment.
@@ -530,7 +527,7 @@ the metamethod is called with @id{func} as its first argument,
followed by the arguments of the original call (@id{args}).
All results of the call
are the result of the operation.
-(This is the only metamethod that allows multiple results.)
+This is the only metamethod that allows multiple results.
}
}
@@ -566,17 +563,17 @@ incremental and generational.
The default GC mode with the default parameters
are adequate for most uses.
-Programs that waste a large proportion of its time
+However, programs that waste a large proportion of their time
allocating and freeing memory can benefit from other settings.
Keep in mind that the GC behavior is non-portable
both across platforms and across different Lua releases;
therefore, optimal settings are also non-portable.
You can change the GC mode and parameters by calling
-@Lid{lua_gc} in C
+@Lid{lua_gc} @N{in C}
or @Lid{collectgarbage} in Lua.
You can also use these functions to control
-the collector directly (e.g., stop and restart it).
+the collector directly (e.g., to stop and restart it).
@sect3{@title{Incremental Garbage Collection}
@@ -601,7 +598,7 @@ to double before starting a new cycle.
The default value is 200; the maximum value is 1000.
The garbage-collector step multiplier
-controls the relative speed of the collector relative to
+controls the speed of the collector relative to
memory allocation,
that is,
how many elements it marks or sweeps for each
@@ -623,7 +620,7 @@ bytes between steps and perform equivalent work during the step.
A large value (e.g., 60) makes the collector a stop-the-world
(non-incremental) collector.
The default value is 13,
-which makes for steps of approximately @N{8 Kbytes}.
+which means steps of approximately @N{8 Kbytes}.
}
@@ -669,8 +666,8 @@ These metamethods, called @def{finalizers},
are called when the garbage collector detects that the
corresponding table or userdata is unreachable.
Finalizers allow you to coordinate Lua's garbage collection
-with external resource management
-such as closing files, network or database connections,
+with external resource management such as closing files,
+network or database connections,
or freeing your own memory.
For an object (table or userdata) to be finalized when collected,
@@ -682,7 +679,7 @@ Note that if you set a metatable without a @idx{__gc} field
and later create that field in the metatable,
the object will not be marked for finalization.
-When a marked object becomes garbage,
+When a marked object becomes unreachable,
it is not collected immediately by the garbage collector.
Instead, Lua puts it in a list.
After the collection,
@@ -693,7 +690,7 @@ If it is present,
Lua calls it with the object as its single argument.
At the end of each garbage-collection cycle,
-the finalizers for objects are called in
+the finalizers are called in
the reverse order that the objects were marked for finalization,
among those collected in that cycle;
that is, the first finalizer to be called is the one associated
@@ -723,9 +720,16 @@ If any finalizer marks objects for collection during that phase,
these marks have no effect.
Finalizers cannot yield.
+Except for that, they can do anything,
+such as raise errors, create new objects,
+or even run the garbage collector.
+However, because they can run in unpredictable times,
+it is good practice to restrict each finalizer
+to the minimum necessary to properly release
+its associated resource.
Any error while running a finalizer generates a warning;
-it is not propagated.
+the error is not propagated.
}
@@ -773,8 +777,10 @@ are not subject to garbage collection,
and therefore are not removed from weak tables
(unless their associated values are collected).
Although strings are subject to garbage collection,
-they do not have an explicit construction,
-and therefore are not removed from weak tables.
+they do not have an explicit construction and
+their equality is by value;
+they behave more like values than like objects.
+Therefore, they are not removed from weak tables.
Resurrected objects
(that is, objects being finalized
@@ -828,7 +834,7 @@ In case of normal termination,
@Lid{coroutine.resume} returns @true,
plus any values returned by the coroutine main function.
In case of errors, @Lid{coroutine.resume} returns @false
-plus an error object.
+plus the error object.
A coroutine yields by calling @Lid{coroutine.yield}.
When a coroutine yields,
@@ -922,7 +928,7 @@ at the end of this manual.
Lua is a @x{free-form} language.
It ignores spaces and comments between lexical elements (@x{tokens}),
-except as delimiters between @x{names} and @x{keywords}.
+except as delimiters between two tokens.
In source code,
Lua recognizes as spaces the standard ASCII white-space
characters space, form feed, newline,
@@ -1001,11 +1007,12 @@ it must be expressed using exactly three digits.)
The @x{UTF-8} encoding of a @x{Unicode} character
can be inserted in a literal string with
the escape sequence @T{\u{@rep{XXX}}}
-(note the mandatory enclosing brackets),
+(with mandatory enclosing brackets),
where @rep{XXX} is a sequence of one or more hexadecimal digits
representing the character code point.
This code point can be any value less than @M{2@sp{31}}.
-(Lua uses the original UTF-8 specification here.)
+(Lua uses the original UTF-8 specification here,
+which is not restricted to valid Unicode code points.)
Literal strings can also be defined using a long format
enclosed by @def{long brackets}.
@@ -1028,9 +1035,9 @@ Any kind of end-of-line sequence
(carriage return, newline, carriage return followed by newline,
or newline followed by carriage return)
is converted to a simple newline.
-
When the opening long bracket is immediately followed by a newline,
the newline is not included in the string.
+
As an example, in a system using ASCII
(in which @Char{a} is coded @N{as 97},
newline is coded @N{as 10}, and @Char{1} is coded @N{as 49}),
@@ -1052,7 +1059,7 @@ However, Lua opens files for parsing in text mode,
and the system's file functions may have problems with
some control characters.
So, it is safer to represent
-non-text data as a quoted literal with
+binary data as a quoted literal with
explicit escape sequences for the non-text characters.
A @def{numeric constant} (or @def{numeral})
@@ -1106,7 +1113,7 @@ which is a particular kind of local variable):
@Produc{
@producname{var}@producbody{@bnfNter{Name}}
}
-@bnfNter{Name} denotes identifiers, as defined in @See{lexical}.
+@bnfNter{Name} denotes identifiers @see{lexical}.
Any variable name is assumed to be global unless explicitly declared
as a local @see{localvar}.
@@ -1139,9 +1146,9 @@ the variable @id{_ENV} itself is never global @see{globalenv}.
@sect2{stats| @title{Statements}
Lua supports an almost conventional set of @x{statements},
-similar to those in Pascal or C.
+similar to those in other conventional languages.
This set includes
-assignments, control structures, function calls,
+blocks, assignments, control structures, function calls,
and variable declarations.
@sect3{@title{Blocks}
@@ -1159,7 +1166,7 @@ or write two semicolons in sequence:
@producname{stat}@producbody{@bnfter{;}}
}
-Function calls and assignments
+Both function calls and assignments
can start with an open parenthesis.
This possibility leads to an ambiguity in Lua's grammar.
Consider the following fragment:
@@ -1167,7 +1174,7 @@ Consider the following fragment:
a = b + c
(print or io.write)('done')
}
-The grammar could see it in two ways:
+The grammar could see this fragment in two ways:
@verbatim{
a = b + c(print or io.write)('done')
@@ -1223,7 +1230,7 @@ and then Lua executes the compiled code
with an interpreter for the virtual machine.
Chunks can also be precompiled into binary form;
-see program @idx{luac} and function @Lid{string.dump} for details.
+see the program @idx{luac} and the function @Lid{string.dump} for details.
Programs in source and compiled forms are interchangeable;
Lua automatically detects the file type and acts accordingly @seeF{load}.
@@ -1249,7 +1256,7 @@ the list of variables.@index{adjustment}
If there are more values than needed,
the excess values are thrown away.
If there are fewer values than needed,
-the list is extended with as many @nil's as needed.
+the list is extended with @nil's.
If the list of expressions ends with a function call,
then all values returned by that call enter the list of values,
before the adjustment
@@ -1306,7 +1313,7 @@ The @x{condition expression} of a
control structure can return any value.
Both @false and @nil test false.
All values different from @nil and @false test true.
-(In particular, the number 0 and the empty string also test true).
+In particular, the number 0 and the empty string also test true.
In the @Rw{repeat}@En@Rw{until} loop,
the inner block does not end at the @Rw{until} keyword,
@@ -1347,7 +1354,7 @@ A @Rw{break} ends the innermost enclosing loop.
The @Rw{return} statement is used to return values
from a function or a chunk
-(which is an anonymous function).
+(which is handled as an anonymous function).
@index{return statement}
Functions can return more than one value,
so the syntax for the @Rw{return} statement is
@@ -1357,7 +1364,7 @@ so the syntax for the @Rw{return} statement is
The @Rw{return} statement can only be written
as the last statement of a block.
-If it is really necessary to @Rw{return} in the middle of a block,
+If it is necessary to @Rw{return} in the middle of a block,
then an explicit inner block can be used,
as in the idiom @T{do return end},
because now @Rw{return} is the last statement in its (inner) block.
@@ -1395,11 +1402,12 @@ until that value passes the limit.
A negative step makes a decreasing sequence;
a step equal to zero raises an error.
If the initial value is already greater than the limit
-(or less than, if the step is negative), the body is not executed.
+(or less than, if the step is negative),
+the body is not executed.
If both the initial value and the step are integers,
the loop is done with integers;
-in this case, the range of the control variable is limited
+in this case, the range of the control variable is clipped
by the range of integers.
Otherwise, the loop is done with floats.
(Beware of floating-point accuracy in this case.)
@@ -1426,52 +1434,37 @@ The generic @Rw{for} loop has the following syntax:
}
A @Rw{for} statement like
@verbatim{
-for @rep{var_1}, @Cdots, @rep{var_n} in @rep{explist} do @rep{block} end
+for @rep{var_1}, @Cdots, @rep{var_n} in @rep{explist} do @rep{body} end
}
-is equivalent to the code:
-@verbatim{
-do
- local @rep{f}, @rep{s}, @rep{var}
- local *toclose @rep{tbc} = nil
- @rep{f}, @rep{s}, @rep{var}, @rep{tbc} = @rep{explist}
- while true do
- local @rep{var_1}, @Cdots, @rep{var_n} = @rep{f}(@rep{s}, @rep{var})
- if @rep{var_1} == nil then break end
- @rep{var} = @rep{var_1}
- @rep{block}
- end
-end
-}
-Note the following:
-@itemize{
+works as follows.
-@item{
-@T{@rep{explist}} is evaluated only once.
-Its results are an @emph{iterator} function,
+The names @rep{var_i} declare loop variables local to the loop body.
+The first of these variables is the @emph{control variable}.
+
+The loop starts by evaluating @rep{explist}
+to produce four values:
+an @emph{iterator function},
a @emph{state},
-an initial value for the first @emph{iterator variable},
-and a to-be-closed variable @see{to-be-closed},
+an initial value for the control variable,
+and a @emph{closing value}.
+
+Then, at each iteration,
+Lua calls the iterator function with two arguments:
+the state and the control variable.
+The results from this call are then assigned to the loop variables,
+following the rules of multiple assignments @see{assignment}.
+If the control variable becomes @nil,
+the loop terminates.
+Otherwise, the body is executed and the loop goes
+to the next iteration.
+
+The closing value behaves like a
+to-be-closed variable @see{to-be-closed},
which can be used to release resources when the loop ends.
-}
-
-@item{
-@T{@rep{f}}, @T{@rep{s}}, @T{@rep{var}}, and @T{@rep{tbc}}
-are invisible variables.
-The names are here for explanatory purposes only.
-}
-
-@item{
-You can use @Rw{break} to exit a @Rw{for} loop.
-}
+Otherwise, it does not interfere with the loop.
-@item{
-The loop variables @T{@rep{var_i}} are local to the loop;
-you cannot use their values after the @Rw{for} ends.
-If you need these values,
-then assign them to other variables before breaking or exiting the loop.
-}
-
-}
+You should not change the value of the control variable
+during the loop.
}
@@ -1541,7 +1534,8 @@ the other pending closing methods will still be called.
If a coroutine yields inside a block and is never resumed again,
the variables visible at that block will never go out of scope,
and therefore they will not be closed.
-(You should use finalizers to handle this case.)
+(You should use finalizers to handle this case,
+or else call @Lid{coroutine.kill} to close the variables.)
}
@@ -1659,7 +1653,7 @@ so that it works for non-integer exponents too.
Floor division (@T{//}) is a division
that rounds the quotient towards minus infinity,
-that is, the floor of the division of its operands.
+resulting in the floor of the division of its operands.
Modulo is defined as the remainder of a division
that rounds the quotient towards minus infinity (floor division).
@@ -1725,21 +1719,30 @@ it is in the range of integer representation).
If it does, that representation is the result.
Otherwise, the conversion fails.
-The string library uses metamethods that try to coerce
-strings to numbers in all arithmetic operations.
-Any string operator is converted to an integer or a float,
+Several places in Lua coerce strings to numbers when necessary.
+A string is converted to an integer or a float
following its syntax and the rules of the Lua lexer.
(The string may have also leading and trailing spaces and a sign.)
All conversions from strings to numbers
accept both a dot and the current locale mark
as the radix character.
(The Lua lexer, however, accepts only a dot.)
+If the string is not a valid numeral,
+the conversion fails.
+If necessary, the result of this first step is then converted
+to the required number subtype following the previous rules
+for conversions between floats and integers.
+
+The string library uses metamethods that try to coerce
+strings to numbers in all arithmetic operations.
+If the conversion fails,
+the library calls the metamethod of the other operand
+(if present) or it raises an error.
The conversion from numbers to strings uses a
non-specified human-readable format.
-For complete control over how numbers are converted to strings,
-use the @id{format} function from the string library
-@seeF{string.format}.
+To convert numbers to strings in any specific way,
+use the function @Lid{string.format}.
}
@@ -1758,7 +1761,7 @@ These operators always result in @false or @true.
Equality (@T{==}) first compares the type of its operands.
If the types are different, then the result is @false.
Otherwise, the values of the operands are compared.
-Strings are equal if they have the same content.
+Strings are equal if they have the same byte content.
Numbers are equal if they denote the same mathematical value.
Tables, userdata, and threads
@@ -1787,8 +1790,8 @@ The operator @T{~=} is exactly the negation of equality (@T{==}).
The order operators work as follows.
If both arguments are numbers,
-then they are compared according to their mathematical values
-(regardless of their subtypes).
+then they are compared according to their mathematical values,
+regardless of their subtypes.
Otherwise, if both arguments are strings,
then their values are compared according to the current locale.
Otherwise, Lua tries to call the @idx{__lt} or the @idx{__le}
@@ -1797,8 +1800,8 @@ A comparison @T{a > b} is translated to @T{b < a}
and @T{a >= b} is translated to @T{b <= a}.
Following the @x{IEEE 754} standard,
-@x{NaN} is considered neither less than,
-nor equal to, nor greater than any value (including itself).
+the special value @x{NaN} is considered neither less than,
+nor equal to, nor greater than any value, including itself.
}
@@ -1836,8 +1839,9 @@ false or nil --> nil
@sect3{concat| @title{Concatenation}
The string @x{concatenation} operator in Lua is
denoted by two dots (@Char{..}).
-If both operands are strings or numbers, then they are converted to
-strings according to the rules described in @See{coercion}.
+If both operands are strings or numbers,
+then the numbers are converted to strings
+in a non-specified format @see{coercion}.
Otherwise, the @idx{__concat} metamethod is called @see{metatable}.
}
@@ -1846,9 +1850,9 @@ Otherwise, the @idx{__concat} metamethod is called @see{metatable}.
The length operator is denoted by the unary prefix operator @T{#}.
-The length of a string is its number of bytes
-(that is, the usual meaning of string length when each
-character is one byte).
+The length of a string is its number of bytes.
+(That is the usual meaning of string length when each
+character is one byte.)
The length operator applied on a table
returns a @x{border} in that table.
@@ -1867,8 +1871,10 @@ For instance, the table @T{{10, 20, 30, 40, 50}} is a sequence,
as it has only one border (5).
The table @T{{10, 20, 30, nil, 50}} has two borders (3 and 5),
and therefore it is not a sequence.
+(The @nil at index 4 is called a @emphx{hole}.)
The table @T{{nil, 20, 30, nil, nil, 60, nil}}
-has three borders (0, 3, and 6),
+has three borders (0, 3, and 6) and three holes
+(at indices 1, 4, and 5),
so it is not a sequence, too.
The table @T{{}} is a sequence with border 0.
Note that non-natural keys do not interfere
@@ -1936,10 +1942,10 @@ Each field of the form @T{[exp1] = exp2} adds to the new table an entry
with key @id{exp1} and value @id{exp2}.
A field of the form @T{name = exp} is equivalent to
@T{["name"] = exp}.
-Finally, fields of the form @id{exp} are equivalent to
+Fields of the form @id{exp} are equivalent to
@T{[i] = exp}, where @id{i} are consecutive integers
-starting with 1.
-Fields in the other formats do not affect this counting.
+starting with 1;
+fields in the other formats do not affect this counting.
For example,
@verbatim{
a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 }
@@ -1982,8 +1988,9 @@ first @bnfNter{prefixexp} and @bnfNter{args} are evaluated.
If the value of @bnfNter{prefixexp} has type @emph{function},
then this function is called
with the given arguments.
-Otherwise, the @bnfNter{prefixexp} @idx{__call} metamethod is called,
-having as first argument the value of @bnfNter{prefixexp},
+Otherwise, if present,
+the @bnfNter{prefixexp} @idx{__call} metamethod is called:
+its first argument is the value of @bnfNter{prefixexp},
followed by the original call arguments
@see{metatable}.
@@ -1991,7 +1998,7 @@ The form
@Produc{
@producname{functioncall}@producbody{prefixexp @bnfter{:} @bnfNter{Name} args}
}
-can be used to call @Q{methods}.
+can be used to emulate methods.
A call @T{v:name(@rep{args})}
is syntactic sugar for @T{v.name(v,@rep{args})},
except that @id{v} is evaluated only once.
@@ -2014,7 +2021,7 @@ that is, the argument list is a single literal string.
A call of the form @T{return @rep{functioncall}} not in the
scope of a to-be-closed variable is called a @def{tail call}.
Lua implements @def{proper tail calls}
-(or @emph{proper tail recursion}):
+(or @def{proper tail recursion}):
in a tail call,
the called function reuses the stack entry of the calling function.
Therefore, there is no limit on the number of nested tail calls that
@@ -2086,10 +2093,11 @@ contains references to @id{f}.)
A function definition is an executable expression,
whose value has type @emph{function}.
When Lua precompiles a chunk,
-all its function bodies are precompiled too.
+all its function bodies are precompiled too,
+but they are not created yet.
Then, whenever Lua executes the function definition,
the function is @emph{instantiated} (or @emph{closed}).
-This function instance (or @emphx{closure})
+This function instance, or @emphx{closure},
is the final value of the expression.
Parameters act as local variables that are
@@ -2152,8 +2160,8 @@ that a function may return.
This limit is guaranteed to be greater than 1000.
The @emphx{colon} syntax
-is used for defining @def{methods},
-that is, functions that have an implicit extra parameter @idx{self}.
+is used to emulate @def{methods},
+adding an implicit extra parameter @idx{self} to the function.
Thus, the statement
@verbatim{
function t.a.b.c:f (@rep{params}) @rep{body} end
@@ -2282,8 +2290,8 @@ For convenience,
most query operations in the API do not follow a strict stack discipline.
Instead, they can refer to any element in the stack
by using an @emph{index}:@index{index (API stack)}
-A positive index represents an absolute stack position
-(starting @N{at 1});
+A positive index represents an absolute stack position,
+starting @N{at 1} as the bottom of the stack;
a negative index represents an offset relative to the top of the stack.
More specifically, if the stack has @rep{n} elements,
then @N{index 1} represents the first element
@@ -2353,7 +2361,7 @@ functions in the API work with acceptable indices.
Acceptable indices serve to avoid extra tests
against the stack top when querying the stack.
For instance, a @N{C function} can query its third argument
-without the need to first check whether there is a third argument,
+without the need to check whether there is a third argument,
that is, without the need to check whether 3 is a valid index.
For functions that can be called with acceptable indices,
@@ -2385,7 +2393,8 @@ current function
which is one plus the maximum number of upvalues in a closure),
produces an acceptable but invalid index.
-A @N{C closure} can also change the values of its corresponding upvalues.
+A @N{C closure} can also change the values
+of its corresponding upvalues.
}
@@ -2394,7 +2403,7 @@ A @N{C closure} can also change the values of its corresponding upvalues.
Lua provides a @def{registry},
a predefined table that can be used by any @N{C code} to
store whatever Lua values it needs to store.
-The registry table is always located at pseudo-index
+The registry table is always accessible at pseudo-index
@defid{LUA_REGISTRYINDEX}.
Any @N{C library} can store data into this table,
but it must take care to choose keys
@@ -2410,7 +2419,8 @@ uppercase letters are reserved for Lua.
The integer keys in the registry are used
by the reference mechanism @seeC{luaL_ref}
and by some predefined values.
-Therefore, integer keys must not be used for other purposes.
+Therefore, integer keys in the registry
+must not be used for other purposes.
When you create a new Lua state,
its registry comes with some predefined values.
@@ -2435,15 +2445,16 @@ the @x{global environment}.
Internally, Lua uses the C @id{longjmp} facility to handle errors.
(Lua will use exceptions if you compile it as C++;
search for @id{LUAI_THROW} in the source code for details.)
-When Lua faces any error
-(such as a @x{memory allocation error} or a type error)
+When Lua faces any error,
+such as a @x{memory allocation error} or a type error,
it @emph{raises} an error;
that is, it does a long jump.
A @emphx{protected environment} uses @id{setjmp}
to set a recovery point;
any error jumps to the most recent active recovery point.
-Inside a @N{C function} you can raise an error by calling @Lid{lua_error}.
+Inside a @N{C function} you can raise an error explicitly
+by calling @Lid{lua_error}.
Most functions in the API can raise an error,
for instance due to a @x{memory allocation error}.
@@ -2503,9 +2514,9 @@ the @emph{original function}.
This original function then calls one of those three functions in the C API,
which we will call the @emph{callee function},
that then yields the current thread.
-(This can happen when the callee function is @Lid{lua_yieldk},
+This can happen when the callee function is @Lid{lua_yieldk},
or when the callee function is either @Lid{lua_callk} or @Lid{lua_pcallk}
-and the function called by them yields.)
+and the function called by them yields.
Suppose the running thread yields while executing the callee function.
After the thread resumes,
@@ -2566,11 +2577,11 @@ you can do the equivalent work directly inside the original function.)
Besides the Lua state,
the continuation function has two other parameters:
-the final status of the call plus the context value (@id{ctx}) that
+the final status of the call and the context value (@id{ctx}) that
was passed originally to @Lid{lua_pcallk}.
-(Lua does not use this context value;
+Lua does not use this context value;
it only passes this value from the original function to the
-continuation function.)
+continuation function.
For @Lid{lua_pcallk},
the status is the same value that would be returned by @Lid{lua_pcallk},
except that it is @Lid{LUA_YIELD} when being executed after a yield
@@ -2617,8 +2628,8 @@ A field in the form @T{x|y} means the function can push (or pop)
depending on the situation;
an interrogation mark @Char{?} means that
we cannot know how many elements the function pops/pushes
-by looking only at its arguments
-(e.g., they may depend on what is on the stack).
+by looking only at its arguments.
+(For instance, they may depend on what is on the stack.)
The third field, @T{x},
tells whether the function may raise errors:
@Char{-} means the function never raises any error;
@@ -2673,11 +2684,11 @@ Lua assumes the following behavior from the allocator function:
When @id{nsize} is zero,
the allocator must behave like @id{free}
-and return @id{NULL}.
+and then return @id{NULL}.
When @id{nsize} is not zero,
the allocator must behave like @id{realloc}.
-The allocator returns @id{NULL}
+In particular, the allocator returns @id{NULL}
if and only if it cannot fulfill the request.
Here is a simple implementation for the @x{allocator function}.
@@ -2752,9 +2763,9 @@ in direct order;
that is, the first argument is pushed first.
Finally you call @Lid{lua_call};
@id{nargs} is the number of arguments that you pushed onto the stack.
-All arguments and the function value are popped from the stack
-when the function is called.
-The function results are pushed onto the stack when the function returns.
+When the function returns,
+all arguments and the function value are popped
+and the function results are pushed onto the stack.
The number of results is adjusted to @id{nresults},
unless @id{nresults} is @defid{LUA_MULTRET}.
In this case, all results from the function are pushed;
@@ -2819,7 +2830,7 @@ The first argument (if any) is at index 1
and its last argument is at index @T{lua_gettop(L)}.
To return values to Lua, a @N{C function} just pushes them onto the stack,
in direct order (the first result is pushed first),
-and returns the number of results.
+and returns in C the number of results.
Any other value in the stack below the results will be properly
discarded by Lua.
Like a Lua function, a @N{C function} called by Lua can also return
@@ -2853,8 +2864,8 @@ static int foo (lua_State *L) {
@APIEntry{int lua_checkstack (lua_State *L, int n);|
@apii{0,0,-}
-Ensures that the stack has space for at least @id{n} extra slots
-(that is, that you can safely push up to @id{n} values into it).
+Ensures that the stack has space for at least @id{n} extra slots,
+that is, that you can safely push up to @id{n} values into it.
It returns false if it cannot fulfill the request,
either because it would cause the stack
to be greater than a fixed maximum size
@@ -2872,6 +2883,7 @@ it is left unchanged.
Destroys all objects in the given Lua state
(calling the corresponding garbage-collection metamethods, if any)
and frees all dynamic memory used by this state.
+
On several platforms, you may not need to call this function,
because all resources are naturally released when the host program ends.
On the other hand, long-running programs that create multiple states,
@@ -2934,7 +2946,7 @@ will have as a sequence;
parameter @id{nrec} is a hint for how many other elements
the table will have.
Lua may use these hints to preallocate memory for the new table.
-This preallocation is useful for performance when you know in advance
+This preallocation may help performance when you know in advance
how many elements the table will have.
Otherwise you can use the function @Lid{lua_newtable}.
@@ -3369,11 +3381,11 @@ Other upvalues are initialized with @nil.
@APIEntry{lua_State *lua_newstate (lua_Alloc f, void *ud);|
@apii{0,0,-}
-Creates a new thread running in a new, independent state.
-Returns @id{NULL} if it cannot create the thread or the state
+Creates a new independent state and returns its main thread.
+Returns @id{NULL} if it cannot create the state
(due to lack of memory).
The argument @id{f} is the @x{allocator function};
-Lua does all memory allocation for this state
+Lua will do all memory allocation for this state
through this function @seeF{lua_Alloc}.
The second argument, @id{ud}, is an opaque pointer that Lua
passes to the allocator in every call.
@@ -3407,7 +3419,7 @@ like any Lua object.
@apii{0,1,m}
This function creates and pushes on the stack a new full userdata,
-with @id{nuvalue} associated Lua values (called @id{user values})
+with @id{nuvalue} associated Lua values, called @id{user values},
plus an associated block of raw memory with @id{size} bytes.
(The user values can be set and read with the functions
@Lid{lua_setiuservalue} and @Lid{lua_getiuservalue}.)
@@ -3420,12 +3432,12 @@ The function returns the address of the block of memory.
@apii{1,2|0,v}
Pops a key from the stack,
-and pushes a key@En{}value pair from the table at the given index
-(the @Q{next} pair after the given key).
+and pushes a key@En{}value pair from the table at the given index,
+the @Q{next} pair after the given key.
If there are no more elements in the table,
-then @Lid{lua_next} returns 0 (and pushes nothing).
+then @Lid{lua_next} returns 0 and pushes nothing.
-A typical traversal looks like this:
+A typical table traversal looks like this:
@verbatim{
/* table is in the stack at index 't' */
lua_pushnil(L); /* first key */
@@ -3440,7 +3452,7 @@ while (lua_next(L, t) != 0) {
}
While traversing a table,
-do not call @Lid{lua_tolstring} directly on a key,
+avoid calling @Lid{lua_tolstring} directly on a key,
unless you know that the key is actually a string.
Recall that @Lid{lua_tolstring} may change
the value at the given index;
@@ -3465,15 +3477,14 @@ but that can be changed to a single float or a long double.
@APIEntry{int lua_numbertointeger (lua_Number n, lua_Integer *p);|
-Converts a Lua float to a Lua integer.
-This macro assumes that @id{n} has an integral value.
+Tries to convert a Lua float to a Lua integer;
+the float @id{n} must have an integral value.
If that value is within the range of Lua integers,
it is converted to an integer and assigned to @T{*p}.
The macro results in a boolean indicating whether the
conversion was successful.
(Note that this range test can be tricky to do
-correctly without this macro,
-due to roundings.)
+correctly without this macro, due to rounding.)
This macro may evaluate its arguments more than once.
@@ -3503,7 +3514,7 @@ Otherwise, @id{msgh} is the stack index of a
@emph{message handler}.
(This index cannot be a pseudo-index.)
In case of runtime errors,
-this function will be called with the error object
+this handler will be called with the error object
and its return value will be the object
returned on the stack by @Lid{lua_pcall}.
@@ -3580,11 +3591,12 @@ and return its results @seeC{lua_CFunction}.
When a @N{C function} is created,
it is possible to associate some values with it,
-thus creating a @x{@N{C closure}} @see{c-closure};
-these values are then accessible to the function whenever it is called.
-To associate values with a @N{C function},
-first these values must be pushed onto the stack
-(when there are multiple values, the first value is pushed first).
+the so called upvalues;
+these upvalues are then accessible to the function whenever it is called.
+This association is called a @x{@N{C closure}} @see{c-closure}.
+To create a @N{C closure},
+first the initial values for its upvalues must be pushed onto the stack.
+(When there are multiple upvalues, the first value is pushed first.)
Then @Lid{lua_pushcclosure}
is called to create and push the @N{C function} onto the stack,
with the argument @id{n} telling how many values will be
@@ -3604,6 +3616,7 @@ In that case, it never raises a memory error.
@apii{0,1,-}
Pushes a @N{C function} onto the stack.
+This function is equivalent to @Lid{lua_pushcclosure} with no upvalues.
}
@@ -3626,7 +3639,7 @@ The conversion specifiers can only be
@Char{%s} (inserts a zero-terminated string, with no size restrictions),
@Char{%f} (inserts a @Lid{lua_Number}),
@Char{%I} (inserts a @Lid{lua_Integer}),
-@Char{%p} (inserts a pointer as a hexadecimal numeral),
+@Char{%p} (inserts a pointer),
@Char{%d} (inserts an @T{int}),
@Char{%c} (inserts an @T{int} as a one-byte character), and
@Char{%U} (inserts a @T{long int} as a @x{UTF-8} byte sequence).
@@ -3670,6 +3683,7 @@ light userdata with the same @N{C address}.
This macro is equivalent to @Lid{lua_pushstring},
but should be used only when @id{s} is a literal string.
+(Lua may optimize this case.)
}
@@ -3678,7 +3692,7 @@ but should be used only when @id{s} is a literal string.
Pushes the string pointed to by @id{s} with size @id{len}
onto the stack.
-Lua makes (or reuses) an internal copy of the given string,
+Lua will make or reuse an internal copy of the given string,
so the memory at @id{s} can be freed or reused immediately after
the function returns.
The string can contain any binary data,
@@ -3707,7 +3721,7 @@ Pushes a float with value @id{n} onto the stack.
Pushes the zero-terminated string pointed to by @id{s}
onto the stack.
-Lua makes (or reuses) an internal copy of the given string,
+Lua will make or reuse an internal copy of the given string,
so the memory at @id{s} can be freed or reused immediately after
the function returns.
@@ -3749,7 +3763,7 @@ instead of a variable number of arguments.
Returns 1 if the two values in indices @id{index1} and
@id{index2} are primitively equal
-(that is, without calling the @idx{__eq} metamethod).
+(that is, equal without calling the @idx{__eq} metamethod).
Otherwise @N{returns 0}.
Also @N{returns 0} if any of the indices are not valid.
@@ -3796,8 +3810,8 @@ for strings, this is the string length;
for tables, this is the result of the length operator (@Char{#})
with no metamethods;
for userdata, this is the size of the block of memory allocated
-for the userdata;
-for other values, it @N{is 0}.
+for the userdata.
+For other values, this call @N{returns 0}.
}
@@ -3842,8 +3856,8 @@ typedef const char * (*lua_Reader) (lua_State *L,
size_t *size);|
The reader function used by @Lid{lua_load}.
-Every time it needs another piece of the chunk,
-@Lid{lua_load} calls the reader,
+Every time @Lid{lua_load} needs another piece of the chunk,
+it calls the reader,
passing along its @id{data} parameter.
The reader must return a pointer to a block of memory
with a new piece of the chunk
@@ -3912,9 +3926,9 @@ then you call @Lid{lua_resume},
with @id{nargs} being the number of arguments.
This call returns when the coroutine suspends or finishes its execution.
When it returns,
-@id{nresults} is updated and
+@id{*nresults} is updated and
the top of the stack contains
-the @id{nresults} values passed to @Lid{lua_yield}
+the @id{*nresults} values passed to @Lid{lua_yield}
or returned by the body function.
@Lid{lua_resume} returns
@Lid{LUA_YIELD} if the coroutine yields,
@@ -3924,10 +3938,8 @@ or an error code in case of errors @seeC{lua_pcall}.
In case of errors,
the error object is on the top of the stack.
-To resume a coroutine,
-you remove all results from the last @Lid{lua_yield},
-put on its stack only the values to
-be passed as results from @id{yield},
+To resume a coroutine, you clear its stack,
+push only the values to be passed as results from @id{yield},
and then call @Lid{lua_resume}.
The parameter @id{from} represents the coroutine that is resuming @id{L}.
@@ -4066,12 +4078,12 @@ which creates a Lua state from scratch.
Returns the status of the thread @id{L}.
-The status can be 0 (@Lid{LUA_OK}) for a normal thread,
+The status can be @Lid{LUA_OK} for a normal thread,
an error code if the thread finished the execution
of a @Lid{lua_resume} with an error,
or @defid{LUA_YIELD} if the thread is suspended.
-You can only call functions in threads with status @Lid{LUA_OK}.
+You can call functions only in threads with status @Lid{LUA_OK}.
You can resume threads with status @Lid{LUA_OK}
(to start a new coroutine) or @Lid{LUA_YIELD}
(to resume a coroutine).
@@ -4127,7 +4139,7 @@ Like a to-be-closed variable in Lua,
the value at that index in the stack will be closed
when it goes out of scope.
Here, in the context of a C function,
-to go out of scope means that the running function returns (to Lua),
+to go out of scope means that the running function returns to Lua,
there is an error,
or the index is removed from the stack through
@Lid{lua_settop} or @Lid{lua_pop}.
@@ -4250,7 +4262,7 @@ otherwise, the function returns @id{NULL}.
If the value at the given index is a full userdata,
returns its memory-block address.
If the value is a light userdata,
-returns its pointer.
+returns its value (a pointer).
Otherwise, returns @id{NULL}.
}
@@ -4259,7 +4271,7 @@ Otherwise, returns @id{NULL}.
@apii{0,0,-}
Returns the type of the value in the given valid index,
-or @id{LUA_TNONE} for a non-valid (but acceptable) index.
+or @id{LUA_TNONE} for a non-valid but acceptable index.
The types returned by @Lid{lua_type} are coded by the following constants
defined in @id{lua.h}:
@defid{LUA_TNIL},
@@ -4335,8 +4347,8 @@ typedef int (*lua_Writer) (lua_State *L,
void* ud);|
The type of the writer function used by @Lid{lua_dump}.
-Every time it produces another piece of chunk,
-@Lid{lua_dump} calls the writer,
+Every time @Lid{lua_dump} produces another piece of chunk,
+it calls the writer,
passing along the buffer to be written (@id{p}),
its size (@id{sz}),
and the @id{ud} parameter supplied to @Lid{lua_dump}.
@@ -4414,7 +4426,7 @@ of the (Lua) function that triggered the hook.
This function can raise an error if it is called from a thread
with a pending C call with no continuation function
-(what is called a @emphx{C-call boundary},
+(what is called a @emphx{C-call boundary}),
or it is called from a thread that is not running inside a resume
(typically the main thread).
@@ -4439,6 +4451,7 @@ typedef struct lua_Debug {
const char *namewhat; /* (n) */
const char *what; /* (S) */
const char *source; /* (S) */
+ size_t srclen; /* (S) */
int currentline; /* (l) */
int linedefined; /* (S) */
int lastlinedefined; /* (S) */
@@ -4459,13 +4472,13 @@ information about a function or an activation record.
@Lid{lua_getstack} fills only the private part
of this structure, for later use.
To fill the other fields of @Lid{lua_Debug} with useful information,
-call @Lid{lua_getinfo}.
+you must call @Lid{lua_getinfo}.
The fields of @Lid{lua_Debug} have the following meaning:
@description{
@item{@id{source}|
-the name of the chunk that created the function.
+the source of the chunk that created the function.
If @T{source} starts with a @Char{@At},
it means that the function was defined in a file where
the file name follows the @Char{@At}.
@@ -4476,6 +4489,10 @@ the function was defined in a string where
@T{source} is that string.
}
+@item{@id{srclen}|
+The length of the string @id{source}.
+}
+
@item{@id{short_src}|
a @Q{printable} version of @T{source}, to be used in error messages.
}
@@ -4694,9 +4711,9 @@ of the function executing at a given level.
@N{Level 0} is the current running function,
whereas level @M{n+1} is the function that has called level @M{n}
(except for tail calls, which do not count on the stack).
-When there are no errors, @Lid{lua_getstack} returns 1;
-when called with a level greater than the stack depth,
-it returns 0.
+When called with a level greater than the stack depth,
+@Lid{lua_getstack} returns 0;
+otherwise it returns 1.
}
@@ -4716,10 +4733,6 @@ as a name for all upvalues.
upvalues are the external local variables that the function uses,
and that are consequently included in its closure.)
-Upvalues have no particular order,
-as they are active through the whole function.
-They are numbered in an arbitrary order.
-
}
@APIEntry{typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);|
@@ -4780,24 +4793,22 @@ before the function gets its arguments.
@item{The return hook| is called when the interpreter returns from a function.
The hook is called just before Lua leaves the function.
-There is no standard way to access the values
-to be returned by the function.
}
@item{The line hook| is called when the interpreter is about to
start the execution of a new line of code,
or when it jumps back in the code (even to the same line).
-(This event only happens while Lua is executing a Lua function.)
+This event only happens while Lua is executing a Lua function.
}
@item{The count hook| is called after the interpreter executes every
@T{count} instructions.
-(This event only happens while Lua is executing a Lua function.)
+This event only happens while Lua is executing a Lua function.
}
}
-A hook is disabled by setting @id{mask} to zero.
+Hooks are disabled by setting @id{mask} to zero.
}
@@ -4813,7 +4824,7 @@ Returns @id{NULL} (and pops nothing)
when the index is greater than
the number of active local variables.
-Parameters @id{ar} and @id{n} are as in function @Lid{lua_getlocal}.
+Parameters @id{ar} and @id{n} are as in the function @Lid{lua_getlocal}.
}
@@ -4828,7 +4839,8 @@ It also pops the value from the stack.
Returns @id{NULL} (and pops nothing)
when the index @id{n} is greater than the number of upvalues.
-Parameters @id{funcindex} and @id{n} are as in function @Lid{lua_getupvalue}.
+Parameters @id{funcindex} and @id{n} are as in
+the function @Lid{lua_getupvalue}.
}
@@ -4844,7 +4856,8 @@ Lua closures that share an upvalue
(that is, that access a same external local variable)
will return identical ids for those upvalue indices.
-Parameters @id{funcindex} and @id{n} are as in function @Lid{lua_getupvalue},
+Parameters @id{funcindex} and @id{n} are as in
+the function @Lid{lua_getupvalue},
but @id{n} cannot be greater than the number of upvalues.
}
@@ -5086,7 +5099,7 @@ this function calls this field passing the object as its only argument.
In this case this function returns true and pushes onto the
stack the value returned by the call.
If there is no metatable or no metamethod,
-this function returns false (without pushing any value on the stack).
+this function returns false without pushing any value on the stack.
}
@@ -5103,7 +5116,7 @@ of any type (including @nil) at position @id{arg}.
Checks whether the function argument @id{arg} is an integer
(or can be converted to an integer)
-and returns this integer cast to a @Lid{lua_Integer}.
+and returns this integer.
}
@@ -5112,7 +5125,7 @@ and returns this integer cast to a @Lid{lua_Integer}.
Checks whether the function argument @id{arg} is a string
and returns this string;
-if @id{l} is not @id{NULL} fills @T{*l}
+if @id{l} is not @id{NULL} fills its referent
with the string's length.
This function uses @Lid{lua_tolstring} to get its result,
@@ -5124,7 +5137,7 @@ so all conversions and caveats of that function apply here.
@apii{0,0,v}
Checks whether the function argument @id{arg} is a number
-and returns this number.
+and returns this number converted to a @id{lua_Number}.
}
@@ -5300,8 +5313,8 @@ const char *luaL_gsub (lua_State *L,
const char *r);|
@apii{0,1,m}
-Creates a copy of string @id{s} by replacing
-any occurrence of the string @id{p}
+Creates a copy of string @id{s},
+replacing any occurrence of the string @id{p}
with the string @id{r}.
Pushes the resulting string on the stack and returns it.
@@ -5314,7 +5327,7 @@ Returns the @Q{length} of the value at the given index
as a number;
it is equivalent to the @Char{#} operator in Lua @see{len-op}.
Raises an error if the result of the operation is not an integer.
-(This case only can happen through metamethods.)
+(This case can only happen through metamethods.)
}
@@ -5345,7 +5358,7 @@ buffer pointed to by @id{buff} with size @id{sz}.
This function returns the same results as @Lid{lua_load}.
@id{name} is the chunk name,
used for debug information and error messages.
-The string @id{mode} works as in function @Lid{lua_load}.
+The string @id{mode} works as in the function @Lid{lua_load}.
}
@@ -5368,7 +5381,7 @@ If @id{filename} is @id{NULL},
then it loads from the standard input.
The first line in the file is ignored if it starts with a @T{#}.
-The string @id{mode} works as in function @Lid{lua_load}.
+The string @id{mode} works as in the function @Lid{lua_load}.
This function returns the same results as @Lid{lua_load},
but it has an extra error code @defid{LUA_ERRFILE}
@@ -5399,7 +5412,7 @@ it does not run it.
@apii{0,1,m}
Creates a new table and registers there
-the functions in list @id{l}.
+the functions in the list @id{l}.
It is implemented as the following macro:
@verbatim{
@@ -5437,7 +5450,8 @@ adds to the registry the pair @T{[tname] = new table},
and returns 1.
(The entry @idx{__name} is used by some error-reporting functions.)
-In both cases pushes onto the stack the final value associated
+In both cases,
+the function pushes onto the stack the final value associated
with @id{tname} in the registry.
}
@@ -5447,10 +5461,9 @@ with @id{tname} in the registry.
Creates a new Lua state.
It calls @Lid{lua_newstate} with an
-allocator based on the @N{standard C} @id{realloc} function
-and then sets a panic function @see{C-error} that prints
-an error message to the standard error output in case of fatal
-errors.
+allocator based on the @N{standard C} allocation functions
+and then sets a warning function and a panic function @see{C-error}
+that print messages to the standard error output.
Returns the new state,
or @id{NULL} if there is a @x{memory allocation error}.
@@ -5488,7 +5501,7 @@ lua_Integer luaL_optinteger (lua_State *L,
@apii{0,0,v}
If the function argument @id{arg} is an integer
-(or convertible to an integer),
+(or it is convertible to an integer),
returns this integer.
If this argument is absent or is @nil,
returns @id{d}.
@@ -5510,7 +5523,7 @@ returns @id{d}.
Otherwise, raises an error.
If @id{l} is not @id{NULL},
-fills the position @T{*l} with the result's length.
+fills its referent with the result's length.
If the result is @id{NULL}
(only possible when returning @id{d} and @T{d == NULL}),
its length is considered zero.
@@ -5524,7 +5537,7 @@ so all conversions and caveats of that function apply here.
@apii{0,0,v}
If the function argument @id{arg} is a number,
-returns this number.
+returns this number as a @id{lua_Number}.
If this argument is absent or is @nil,
returns @id{d}.
Otherwise, raises an error.
@@ -5588,11 +5601,11 @@ in the table at index @id{t},
for the object on the top of the stack (and pops the object).
A reference is a unique integer key.
-As long as you do not manually add integer keys into table @id{t},
+As long as you do not manually add integer keys into the table @id{t},
@Lid{luaL_ref} ensures the uniqueness of the key it returns.
-You can retrieve an object referred by reference @id{r}
+You can retrieve an object referred by the reference @id{r}
by calling @T{lua_rawgeti(L, t, r)}.
-Function @Lid{luaL_unref} frees a reference and its associated object.
+The function @Lid{luaL_unref} frees a reference.
If the object on the top of the stack is @nil,
@Lid{luaL_ref} returns the constant @defid{LUA_REFNIL}.
@@ -5623,12 +5636,12 @@ void luaL_requiref (lua_State *L, const char *modname,
@apii{0,1,e}
If @T{package.loaded[modname]} is not true,
-calls function @id{openf} with string @id{modname} as an argument
+calls the function @id{openf} with the string @id{modname} as an argument
and sets the call result to @T{package.loaded[modname]},
as if that function has been called through @Lid{require}.
If @id{glb} is true,
-also stores the module into global @id{modname}.
+also stores the module into the global @id{modname}.
Leaves a copy of the module on the stack.
@@ -5666,8 +5679,8 @@ typedef struct luaL_Stream {
} luaL_Stream;
|
-The standard representation for @x{file handles},
-which is used by the standard I/O library.
+The standard representation for @x{file handles}
+used by the standard I/O library.
A file handle is implemented as a full userdata,
with a metatable called @id{LUA_FILEHANDLE}
@@ -5677,14 +5690,14 @@ The metatable is created by the I/O library
This userdata must start with the structure @id{luaL_Stream};
it can contain other data after this initial structure.
-Field @id{f} points to the corresponding C stream
+The field @id{f} points to the corresponding C stream
(or it can be @id{NULL} to indicate an incompletely created handle).
-Field @id{closef} points to a Lua function
+The field @id{closef} points to a Lua function
that will be called to close the stream
when the handle is closed or collected;
this function receives the file handle as its sole argument and
-must return either @true (in case of success)
-or @nil plus an error message (in case of error).
+must return either @true, in case of success,
+or @nil plus an error message, in case of error.
Once Lua calls this field,
it changes the field value to @id{NULL}
to signal that the handle is closed.
@@ -5723,7 +5736,7 @@ void luaL_traceback (lua_State *L, lua_State *L1, const char *msg,
@apii{0,1,m}
Creates and pushes a traceback of the stack @id{L1}.
-If @id{msg} is not @id{NULL} it is appended
+If @id{msg} is not @id{NULL}, it is appended
at the beginning of the traceback.
The @id{level} parameter tells at which level
to start the traceback.
@@ -5735,7 +5748,7 @@ to start the traceback.
const char *tname);|
@apii{0,0,v}
-Raises a type error for argument @id{arg}
+Raises a type error for the argument @id{arg}
of the @N{C function} that called it,
using a standard message;
@id{tname} is a @Q{name} for the expected type.
@@ -5753,7 +5766,7 @@ Returns the name of the type of the value at the given index.
@APIEntry{void luaL_unref (lua_State *L, int t, int ref);|
@apii{0,0,-}
-Releases reference @id{ref} from the table at index @id{t}
+Releases the reference @id{ref} from the table at index @id{t}
@seeC{luaL_ref}.
The entry is removed from the table,
so that the referred object can be collected.
@@ -5787,15 +5800,15 @@ This function is used to build a prefix for error messages.
@C{-------------------------------------------------------------------------}
-@sect1{libraries| @title{Standard Libraries}
+@sect1{libraries| @title{The Standard Libraries}
The standard Lua libraries provide useful functions
-that are implemented directly through the @N{C API}.
+that are implemented @N{in C} through the @N{C API}.
Some of these functions provide essential services to the language
(e.g., @Lid{type} and @Lid{getmetatable});
-others provide access to @Q{outside} services (e.g., I/O);
+others provide access to outside services (e.g., I/O);
and others could be implemented in Lua itself,
-but are quite useful or have critical performance requirements that
+but that for different reasons
deserve an implementation in C (e.g., @Lid{table.sort}).
All libraries are implemented through the official @N{C API}
@@ -5844,7 +5857,7 @@ the host program can open them individually by using
@defid{luaopen_package} (for the package library),
@defid{luaopen_coroutine} (for the coroutine library),
@defid{luaopen_string} (for the string library),
-@defid{luaopen_utf8} (for the UTF8 library),
+@defid{luaopen_utf8} (for the UTF-8 library),
@defid{luaopen_table} (for the table library),
@defid{luaopen_math} (for the mathematical library),
@defid{luaopen_io} (for the I/O library),
@@ -5896,8 +5909,7 @@ restarts automatic execution of the garbage collector.
returns the total memory in use by Lua in Kbytes.
The value has a fractional part,
so that it multiplied by 1024
-gives the exact number of bytes in use by Lua
-(except for overflows).
+gives the exact number of bytes in use by Lua.
}
@item{@St{step}|
@@ -5938,6 +5950,8 @@ returns a boolean that tells whether the collector is running
}
}
+See @See{GC} for more details about garbage collection
+and some of these options.
}
@@ -5947,14 +5961,15 @@ When called without arguments,
@id{dofile} executes the contents of the standard input (@id{stdin}).
Returns all values returned by the chunk.
In case of errors, @id{dofile} propagates the error
-to its caller (that is, @id{dofile} does not run in protected mode).
+to its caller.
+(That is, @id{dofile} does not run in protected mode.)
}
@LibEntry{error (message [, level])|
Terminates the last protected function called
and returns @id{message} as the error object.
-Function @id{error} never returns.
+This function never returns.
Usually, @id{error} adds some information about the error position
at the beginning of the message, if the message is a string.
@@ -6066,7 +6081,7 @@ if no file name is given.
Allows a program to traverse all fields of a table.
Its first argument is a table and its second argument
is an index in this table.
-@id{next} returns the next index of the table
+A call to @id{next} returns the next index of the table
and its associated value.
When called with @nil as its second argument,
@id{next} returns an initial index
@@ -6112,7 +6127,7 @@ the table during its traversal.
@LibEntry{pcall (f [, arg1, @Cdots])|
-Calls function @id{f} with
+Calls the function @id{f} with
the given arguments in @def{protected mode}.
This means that any error @N{inside @T{f}} is not propagated;
instead, @id{pcall} catches the error
@@ -6121,7 +6136,7 @@ Its first result is the status code (a boolean),
which is true if the call succeeds without errors.
In such case, @id{pcall} also returns all results from the call,
after this first result.
-In case of any error, @id{pcall} returns @false plus the error message.
+In case of any error, @id{pcall} returns @false plus the error object.
}
@@ -6184,8 +6199,6 @@ and @id{select} returns the total number of extra arguments it received.
@LibEntry{setmetatable (table, metatable)|
Sets the metatable for the given table.
-(To change the metatable of other types from Lua code,
-you must use the @link{debuglib|debug library}.)
If @id{metatable} is @nil,
removes the metatable of the given table.
If the original metatable has a @idx{__metatable} field,
@@ -6193,6 +6206,9 @@ raises an error.
This function returns @id{table}.
+To change the metatable of other types from Lua code,
+you must use the @link{debuglib|debug library}.
+
}
@LibEntry{tonumber (e [, base])|
@@ -6206,7 +6222,7 @@ otherwise, it returns @nil.
The conversion of strings can result in integers or floats,
according to the lexical conventions of Lua @see{lexical}.
-(The string may have leading and trailing spaces and a sign.)
+The string may have leading and trailing spaces and a sign.
When called with @id{base},
then @id{e} must be a string to be interpreted as
@@ -6298,7 +6314,7 @@ it is not inside a non-yieldable @N{C function}.
}
-@LibEntry{coroutine.kill(co)|
+@LibEntry{coroutine.kill (co)|
Kills coroutine @id{co},
closing all its pending to-be-closed variables
@@ -6339,7 +6355,7 @@ true when the running coroutine is the main one.
@LibEntry{coroutine.status (co)|
-Returns the status of coroutine @id{co}, as a string:
+Returns the status of the coroutine @id{co}, as a string:
@T{"running"},
if the coroutine is running (that is, it called @id{status});
@T{"suspended"}, if the coroutine is suspended in a call to @id{yield},
@@ -6353,7 +6369,7 @@ or if it has stopped with an error.
@LibEntry{coroutine.wrap (f)|
-Creates a new coroutine, with body @id{f}.
+Creates a new coroutine, with body @id{f};
@id{f} must be a function.
Returns a function that resumes the coroutine each time it is called.
Any arguments passed to the function behave as the
@@ -6379,7 +6395,7 @@ The package library provides basic
facilities for loading modules in Lua.
It exports one function directly in the global environment:
@Lid{require}.
-Everything else is exported in a table @defid{package}.
+Everything else is exported in the table @defid{package}.
@LibEntry{require (modname)|
@@ -6729,7 +6745,8 @@ after the two indices.
@LibEntry{string.format (formatstring, @Cdots)|
Returns a formatted version of its variable number of arguments
-following the description given in its first argument (which must be a string).
+following the description given in its first argument,
+which must be a string.
The format string follows the same rules as the @ANSI{sprintf}.
The only differences are that the conversion specifiers and modifiers
@T{*}, @id{h}, @id{L}, @id{l}, and @id{n} are not supported
@@ -6830,9 +6847,9 @@ If @id{repl} is a string, then its value is used for replacement.
The @N{character @T{%}} works as an escape character:
any sequence in @id{repl} of the form @T{%@rep{d}},
with @rep{d} between 1 and 9,
-stands for the value of the @rep{d}-th captured substring.
-The sequence @T{%0} stands for the whole match.
-The sequence @T{%%} stands for a @N{single @T{%}}.
+stands for the value of the @rep{d}-th captured substring;
+the sequence @T{%0} stands for the whole match;
+the sequence @T{%%} stands for a @N{single @T{%}}.
If @id{repl} is a table, then the table is queried for every match,
using the first capture as the key.
@@ -6899,7 +6916,7 @@ The definition of what an uppercase letter is depends on the current locale.
@LibEntry{string.match (s, pattern [, init])|
Looks for the first @emph{match} of
-@id{pattern} @see{pm} in the string @id{s}.
+the @id{pattern} @see{pm} in the string @id{s}.
If it finds one, then @id{match} returns
the captures from the pattern;
otherwise it returns @nil.
@@ -6914,7 +6931,7 @@ its default value @N{is 1} and can be negative.
@LibEntry{string.pack (fmt, v1, v2, @Cdots)|
Returns a binary string containing the values @id{v1}, @id{v2}, etc.
-packed (that is, serialized in binary form)
+serialized in binary form (packed)
according to the format string @id{fmt} @see{pack}.
}
@@ -7042,8 +7059,7 @@ represents the character @rep{x}.
This is the standard way to escape the magic characters.
Any non-alphanumeric character
(including all punctuation characters, even the non-magical)
-can be preceded by a @Char{%}
-when used to represent itself in a pattern.
+can be preceded by a @Char{%} to represent itself in a pattern.
}
@item{@T{[@rep{set}]}|
@@ -7099,19 +7115,19 @@ which matches any single character in the class;
@item{
a single character class followed by @Char{*},
-which matches zero or more repetitions of characters in the class.
+which matches sequences of zero or more characters in the class.
These repetition items will always match the longest possible sequence;
}
@item{
a single character class followed by @Char{+},
-which matches one or more repetitions of characters in the class.
+which matches sequences of one or more characters in the class.
These repetition items will always match the longest possible sequence;
}
@item{
a single character class followed by @Char{-},
-which also matches zero or more repetitions of characters in the class.
+which also matches sequences of zero or more characters in the class.
Unlike @Char{*},
these repetition items will always match the shortest possible sequence;
}
@@ -7172,7 +7188,7 @@ that match captures are stored (@emph{captured}) for future use.
Captures are numbered according to their left parentheses.
For instance, in the pattern @T{"(a*(.)%w(%s*))"},
the part of the string matching @T{"a*(.)%w(%s*)"} is
-stored as the first capture (and therefore has @N{number 1});
+stored as the first capture, and therefore has @N{number 1};
the character matching @St{.} is captured with @N{number 2},
and the part matching @St{%s*} has @N{number 3}.
@@ -7188,7 +7204,7 @@ The function @Lid{string.gsub} and the iterator @Lid{string.gmatch}
match multiple occurrences of the given pattern in the subject.
For these functions,
a new match is considered valid only
-if it ends at least one byte after the previous match.
+if it ends at least one byte after the end of the previous match.
In other words, the pattern machine never accepts the
empty string as a match immediately after another match.
As an example,
@@ -7253,14 +7269,16 @@ according to option @id{op}
(A @St{[@rep{n}]} means an optional integral numeral.)
Except for padding, spaces, and configurations
(options @St{xX <=>!}),
-each option corresponds to an argument (in @Lid{string.pack})
-or a result (in @Lid{string.unpack}).
+each option corresponds to an argument in @Lid{string.pack}
+or a result in @Lid{string.unpack}.
For options @St{!@rep{n}}, @St{s@rep{n}}, @St{i@rep{n}}, and @St{I@rep{n}},
@id{n} can be any integer between 1 and 16.
All integral options check overflows;
@Lid{string.pack} checks whether the given value fits in the given size;
@Lid{string.unpack} checks whether the read value fits in a Lua integer.
+For the unsigned options,
+Lua integers are treated as unsigned values too.
Any format string starts as if prefixed by @St{!1=},
that is,
@@ -7283,7 +7301,7 @@ option @St{s} follows the alignment of its starting integer.
All padding is filled with zeros by @Lid{string.pack}
-(and ignored by @Lid{string.unpack}).
+and ignored by @Lid{string.unpack}.
}
@@ -7344,7 +7362,7 @@ Returns values so that the construction
@verbatim{
for p, c in utf8.codes(s) do @rep{body} end
}
-will iterate over all characters in string @id{s},
+will iterate over all UTF-8 characters in string @id{s},
with @id{p} being the position (in bytes) and @id{c} the code point
of each character.
It raises an error if it meets any invalid byte sequence.
@@ -7353,7 +7371,7 @@ It raises an error if it meets any invalid byte sequence.
@LibEntry{utf8.codepoint (s [, i [, j [, lax]]])|
-Returns the codepoints (as integers) from all characters in @id{s}
+Returns the code points (as integers) from all characters in @id{s}
that start between byte position @id{i} and @id{j} (both included).
The default for @id{i} is 1 and for @id{j} is @id{i}.
It raises an error if it meets any invalid byte sequence.
@@ -7423,13 +7441,13 @@ shifting up the elements
@T{list[pos], list[pos+1], @Cdots, list[#list]}.
The default value for @id{pos} is @T{#list+1},
so that a call @T{table.insert(t,x)} inserts @id{x} at the end
-of list @id{t}.
+of the list @id{t}.
}
@LibEntry{table.move (a1, f, e, t [,a2])|
-Moves elements from table @id{a1} to table @id{a2},
+Moves elements from the table @id{a1} to the table @id{a2},
performing the equivalent to the following
multiple assignment:
@T{a2[t],@Cdots = a1[f],@Cdots,a1[e]}.
@@ -7463,13 +7481,13 @@ or @T{#list + 1}.
The default value for @id{pos} is @T{#list},
so that a call @T{table.remove(l)} removes the last element
-of list @id{l}.
+of the list @id{l}.
}
@LibEntry{table.sort (list [, comp])|
-Sorts list elements in a given order, @emph{in-place},
+Sorts the list elements in a given order, @emph{in-place},
from @T{list[1]} to @T{list[#list]}.
If @id{comp} is given,
then it must be a function that receives two list elements
@@ -7511,8 +7529,8 @@ It provides all its functions and constants inside the table @defid{math}.
Functions with the annotation @St{integer/float} give
integer results for integer arguments
and float results for float (or mixed) arguments.
-Rounding functions
-(@Lid{math.ceil}, @Lid{math.floor}, and @Lid{math.modf})
+the rounding functions
+@Lid{math.ceil}, @Lid{math.floor}, and @Lid{math.modf}
return an integer when the result fits in the range of an integer,
or a float otherwise.
@@ -7540,7 +7558,7 @@ Returns the arc sine of @id{x} (in radians).
Returns the arc tangent of @T{y/x} (in radians),
but uses the signs of both arguments to find the
quadrant of the result.
-(It also handles correctly the case of @id{x} being zero.)
+It also handles correctly the case of @id{x} being zero.
The default value for @id{x} is 1,
so that the call @T{math.atan(y)}
@@ -7604,7 +7622,7 @@ The default for @id{base} is @M{e}
@LibEntry{math.max (x, @Cdots)|
Returns the argument with the maximum value,
-according to the Lua operator @T{<}. (integer/float)
+according to the Lua operator @T{<}.
}
@@ -7616,7 +7634,7 @@ An integer with the maximum value for an integer.
@LibEntry{math.min (x, @Cdots)|
Returns the argument with the minimum value,
-according to the Lua operator @T{<}. (integer/float)
+according to the Lua operator @T{<}.
}
@@ -7674,7 +7692,7 @@ some number of previous results.)
When called with at least one argument,
the integer parameters @id{x} and @id{y} are
-concatenated into a 128-bit @emphx{seed} that
+joined into a 128-bit @emphx{seed} that
is used to reinitialize the pseudo-random generator;
equal seeds produce equal sequences of numbers.
The default for @id{y} is zero.
@@ -7741,7 +7759,7 @@ The I/O library provides two different styles for file manipulation.
The first one uses implicit file handles;
that is, there are operations to set a default input file and a
default output file,
-and all input/output operations are over these default files.
+and all input/output operations are done over these default files.
The second style uses explicit file handles.
When using implicit file handles,
@@ -7750,18 +7768,19 @@ When using explicit file handles,
the operation @Lid{io.open} returns a file handle
and then all operations are supplied as methods of the file handle.
+The metatable for file handles provides metamethods
+for @idx{__gc} and @idx{__close} that try
+to close the file when called.
+
The table @id{io} also provides
three predefined file handles with their usual meanings from C:
@defid{io.stdin}, @defid{io.stdout}, and @defid{io.stderr}.
The I/O library never closes these files.
-The metatable for file handles provides metamethods
-for @idx{__gc} and @idx{__close} that try
-to close the file when called.
Unless otherwise stated,
-all I/O functions return @nil on failure
-(plus an error message as a second result and
-a system-dependent error code as a third result)
+all I/O functions return @nil on failure,
+plus an error message as a second result and
+a system-dependent error code as a third result,
and some value different from @nil on success.
On non-POSIX systems,
the computation of the error message and error code
@@ -7854,7 +7873,7 @@ Similar to @Lid{io.input}, but operates over the default output file.
This function is system dependent and is not available
on all platforms.
-Starts program @id{prog} in a separated process and returns
+Starts the program @id{prog} in a separated process and returns
a file handle that you can use to read data from this program
(if @id{mode} is @T{"r"}, the default)
or to write data to this program
@@ -8097,10 +8116,9 @@ If @id{format} is not @St{*t},
then @id{date} returns the date as a string,
formatted according to the same rules as the @ANSI{strftime}.
-When called without arguments,
-@id{date} returns a reasonable date and time representation that depends on
-the host system and on the current locale.
-(More specifically, @T{os.date()} is equivalent to @T{os.date("%c")}.)
+If @id{format} is absent, it defaults to @St{%c},
+which gives a reasonable date and time representation
+using the current locale.
On non-POSIX systems,
this function may be not @x{thread safe}
@@ -8314,8 +8332,8 @@ within any function and so have no direct access to local variables.
Returns the current hook settings of the thread, as three values:
the current hook function, the current hook mask,
-and the current hook count
-(as set by the @Lid{debug.sethook} function).
+and the current hook count,
+as set by the @Lid{debug.sethook} function.
}
@@ -8359,7 +8377,7 @@ about the @Lid{print} function.
This function returns the name and the value of the local variable
with index @id{local} of the function at level @id{f} of the stack.
This function accesses not only explicit local variables,
-but also parameters, temporaries, etc.
+but also parameters and temporary values.
The first parameter or local variable has @N{index 1}, and so on,
following the order that they are declared in the code,
@@ -8416,7 +8434,7 @@ to the userdata @id{u} plus a boolean,
@LibEntry{debug.sethook ([thread,] hook, mask [, count])|
-Sets the given function as a hook.
+Sets the given function as the debug hook.
The string @id{mask} and the number @id{count} describe
when the hook will be called.
The string mask may have any combination of the following characters,
@@ -8435,16 +8453,15 @@ When called without arguments,
When the hook is called, its first parameter is a string
describing the event that has triggered its call:
-@T{"call"} (or @T{"tail call"}),
-@T{"return"},
+@T{"call"}, @T{"tail call"}, @T{"return"},
@T{"line"}, and @T{"count"}.
For line events,
the hook also gets the new line number as its second parameter.
Inside a hook,
you can call @id{getinfo} with @N{level 2} to get more information about
-the running function
-(@N{level 0} is the @id{getinfo} function,
-and @N{level 1} is the hook function).
+the running function.
+(@N{Level 0} is the @id{getinfo} function,
+and @N{level 1} is the hook function.)
}
@@ -8542,7 +8559,7 @@ An interpreter for Lua as a standalone language,
called simply @id{lua},
is provided with the standard distribution.
The @x{standalone interpreter} includes
-all standard libraries, including the debug library.
+all standard libraries.
Its usage is:
@verbatim{
lua [options] [script [args]]
@@ -8564,7 +8581,7 @@ When called without arguments,
when the standard input (@id{stdin}) is a terminal,
and as @T{lua -} otherwise.
-When called without option @T{-E},
+When called without the option @T{-E},
the interpreter checks for an environment variable @defid{LUA_INIT_5_4}
(or @defid{LUA_INIT} if the versioned name is not defined)
before running any argument.
@@ -8572,7 +8589,7 @@ If the variable content has the format @T{@At@rep{filename}},
then @id{lua} executes the file.
Otherwise, @id{lua} executes the string itself.
-When called with option @T{-E},
+When called with the option @T{-E},
besides ignoring @id{LUA_INIT},
Lua also ignores
the values of @id{LUA_PATH} and @id{LUA_CPATH},
@@ -8619,8 +8636,8 @@ will print @St{-e}.
If there is a script,
the script is called with arguments
@T{arg[1]}, @Cdots, @T{arg[#arg]}.
-(Like all chunks in Lua,
-the script is compiled as a vararg function.)
+Like all chunks in Lua,
+the script is compiled as a vararg function.
In interactive mode,
Lua repeatedly prompts and waits for a line.
@@ -8645,6 +8662,7 @@ has a metamethod @idx{__tostring},
the interpreter calls this metamethod to produce the final message.
Otherwise, the interpreter converts the error object to a string
and adds a stack traceback to it.
+Warnings are simply printed in the standard error output.
When finishing normally,
the interpreter closes its main Lua state
@@ -8654,22 +8672,21 @@ calling @Lid{os.exit} to terminate.
To allow the use of Lua as a
script interpreter in Unix systems,
-the standalone interpreter skips
-the first line of a chunk if it starts with @T{#}.
+Lua skips the first line of a file chunk if it starts with @T{#}.
Therefore, Lua scripts can be made into executable programs
by using @T{chmod +x} and @N{the @T{#!}} form,
as in
@verbatim{
#!/usr/local/bin/lua
}
-(Of course,
+Of course,
the location of the Lua interpreter may be different in your machine.
If @id{lua} is in your @id{PATH},
then
@verbatim{
#!/usr/bin/env lua
}
-is a more portable solution.)
+is a more portable solution.
}
@@ -8688,7 +8705,7 @@ do not imply source-code changes in a program,
such as the numeric values for constants
or the implementation of functions as macros.
Therefore,
-you should not assume that binaries are compatible between
+you should never assume that binaries are compatible between
different Lua versions.
Always recompile clients of the Lua API when
using a new version.
@@ -8700,7 +8717,7 @@ precompiled chunks are not compatible between different Lua versions.
The standard paths in the official distribution may
change between versions.
-@sect2{@title{Changes in the Language}
+@sect2{@title{Incompatibilities in the Language}
@itemize{
@item{
@@ -8752,7 +8769,7 @@ like any other error when calling a finalizer.)
}
-@sect2{@title{Changes in the Libraries}
+@sect2{@title{Incompatibilities in the Libraries}
@itemize{
@item{
@@ -8761,13 +8778,6 @@ now starts with a somewhat random seed.
Moreover, it uses a different algorithm.
}
-@item{
-The function @Lid{io.lines} now returns three extra values,
-besides the iterator function.
-You can enclose the call in parentheses if you need to
-discard these extra results.
-}
-
@item{
By default, the decoding functions in the @Lid{utf8} library
do not accept surrogates as valid code points.
@@ -8778,7 +8788,7 @@ An extra parameter in these functions makes them more permissive.
}
-@sect2{@title{Changes in the API}
+@sect2{@title{Incompatibilities in the API}
@itemize{
@@ -8792,8 +8802,8 @@ which have an extra argument.
For compatibility, the old names still work as macros assuming
one single user value.
-Note, however, that the call @T{lua_newuserdatauv(L,size,0)}
-produces a smaller userdata.
+Note, however, that userdata with zero user values
+are more efficient memory-wise.
}
@item{
@@ -8807,10 +8817,10 @@ those values were the entire stack.)
@item{
The function @Lid{lua_version} returns the version number,
instead of an address of the version number.
-(The Lua core should work correctly with libraries using their
+The Lua core should work correctly with libraries using their
own static copies of the same core,
so there is no need to check whether they are using the same
-address space.)
+address space.
}
@item{
From 8ba4523cccf59093543cec988b07957193d55692 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 10 Apr 2019 12:58:14 -0300
Subject: [PATCH 144/889] 'print' does not call 'tostring' to format its
arguments
---
lbaselib.c | 16 +++++-----------
manual/manual.of | 13 +++++++++++--
testes/calls.lua | 15 ---------------
3 files changed, 16 insertions(+), 28 deletions(-)
diff --git a/lbaselib.c b/lbaselib.c
index d4b619a530..83f61e6d11 100644
--- a/lbaselib.c
+++ b/lbaselib.c
@@ -24,18 +24,12 @@
static int luaB_print (lua_State *L) {
int n = lua_gettop(L); /* number of arguments */
int i;
- lua_getglobal(L, "tostring");
- for (i=1; i<=n; i++) {
- const char *s;
+ for (i = 1; i <= n; i++) { /* for each argument */
size_t l;
- lua_pushvalue(L, -1); /* function to be called */
- lua_pushvalue(L, i); /* value to print */
- lua_call(L, 1, 1);
- s = lua_tolstring(L, -1, &l); /* get result */
- if (s == NULL)
- return luaL_error(L, "'tostring' must return a string to 'print'");
- if (i>1) lua_writestring("\t", 1);
- lua_writestring(s, l);
+ const char *s = luaL_tolstring(L, i, &l); /* convert it to string */
+ if (i > 1) /* not the first element? */
+ lua_writestring("\t", 1); /* add a tab before it */
+ lua_writestring(s, l); /* print it */
lua_pop(L, 1); /* pop result */
}
lua_writeline();
diff --git a/manual/manual.of b/manual/manual.of
index 9f1ef631c5..6da2e49432 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -6143,8 +6143,10 @@ In case of any error, @id{pcall} returns @false plus the error object.
@LibEntry{print (@Cdots)|
Receives any number of arguments
and prints their values to @id{stdout},
-using the @Lid{tostring} function to convert each argument to a string.
-@id{print} is not intended for formatted output,
+converting each argument to a string
+following the same rules of @Lid{tostring}.
+
+The function @id{print} is not intended for formatted output,
but only as a quick way to show a value,
for instance for debugging.
For complete control over the output,
@@ -8772,6 +8774,13 @@ like any other error when calling a finalizer.)
@sect2{@title{Incompatibilities in the Libraries}
@itemize{
+@item{
+The function @Lid{print} does not call @Lid{tostring}
+to format its arguments;
+instead, it has this functionality hardwired.
+You should use @id{__tostring} to modify how values are printed.
+}
+
@item{
The pseudo-random number generator used by the function @Lid{math.random}
now starts with a somewhat random seed.
diff --git a/testes/calls.lua b/testes/calls.lua
index 941493b131..56a12ae670 100644
--- a/testes/calls.lua
+++ b/testes/calls.lua
@@ -21,21 +21,6 @@ assert(type(f) == 'function')
assert(not pcall(type))
-do -- test error in 'print' too...
- local tostring = _ENV.tostring
-
- _ENV.tostring = nil
- local st, msg = pcall(print, 1)
- assert(st == false and string.find(msg, "attempt to call a nil value"))
-
- _ENV.tostring = function () return {} end
- local st, msg = pcall(print, 1)
- assert(st == false and string.find(msg, "must return a string"))
-
- _ENV.tostring = tostring
-end
-
-
-- testing local-function recursion
fact = false
do
From a93e0144479f1eb0ac19b8c31862f4cbc2fbe1c4 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 10 Apr 2019 13:23:14 -0300
Subject: [PATCH 145/889] Added an optional parameter to
'coroutine.isyieldable'
---
lcorolib.c | 3 ++-
manual/manual.of | 7 ++++---
testes/coroutine.lua | 7 +++++--
3 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/lcorolib.c b/lcorolib.c
index cdb5fedc93..f7c9e165f5 100644
--- a/lcorolib.c
+++ b/lcorolib.c
@@ -146,7 +146,8 @@ static int luaB_costatus (lua_State *L) {
static int luaB_yieldable (lua_State *L) {
- lua_pushboolean(L, lua_isyieldable(L));
+ lua_State *co = lua_isnone(L, 1) ? L : getco(L);
+ lua_pushboolean(L, lua_isyieldable(co));
return 1;
}
diff --git a/manual/manual.of b/manual/manual.of
index 6da2e49432..fea6922e6e 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -6307,11 +6307,12 @@ an object with type @T{"thread"}.
}
-@LibEntry{coroutine.isyieldable ()|
+@LibEntry{coroutine.isyieldable ([co])|
-Returns true when the running coroutine can yield.
+Returns true when the coroutine @id{co} can yield.
+The default for @id{co} is the running coroutine.
-A running coroutine is yieldable if it is not the main thread and
+A coroutine is yieldable if it is not the main thread and
it is not inside a non-yieldable @N{C function}.
}
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index a4321bedaf..35ff27fb7f 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -10,7 +10,7 @@ local f
local main, ismain = coroutine.running()
assert(type(main) == "thread" and ismain)
assert(not coroutine.resume(main))
-assert(not coroutine.isyieldable())
+assert(not coroutine.isyieldable(main) and not coroutine.isyieldable())
assert(not pcall(coroutine.yield))
@@ -38,7 +38,7 @@ function foo (a, ...)
assert(coroutine.resume(f) == false)
assert(coroutine.status(f) == "running")
local arg = {...}
- assert(coroutine.isyieldable())
+ assert(coroutine.isyieldable(x))
for i=1,#arg do
_G.x = {coroutine.yield(table.unpack(arg[i]))}
end
@@ -46,14 +46,17 @@ function foo (a, ...)
end
f = coroutine.create(foo)
+assert(coroutine.isyieldable(f))
assert(type(f) == "thread" and coroutine.status(f) == "suspended")
assert(string.find(tostring(f), "thread"))
local s,a,b,c,d
s,a,b,c,d = coroutine.resume(f, {1,2,3}, {}, {1}, {'a', 'b', 'c'})
+assert(coroutine.isyieldable(f))
assert(s and a == nil and coroutine.status(f) == "suspended")
s,a,b,c,d = coroutine.resume(f)
eqtab(_G.x, {})
assert(s and a == 1 and b == nil)
+assert(coroutine.isyieldable(f))
s,a,b,c,d = coroutine.resume(f, 1, 2, 3)
eqtab(_G.x, {1, 2, 3})
assert(s and a == 'a' and b == 'b' and c == 'c' and d == nil)
From b0810c51c3f075cc8a309bfb3c1714ac42b0f020 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 11 Apr 2019 11:29:16 -0300
Subject: [PATCH 146/889] Small optimizations in 'string.gsub'
Avoid creating extra strings when possible:
- avoid creating new resulting string when subject was not modified
(instead, return the subject itself);
- avoid creating strings representing the captured substrings when
handling replacements like '%1' (instead, add the substring directly
to the buffer).
---
lstrlib.c | 130 ++++++++++++++++++++++++++++++++------------------
testes/gc.lua | 2 +-
testes/pm.lua | 30 ++++++++++++
3 files changed, 115 insertions(+), 47 deletions(-)
diff --git a/lstrlib.c b/lstrlib.c
index 6230cd0c00..53ed80a3bf 100644
--- a/lstrlib.c
+++ b/lstrlib.c
@@ -660,25 +660,46 @@ static const char *lmemfind (const char *s1, size_t l1,
}
-static void push_onecapture (MatchState *ms, int i, const char *s,
- const char *e) {
+/*
+** get information about the i-th capture. If there are no captures
+** and 'i==0', return information about the whole match, which
+** is the range 's'..'e'. If the capture is a string, return
+** its length and put its address in '*cap'. If it is an integer
+** (a position), push it on the stack and return CAP_POSITION.
+*/
+static size_t get_onecapture (MatchState *ms, int i, const char *s,
+ const char *e, const char **cap) {
if (i >= ms->level) {
- if (i == 0) /* ms->level == 0, too */
- lua_pushlstring(ms->L, s, e - s); /* add whole match */
- else
+ if (i != 0)
luaL_error(ms->L, "invalid capture index %%%d", i + 1);
+ *cap = s;
+ return e - s;
}
else {
- ptrdiff_t l = ms->capture[i].len;
- if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture");
- if (l == CAP_POSITION)
+ ptrdiff_t capl = ms->capture[i].len;
+ *cap = ms->capture[i].init;
+ if (capl == CAP_UNFINISHED)
+ luaL_error(ms->L, "unfinished capture");
+ else if (capl == CAP_POSITION)
lua_pushinteger(ms->L, (ms->capture[i].init - ms->src_init) + 1);
- else
- lua_pushlstring(ms->L, ms->capture[i].init, l);
+ return capl;
}
}
+/*
+** Push the i-th capture on the stack.
+*/
+static void push_onecapture (MatchState *ms, int i, const char *s,
+ const char *e) {
+ const char *cap;
+ ptrdiff_t l = get_onecapture(ms, i, s, e, &cap);
+ if (l != CAP_POSITION)
+ lua_pushlstring(ms->L, cap, l);
+ /* else position was already pushed */
+}
+
+
static int push_captures (MatchState *ms, const char *s, const char *e) {
int i;
int nlevels = (ms->level == 0 && s) ? 1 : ms->level;
@@ -817,60 +838,72 @@ static int gmatch (lua_State *L) {
static void add_s (MatchState *ms, luaL_Buffer *b, const char *s,
const char *e) {
- size_t l, i;
+ size_t l;
lua_State *L = ms->L;
const char *news = lua_tolstring(L, 3, &l);
- for (i = 0; i < l; i++) {
- if (news[i] != L_ESC)
- luaL_addchar(b, news[i]);
- else {
- i++; /* skip ESC */
- if (!isdigit(uchar(news[i]))) {
- if (news[i] != L_ESC)
- luaL_error(L, "invalid use of '%c' in replacement string", L_ESC);
- luaL_addchar(b, news[i]);
- }
- else if (news[i] == '0')
- luaL_addlstring(b, s, e - s);
- else {
- push_onecapture(ms, news[i] - '1', s, e);
- luaL_tolstring(L, -1, NULL); /* if number, convert it to string */
- lua_remove(L, -2); /* remove original value */
- luaL_addvalue(b); /* add capture to accumulated result */
- }
+ const char *p;
+ while ((p = (char *)memchr(news, L_ESC, l)) != NULL) {
+ luaL_addlstring(b, news, p - news);
+ p++; /* skip ESC */
+ if (*p == L_ESC) /* '%%' */
+ luaL_addchar(b, *p);
+ else if (*p == '0') /* '%0' */
+ luaL_addlstring(b, s, e - s);
+ else if (isdigit(uchar(*p))) { /* '%n' */
+ const char *cap;
+ ptrdiff_t resl = get_onecapture(ms, *p - '1', s, e, &cap);
+ if (resl == CAP_POSITION)
+ luaL_addvalue(b); /* add position to accumulated result */
+ else
+ luaL_addlstring(b, cap, resl);
}
+ else
+ luaL_error(L, "invalid use of '%c' in replacement string", L_ESC);
+ l -= p + 1 - news;
+ news = p + 1;
}
+ luaL_addlstring(b, news, l);
}
-static void add_value (MatchState *ms, luaL_Buffer *b, const char *s,
- const char *e, int tr) {
+/*
+** Add the replacement value to the string buffer 'b'.
+** Return true if the original string was changed. (Function calls and
+** table indexing resulting in nil or false do not change the subject.)
+*/
+static int add_value (MatchState *ms, luaL_Buffer *b, const char *s,
+ const char *e, int tr) {
lua_State *L = ms->L;
switch (tr) {
- case LUA_TFUNCTION: {
+ case LUA_TFUNCTION: { /* call the function */
int n;
- lua_pushvalue(L, 3);
- n = push_captures(ms, s, e);
- lua_call(L, n, 1);
+ lua_pushvalue(L, 3); /* push the function */
+ n = push_captures(ms, s, e); /* all captures as arguments */
+ lua_call(L, n, 1); /* call it */
break;
}
- case LUA_TTABLE: {
- push_onecapture(ms, 0, s, e);
+ case LUA_TTABLE: { /* index the table */
+ push_onecapture(ms, 0, s, e); /* first capture is the index */
lua_gettable(L, 3);
break;
}
default: { /* LUA_TNUMBER or LUA_TSTRING */
- add_s(ms, b, s, e);
- return;
+ add_s(ms, b, s, e); /* add value to the buffer */
+ return 1; /* something changed */
}
}
if (!lua_toboolean(L, -1)) { /* nil or false? */
- lua_pop(L, 1);
- lua_pushlstring(L, s, e - s); /* keep original text */
+ lua_pop(L, 1); /* remove value */
+ luaL_addlstring(b, s, e - s); /* keep original text */
+ return 0; /* no changes */
}
else if (!lua_isstring(L, -1))
- luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1));
- luaL_addvalue(b); /* add result to accumulator */
+ return luaL_error(L, "invalid replacement value (a %s)",
+ luaL_typename(L, -1));
+ else {
+ luaL_addvalue(b); /* add result to accumulator */
+ return 1; /* something changed */
+ }
}
@@ -883,6 +916,7 @@ static int str_gsub (lua_State *L) {
lua_Integer max_s = luaL_optinteger(L, 4, srcl + 1); /* max replacements */
int anchor = (*p == '^');
lua_Integer n = 0; /* replacement count */
+ int changed = 0; /* change flag */
MatchState ms;
luaL_Buffer b;
luaL_argexpected(L, tr == LUA_TNUMBER || tr == LUA_TSTRING ||
@@ -898,7 +932,7 @@ static int str_gsub (lua_State *L) {
reprepstate(&ms); /* (re)prepare state for new match */
if ((e = match(&ms, src, p)) != NULL && e != lastmatch) { /* match? */
n++;
- add_value(&ms, &b, src, e, tr); /* add replacement to buffer */
+ changed = add_value(&ms, &b, src, e, tr) | changed;
src = lastmatch = e;
}
else if (src < ms.src_end) /* otherwise, skip one character */
@@ -906,8 +940,12 @@ static int str_gsub (lua_State *L) {
else break; /* end of subject */
if (anchor) break;
}
- luaL_addlstring(&b, src, ms.src_end-src);
- luaL_pushresult(&b);
+ if (!changed) /* no changes? */
+ lua_pushvalue(L, 1); /* return original string */
+ else { /* something changed */
+ luaL_addlstring(&b, src, ms.src_end-src);
+ luaL_pushresult(&b); /* create and return new string */
+ }
lua_pushinteger(L, n); /* number of substitutions */
return 2;
}
diff --git a/testes/gc.lua b/testes/gc.lua
index 91e78a4822..6d24e0d82d 100644
--- a/testes/gc.lua
+++ b/testes/gc.lua
@@ -113,7 +113,7 @@ do
contCreate = 0
while contCreate <= limit do
a = contCreate .. "b";
- a = string.gsub(a, '(%d%d*)', string.upper)
+ a = string.gsub(a, '(%d%d*)', "%1 %1")
a = "a"
contCreate = contCreate+1
end
diff --git a/testes/pm.lua b/testes/pm.lua
index 8cc8772e87..4d87fad210 100644
--- a/testes/pm.lua
+++ b/testes/pm.lua
@@ -387,5 +387,35 @@ assert(string.match("abc\0\0\0", "%\0%\0?") == "\0\0")
assert(string.find("abc\0\0","\0.") == 4)
assert(string.find("abcx\0\0abc\0abc","x\0\0abc\0a.") == 4)
+
+do -- test reuse of original string in gsub
+ local s = string.rep("a", 100)
+ local r = string.gsub(s, "b", "c") -- no match
+ assert(string.format("%p", s) == string.format("%p", r))
+
+ r = string.gsub(s, ".", {x = "y"}) -- no substitutions
+ assert(string.format("%p", s) == string.format("%p", r))
+
+ local count = 0
+ r = string.gsub(s, ".", function (x)
+ assert(x == "a")
+ count = count + 1
+ return nil -- no substitution
+ end)
+ r = string.gsub(r, ".", {b = 'x'}) -- "a" is not a key; no subst.
+ assert(count == 100)
+ assert(string.format("%p", s) == string.format("%p", r))
+
+ count = 0
+ r = string.gsub(s, ".", function (x)
+ assert(x == "a")
+ count = count + 1
+ return x -- substitution...
+ end)
+ assert(count == 100)
+ -- no reuse in this case
+ assert(r == s and string.format("%p", s) ~= string.format("%p", r))
+end
+
print('OK')
From 2d3f09544895b422eeecf89e0d108da8b8fcdfca Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 12 Apr 2019 11:48:24 -0300
Subject: [PATCH 147/889] Avoid using large buffers in 'string.format'
The result of "string.format("%.99f", -1e308) is 410 characters long,
but all other formats have much smaller limits (at most 99 plus a fex
extras). This commit avoids 'string.format' asking for a buffer
~400 chars large when ~100 will do.
---
lstrlib.c | 40 ++++++++++++++++++++++++++++------------
luaconf.h | 9 +--------
2 files changed, 29 insertions(+), 20 deletions(-)
diff --git a/lstrlib.c b/lstrlib.c
index 53ed80a3bf..563d5ca52c 100644
--- a/lstrlib.c
+++ b/lstrlib.c
@@ -1038,13 +1038,23 @@ static int lua_number2strx (lua_State *L, char *buff, int sz,
/*
-** Maximum size of each formatted item. This maximum size is produced
+** Maximum size for items formatted with '%f'. This size is produced
** by format('%.99f', -maxfloat), and is equal to 99 + 3 ('-', '.',
** and '\0') + number of decimal digits to represent maxfloat (which
-** is maximum exponent + 1). (99+3+1 then rounded to 120 for "extra
-** expenses", such as locale-dependent stuff)
+** is maximum exponent + 1). (99+3+1, adding some extra, 110)
*/
-#define MAX_ITEM (120 + l_mathlim(MAX_10_EXP))
+#define MAX_ITEMF (110 + l_mathlim(MAX_10_EXP))
+
+
+/*
+** All formats except '%f' do not need that large limit. The other
+** float formats use exponents, so that they fit in the 99 limit for
+** significant digits; 's' for large strings and 'q' add items directly
+** to the buffer; all integer formats also fit in the 99 limit. The
+** worst case are floats: they may need 99 significant digits, plus
+** '0x', '-', '.', 'e+XXXX', and '\0'. Adding some extra, 120.
+*/
+#define MAX_ITEM 120
/* valid flags in a format specification */
@@ -1194,38 +1204,44 @@ static int str_format (lua_State *L) {
luaL_addchar(&b, *strfrmt++); /* %% */
else { /* format item */
char form[MAX_FORMAT]; /* to store the format ('%...') */
- char *buff = luaL_prepbuffsize(&b, MAX_ITEM); /* to put formatted item */
+ int maxitem = MAX_ITEM;
+ char *buff = luaL_prepbuffsize(&b, maxitem); /* to put formatted item */
int nb = 0; /* number of bytes in added item */
if (++arg > top)
return luaL_argerror(L, arg, "no value");
strfrmt = scanformat(L, strfrmt, form);
switch (*strfrmt++) {
case 'c': {
- nb = l_sprintf(buff, MAX_ITEM, form, (int)luaL_checkinteger(L, arg));
+ nb = l_sprintf(buff, maxitem, form, (int)luaL_checkinteger(L, arg));
break;
}
case 'd': case 'i':
case 'o': case 'u': case 'x': case 'X': {
lua_Integer n = luaL_checkinteger(L, arg);
addlenmod(form, LUA_INTEGER_FRMLEN);
- nb = l_sprintf(buff, MAX_ITEM, form, (LUAI_UACINT)n);
+ nb = l_sprintf(buff, maxitem, form, (LUAI_UACINT)n);
break;
}
case 'a': case 'A':
addlenmod(form, LUA_NUMBER_FRMLEN);
- nb = lua_number2strx(L, buff, MAX_ITEM, form,
+ nb = lua_number2strx(L, buff, maxitem, form,
luaL_checknumber(L, arg));
break;
case 'e': case 'E': case 'f':
case 'g': case 'G': {
lua_Number n = luaL_checknumber(L, arg);
+ if (*(strfrmt - 1) == 'f' && l_mathop(fabs)(n) >= 1e100) {
+ /* 'n' needs more than 99 digits */
+ maxitem = MAX_ITEMF; /* extra space for '%f' */
+ buff = luaL_prepbuffsize(&b, maxitem);
+ }
addlenmod(form, LUA_NUMBER_FRMLEN);
- nb = l_sprintf(buff, MAX_ITEM, form, (LUAI_UACNUMBER)n);
+ nb = l_sprintf(buff, maxitem, form, (LUAI_UACNUMBER)n);
break;
}
case 'p': {
const void *p = lua_topointer(L, arg);
- nb = l_sprintf(buff, MAX_ITEM, form, p);
+ nb = l_sprintf(buff, maxitem, form, p);
break;
}
case 'q': {
@@ -1246,7 +1262,7 @@ static int str_format (lua_State *L) {
luaL_addvalue(&b); /* keep entire string */
}
else { /* format the string into 'buff' */
- nb = l_sprintf(buff, MAX_ITEM, form, s);
+ nb = l_sprintf(buff, maxitem, form, s);
lua_pop(L, 1); /* remove result from 'luaL_tolstring' */
}
}
@@ -1256,7 +1272,7 @@ static int str_format (lua_State *L) {
return luaL_error(L, "invalid conversion '%s' to 'format'", form);
}
}
- lua_assert(nb < MAX_ITEM);
+ lua_assert(nb < maxitem);
luaL_addsize(&b, nb);
}
}
diff --git a/luaconf.h b/luaconf.h
index 5c714d4e53..76a6161675 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -709,16 +709,9 @@
/*
@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system.
-** CHANGE it if it uses too much C-stack space. (For long double,
-** 'string.format("%.99f", -1e4932)' needs 5052 bytes, so a
-** smaller buffer would force a memory allocation for each call to
-** 'string.format'.)
*/
-#if LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE
-#define LUAL_BUFFERSIZE 8192
-#else
#define LUAL_BUFFERSIZE ((int)(16 * sizeof(void*) * sizeof(lua_Number)))
-#endif
+
/*
@@ LUAI_MAXALIGN defines fields that, when used in a union, ensure
From ed2872cd3bf6d352f36bbd34529738a60b0b51eb Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 17 Apr 2019 14:57:29 -0300
Subject: [PATCH 148/889] 'require' returns where module was found
The function 'require' returns the *loader data* as a second result.
For file searchers, this data is the path where they found the module.
---
loadlib.c | 23 +++++++++++++++++------
lundump.c | 4 ++--
manual/manual.of | 38 +++++++++++++++++++++++++++-----------
testes/attrib.lua | 35 ++++++++++++++++++++---------------
4 files changed, 66 insertions(+), 34 deletions(-)
diff --git a/loadlib.c b/loadlib.c
index a6ce30d4f3..4cf9aec31f 100644
--- a/loadlib.c
+++ b/loadlib.c
@@ -576,9 +576,14 @@ static int searcher_Croot (lua_State *L) {
static int searcher_preload (lua_State *L) {
const char *name = luaL_checkstring(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
- if (lua_getfield(L, -1, name) == LUA_TNIL) /* not found? */
+ if (lua_getfield(L, -1, name) == LUA_TNIL) { /* not found? */
lua_pushfstring(L, "\n\tno field package.preload['%s']", name);
- return 1;
+ return 1;
+ }
+ else {
+ lua_pushliteral(L, ":preload:");
+ return 2;
+ }
}
@@ -620,17 +625,23 @@ static int ll_require (lua_State *L) {
/* else must load package */
lua_pop(L, 1); /* remove 'getfield' result */
findloader(L, name);
- lua_pushstring(L, name); /* pass name as argument to module loader */
- lua_insert(L, -2); /* name is 1st argument (before search data) */
+ lua_rotate(L, -2, 1); /* function <-> loader data */
+ lua_pushvalue(L, 1); /* name is 1st argument to module loader */
+ lua_pushvalue(L, -3); /* loader data is 2nd argument */
+ /* stack: ...; loader data; loader function; mod. name; loader data */
lua_call(L, 2, 1); /* run loader to load module */
+ /* stack: ...; loader data; result from loader */
if (!lua_isnil(L, -1)) /* non-nil return? */
lua_setfield(L, 2, name); /* LOADED[name] = returned value */
+ else
+ lua_pop(L, 1); /* pop nil */
if (lua_getfield(L, 2, name) == LUA_TNIL) { /* module set no value? */
lua_pushboolean(L, 1); /* use true as result */
- lua_pushvalue(L, -1); /* extra copy to be returned */
+ lua_copy(L, -1, -2); /* replace loader result */
lua_setfield(L, 2, name); /* LOADED[name] = true */
}
- return 1;
+ lua_rotate(L, -2, 1); /* loader data <-> module result */
+ return 2; /* return module result and loader data */
}
/* }====================================================== */
diff --git a/lundump.c b/lundump.c
index a600493367..c1cff9e1ab 100644
--- a/lundump.c
+++ b/lundump.c
@@ -271,8 +271,8 @@ static void fchecksize (LoadState *S, size_t size, const char *tname) {
#define checksize(S,t) fchecksize(S,sizeof(t),#t)
static void checkHeader (LoadState *S) {
- /* 1st char already checked */
- checkliteral(S, LUA_SIGNATURE + 1, "not a binary chunk");
+ /* skip 1st char (already read and checked) */
+ checkliteral(S, &LUA_SIGNATURE[1], "not a binary chunk");
if (LoadInt(S) != LUAC_VERSION)
error(S, "version mismatch");
if (LoadByte(S) != LUAC_FORMAT)
diff --git a/manual/manual.of b/manual/manual.of
index fea6922e6e..24ac45ae80 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -6408,11 +6408,15 @@ The function starts by looking into the @Lid{package.loaded} table
to determine whether @id{modname} is already loaded.
If it is, then @id{require} returns the value stored
at @T{package.loaded[modname]}.
+(The absence of a second result in this case
+signals that this call did not have to load the module.)
Otherwise, it tries to find a @emph{loader} for the module.
To find a loader,
-@id{require} is guided by the @Lid{package.searchers} sequence.
-By changing this sequence,
+@id{require} is guided by the table @Lid{package.searchers}.
+Each item in this table is a search function,
+that searches for the module in a particular way.
+By changing this table,
we can change how @id{require} looks for a module.
The following explanation is based on the default configuration
for @Lid{package.searchers}.
@@ -6429,9 +6433,14 @@ it tries an @emph{all-in-one} loader @seeF{package.searchers}.
Once a loader is found,
@id{require} calls the loader with two arguments:
-@id{modname} and an extra value dependent on how it got the loader.
-(If the loader came from a file,
-this extra value is the file name.)
+@id{modname} and an extra value,
+a @emph{loader data},
+also returned by the searcher.
+The loader data can be any value useful to the module;
+for the default searchers,
+it indicates where the loader was found.
+(For instance, if the loader came from a file,
+this extra value is the file path.)
If the loader returns any non-nil value,
@id{require} assigns the returned value to @T{package.loaded[modname]}.
If the loader does not return a non-nil value and
@@ -6439,6 +6448,9 @@ has not assigned any value to @T{package.loaded[modname]},
then @id{require} assigns @Rw{true} to this entry.
In any case, @id{require} returns the
final value of @T{package.loaded[modname]}.
+Besides that value, @id{require} also returns as a second result
+the loader data returned by the searcher,
+which indicates how @id{require} found the module.
If there is any error loading or running the module,
or if it cannot find any loader for the module,
@@ -6558,16 +6570,20 @@ table used by @Lid{require}.
@LibEntry{package.searchers|
-A table used by @Lid{require} to control how to load modules.
+A table used by @Lid{require} to control how to find modules.
Each entry in this table is a @def{searcher function}.
When looking for a module,
@Lid{require} calls each of these searchers in ascending order,
with the module name (the argument given to @Lid{require}) as its
sole argument.
-The function can return another function (the module @def{loader})
-plus an extra value that will be passed to that loader,
-or a string explaining why it did not find that module
+If the searcher finds the module,
+it returns another function, the module @def{loader},
+plus an extra value, a @emph{loader data},
+that will be passed to that loader and
+returned as a second result by @Lid{require}.
+If it cannot find the module,
+it returns a string explaining why
(or @nil if it has nothing to say).
Lua initializes this table with four searcher functions.
@@ -6617,9 +6633,9 @@ into one single library,
with each submodule keeping its original open function.
All searchers except the first one (preload) return as the extra value
-the file name where the module was found,
+the file path where the module was found,
as returned by @Lid{package.searchpath}.
-The first searcher returns no extra value.
+The first searcher always returns the string @St{:preload:}.
}
diff --git a/testes/attrib.lua b/testes/attrib.lua
index dcafd6345d..4adb42e0d2 100644
--- a/testes/attrib.lua
+++ b/testes/attrib.lua
@@ -122,12 +122,13 @@ local oldpath = package.path
package.path = string.gsub("D/?.lua;D/?.lc;D/?;D/??x?;D/L", "D/", DIR)
-local try = function (p, n, r)
+local try = function (p, n, r, ext)
NAME = nil
- local rr = require(p)
+ local rr, x = require(p)
assert(NAME == n)
assert(REQUIRED == p)
assert(rr == r)
+ assert(ext == x)
end
a = require"names"
@@ -143,27 +144,27 @@ assert(package.searchpath("C", package.path) == D"C.lua")
assert(require"C" == 25)
assert(require"C" == 25)
AA = nil
-try('B', 'B.lua', true)
+try('B', 'B.lua', true, "libs/B.lua")
assert(package.loaded.B)
assert(require"B" == true)
assert(package.loaded.A)
assert(require"C" == 25)
package.loaded.A = nil
-try('B', nil, true) -- should not reload package
-try('A', 'A.lua', true)
+try('B', nil, true, nil) -- should not reload package
+try('A', 'A.lua', true, "libs/A.lua")
package.loaded.A = nil
os.remove(D'A.lua')
AA = {}
-try('A', 'A.lc', AA) -- now must find second option
+try('A', 'A.lc', AA, "libs/A.lc") -- now must find second option
assert(package.searchpath("A", package.path) == D"A.lc")
assert(require("A") == AA)
AA = false
-try('K', 'L', false) -- default option
-try('K', 'L', false) -- default option (should reload it)
+try('K', 'L', false, "libs/L") -- default option
+try('K', 'L', false, "libs/L") -- default option (should reload it)
assert(rawget(_G, "_REQUIREDNAME") == nil)
AA = "x"
-try("X", "XXxX", AA)
+try("X", "XXxX", AA, "libs/XXxX")
removefiles(files)
@@ -183,14 +184,16 @@ files = {
createfiles(files, "_ENV = {}\n", "\nreturn _ENV\n")
AA = 0
-local m = assert(require"P1")
+local m, ext = assert(require"P1")
+assert(ext == "libs/P1/init.lua")
assert(AA == 0 and m.AA == 10)
assert(require"P1" == m)
assert(require"P1" == m)
assert(package.searchpath("P1.xuxu", package.path) == D"P1/xuxu.lua")
-m.xuxu = assert(require"P1.xuxu")
+m.xuxu, ext = assert(require"P1.xuxu")
assert(AA == 0 and m.xuxu.AA == 20)
+assert(ext == "libs/P1/xuxu.lua")
assert(require"P1.xuxu" == m.xuxu)
assert(require"P1.xuxu" == m.xuxu)
assert(require"P1" == m and m.AA == 10)
@@ -267,15 +270,17 @@ else
-- test C modules with prefixes in names
package.cpath = DC"?"
- local lib2 = require"lib2-v2"
+ local lib2, ext = require"lib2-v2"
+ assert(string.find(ext, "libs/lib2-v2", 1, true))
-- check correct access to global environment and correct
-- parameters
assert(_ENV.x == "lib2-v2" and _ENV.y == DC"lib2-v2")
assert(lib2.id("x") == "x")
-- test C submodules
- local fs = require"lib1.sub"
+ local fs, ext = require"lib1.sub"
assert(_ENV.x == "lib1.sub" and _ENV.y == DC"lib1")
+ assert(string.find(ext, "libs/lib1", 1, true))
assert(fs.id(45) == 45)
end
@@ -293,10 +298,10 @@ do
return _ENV
end
- local pl = require"pl"
+ local pl, ext = require"pl"
assert(require"pl" == pl)
assert(pl.xuxu(10) == 30)
- assert(pl[1] == "pl" and pl[2] == nil)
+ assert(pl[1] == "pl" and pl[2] == ":preload:" and ext == ":preload:")
package = p
assert(type(package.path) == "string")
From 20b161e2859837e4f7fb1c19440ad7efe1588f1f Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 22 Apr 2019 12:31:29 -0300
Subject: [PATCH 149/889] Small correction in test about 'isdst'
The field 'isdst' can be false, so we cannot test its absence with
'if not D.isdst'; we must compare with nil for a correct test.
---
testes/files.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/testes/files.lua b/testes/files.lua
index 0a05cf6046..38d3a6693b 100644
--- a/testes/files.lua
+++ b/testes/files.lua
@@ -832,7 +832,7 @@ load(os.date([[!assert(D.year==%Y and D.month==%m and D.day==%d and
do
local D = os.date("*t")
local t = os.time(D)
- if not D.isdst then
+ if D.isdst == nil then
print("no daylight saving information")
else
assert(type(D.isdst) == 'boolean')
From 3da34a5fa70a51f0cf06d677a4f07b470693260c Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 24 Apr 2019 14:01:20 -0300
Subject: [PATCH 150/889] Revamp of 'lua_pushfstring' / 'luaO_pushvfstring'
The function 'luaO_pushvfstring' now uses an internal buffer to
concatenate small strings, instead of pushing all pieces on the
stack. This avoids the creation of several small Lua strings for each
piece of the result. (For instance, a format like "n: '%d'" used to
create three intermediate strings: "n: '", the numeral, and "'".
Now it creates none.)
---
lobject.c | 144 +++++++++++++++++++++++++++++++++------------
ltests.c | 9 +++
luaconf.h | 7 ---
testes/strings.lua | 61 +++++++++++++++++++
4 files changed, 177 insertions(+), 44 deletions(-)
diff --git a/lobject.c b/lobject.c
index 67c37124f3..123f0e570f 100644
--- a/lobject.c
+++ b/lobject.c
@@ -364,85 +364,154 @@ int luaO_utf8esc (char *buff, unsigned long x) {
/*
-** Convert a number object to a string
+** Convert a number object to a string, adding it to a buffer
*/
-void luaO_tostring (lua_State *L, TValue *obj) {
- char buff[MAXNUMBER2STR];
+static size_t tostringbuff (TValue *obj, char *buff) {
size_t len;
lua_assert(ttisnumber(obj));
if (ttisinteger(obj))
- len = lua_integer2str(buff, sizeof(buff), ivalue(obj));
+ len = lua_integer2str(buff, MAXNUMBER2STR, ivalue(obj));
else {
- len = lua_number2str(buff, sizeof(buff), fltvalue(obj));
+ len = lua_number2str(buff, MAXNUMBER2STR, fltvalue(obj));
if (buff[strspn(buff, "-0123456789")] == '\0') { /* looks like an int? */
buff[len++] = lua_getlocaledecpoint();
buff[len++] = '0'; /* adds '.0' to result */
}
}
+ return len;
+}
+
+
+/*
+** Convert a number object to a Lua string, replacing the value at 'obj'
+*/
+void luaO_tostring (lua_State *L, TValue *obj) {
+ char buff[MAXNUMBER2STR];
+ size_t len = tostringbuff(obj, buff);
setsvalue(L, obj, luaS_newlstr(L, buff, len));
}
+/* size for buffer used by 'luaO_pushvfstring' */
+#define BUFVFS 400
+
+/* buffer used by 'luaO_pushvfstring' */
+typedef struct BuffFS {
+ int blen; /* length of partial string in 'buff' */
+ char buff[BUFVFS]; /* holds last part of the result */
+} BuffFS;
+
+
static void pushstr (lua_State *L, const char *str, size_t l) {
setsvalue2s(L, L->top, luaS_newlstr(L, str, l));
L->top++;
}
+/*
+** empty the buffer into the stack
+*/
+static void clearbuff (lua_State *L, BuffFS *buff) {
+ pushstr(L, buff->buff, buff->blen); /* push buffer */
+ buff->blen = 0; /* buffer now is empty */
+}
+
+
+/*
+** Add 'str' to the buffer. It buffer has no enough space,
+** empty the buffer. If string is still larger than the buffer,
+** push the string directly to the stack. Return number of items
+** pushed.
+*/
+static int addstr2buff (lua_State *L, BuffFS *buff, const char *str,
+ size_t slen) {
+ int pushed = 0; /* number of items pushed to the stack */
+ lua_assert(buff->blen <= BUFVFS);
+ if (slen > BUFVFS - cast_sizet(buff->blen)) { /* string does not fit? */
+ clearbuff(L, buff);
+ pushed = 1;
+ if (slen >= BUFVFS) { /* string still does not fit into buffer? */
+ pushstr(L, str, slen); /* push string */
+ return 2;
+ }
+ }
+ memcpy(buff->buff + buff->blen, str, slen); /* add string to buffer */
+ buff->blen += slen;
+ return pushed;
+}
+
+
+/*
+** Add a number to the buffer; return number of strings pushed into
+** the stack. (At most one, to free buffer space.)
+*/
+static int addnum2buff (lua_State *L, BuffFS *buff, TValue *num) {
+ char numbuff[MAXNUMBER2STR];
+ size_t len = tostringbuff(num, numbuff); /* format number into 'numbuff' */
+ return addstr2buff(L, buff, numbuff, len);
+}
+
+
/*
** this function handles only '%d', '%c', '%f', '%p', and '%s'
conventional formats, plus Lua-specific '%I' and '%U'
*/
const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {
- int n = 0; /* number of strings in the stack to concatenate */
- const char *e; /* points to next conversion specifier */
+ BuffFS buff; /* holds last part of the result */
+ int pushed = 0; /* number of strings in the stack to concatenate */
+ const char *e; /* points to next '%' */
+ buff.blen = 0;
while ((e = strchr(fmt, '%')) != NULL) {
- pushstr(L, fmt, e - fmt); /* string up to conversion specifier */
- switch (*(e+1)) {
+ pushed += addstr2buff(L, &buff, fmt, e - fmt); /* add 'fmt' up to '%' */
+ switch (*(e + 1)) { /* conversion specifier */
case 's': { /* zero-terminated string */
const char *s = va_arg(argp, char *);
if (s == NULL) s = "(null)";
- pushstr(L, s, strlen(s));
+ pushed += addstr2buff(L, &buff, s, strlen(s));
break;
}
case 'c': { /* an 'int' as a character */
- char buff = cast_char(va_arg(argp, int));
- if (lisprint(cast_uchar(buff)))
- pushstr(L, &buff, 1);
- else /* non-printable character; print its code */
- luaO_pushfstring(L, "<\\%d>", cast_uchar(buff));
+ /* if non-printable character, print its code */
+ char bf[10];
+ int c = va_arg(argp, int);
+ int l = (lisprint(c)) ? l_sprintf(bf, sizeof(bf), "%c", c)
+ : l_sprintf(bf, sizeof(bf), "<\\%u>", c);
+ pushed += addstr2buff(L, &buff, bf, l);
break;
}
case 'd': { /* an 'int' */
- setivalue(s2v(L->top), va_arg(argp, int));
- goto top2str;
+ TValue num;
+ setivalue(&num, va_arg(argp, int));
+ pushed += addnum2buff(L, &buff, &num);
+ break;
}
case 'I': { /* a 'lua_Integer' */
- setivalue(s2v(L->top), cast(lua_Integer, va_arg(argp, l_uacInt)));
- goto top2str;
+ TValue num;
+ setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt)));
+ pushed += addnum2buff(L, &buff, &num);
+ break;
}
case 'f': { /* a 'lua_Number' */
- setfltvalue(s2v(L->top), cast_num(va_arg(argp, l_uacNumber)));
- top2str: /* convert the top element to a string */
- L->top++;
- luaO_tostring(L, s2v(L->top - 1));
+ TValue num;
+ setfltvalue(&num, cast_num(va_arg(argp, l_uacNumber)));
+ pushed += addnum2buff(L, &buff, &num);
break;
}
case 'p': { /* a pointer */
- char buff[4*sizeof(void *) + 8]; /* should be enough space for a '%p' */
+ char bf[3 * sizeof(void*) + 8]; /* should be enough space for '%p' */
void *p = va_arg(argp, void *);
- int l = lua_pointer2str(buff, sizeof(buff), p);
- pushstr(L, buff, l);
+ int l = l_sprintf(bf, sizeof(bf), "%p", p);
+ pushed += addstr2buff(L, &buff, bf, l);
break;
}
case 'U': { /* a 'long' as a UTF-8 sequence */
- char buff[UTF8BUFFSZ];
- int l = luaO_utf8esc(buff, va_arg(argp, long));
- pushstr(L, buff + UTF8BUFFSZ - l, l);
+ char bf[UTF8BUFFSZ];
+ int l = luaO_utf8esc(bf, va_arg(argp, long));
+ pushed += addstr2buff(L, &buff, bf + UTF8BUFFSZ - l, l);
break;
}
case '%': {
- pushstr(L, "%", 1);
+ pushed += addstr2buff(L, &buff, "%", 1);
break;
}
default: {
@@ -450,15 +519,16 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {
*(e + 1));
}
}
- n += 2;
- if (L->top + 2 > L->stack_last) { /* no free stack space? */
- luaV_concat(L, n);
- n = 1;
+ if (pushed > 1 && L->top + 2 > L->stack_last) { /* no free stack space? */
+ luaV_concat(L, pushed); /* join all partial results into one */
+ pushed = 1;
}
- fmt = e + 2;
+ fmt = e + 2; /* skip '%' and the specifier */
}
- pushstr(L, fmt, strlen(fmt));
- if (n > 0) luaV_concat(L, n + 1);
+ pushed += addstr2buff(L, &buff, fmt, strlen(fmt)); /* rest of 'fmt' */
+ clearbuff(L, &buff); /* empty buffer into the stack */
+ if (pushed > 0)
+ luaV_concat(L, pushed + 1); /* join all partial results */
return svalue(s2v(L->top - 1));
}
diff --git a/ltests.c b/ltests.c
index 40de22927b..7d441d1ac2 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1481,6 +1481,15 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) {
else if EQ("pushvalue") {
lua_pushvalue(L1, getindex);
}
+ else if EQ("pushfstringI") {
+ lua_pushfstring(L1, lua_tostring(L, -2), (int)lua_tointeger(L, -1));
+ }
+ else if EQ("pushfstringS") {
+ lua_pushfstring(L1, lua_tostring(L, -2), lua_tostring(L, -1));
+ }
+ else if EQ("pushfstringP") {
+ lua_pushfstring(L1, lua_tostring(L, -2), lua_topointer(L, -1));
+ }
else if EQ("rawgeti") {
int t = getindex;
lua_rawgeti(L1, t, getnum);
diff --git a/luaconf.h b/luaconf.h
index 76a6161675..019f2eb68a 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -578,13 +578,6 @@
#endif
-/*
-@@ lua_pointer2str converts a pointer to a readable string in a
-** non-specified way.
-*/
-#define lua_pointer2str(buff,sz,p) l_sprintf(buff,sz,"%p",p)
-
-
/*
@@ lua_number2strx converts a float to a hexadecimal numeric string.
** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that.
diff --git a/testes/strings.lua b/testes/strings.lua
index 8bcbb39156..66c1176db5 100644
--- a/testes/strings.lua
+++ b/testes/strings.lua
@@ -400,5 +400,66 @@ do
assert(co() == "2")
end
+
+if T==nil then
+ (Message or print)
+ ("\n >>> testC not active: skipping 'pushfstring' tests <<<\n")
+else
+
+ print"testing 'pushfstring'"
+
+ -- formats %U, %f, %I already tested elsewhere
+
+ local blen = 400 -- internal buffer length in 'luaO_pushfstring'
+
+ local function callpfs (op, fmt, n)
+ local x = {T.testC("pushfstring" .. op .. "; return *", fmt, n)}
+ -- stack has code, 'fmt', 'n', and result from operation
+ assert(#x == 4) -- make sure nothing else was left in the stack
+ return x[4]
+ end
+
+ local function testpfs (op, fmt, n)
+ assert(callpfs(op, fmt, n) == string.format(fmt, n))
+ end
+
+ testpfs("I", "", 0)
+ testpfs("I", string.rep("a", blen - 1), 0)
+ testpfs("I", string.rep("a", blen), 0)
+ testpfs("I", string.rep("a", blen + 1), 0)
+
+ local str = string.rep("ab", blen) .. "%d" .. string.rep("d", blen / 2)
+ testpfs("I", str, 2^14)
+ testpfs("I", str, -2^15)
+
+ str = "%d" .. string.rep("cd", blen)
+ testpfs("I", str, 2^14)
+ testpfs("I", str, -2^15)
+
+ str = string.rep("c", blen - 2) .. "%d"
+ testpfs("I", str, 2^14)
+ testpfs("I", str, -2^15)
+
+ for l = 12, 14 do
+ local str1 = string.rep("a", l)
+ for i = 0, 500, 13 do
+ for j = 0, 500, 13 do
+ str = string.rep("a", i) .. "%s" .. string.rep("d", j)
+ testpfs("S", str, str1)
+ testpfs("S", str, str)
+ end
+ end
+ end
+
+ str = "abc %c def"
+ testpfs("I", str, string.byte("A"))
+ -- non-printable character
+ assert(callpfs("I", str, 255) == "abc <\\255> def")
+
+ str = string.rep("a", blen - 1) .. "%p" .. string.rep("cd", blen)
+ testpfs("P", str, {})
+end
+
+
print('OK')
From c65605151c5a335baf0a9ea251b19df5b2d3a905 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 24 Apr 2019 14:41:41 -0300
Subject: [PATCH 151/889] New function 'luaL_addgsub'
Added a new function 'luaL_addgsub', similar to 'luaL_gsub' but that
adds its result directly to a preexisting buffer, avoiding the creation
of one extra intermediate string. Also added two simple macros,
'luaL_bufflen' and 'luaL_buffaddr', to query the current length
and the contents address of a buffer.
---
lauxlib.c | 20 +++++++++++++-------
lauxlib.h | 16 ++++++++++++----
manual/manual.of | 35 +++++++++++++++++++++++++++++++++--
3 files changed, 58 insertions(+), 13 deletions(-)
diff --git a/lauxlib.c b/lauxlib.c
index e9c02d3660..dfe501a733 100644
--- a/lauxlib.c
+++ b/lauxlib.c
@@ -951,18 +951,24 @@ LUALIB_API void luaL_requiref (lua_State *L, const char *modname,
}
-LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, const char *p,
- const char *r) {
+LUALIB_API void luaL_addgsub (luaL_Buffer *b, const char *s,
+ const char *p, const char *r) {
const char *wild;
size_t l = strlen(p);
- luaL_Buffer b;
- luaL_buffinit(L, &b);
while ((wild = strstr(s, p)) != NULL) {
- luaL_addlstring(&b, s, wild - s); /* push prefix */
- luaL_addstring(&b, r); /* push replacement in place of pattern */
+ luaL_addlstring(b, s, wild - s); /* push prefix */
+ luaL_addstring(b, r); /* push replacement in place of pattern */
s = wild + l; /* continue after 'p' */
}
- luaL_addstring(&b, s); /* push last suffix */
+ luaL_addstring(b, s); /* push last suffix */
+}
+
+
+LUALIB_API const char *luaL_gsub (lua_State *L, const char *s,
+ const char *p, const char *r) {
+ luaL_Buffer b;
+ luaL_buffinit(L, &b);
+ luaL_addgsub(&b, s, p, r);
luaL_pushresult(&b);
return lua_tostring(L, -1);
}
diff --git a/lauxlib.h b/lauxlib.h
index e5d378aebe..f68f6af18c 100644
--- a/lauxlib.h
+++ b/lauxlib.h
@@ -19,6 +19,8 @@
#define LUA_GNAME "_G"
+typedef struct luaL_Buffer luaL_Buffer;
+
/* extra error code for 'luaL_loadfilex' */
#define LUA_ERRFILE (LUA_ERRERR+1)
@@ -99,8 +101,10 @@ LUALIB_API lua_State *(luaL_newstate) (void);
LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx);
-LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p,
- const char *r);
+LUALIB_API void luaL_addgsub (luaL_Buffer *b, const char *s,
+ const char *p, const char *r);
+LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s,
+ const char *p, const char *r);
LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);
@@ -155,7 +159,7 @@ LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname,
** =======================================================
*/
-typedef struct luaL_Buffer {
+struct luaL_Buffer {
char *b; /* buffer address */
size_t size; /* buffer size */
size_t n; /* number of characters in buffer */
@@ -164,7 +168,11 @@ typedef struct luaL_Buffer {
LUAI_MAXALIGN; /* ensure maximum alignment for buffer */
char b[LUAL_BUFFERSIZE]; /* initial buffer */
} init;
-} luaL_Buffer;
+};
+
+
+#define luaL_bufflen(bf) ((bf)->n)
+#define luaL_buffaddr(bf) ((bf)->b)
#define luaL_addchar(B,c) \
diff --git a/manual/manual.of b/manual/manual.of
index 24ac45ae80..5f26570893 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -591,7 +591,7 @@ controls how long the collector waits before starting a new cycle.
The collector starts a new cycle when the use of memory
hits @M{n%} of the use after the previous collection.
Larger values make the collector less aggressive.
-Values less than 100 mean the collector will not wait to
+Values equal to or less than 100 mean the collector will not wait to
start a new cycle.
A value of 200 means that the collector waits for the total memory in use
to double before starting a new cycle.
@@ -4928,6 +4928,18 @@ Adds the byte @id{c} to the buffer @id{B}
}
+@APIEntry{
+const void luaL_addgsub (luaL_Buffer *B, const char *s,
+ const char *p, const char *r);|
+@apii{0,0,m}
+
+Adds a copy of the string @id{s} to the buffer @id{B},
+replacing any occurrence of the string @id{p}
+with the string @id{r}.
+@seeC{luaL_Buffer}.
+
+}
+
@APIEntry{void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l);|
@apii{?,?,m}
@@ -5070,6 +5082,15 @@ plus the final string on its top.
}
+@APIEntry{char *luaL_buffaddr (luaL_Buffer *B);|
+@apii{0,0,-}
+
+Returns the address of the current contents of buffer @id{B}.
+Note that any addition to the buffer may invalidate this address.
+@seeC{luaL_Buffer}.
+
+}
+
@APIEntry{void luaL_buffinit (lua_State *L, luaL_Buffer *B);|
@apii{0,0,-}
@@ -5080,6 +5101,14 @@ the buffer must be declared as a variable
}
+@APIEntry{size_t luaL_bufflen (luaL_Buffer *B);|
+@apii{0,0,-}
+
+Returns the length of the current contents of buffer @id{B}.
+@seeC{luaL_Buffer}.
+
+}
+
@APIEntry{char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz);|
@apii{?,?,m}
@@ -5935,6 +5964,7 @@ This option can be followed by three numbers:
the garbage-collector pause,
the step multiplier,
and the step size.
+A zero means to not change that value.
}
@item{@St{generational}|
@@ -5942,6 +5972,7 @@ Change the collector mode to generational.
This option can be followed by two numbers:
the garbage-collector minor multiplier
and the major multiplier.
+A zero means to not change that value.
}
@item{@St{isrunning}|
@@ -6552,7 +6583,7 @@ the value of the environment variable @defid{LUA_PATH_5_4} or
the environment variable @defid{LUA_PATH} or
with a default path defined in @id{luaconf.h},
if those environment variables are not defined.
-Any @St{;;} in the value of the environment variable
+A @St{;;} in the value of the environment variable
is replaced by the default path.
}
From 969b8c1f14f69c1406f00df3b5f375a9efb7b78f Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 26 Apr 2019 11:13:01 -0300
Subject: [PATCH 152/889] Fixed bug with to-be-closed variables in base C level
To-be-closed variables in C use 'ci.nresults' to code that there is
a variable to be closed in that function. The intialization of the
base C level (the one "running" when calling API functions outside
any Lua call) did not initialize 'ci.nresults', creating (correct)
warnings in valgrind.
---
lstate.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lstate.c b/lstate.c
index 463a47d2bf..387cd362dc 100644
--- a/lstate.c
+++ b/lstate.c
@@ -196,6 +196,8 @@ static void stack_init (lua_State *L1, lua_State *L) {
ci->next = ci->previous = NULL;
ci->callstatus = CIST_C;
ci->func = L1->top;
+ ci->u.c.k = NULL;
+ ci->nresults = 0;
setnilvalue(s2v(L1->top)); /* 'function' entry for this 'ci' */
L1->top++;
ci->top = L1->top + LUA_MINSTACK;
From b36e26f51b117df98f0f5376f352c2381df3025f Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 26 Apr 2019 11:24:39 -0300
Subject: [PATCH 153/889] Some more small improvements to 'luaO_pushvfstring'
Details:
- counter 'pushed' moved to the struct 'BuffFS'
- new auxiliar function 'getbuff' to build strings directly on
the buffer.
---
lobject.c | 122 +++++++++++++++++++++++++--------------------
testes/strings.lua | 3 ++
2 files changed, 71 insertions(+), 54 deletions(-)
diff --git a/lobject.c b/lobject.c
index 123f0e570f..58eecd4a36 100644
--- a/lobject.c
+++ b/lobject.c
@@ -392,126 +392,144 @@ void luaO_tostring (lua_State *L, TValue *obj) {
}
-/* size for buffer used by 'luaO_pushvfstring' */
+/* size for buffer space used by 'luaO_pushvfstring' */
#define BUFVFS 400
/* buffer used by 'luaO_pushvfstring' */
typedef struct BuffFS {
- int blen; /* length of partial string in 'buff' */
- char buff[BUFVFS]; /* holds last part of the result */
+ int pushed; /* number of string pieces already on the stack */
+ int blen; /* length of partial string in 'space' */
+ char space[BUFVFS]; /* holds last part of the result */
} BuffFS;
-static void pushstr (lua_State *L, const char *str, size_t l) {
+/*
+** Push given string to the stack, as part of the buffer. If the stack
+** is almost full, join all partial strings in the stack into one.
+*/
+static void pushstr (lua_State *L, BuffFS *buff, const char *str, size_t l) {
setsvalue2s(L, L->top, luaS_newlstr(L, str, l));
L->top++;
+ buff->pushed++;
+ if (buff->pushed > 1 && L->top + 2 > L->stack_last) {
+ luaV_concat(L, buff->pushed); /* join all partial results into one */
+ buff->pushed = 1;
+ }
}
/*
-** empty the buffer into the stack
+** empty the buffer space into the stack
*/
static void clearbuff (lua_State *L, BuffFS *buff) {
- pushstr(L, buff->buff, buff->blen); /* push buffer */
- buff->blen = 0; /* buffer now is empty */
+ pushstr(L, buff, buff->space, buff->blen); /* push buffer contents */
+ buff->blen = 0; /* space now is empty */
}
/*
-** Add 'str' to the buffer. It buffer has no enough space,
-** empty the buffer. If string is still larger than the buffer,
-** push the string directly to the stack. Return number of items
-** pushed.
+** Get a space of size 'sz' in the buffer. If buffer has not enough
+** space, empty it. 'sz' must fit in an empty space.
*/
-static int addstr2buff (lua_State *L, BuffFS *buff, const char *str,
- size_t slen) {
- int pushed = 0; /* number of items pushed to the stack */
- lua_assert(buff->blen <= BUFVFS);
- if (slen > BUFVFS - cast_sizet(buff->blen)) { /* string does not fit? */
+static char *getbuff (lua_State *L, BuffFS *buff, size_t sz) {
+ lua_assert(buff->blen <= BUFVFS); lua_assert(sz <= BUFVFS);
+ if (sz > BUFVFS - cast_sizet(buff->blen)) /* string does not fit? */
clearbuff(L, buff);
- pushed = 1;
- if (slen >= BUFVFS) { /* string still does not fit into buffer? */
- pushstr(L, str, slen); /* push string */
- return 2;
- }
+ return buff->space + buff->blen;
+}
+
+
+#define addsize(b,sz) ((b)->blen += (sz))
+
+
+/*
+** Add 'str' to the buffer. If string is larger than the buffer space,
+** push the string directly to the stack.
+*/
+static void addstr2buff (lua_State *L, BuffFS *buff, const char *str,
+ size_t slen) {
+ if (slen <= BUFVFS) { /* does string fit into buffer? */
+ char *bf = getbuff(L, buff, slen);
+ memcpy(bf, str, slen); /* add string to buffer */
+ addsize(buff, slen);
+ }
+ else { /* string larger than buffer */
+ clearbuff(L, buff); /* string comes after buffer's content */
+ pushstr(L, buff, str, slen); /* push string */
}
- memcpy(buff->buff + buff->blen, str, slen); /* add string to buffer */
- buff->blen += slen;
- return pushed;
}
/*
-** Add a number to the buffer; return number of strings pushed into
-** the stack. (At most one, to free buffer space.)
+** Add a number to the buffer.
*/
-static int addnum2buff (lua_State *L, BuffFS *buff, TValue *num) {
- char numbuff[MAXNUMBER2STR];
+static void addnum2buff (lua_State *L, BuffFS *buff, TValue *num) {
+ char *numbuff = getbuff(L, buff, MAXNUMBER2STR);
size_t len = tostringbuff(num, numbuff); /* format number into 'numbuff' */
- return addstr2buff(L, buff, numbuff, len);
+ addsize(buff, len);
}
/*
-** this function handles only '%d', '%c', '%f', '%p', and '%s'
+** this function handles only '%d', '%c', '%f', '%p', '%s', and '%%'
conventional formats, plus Lua-specific '%I' and '%U'
*/
const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {
BuffFS buff; /* holds last part of the result */
- int pushed = 0; /* number of strings in the stack to concatenate */
const char *e; /* points to next '%' */
- buff.blen = 0;
+ buff.pushed = buff.blen = 0;
while ((e = strchr(fmt, '%')) != NULL) {
- pushed += addstr2buff(L, &buff, fmt, e - fmt); /* add 'fmt' up to '%' */
+ addstr2buff(L, &buff, fmt, e - fmt); /* add 'fmt' up to '%' */
switch (*(e + 1)) { /* conversion specifier */
case 's': { /* zero-terminated string */
const char *s = va_arg(argp, char *);
if (s == NULL) s = "(null)";
- pushed += addstr2buff(L, &buff, s, strlen(s));
+ addstr2buff(L, &buff, s, strlen(s));
break;
}
case 'c': { /* an 'int' as a character */
/* if non-printable character, print its code */
- char bf[10];
+ char *bf = getbuff(L, &buff, 10);
int c = va_arg(argp, int);
- int l = (lisprint(c)) ? l_sprintf(bf, sizeof(bf), "%c", c)
- : l_sprintf(bf, sizeof(bf), "<\\%u>", c);
- pushed += addstr2buff(L, &buff, bf, l);
+ int len = (lisprint(c)) ? l_sprintf(bf, 10, "%c", c)
+ : l_sprintf(bf, 10, "<\\%u>", c);
+ addsize(&buff, len);
break;
}
case 'd': { /* an 'int' */
TValue num;
setivalue(&num, va_arg(argp, int));
- pushed += addnum2buff(L, &buff, &num);
+ addnum2buff(L, &buff, &num);
break;
}
case 'I': { /* a 'lua_Integer' */
TValue num;
setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt)));
- pushed += addnum2buff(L, &buff, &num);
+ addnum2buff(L, &buff, &num);
break;
}
case 'f': { /* a 'lua_Number' */
TValue num;
setfltvalue(&num, cast_num(va_arg(argp, l_uacNumber)));
- pushed += addnum2buff(L, &buff, &num);
+ addnum2buff(L, &buff, &num);
break;
}
case 'p': { /* a pointer */
- char bf[3 * sizeof(void*) + 8]; /* should be enough space for '%p' */
+ const int sz = 3 * sizeof(void*) + 8; /* enough space for '%p' */
+ char *bf = getbuff(L, &buff, sz);
void *p = va_arg(argp, void *);
- int l = l_sprintf(bf, sizeof(bf), "%p", p);
- pushed += addstr2buff(L, &buff, bf, l);
+ int len = l_sprintf(bf, sz, "%p", p);
+ addsize(&buff, len);
break;
}
case 'U': { /* a 'long' as a UTF-8 sequence */
char bf[UTF8BUFFSZ];
- int l = luaO_utf8esc(bf, va_arg(argp, long));
- pushed += addstr2buff(L, &buff, bf + UTF8BUFFSZ - l, l);
+ int len = luaO_utf8esc(bf, va_arg(argp, long));
+ addstr2buff(L, &buff, bf + UTF8BUFFSZ - len, len);
break;
}
case '%': {
- pushed += addstr2buff(L, &buff, "%", 1);
+ addstr2buff(L, &buff, "%", 1);
break;
}
default: {
@@ -519,16 +537,12 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {
*(e + 1));
}
}
- if (pushed > 1 && L->top + 2 > L->stack_last) { /* no free stack space? */
- luaV_concat(L, pushed); /* join all partial results into one */
- pushed = 1;
- }
fmt = e + 2; /* skip '%' and the specifier */
}
- pushed += addstr2buff(L, &buff, fmt, strlen(fmt)); /* rest of 'fmt' */
+ addstr2buff(L, &buff, fmt, strlen(fmt)); /* rest of 'fmt' */
clearbuff(L, &buff); /* empty buffer into the stack */
- if (pushed > 0)
- luaV_concat(L, pushed + 1); /* join all partial results */
+ if (buff.pushed > 1)
+ luaV_concat(L, buff.pushed); /* join all partial results */
return svalue(s2v(L->top - 1));
}
diff --git a/testes/strings.lua b/testes/strings.lua
index 66c1176db5..bc123d1ab4 100644
--- a/testes/strings.lua
+++ b/testes/strings.lua
@@ -458,6 +458,9 @@ else
str = string.rep("a", blen - 1) .. "%p" .. string.rep("cd", blen)
testpfs("P", str, {})
+
+ str = string.rep("%%", 3 * blen) .. "%p" .. string.rep("%%", 2 * blen)
+ testpfs("P", str, {})
end
From b14609032cf328dea48b0803f3e585e223283b3d Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 3 May 2019 10:14:25 -0300
Subject: [PATCH 154/889] Avoid the creation of too many strings in 'package'
Both when setting a path and searching for a file ('searchpath'),
this commit reduces the number of intermediate strings created
in Lua.
(For setting a path the change is not relevant, because this is
done only twice when loading the module. Anyway, it is a nice example
of how to use auxlib buffers to manipulate strings in the C API.)
---
lgc.h | 2 +-
llimits.h | 2 +-
loadlib.c | 89 ++++++++++++++++++++++++++++++++-----------------
testes/main.lua | 27 ++++++++++-----
4 files changed, 79 insertions(+), 41 deletions(-)
diff --git a/lgc.h b/lgc.h
index 9ba7ecb050..b972472f8d 100644
--- a/lgc.h
+++ b/lgc.h
@@ -127,7 +127,7 @@
/*
** some gc parameters are stored divided by 4 to allow a maximum value
-** larger than 1000 in a 'lu_byte'.
+** up to 1023 in a 'lu_byte'.
*/
#define getgcparam(p) ((p) * 4)
#define setgcparam(p,v) ((p) = (v) / 4)
diff --git a/llimits.h b/llimits.h
index cc983972ef..febf7555cb 100644
--- a/llimits.h
+++ b/llimits.h
@@ -39,7 +39,7 @@ typedef signed char ls_byte;
/* maximum value for size_t */
#define MAX_SIZET ((size_t)(~(size_t)0))
-/* maximum size visible for Lua (must be representable in a lua_Integer */
+/* maximum size visible for Lua (must be representable in a lua_Integer) */
#define MAX_SIZE (sizeof(size_t) < sizeof(lua_Integer) ? MAX_SIZET \
: (size_t)(LUA_MAXINTEGER))
diff --git a/loadlib.c b/loadlib.c
index 4cf9aec31f..ff73a4599f 100644
--- a/loadlib.c
+++ b/loadlib.c
@@ -290,22 +290,33 @@ static int noenv (lua_State *L) {
static void setpath (lua_State *L, const char *fieldname,
const char *envname,
const char *dft) {
+ const char *dftmark;
const char *nver = lua_pushfstring(L, "%s%s", envname, LUA_VERSUFFIX);
- const char *path = getenv(nver); /* use versioned name */
- if (path == NULL) /* no environment variable? */
+ const char *path = getenv(nver); /* try versioned name */
+ if (path == NULL) /* no versioned environment variable? */
path = getenv(envname); /* try unversioned name */
if (path == NULL || noenv(L)) /* no environment variable? */
lua_pushstring(L, dft); /* use default */
- else {
- /* replace ";;" by ";AUXMARK;" and then AUXMARK by default path */
- path = luaL_gsub(L, path, LUA_PATH_SEP LUA_PATH_SEP,
- LUA_PATH_SEP AUXMARK LUA_PATH_SEP);
- luaL_gsub(L, path, AUXMARK, dft);
- lua_remove(L, -2); /* remove result from 1st 'gsub' */
+ else if ((dftmark = strstr(path, LUA_PATH_SEP LUA_PATH_SEP)) == NULL)
+ lua_pushstring(L, path); /* nothing to change */
+ else { /* path contains a ";;": insert default path in its place */
+ size_t len = strlen(path);
+ luaL_Buffer b;
+ luaL_buffinit(L, &b);
+ if (path < dftmark) { /* is there a prefix before ';;'? */
+ luaL_addlstring(&b, path, dftmark - path); /* add it */
+ luaL_addchar(&b, *LUA_PATH_SEP);
+ }
+ luaL_addstring(&b, dft); /* add default */
+ if (dftmark < path + len - 2) { /* is there a sufix after ';;'? */
+ luaL_addchar(&b, *LUA_PATH_SEP);
+ luaL_addlstring(&b, dftmark + 2, (path + len - 2) - dftmark);
+ }
+ luaL_pushresult(&b);
}
setprogdir(L);
lua_setfield(L, -3, fieldname); /* package[fieldname] = path value */
- lua_pop(L, 1); /* pop versioned variable name */
+ lua_pop(L, 1); /* pop versioned variable name ('nver') */
}
/* }================================================================== */
@@ -421,17 +432,26 @@ static int readable (const char *filename) {
}
-static const char *pushnextfilename (lua_State *L, const char *path) {
- const char *l;
- if (*path == *LUA_PATH_SEP)
- path++; /* skip separator */
- if (*path == '\0')
+/*
+** Get the next name in '*path' = 'name1;name2;name3;...', changing
+** the ending ';' to '\0' to create a zero-terminated string. Return
+** NULL when list ends.
+*/
+static const char *getnextfilename (char **path, char *end) {
+ char *sep;
+ char *name = *path;
+ if (name == end)
return NULL; /* no more names */
- l = strchr(path, *LUA_PATH_SEP); /* find next separator */
- if (l == NULL) /* no more separators? */
- l = path + strlen(path); /* go until the end */
- lua_pushlstring(L, path, l - path); /* file name */
- return l; /* rest of the path */
+ else if (*name == '\0') { /* from previous iteration? */
+ *name = *LUA_PATH_SEP; /* restore separator */
+ name++; /* skip it */
+ }
+ sep = strchr(name, *LUA_PATH_SEP); /* find next separator */
+ if (sep == NULL) /* separator not found? */
+ sep = end; /* name goes until the end */
+ *sep = '\0'; /* finish file name */
+ *path = sep; /* will start next search from here */
+ return name;
}
@@ -442,12 +462,12 @@ static const char *pushnextfilename (lua_State *L, const char *path) {
** no file 'blublu.so'
*/
static void pusherrornotfound (lua_State *L, const char *path) {
- if (*path == *LUA_PATH_SEP)
- path++; /* skip separator */
- lua_pushstring(L, "\n\tno file '");
- luaL_gsub(L, path, LUA_PATH_SEP, "'\n\tno file '");
- lua_pushstring(L, "'");
- lua_concat(L, 3);
+ luaL_Buffer b;
+ luaL_buffinit(L, &b);
+ luaL_addstring(&b, "\n\tno file '");
+ luaL_addgsub(&b, path, LUA_PATH_SEP, "'\n\tno file '");
+ luaL_addstring(&b, "'");
+ luaL_pushresult(&b);
}
@@ -455,17 +475,24 @@ static const char *searchpath (lua_State *L, const char *name,
const char *path,
const char *sep,
const char *dirsep) {
+ luaL_Buffer buff;
+ char *pathname; /* path with name inserted */
+ char *endpathname; /* its end */
+ const char *filename;
/* separator is non-empty and appears in 'name'? */
if (*sep != '\0' && strchr(name, *sep) != NULL)
name = luaL_gsub(L, name, sep, dirsep); /* replace it by 'dirsep' */
- /* replace marks ('?') in 'path' by the file name */
- path = luaL_gsub(L, path, LUA_PATH_MARK, name);
- while ((path = pushnextfilename(L, path)) != NULL) {
- const char *filename = lua_tostring(L, -1);
+ luaL_buffinit(L, &buff);
+ /* add path to the buffer, replacing marks ('?') with the file name */
+ luaL_addgsub(&buff, path, LUA_PATH_MARK, name);
+ luaL_addchar(&buff, '\0');
+ pathname = luaL_buffaddr(&buff); /* writable list of file names */
+ endpathname = pathname + luaL_bufflen(&buff) - 1;
+ while ((filename = getnextfilename(&pathname, endpathname)) != NULL) {
if (readable(filename)) /* does file exist and is readable? */
- return filename; /* return that file name */
- lua_pop(L, 1); /* else remove file name */
+ return lua_pushstring(L, filename); /* save and return name */
}
+ luaL_pushresult(&buff); /* push path to create error message */
pusherrornotfound(L, lua_tostring(L, -1)); /* create error message */
return NULL; /* not found */
}
diff --git a/testes/main.lua b/testes/main.lua
index b9dcab1c12..aab490c884 100644
--- a/testes/main.lua
+++ b/testes/main.lua
@@ -142,12 +142,18 @@ do
prepfile("print(package.path, package.cpath)")
RUN('env LUA_INIT="error(10)" LUA_PATH=xxx LUA_CPATH=xxx lua -E %s > %s',
prog, out)
+ local output = getoutput()
+ defaultpath = string.match(output, "^(.-)\t")
+ defaultCpath = string.match(output, "\t(.-)$")
+
+ -- running with an empty environment
+ RUN('env -i lua %s > %s', prog, out)
local out = getoutput()
- defaultpath = string.match(out, "^(.-)\t")
- defaultCpath = string.match(out, "\t(.-)$")
+ assert(defaultpath == string.match(output, "^(.-)\t"))
+ assert(defaultCpath == string.match(output, "\t(.-)$"))
end
--- paths did not changed
+-- paths did not change
assert(not string.find(defaultpath, "xxx") and
string.find(defaultpath, "lua") and
not string.find(defaultCpath, "xxx") and
@@ -160,15 +166,20 @@ local function convert (p)
RUN('env LUA_PATH="%s" lua %s > %s', p, prog, out)
local expected = getoutput()
expected = string.sub(expected, 1, -2) -- cut final end of line
- assert(string.gsub(p, ";;", ";"..defaultpath..";") == expected)
+ if string.find(p, ";;") then
+ p = string.gsub(p, ";;", ";"..defaultpath..";")
+ p = string.gsub(p, "^;", "") -- remove ';' at the beginning
+ p = string.gsub(p, ";$", "") -- remove ';' at the end
+ end
+ assert(p == expected)
end
convert(";")
convert(";;")
-convert(";;;")
-convert(";;;;")
-convert(";;;;;")
-convert(";;a;;;bc")
+convert("a;;b")
+convert(";;b")
+convert("a;;")
+convert("a;b;;c")
-- test -l over multiple libraries
From 7c5786479c1d617ec7c133f2c2b955726436267a Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 3 May 2019 10:18:44 -0300
Subject: [PATCH 155/889] A few more improvements in 'luaO_pushvfstring'
- 'L' added to the 'BuffFS' structure
- '%c' does not handle control characters (it is not its business.
This now is done by the lexer, who is the one in charge of that
kind of errors.)
- avoid the direct use of 'l_sprintf' in the Lua kernel
---
llex.c | 5 +++-
lobject.c | 66 ++++++++++++++++++++++++++--------------------
luaconf.h | 13 ++++++---
testes/strings.lua | 3 +--
4 files changed, 52 insertions(+), 35 deletions(-)
diff --git a/llex.c b/llex.c
index bb81cec430..226127f69a 100644
--- a/llex.c
+++ b/llex.c
@@ -82,7 +82,10 @@ void luaX_init (lua_State *L) {
const char *luaX_token2str (LexState *ls, int token) {
if (token < FIRST_RESERVED) { /* single-byte symbols? */
lua_assert(token == cast_uchar(token));
- return luaO_pushfstring(ls->L, "'%c'", token);
+ if (lisprint(token))
+ return luaO_pushfstring(ls->L, "'%c'", token);
+ else /* control character */
+ return luaO_pushfstring(ls->L, "'<\\%d>'", token);
}
else {
const char *s = luaX_tokens[token - FIRST_RESERVED];
diff --git a/lobject.c b/lobject.c
index 58eecd4a36..ce14059f47 100644
--- a/lobject.c
+++ b/lobject.c
@@ -392,11 +392,20 @@ void luaO_tostring (lua_State *L, TValue *obj) {
}
+
+
+/*
+** {==================================================================
+** 'luaO_pushvfstring'
+** ===================================================================
+*/
+
/* size for buffer space used by 'luaO_pushvfstring' */
#define BUFVFS 400
/* buffer used by 'luaO_pushvfstring' */
typedef struct BuffFS {
+ lua_State *L;
int pushed; /* number of string pieces already on the stack */
int blen; /* length of partial string in 'space' */
char space[BUFVFS]; /* holds last part of the result */
@@ -407,7 +416,8 @@ typedef struct BuffFS {
** Push given string to the stack, as part of the buffer. If the stack
** is almost full, join all partial strings in the stack into one.
*/
-static void pushstr (lua_State *L, BuffFS *buff, const char *str, size_t l) {
+static void pushstr (BuffFS *buff, const char *str, size_t l) {
+ lua_State *L = buff->L;
setsvalue2s(L, L->top, luaS_newlstr(L, str, l));
L->top++;
buff->pushed++;
@@ -421,8 +431,8 @@ static void pushstr (lua_State *L, BuffFS *buff, const char *str, size_t l) {
/*
** empty the buffer space into the stack
*/
-static void clearbuff (lua_State *L, BuffFS *buff) {
- pushstr(L, buff, buff->space, buff->blen); /* push buffer contents */
+static void clearbuff (BuffFS *buff) {
+ pushstr(buff, buff->space, buff->blen); /* push buffer contents */
buff->blen = 0; /* space now is empty */
}
@@ -431,10 +441,10 @@ static void clearbuff (lua_State *L, BuffFS *buff) {
** Get a space of size 'sz' in the buffer. If buffer has not enough
** space, empty it. 'sz' must fit in an empty space.
*/
-static char *getbuff (lua_State *L, BuffFS *buff, size_t sz) {
+static char *getbuff (BuffFS *buff, size_t sz) {
lua_assert(buff->blen <= BUFVFS); lua_assert(sz <= BUFVFS);
if (sz > BUFVFS - cast_sizet(buff->blen)) /* string does not fit? */
- clearbuff(L, buff);
+ clearbuff(buff);
return buff->space + buff->blen;
}
@@ -446,16 +456,15 @@ static char *getbuff (lua_State *L, BuffFS *buff, size_t sz) {
** Add 'str' to the buffer. If string is larger than the buffer space,
** push the string directly to the stack.
*/
-static void addstr2buff (lua_State *L, BuffFS *buff, const char *str,
- size_t slen) {
+static void addstr2buff (BuffFS *buff, const char *str, size_t slen) {
if (slen <= BUFVFS) { /* does string fit into buffer? */
- char *bf = getbuff(L, buff, slen);
+ char *bf = getbuff(buff, slen);
memcpy(bf, str, slen); /* add string to buffer */
addsize(buff, slen);
}
else { /* string larger than buffer */
- clearbuff(L, buff); /* string comes after buffer's content */
- pushstr(L, buff, str, slen); /* push string */
+ clearbuff(buff); /* string comes after buffer's content */
+ pushstr(buff, str, slen); /* push string */
}
}
@@ -463,8 +472,8 @@ static void addstr2buff (lua_State *L, BuffFS *buff, const char *str,
/*
** Add a number to the buffer.
*/
-static void addnum2buff (lua_State *L, BuffFS *buff, TValue *num) {
- char *numbuff = getbuff(L, buff, MAXNUMBER2STR);
+static void addnum2buff (BuffFS *buff, TValue *num) {
+ char *numbuff = getbuff(buff, MAXNUMBER2STR);
size_t len = tostringbuff(num, numbuff); /* format number into 'numbuff' */
addsize(buff, len);
}
@@ -478,58 +487,55 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {
BuffFS buff; /* holds last part of the result */
const char *e; /* points to next '%' */
buff.pushed = buff.blen = 0;
+ buff.L = L;
while ((e = strchr(fmt, '%')) != NULL) {
- addstr2buff(L, &buff, fmt, e - fmt); /* add 'fmt' up to '%' */
+ addstr2buff(&buff, fmt, e - fmt); /* add 'fmt' up to '%' */
switch (*(e + 1)) { /* conversion specifier */
case 's': { /* zero-terminated string */
const char *s = va_arg(argp, char *);
if (s == NULL) s = "(null)";
- addstr2buff(L, &buff, s, strlen(s));
+ addstr2buff(&buff, s, strlen(s));
break;
}
case 'c': { /* an 'int' as a character */
- /* if non-printable character, print its code */
- char *bf = getbuff(L, &buff, 10);
- int c = va_arg(argp, int);
- int len = (lisprint(c)) ? l_sprintf(bf, 10, "%c", c)
- : l_sprintf(bf, 10, "<\\%u>", c);
- addsize(&buff, len);
+ char c = cast_uchar(va_arg(argp, int));
+ addstr2buff(&buff, &c, sizeof(char));
break;
}
case 'd': { /* an 'int' */
TValue num;
setivalue(&num, va_arg(argp, int));
- addnum2buff(L, &buff, &num);
+ addnum2buff(&buff, &num);
break;
}
case 'I': { /* a 'lua_Integer' */
TValue num;
setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt)));
- addnum2buff(L, &buff, &num);
+ addnum2buff(&buff, &num);
break;
}
case 'f': { /* a 'lua_Number' */
TValue num;
setfltvalue(&num, cast_num(va_arg(argp, l_uacNumber)));
- addnum2buff(L, &buff, &num);
+ addnum2buff(&buff, &num);
break;
}
case 'p': { /* a pointer */
const int sz = 3 * sizeof(void*) + 8; /* enough space for '%p' */
- char *bf = getbuff(L, &buff, sz);
+ char *bf = getbuff(&buff, sz);
void *p = va_arg(argp, void *);
- int len = l_sprintf(bf, sz, "%p", p);
+ int len = lua_pointer2str(bf, sz, p);
addsize(&buff, len);
break;
}
case 'U': { /* a 'long' as a UTF-8 sequence */
char bf[UTF8BUFFSZ];
int len = luaO_utf8esc(bf, va_arg(argp, long));
- addstr2buff(L, &buff, bf + UTF8BUFFSZ - len, len);
+ addstr2buff(&buff, bf + UTF8BUFFSZ - len, len);
break;
}
case '%': {
- addstr2buff(L, &buff, "%", 1);
+ addstr2buff(&buff, "%", 1);
break;
}
default: {
@@ -539,8 +545,8 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {
}
fmt = e + 2; /* skip '%' and the specifier */
}
- addstr2buff(L, &buff, fmt, strlen(fmt)); /* rest of 'fmt' */
- clearbuff(L, &buff); /* empty buffer into the stack */
+ addstr2buff(&buff, fmt, strlen(fmt)); /* rest of 'fmt' */
+ clearbuff(&buff); /* empty buffer into the stack */
if (buff.pushed > 1)
luaV_concat(L, buff.pushed); /* join all partial results */
return svalue(s2v(L->top - 1));
@@ -556,6 +562,8 @@ const char *luaO_pushfstring (lua_State *L, const char *fmt, ...) {
return msg;
}
+/* }================================================================== */
+
#define RETS "..."
#define PRE "[string \""
diff --git a/luaconf.h b/luaconf.h
index 019f2eb68a..9240d9c80e 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -376,7 +376,7 @@
@@ lua_number2str converts a float to a string.
@@ l_mathop allows the addition of an 'l' or 'f' to all math operations.
@@ l_floor takes the floor of a float.
-@@ lua_str2number converts a decimal numeric string to a number.
+@@ lua_str2number converts a decimal numeral to a number.
*/
@@ -568,7 +568,7 @@
/*
-@@ lua_strx2number converts a hexadecimal numeric string to a number.
+@@ lua_strx2number converts a hexadecimal numeral to a number.
** In C99, 'strtod' does that conversion. Otherwise, you can
** leave 'lua_strx2number' undefined and Lua will provide its own
** implementation.
@@ -579,7 +579,14 @@
/*
-@@ lua_number2strx converts a float to a hexadecimal numeric string.
+@@ lua_pointer2str converts a pointer to a readable string in a
+** non-specified way.
+*/
+#define lua_pointer2str(buff,sz,p) l_sprintf(buff,sz,"%p",p)
+
+
+/*
+@@ lua_number2strx converts a float to a hexadecimal numeral.
** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that.
** Otherwise, you can leave 'lua_number2strx' undefined and Lua will
** provide its own implementation.
diff --git a/testes/strings.lua b/testes/strings.lua
index bc123d1ab4..3e32f2c476 100644
--- a/testes/strings.lua
+++ b/testes/strings.lua
@@ -453,8 +453,7 @@ else
str = "abc %c def"
testpfs("I", str, string.byte("A"))
- -- non-printable character
- assert(callpfs("I", str, 255) == "abc <\\255> def")
+ testpfs("I", str, 255)
str = string.rep("a", blen - 1) .. "%p" .. string.rep("cd", blen)
testpfs("P", str, {})
From 01bded3d8cd88a2d7f472b45f706565f1a9ef3b1 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 3 May 2019 10:36:19 -0300
Subject: [PATCH 156/889] File 'lib2-v2.so' generated from its own source
Instead of being a copy of 'lib2.so', 'lib2-v2.so' has its own source
file ('lib22.c'), so that the test can distinguish both libraries.
---
testes/attrib.lua | 2 +-
testes/libs/lib22.c | 25 +++++++++++++++++++++++++
testes/libs/makefile | 4 ++--
3 files changed, 28 insertions(+), 3 deletions(-)
create mode 100644 testes/libs/lib22.c
diff --git a/testes/attrib.lua b/testes/attrib.lua
index 4adb42e0d2..b1a4a1994e 100644
--- a/testes/attrib.lua
+++ b/testes/attrib.lua
@@ -275,7 +275,7 @@ else
-- check correct access to global environment and correct
-- parameters
assert(_ENV.x == "lib2-v2" and _ENV.y == DC"lib2-v2")
- assert(lib2.id("x") == "x")
+ assert(lib2.id("x") == true) -- a different "id" implementation
-- test C submodules
local fs, ext = require"lib1.sub"
diff --git a/testes/libs/lib22.c b/testes/libs/lib22.c
new file mode 100644
index 0000000000..8e6565022e
--- /dev/null
+++ b/testes/libs/lib22.c
@@ -0,0 +1,25 @@
+#include "lua.h"
+#include "lauxlib.h"
+
+static int id (lua_State *L) {
+ lua_pushboolean(L, 1);
+ lua_insert(L, 1);
+ return lua_gettop(L);
+}
+
+
+static const struct luaL_Reg funcs[] = {
+ {"id", id},
+ {NULL, NULL}
+};
+
+
+LUAMOD_API int luaopen_lib2 (lua_State *L) {
+ lua_settop(L, 2);
+ lua_setglobal(L, "y"); /* y gets 2nd parameter */
+ lua_setglobal(L, "x"); /* x gets 1st parameter */
+ luaL_newlib(L, funcs);
+ return 1;
+}
+
+
diff --git a/testes/libs/makefile b/testes/libs/makefile
index acff4848c2..698f8984f5 100644
--- a/testes/libs/makefile
+++ b/testes/libs/makefile
@@ -23,5 +23,5 @@ lib2.so: lib2.c $(LUA_DIR)/luaconf.h $(LUA_DIR)/ltests.h
lib21.so: lib21.c $(LUA_DIR)/luaconf.h $(LUA_DIR)/ltests.h
$(CC) $(CFLAGS) -o lib21.so lib21.c
-lib2-v2.so: lib2.so
- cp lib2.so ./lib2-v2.so
+lib2-v2.so: lib21.c $(LUA_DIR)/luaconf.h $(LUA_DIR)/ltests.h
+ $(CC) $(CFLAGS) -o lib2-v2.so lib22.c
From 389116d8abcc96db3cfe2f3cc25789c089fe12d6 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 9 May 2019 11:13:45 -0300
Subject: [PATCH 157/889] Coroutines do not unwind the stack in case of errors
Back to how it was, a coroutine does not unwind its stack in case of
errors (and therefore do not close its to-be-closed variables). This
allows the stack to be examined after the error. The program can
use 'coroutine.kill' to close the variables.
The function created by 'coroutine.wrap', however, closes the
coroutine's variables in case of errors, as it is impossible to examine
the stack any way.
---
lcorolib.c | 12 +++++++++++-
ldo.c | 4 +---
manual/manual.of | 33 +++++++++++++++++++++++++--------
testes/coroutine.lua | 8 ++++++--
testes/db.lua | 14 ++++++++++----
testes/locals.lua | 35 +++++++++++++++++++++++++++--------
6 files changed, 80 insertions(+), 26 deletions(-)
diff --git a/lcorolib.c b/lcorolib.c
index f7c9e165f5..a21880d5e0 100644
--- a/lcorolib.c
+++ b/lcorolib.c
@@ -25,6 +25,10 @@ static lua_State *getco (lua_State *L) {
}
+/*
+** Resumes a coroutine. Returns the number of results for non-error
+** cases or -1 for errors.
+*/
static int auxresume (lua_State *L, lua_State *co, int narg) {
int status, nres;
if (!lua_checkstack(co, narg)) {
@@ -74,8 +78,14 @@ static int luaB_auxwrap (lua_State *L) {
lua_State *co = lua_tothread(L, lua_upvalueindex(1));
int r = auxresume(L, co, lua_gettop(L));
if (r < 0) {
+ int stat = lua_status(co);
+ if (stat != LUA_OK && stat != LUA_YIELD) {
+ stat = lua_resetthread(co); /* close variables in case of errors */
+ if (stat != LUA_OK) /* error closing variables? */
+ lua_xmove(co, L, 1); /* get new error object */
+ }
if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */
- luaL_where(L, 1); /* add extra info */
+ luaL_where(L, 1); /* add extra info, if available */
lua_insert(L, -2);
lua_concat(L, 2);
}
diff --git a/ldo.c b/ldo.c
index 2a98c3977a..d474c0a02c 100644
--- a/ldo.c
+++ b/ldo.c
@@ -686,10 +686,8 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
if (likely(!errorstatus(status)))
lua_assert(status == L->status); /* normal end or yield */
else { /* unrecoverable error */
- status = luaF_close(L, L->stack, status); /* close all upvalues */
L->status = cast_byte(status); /* mark thread as 'dead' */
- luaD_seterrorobj(L, status, L->stack + 1); /* push error message */
- L->ci = &L->base_ci; /* back to the original C level */
+ luaD_seterrorobj(L, status, L->top); /* push error message */
L->ci->top = L->top;
}
*nresults = (status == LUA_YIELD) ? L->ci->u2.nyield
diff --git a/manual/manual.of b/manual/manual.of
index 5f26570893..cf44b4f2ec 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -286,7 +286,7 @@ Lua also offers a system of @emph{warnings} @seeF{warn}.
Unlike errors, warnings do not interfere
in any way with program execution.
They typically only generate a message to the user,
-although this behavior can be adapted from C @see{lua_setwarnf}.
+although this behavior can be adapted from C @seeC{lua_setwarnf}.
}
@@ -835,6 +835,9 @@ In case of normal termination,
plus any values returned by the coroutine main function.
In case of errors, @Lid{coroutine.resume} returns @false
plus the error object.
+In this case, the coroutine does not unwind its stack,
+so that it is possible to inspect it after the error
+with the debug API.
A coroutine yields by calling @Lid{coroutine.yield}.
When a coroutine yields,
@@ -858,8 +861,10 @@ go as extra arguments to @Lid{coroutine.resume}.
@Lid{coroutine.wrap} returns all the values returned by @Lid{coroutine.resume},
except the first one (the boolean error code).
Unlike @Lid{coroutine.resume},
-@Lid{coroutine.wrap} does not catch errors;
-any error is propagated to the caller.
+the function created by @Lid{coroutine.wrap}
+propagates any error to the caller.
+In this case,
+the function also kills the coroutine @seeF{coroutine.kill}.
As an example of how coroutines work,
consider the following code:
@@ -1534,8 +1539,15 @@ the other pending closing methods will still be called.
If a coroutine yields inside a block and is never resumed again,
the variables visible at that block will never go out of scope,
and therefore they will not be closed.
-(You should use finalizers to handle this case,
-or else call @Lid{coroutine.kill} to close the variables.)
+Similarly, if a coroutine ends with an error,
+it does not unwind its stack,
+so it does not close any variable.
+You should either use finalizers
+or call @Lid{coroutine.kill} to close the variables in these cases.
+However, note that if the coroutine was created
+through @Lid{coroutine.wrap},
+then its corresponding function will close all variables
+in case of errors.
}
@@ -6406,11 +6418,12 @@ or if it has stopped with an error.
Creates a new coroutine, with body @id{f};
@id{f} must be a function.
Returns a function that resumes the coroutine each time it is called.
-Any arguments passed to the function behave as the
+Any arguments passed to this function behave as the
extra arguments to @id{resume}.
-Returns the same values returned by @id{resume},
+The function returns the same values returned by @id{resume},
except the first boolean.
-In case of error, propagates the error.
+In case of error,
+the function kills the coroutine and propagates the error.
}
@@ -6668,6 +6681,10 @@ the file path where the module was found,
as returned by @Lid{package.searchpath}.
The first searcher always returns the string @St{:preload:}.
+Searchers should raise no errors and have no side effects in Lua.
+(They may have side effects in C,
+for instance by linking the application with a library.)
+
}
@LibEntry{package.searchpath (name, path [, sep [, rep]])|
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index 35ff27fb7f..9dd501e7fb 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -346,9 +346,13 @@ do
local st, res = coroutine.resume(B)
assert(st == true and res == false)
- A = coroutine.wrap(function() return pcall(A, 1) end)
+ local X = false
+ A = coroutine.wrap(function()
+ local *toclose _ = setmetatable({}, {__close = function () X = true end})
+ return pcall(A, 1)
+ end)
st, res = A()
- assert(not st and string.find(res, "non%-suspended"))
+ assert(not st and string.find(res, "non%-suspended") and X == true)
end
diff --git a/testes/db.lua b/testes/db.lua
index 95275fb41d..3d94f77612 100644
--- a/testes/db.lua
+++ b/testes/db.lua
@@ -734,18 +734,24 @@ a, b = coroutine.resume(co, 100)
assert(a and b == 30)
--- check traceback of suspended coroutines
+-- check traceback of suspended (or dead with error) coroutines
+
+function f(i)
+ if i == 0 then error(i)
+ else coroutine.yield(); f(i-1)
+ end
+end
-function f(i) coroutine.yield(i == 0); f(i - 1) end
co = coroutine.create(function (x) f(x) end)
a, b = coroutine.resume(co, 3)
t = {"'coroutine.yield'", "'f'", "in function <"}
-repeat
+while coroutine.status(co) == "suspended" do
checktraceback(co, t)
a, b = coroutine.resume(co)
table.insert(t, 2, "'f'") -- one more recursive call to 'f'
-until b
+end
+t[1] = "'error'"
checktraceback(co, t)
diff --git a/testes/locals.lua b/testes/locals.lua
index de47ae3182..814d1b165c 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -417,12 +417,13 @@ if rawget(_G, "T") then
end
--- to-be-closed variables in coroutines
+print "to-be-closed variables in coroutines"
+
do
- -- an error in a coroutine closes variables
+ -- an error in a wrapped coroutine closes variables
local x = false
local y = false
- local co = coroutine.create(function ()
+ local co = coroutine.wrap(function ()
local *toclose xv = func2close(function () x = true end)
do
local *toclose yv = func2close(function () y = true end)
@@ -432,14 +433,31 @@ do
error(23) -- error does
end)
- local a, b = coroutine.resume(co)
- assert(a and b == 100 and not x and not y)
- a, b = coroutine.resume(co)
- assert(a and b == 200 and not x and y)
- a, b = coroutine.resume(co)
+ local b = co()
+ assert(b == 100 and not x and not y)
+ b = co()
+ assert(b == 200 and not x and y)
+ local a, b = pcall(co)
assert(not a and b == 23 and x and y)
end
+
+do
+ -- error in a wrapped coroutine raising errors when closing a variable
+ local x = false
+ local co = coroutine.wrap(function ()
+ local *toclose xv = func2close(function () error("XXX") end)
+ coroutine.yield(100)
+ error(200)
+ end)
+ assert(co() == 100)
+ local st, msg = pcall(co)
+print(msg)
+ -- should get last error raised
+ assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
+end
+
+
-- a suspended coroutine should not close its variables when collected
local co
co = coroutine.wrap(function()
@@ -449,6 +467,7 @@ co = coroutine.wrap(function()
end)
co() -- start coroutine
assert(co == nil) -- eventually it will be collected
+collectgarbage()
-- to-be-closed variables in generic for loops
From 3f253f116e8e292977a1bded964544fb35b3d1e3 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 9 May 2019 11:32:20 -0300
Subject: [PATCH 158/889] Test for dead coroutine moved to 'lua_resume'
The test for dead coroutines done in the 'coro' library was moved
to 'lua_resume', in the kernel, which already does other similar
tests.
---
lcorolib.c | 4 ----
ldo.c | 2 ++
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/lcorolib.c b/lcorolib.c
index a21880d5e0..3fc9fb1c1f 100644
--- a/lcorolib.c
+++ b/lcorolib.c
@@ -35,10 +35,6 @@ static int auxresume (lua_State *L, lua_State *co, int narg) {
lua_pushliteral(L, "too many arguments to resume");
return -1; /* error flag */
}
- if (lua_status(co) == LUA_OK && lua_gettop(co) == 0) {
- lua_pushliteral(L, "cannot resume dead coroutine");
- return -1; /* error flag */
- }
lua_xmove(L, co, narg);
status = lua_resume(co, L, narg, &nres);
if (status == LUA_OK || status == LUA_YIELD) {
diff --git a/ldo.c b/ldo.c
index d474c0a02c..e9a88e9e50 100644
--- a/ldo.c
+++ b/ldo.c
@@ -666,6 +666,8 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
if (L->status == LUA_OK) { /* may be starting a coroutine */
if (L->ci != &L->base_ci) /* not in base level? */
return resume_error(L, "cannot resume non-suspended coroutine", nargs);
+ else if (L->top - (L->ci->func + 1) == nargs) /* no function? */
+ return resume_error(L, "cannot resume dead coroutine", nargs);
}
else if (L->status != LUA_YIELD)
return resume_error(L, "cannot resume dead coroutine", nargs);
From d881325c2fcbb6d2c434ec403b0bbe51ac200c7b Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Thu, 9 May 2019 12:10:31 -0300
Subject: [PATCH 159/889] Flag for to-be-closed variables changed to
''
The flag for to-be-closed variables was changed from '*toclose'
to ''. Several people found confusing the old syntax and
the new one has a clear terminator, making it more flexible for
future changes.
---
lparser.c | 3 ++-
manual/manual.of | 4 ++--
testes/api.lua | 3 ++-
testes/coroutine.lua | 6 +++---
testes/files.lua | 6 +++---
testes/goto.lua | 2 +-
testes/locals.lua | 50 ++++++++++++++++++++++----------------------
testes/main.lua | 4 ++--
8 files changed, 40 insertions(+), 38 deletions(-)
diff --git a/lparser.c b/lparser.c
index 4c2ddbfe50..4e6c27fe00 100644
--- a/lparser.c
+++ b/lparser.c
@@ -1621,6 +1621,7 @@ static void tocloselocalstat (LexState *ls) {
if (strcmp(getstr(attr), "toclose") != 0)
luaK_semerror(ls,
luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr)));
+ testnext(ls, '>');
new_localvar(ls, str_checkname(ls));
checknext(ls, '=');
exp1(ls);
@@ -1634,7 +1635,7 @@ static void tocloselocalstat (LexState *ls) {
static void localstat (LexState *ls) {
/* stat -> LOCAL NAME {',' NAME} ['=' explist]
| LOCAL *toclose NAME '=' exp */
- if (testnext(ls, '*'))
+ if (testnext(ls, '<'))
tocloselocalstat(ls);
else
commonlocalstat(ls);
diff --git a/manual/manual.of b/manual/manual.of
index cf44b4f2ec..54a0787927 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1509,7 +1509,7 @@ A local variable can be declared as a @def{to-be-closed} variable,
with the following syntax:
@Produc{
@producname{stat}@producbody{
- @Rw{local} @bnfter{*} @bnfter{toclose} Name @bnfter{=} exp
+ @Rw{local} @bnfter{<} @bnfter{toclose} @bnfter{>} Name @bnfter{=} exp
}}
A to-be-closed variable behaves like a normal local variable,
except that its value is @emph{closed} whenever the variable
@@ -8949,7 +8949,7 @@ and @bnfNter{LiteralString}, see @See{lexical}.)
@OrNL @Rw{function} funcname funcbody
@OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody
@OrNL @Rw{local} namelist @bnfopt{@bnfter{=} explist}
-@OrNL @Rw{local} @bnfter{*} @bnfter{toclose} Name @bnfter{=} exp
+@OrNL @Rw{local} @bnfter{<} @bnfter{toclose} @bnfter{>} Name @bnfter{=} exp
}
@producname{retstat}@producbody{@Rw{return}
diff --git a/testes/api.lua b/testes/api.lua
index 08672e8abe..bcc04dac54 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -1137,7 +1137,8 @@ end)
testamem("to-be-closed variables", function()
local flag
do
- local *toclose x = setmetatable({}, {__close = function () flag = true end})
+ local x =
+ setmetatable({}, {__close = function () flag = true end})
flag = false
local x = {}
end
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index 9dd501e7fb..db6d074ee1 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -151,7 +151,7 @@ do
end
co = coroutine.create(function ()
- local *toclose x = func2close(function (self, err)
+ local x = func2close(function (self, err)
assert(err == nil); X = false
end)
X = true
@@ -164,7 +164,7 @@ do
-- error killing a coroutine
co = coroutine.create(function()
- local *toclose x = func2close(function (self, err)
+ local x = func2close(function (self, err)
assert(err == nil); error(111)
end)
coroutine.yield()
@@ -348,7 +348,7 @@ do
local X = false
A = coroutine.wrap(function()
- local *toclose _ = setmetatable({}, {__close = function () X = true end})
+ local _ = setmetatable({}, {__close = function () X = true end})
return pcall(A, 1)
end)
st, res = A()
diff --git a/testes/files.lua b/testes/files.lua
index 38d3a6693b..eb100fe137 100644
--- a/testes/files.lua
+++ b/testes/files.lua
@@ -125,7 +125,7 @@ do
-- closing file by scope
local F = nil
do
- local *toclose f = assert(io.open(file, "w"))
+ local f = assert(io.open(file, "w"))
F = f
end
assert(tostring(F) == "file (closed)")
@@ -135,7 +135,7 @@ assert(os.remove(file))
do
-- test writing/reading numbers
- local *toclose f = assert(io.open(file, "w"))
+ local f = assert(io.open(file, "w"))
f:write(maxint, '\n')
f:write(string.format("0X%x\n", maxint))
f:write("0xABCp-3", '\n')
@@ -158,7 +158,7 @@ assert(os.remove(file))
-- testing multiple arguments to io.read
do
- local *toclose f = assert(io.open(file, "w"))
+ local f = assert(io.open(file, "w"))
f:write[[
a line
another line
diff --git a/testes/goto.lua b/testes/goto.lua
index f3dcfd4a3e..c9e480734d 100644
--- a/testes/goto.lua
+++ b/testes/goto.lua
@@ -258,7 +258,7 @@ do
::L2:: goto L3
::L1:: do
- local *toclose a = setmetatable({}, {__close = function () X = true end})
+ local a = setmetatable({}, {__close = function () X = true end})
assert(X == nil)
if a then goto L2 end -- jumping back out of scope of 'a'
end
diff --git a/testes/locals.lua b/testes/locals.lua
index 814d1b165c..c176f50663 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -185,9 +185,9 @@ end
do
local a = {}
do
- local *toclose x = setmetatable({"x"}, {__close = function (self)
+ local x = setmetatable({"x"}, {__close = function (self)
a[#a + 1] = self[1] end})
- local *toclose y = func2close(function (self, err)
+ local y = func2close(function (self, err)
assert(err == nil); a[#a + 1] = "y"
end)
a[#a + 1] = "in"
@@ -203,7 +203,7 @@ do
-- closing functions do not corrupt returning values
local function foo (x)
- local *toclose _ = closescope
+ local _ = closescope
return x, X, 23
end
@@ -212,7 +212,7 @@ do
X = false
foo = function (x)
- local *toclose _ = closescope
+ local _ = closescope
local y = 15
return y
end
@@ -221,7 +221,7 @@ do
X = false
foo = function ()
- local *toclose x = closescope
+ local x = closescope
return x
end
@@ -234,13 +234,13 @@ do
-- calls cannot be tail in the scope of to-be-closed variables
local X, Y
local function foo ()
- local *toclose _ = func2close(function () Y = 10 end)
+ local _ = func2close(function () Y = 10 end)
assert(X == true and Y == nil) -- 'X' not closed yet
return 1,2,3
end
local function bar ()
- local *toclose _ = func2close(function () X = false end)
+ local _ = func2close(function () X = false end)
X = true
do
return foo() -- not a tail call!
@@ -255,14 +255,14 @@ end
do -- errors in __close
local log = {}
local function foo (err)
- local *toclose x =
+ local x =
func2close(function (self, msg) log[#log + 1] = msg; error(1) end)
- local *toclose x1 =
+ local x1 =
func2close(function (self, msg) log[#log + 1] = msg; end)
- local *toclose gc = func2close(function () collectgarbage() end)
- local *toclose y =
+ local gc = func2close(function () collectgarbage() end)
+ local y =
func2close(function (self, msg) log[#log + 1] = msg; error(2) end)
- local *toclose z =
+ local z =
func2close(function (self, msg) log[#log + 1] = msg or 10; error(3) end)
if err then error(4) end
end
@@ -283,7 +283,7 @@ do
-- errors due to non-closable values
local function foo ()
- local *toclose x = 34
+ local x = 34
end
local stat, msg = pcall(foo)
assert(not stat and string.find(msg, "variable 'x'"))
@@ -291,8 +291,8 @@ do
-- with other errors, non-closable values are ignored
local function foo ()
- local *toclose x = 34
- local *toclose y = func2close(function () error(32) end)
+ local x = 34
+ local y = func2close(function () error(32) end)
end
local stat, msg = pcall(foo)
assert(not stat and msg == 32)
@@ -304,8 +304,8 @@ if rawget(_G, "T") then
-- memory error inside closing function
local function foo ()
- local *toclose y = func2close(function () T.alloccount() end)
- local *toclose x = setmetatable({}, {__close = function ()
+ local y = func2close(function () T.alloccount() end)
+ local x = setmetatable({}, {__close = function ()
T.alloccount(0); local x = {} -- force a memory error
end})
error("a") -- common error inside the function's body
@@ -331,7 +331,7 @@ if rawget(_G, "T") then
end
local function test ()
- local *toclose x = enter(0) -- set a memory limit
+ local x = enter(0) -- set a memory limit
-- creation of previous upvalue will raise a memory error
assert(false) -- should not run
end
@@ -346,14 +346,14 @@ if rawget(_G, "T") then
-- repeat test with extra closing upvalues
local function test ()
- local *toclose xxx = func2close(function (self, msg)
+ local xxx = func2close(function (self, msg)
assert(msg == "not enough memory");
error(1000) -- raise another error
end)
- local *toclose xx = func2close(function (self, msg)
+ local xx = func2close(function (self, msg)
assert(msg == "not enough memory");
end)
- local *toclose x = enter(0) -- set a memory limit
+ local x = enter(0) -- set a memory limit
-- creation of previous upvalue will raise a memory error
os.exit(false) -- should not run
end
@@ -424,9 +424,9 @@ do
local x = false
local y = false
local co = coroutine.wrap(function ()
- local *toclose xv = func2close(function () x = true end)
+ local xv = func2close(function () x = true end)
do
- local *toclose yv = func2close(function () y = true end)
+ local yv = func2close(function () y = true end)
coroutine.yield(100) -- yield doesn't close variable
end
coroutine.yield(200) -- yield doesn't close variable
@@ -446,7 +446,7 @@ do
-- error in a wrapped coroutine raising errors when closing a variable
local x = false
local co = coroutine.wrap(function ()
- local *toclose xv = func2close(function () error("XXX") end)
+ local xv = func2close(function () error("XXX") end)
coroutine.yield(100)
error(200)
end)
@@ -461,7 +461,7 @@ end
-- a suspended coroutine should not close its variables when collected
local co
co = coroutine.wrap(function()
- local *toclose x = function () os.exit(false) end -- should not run
+ local x = function () os.exit(false) end -- should not run
co = nil
coroutine.yield()
end)
diff --git a/testes/main.lua b/testes/main.lua
index aab490c884..47d84d4ced 100644
--- a/testes/main.lua
+++ b/testes/main.lua
@@ -320,11 +320,11 @@ NoRun("", "lua %s", prog) -- no message
-- to-be-closed variables in main chunk
prepfile[[
- local *toclose x = function (err)
+ local x = function (err)
assert(err == 120)
print("Ok")
end
- local *toclose e1 = function () error(120) end
+ local e1 = function () error(120) end
os.exit(true, true)
]]
RUN('lua %s > %s', prog, out)
From 49c42f3615bd876657bf697e3bf040ce796ae238 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 13 May 2019 14:24:10 -0300
Subject: [PATCH 160/889] Some improvements in 'luaconf.h'
Added '#if !defined' in some definitions to allow external definitions;
more comments; other small changes.
---
llimits.h | 11 ++++---
luaconf.h | 88 +++++++++++++++++++++++++++++++++++-------------------
lutf8lib.c | 2 +-
3 files changed, 65 insertions(+), 36 deletions(-)
diff --git a/llimits.h b/llimits.h
index febf7555cb..950e7e6402 100644
--- a/llimits.h
+++ b/llimits.h
@@ -14,6 +14,11 @@
#include "lua.h"
+
+/* minimum number of bits in an integer */
+#define LUAI_BITSINT (LUAI_IS32INT ? 32 : 16)
+
+
/*
** 'lu_mem' and 'l_mem' are unsigned/signed integers big enough to count
** the total memory used by Lua (in bytes). Usually, 'size_t' and
@@ -22,7 +27,7 @@
#if defined(LUAI_MEM) /* { external definitions? */
typedef LUAI_UMEM lu_mem;
typedef LUAI_MEM l_mem;
-#elif LUAI_BITSINT >= 32 /* }{ */
+#elif LUAI_IS32INT /* }{ */
typedef size_t lu_mem;
typedef ptrdiff_t l_mem;
#else /* 16-bit ints */ /* }{ */
@@ -172,13 +177,11 @@ typedef LUAI_UACINT l_uacInt;
#endif
-
-
/*
** type for virtual-machine instructions;
** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h)
*/
-#if LUAI_BITSINT >= 32
+#if LUAI_IS32INT
typedef unsigned int l_uint32;
#else
typedef unsigned long l_uint32;
diff --git a/luaconf.h b/luaconf.h
index 9240d9c80e..4647ba1785 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -14,6 +14,16 @@
/*
** ===================================================================
+** General Configuration File for Lua
+**
+** Some definitions here can be changed externally, through the
+** compiler (e.g., with '-D' options). Those are protected by
+** '#if !defined' guards. However, several other definitions should
+** be changed directly here, either because they affect the Lua
+** ABI (by making the changes here, you ensure that all software
+** connected to Lua, such as C libraries, will be compiled with the
+** same configuration); or because they are seldom changed.
+**
** Search for "@@" to find all configurable definitions.
** ===================================================================
*/
@@ -22,8 +32,7 @@
/*
** {====================================================================
** System Configuration: macros to adapt (if needed) Lua to some
-** particular platform, for instance compiling it with 32-bit numbers or
-** restricting it to C89.
+** particular platform, for instance restricting it to C89.
** =====================================================================
*/
@@ -42,15 +51,6 @@
#endif
-/*
-@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. You
-** can also define LUA_32BITS in the make file, but changing here you
-** ensure that all software connected to Lua will be compiled with the
-** same configuration.
-*/
-/* #define LUA_32BITS */
-
-
/*
@@ LUA_USE_C89 controls the use of non-ISO-C89 features.
** Define it if you want Lua to avoid the use of a few C99 features
@@ -86,33 +86,42 @@
/*
-@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for
-** C89 ('long' and 'double'); Windows always has '__int64', so it does
-** not need to use this case.
+@@ LUAI_IS32INT is true iff 'int' has (at least) 32 bits.
+** (the use of two shifts avoids undefined shifts)
*/
-#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS)
-#define LUA_C89_NUMBERS
-#endif
+#define LUAI_IS32INT (((UINT_MAX >> 15) >> 15) >= 3)
+
+/* }================================================================== */
/*
-@@ LUAI_BITSINT defines the (minimum) number of bits in an 'int'.
+** {==================================================================
+** Configuration for Number types.
+** ===================================================================
*/
-/* avoid undefined shifts */
-#if ((INT_MAX >> 15) >> 15) >= 1
-#define LUAI_BITSINT 32
-#else
-/* 'int' always must have at least 16 bits */
-#define LUAI_BITSINT 16
+
+/*
+@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats.
+*/
+/* #define LUA_32BITS */
+
+
+/*
+@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for
+** C89 ('long' and 'double'); Windows always has '__int64', so it does
+** not need to use this case.
+*/
+#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS)
+#define LUA_C89_NUMBERS
#endif
/*
@@ LUA_INT_TYPE defines the type for Lua integers.
@@ LUA_FLOAT_TYPE defines the type for Lua floats.
-** Lua should work fine with any mix of these options (if supported
-** by your C compiler). The usual configurations are 64-bit integers
+** Lua should work fine with any mix of these options supported
+** by your C compiler. The usual configurations are 64-bit integers
** and 'double' (the default), 32-bit integers and 'float' (for
** restricted platforms), and 'long'/'double' (for C compilers not
** compliant with C99, which may not have support for 'long long').
@@ -132,7 +141,7 @@
/*
** 32-bit integers and 'float'
*/
-#if LUAI_BITSINT >= 32 /* use 'int' if big enough */
+#if LUAI_IS32INT /* use 'int' if big enough */
#define LUA_INT_TYPE LUA_INT_INT
#else /* otherwise use 'long' */
#define LUA_INT_TYPE LUA_INT_LONG
@@ -164,7 +173,6 @@
-
/*
** {==================================================================
** Configuration for Paths.
@@ -192,6 +200,7 @@
** hierarchy or if you want to install your libraries in
** non-conventional directories.
*/
+
#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR
#if defined(_WIN32) /* { */
/*
@@ -201,27 +210,40 @@
#define LUA_LDIR "!\\lua\\"
#define LUA_CDIR "!\\"
#define LUA_SHRDIR "!\\..\\share\\lua\\" LUA_VDIR "\\"
+
+#if !defined(LUA_PATH_DEFAULT)
#define LUA_PATH_DEFAULT \
LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \
LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" \
LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \
".\\?.lua;" ".\\?\\init.lua"
+#endif
+
+#if !defined(LUA_CPATH_DEFAULT)
#define LUA_CPATH_DEFAULT \
LUA_CDIR"?.dll;" \
LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \
LUA_CDIR"loadall.dll;" ".\\?.dll"
+#endif
#else /* }{ */
#define LUA_ROOT "/usr/local/"
#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR "/"
#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR "/"
+
+#if !defined(LUA_PATH_DEFAULT)
#define LUA_PATH_DEFAULT \
LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \
LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" \
"./?.lua;" "./?/init.lua"
+#endif
+
+#if !defined(LUA_CPATH_DEFAULT)
#define LUA_CPATH_DEFAULT \
LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so"
+#endif
+
#endif /* } */
@@ -230,12 +252,16 @@
** CHANGE it if your machine does not use "/" as the directory separator
** and is not Windows. (On Windows Lua automatically uses "\".)
*/
+#if !defined(LUA_DIRSEP)
+
#if defined(_WIN32)
#define LUA_DIRSEP "\\"
#else
#define LUA_DIRSEP "/"
#endif
+#endif
+
/* }================================================================== */
@@ -632,7 +658,7 @@
/*
@@ lua_getlocaledecpoint gets the locale "radix character" (decimal point).
** Change that if you do not want to use C locales. (Code using this
-** macro must include header 'locale.h'.)
+** macro must include the header 'locale.h'.)
*/
#if !defined(lua_getlocaledecpoint)
#define lua_getlocaledecpoint() (localeconv()->decimal_point[0])
@@ -673,7 +699,7 @@
** {==================================================================
** Macros that affect the API and must be stable (that is, must be the
** same when you compile Lua and when you compile code that links to
-** Lua). You probably do not want/need to change them.
+** Lua).
** =====================================================================
*/
@@ -684,7 +710,7 @@
** space (and to reserve some numbers for pseudo-indices).
** (It must fit into max(size_t)/32.)
*/
-#if LUAI_BITSINT >= 32
+#if LUAI_IS32INT
#define LUAI_MAXSTACK 1000000
#else
#define LUAI_MAXSTACK 15000
diff --git a/lutf8lib.c b/lutf8lib.c
index 16cd68ad68..9786b60ce0 100644
--- a/lutf8lib.c
+++ b/lutf8lib.c
@@ -28,7 +28,7 @@
/*
** Integer type for decoded UTF-8 values; MAXUTF needs 31 bits.
*/
-#if LUAI_BITSINT >= 31
+#if ((UINT_MAX >> 15) >> 15) >= 1
typedef unsigned int utfint;
#else
typedef unsigned long utfint;
From 279c3a6961c60252f0368fdea889caf977f85fe0 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 13 May 2019 16:17:21 -0300
Subject: [PATCH 161/889] A few changes in tests about number of bits in
integers
- The preprocessor must work with at least 'long', and therefore must
do shifts of up to 31 bits correctly.
- Whenever possible, use unsigned types in shifts.
---
llimits.h | 4 ----
lmathlib.c | 8 ++++----
lopcodes.h | 16 +++++++++++-----
ltable.c | 4 ++--
luaconf.h | 3 +--
lutf8lib.c | 2 +-
6 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/llimits.h b/llimits.h
index 950e7e6402..2b52c83bde 100644
--- a/llimits.h
+++ b/llimits.h
@@ -15,10 +15,6 @@
#include "lua.h"
-/* minimum number of bits in an integer */
-#define LUAI_BITSINT (LUAI_IS32INT ? 32 : 16)
-
-
/*
** 'lu_mem' and 'l_mem' are unsigned/signed integers big enough to count
** the total memory used by Lua (in bytes). Usually, 'size_t' and
diff --git a/lmathlib.c b/lmathlib.c
index e3ccc3ee58..3454c41fe1 100644
--- a/lmathlib.c
+++ b/lmathlib.c
@@ -266,7 +266,7 @@ static int math_type (lua_State *L) {
/* try to find an integer type with at least 64 bits */
-#if (LONG_MAX >> 31 >> 31) >= 1
+#if (ULONG_MAX >> 31 >> 31) >= 3
/* 'long' has at least 64 bits */
#define Rand64 unsigned long
@@ -276,7 +276,7 @@ static int math_type (lua_State *L) {
/* there is a 'long long' type (which must have at least 64 bits) */
#define Rand64 unsigned long long
-#elif (LUA_MAXINTEGER >> 31 >> 31) >= 1
+#elif (LUA_MAXINTEGER >> 30 >> 30) >= 7
/* 'lua_Integer' has at least 64 bits */
#define Rand64 lua_Unsigned
@@ -347,7 +347,7 @@ static lua_Number I2d (Rand64 x) {
#else /* no 'Rand64' }{ */
/* get an integer with at least 32 bits */
-#if (INT_MAX >> 30) >= 1
+#if LUAI_IS32INT
typedef unsigned int lu_int32;
#else
typedef unsigned long lu_int32;
@@ -538,7 +538,7 @@ static lua_Unsigned project (lua_Unsigned ran, lua_Unsigned n,
lim |= (lim >> 4);
lim |= (lim >> 8);
lim |= (lim >> 16);
-#if (LUA_MAXINTEGER >> 30 >> 1) > 0
+#if (LUA_MAXINTEGER >> 30) >= 3
lim |= (lim >> 32); /* integer type has more than 32 bits */
#endif
}
diff --git a/lopcodes.h b/lopcodes.h
index bbdd68978b..a314dcd18c 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -57,12 +57,18 @@ enum OpMode {iABC, iABx, iAsBx, iAx, isJ}; /* basic instruction formats */
#define POS_sJ POS_A
+
/*
** limits for opcode arguments.
-** we use (signed) int to manipulate most arguments,
-** so they must fit in LUAI_BITSINT-1 bits (-1 for sign)
+** we use (signed) 'int' to manipulate most arguments,
+** so they must fit in ints.
*/
-#if SIZE_Bx < LUAI_BITSINT-1
+
+/* Check whether type 'int' has at least 'b' bits ('b' < 32) */
+#define L_INTHASBITS(b) ((UINT_MAX >> ((b) - 1)) >= 1)
+
+
+#if L_INTHASBITS(SIZE_Bx)
#define MAXARG_Bx ((1<>1) /* 'sBx' is signed */
-#if SIZE_Ax < LUAI_BITSINT-1
+#if L_INTHASBITS(SIZE_Ax)
#define MAXARG_Ax ((1<> 4);
size |= (size >> 8);
size |= (size >> 16);
-#if (INT_MAX >> 30 >> 1) > 0
- size |= (size >> 32); /* int has more than 32 bits */
+#if (UINT_MAX >> 30) > 3
+ size |= (size >> 32); /* unsigned int has more than 32 bits */
#endif
size++;
lua_assert(ispow2(size) && size/2 < t->alimit && t->alimit < size);
diff --git a/luaconf.h b/luaconf.h
index 4647ba1785..e6271b8028 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -87,9 +87,8 @@
/*
@@ LUAI_IS32INT is true iff 'int' has (at least) 32 bits.
-** (the use of two shifts avoids undefined shifts)
*/
-#define LUAI_IS32INT (((UINT_MAX >> 15) >> 15) >= 3)
+#define LUAI_IS32INT ((UINT_MAX >> 30) >= 3)
/* }================================================================== */
diff --git a/lutf8lib.c b/lutf8lib.c
index 9786b60ce0..b4b787e7f2 100644
--- a/lutf8lib.c
+++ b/lutf8lib.c
@@ -28,7 +28,7 @@
/*
** Integer type for decoded UTF-8 values; MAXUTF needs 31 bits.
*/
-#if ((UINT_MAX >> 15) >> 15) >= 1
+#if (UINT_MAX >> 30) >= 1
typedef unsigned int utfint;
#else
typedef unsigned long utfint;
From 0b63d79b36790febd4c081bf8d6737df27529f8d Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 13 May 2019 16:20:40 -0300
Subject: [PATCH 162/889] Details
- 'luaL_setfuncs' avoids creating closures for placeholders.
- Fixed some warnings about unused values in comma expressions.
- Comments.
---
lauxlib.c | 10 +++++++---
ldo.c | 2 +-
liolib.c | 2 +-
ltablib.c | 2 +-
ltests.c | 8 +++-----
5 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/lauxlib.c b/lauxlib.c
index dfe501a733..89e53dc193 100644
--- a/lauxlib.c
+++ b/lauxlib.c
@@ -898,9 +898,13 @@ LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {
luaL_checkstack(L, nup, "too many upvalues");
for (; l->name != NULL; l++) { /* fill the table with given functions */
int i;
- for (i = 0; i < nup; i++) /* copy upvalues to the top */
- lua_pushvalue(L, -nup);
- lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */
+ if (l->func == NULL) /* place holder? */
+ lua_pushboolean(L, 0);
+ else {
+ for (i = 0; i < nup; i++) /* copy upvalues to the top */
+ lua_pushvalue(L, -nup);
+ lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */
+ }
lua_setfield(L, -(nup + 2), l->name);
}
lua_pop(L, nup); /* remove upvalues */
diff --git a/ldo.c b/ldo.c
index e9a88e9e50..e7e76a652a 100644
--- a/ldo.c
+++ b/ldo.c
@@ -669,7 +669,7 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
else if (L->top - (L->ci->func + 1) == nargs) /* no function? */
return resume_error(L, "cannot resume dead coroutine", nargs);
}
- else if (L->status != LUA_YIELD)
+ else if (L->status != LUA_YIELD) /* ended with errors? */
return resume_error(L, "cannot resume dead coroutine", nargs);
if (from == NULL)
L->nCcalls = 1;
diff --git a/liolib.c b/liolib.c
index 7d6d51e672..fa6a093925 100644
--- a/liolib.c
+++ b/liolib.c
@@ -39,7 +39,7 @@
/* Check whether 'mode' matches '[rwa]%+?[L_MODEEXT]*' */
static int l_checkmode (const char *mode) {
return (*mode != '\0' && strchr("rwa", *(mode++)) != NULL &&
- (*mode != '+' || (++mode, 1)) && /* skip if char is '+' */
+ (*mode != '+' || ((void)(++mode), 1)) && /* skip if char is '+' */
(strspn(mode, L_MODEEXT) == strlen(mode))); /* check extensions */
}
diff --git a/ltablib.c b/ltablib.c
index a9169f9e3e..48a6bdfe27 100644
--- a/ltablib.c
+++ b/ltablib.c
@@ -299,7 +299,7 @@ static IdxT partition (lua_State *L, IdxT lo, IdxT up) {
/* loop invariant: a[lo .. i] <= P <= a[j .. up] */
for (;;) {
/* next loop: repeat ++i while a[i] < P */
- while (lua_geti(L, 1, ++i), sort_comp(L, -1, -2)) {
+ while ((void)lua_geti(L, 1, ++i), sort_comp(L, -1, -2)) {
if (i == up - 1) /* a[i] < P but a[up - 1] == P ?? */
luaL_error(L, "invalid order function for sorting");
lua_pop(L, 1); /* remove a[i] */
diff --git a/ltests.c b/ltests.c
index 7d441d1ac2..f786eeb3c2 100644
--- a/ltests.c
+++ b/ltests.c
@@ -51,9 +51,8 @@ static int runC (lua_State *L, lua_State *L1, const char *pc);
static void setnameval (lua_State *L, const char *name, int val) {
- lua_pushstring(L, name);
lua_pushinteger(L, val);
- lua_settable(L, -3);
+ lua_setfield(L, -2, name);
}
@@ -710,12 +709,11 @@ static void printstack (lua_State *L) {
static int get_limits (lua_State *L) {
- lua_createtable(L, 0, 5);
- setnameval(L, "BITS_INT", LUAI_BITSINT);
+ lua_createtable(L, 0, 6);
+ setnameval(L, "IS32INT", LUAI_IS32INT);
setnameval(L, "MAXARG_Ax", MAXARG_Ax);
setnameval(L, "MAXARG_Bx", MAXARG_Bx);
setnameval(L, "OFFSET_sBx", OFFSET_sBx);
- setnameval(L, "BITS_INT", LUAI_BITSINT);
setnameval(L, "LFPF", LFIELDS_PER_FLUSH);
setnameval(L, "NUM_OPCODES", NUM_OPCODES);
return 1;
From 347d6961ac14213264c7176e3d125c9ba8475b01 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 14 May 2019 11:10:24 -0300
Subject: [PATCH 163/889] Define LUA_MAXUNSIGNED as a preprocessor constant
The previous definition of LUA_MAXUNSIGNED used a typecast,
making it unsuitable for constant expressions in the preprocessor.
---
lmathlib.c | 4 ++--
luaconf.h | 9 ++++++++-
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/lmathlib.c b/lmathlib.c
index 3454c41fe1..f6f0b4265f 100644
--- a/lmathlib.c
+++ b/lmathlib.c
@@ -276,7 +276,7 @@ static int math_type (lua_State *L) {
/* there is a 'long long' type (which must have at least 64 bits) */
#define Rand64 unsigned long long
-#elif (LUA_MAXINTEGER >> 30 >> 30) >= 7
+#elif (LUA_MAXUNSIGNED >> 31 >> 31) >= 3
/* 'lua_Integer' has at least 64 bits */
#define Rand64 lua_Unsigned
@@ -538,7 +538,7 @@ static lua_Unsigned project (lua_Unsigned ran, lua_Unsigned n,
lim |= (lim >> 4);
lim |= (lim >> 8);
lim |= (lim >> 16);
-#if (LUA_MAXINTEGER >> 30) >= 3
+#if (LUA_MAXUNSIGNED >> 31) >= 3
lim |= (lim >> 32); /* integer type has more than 32 bits */
#endif
}
diff --git a/luaconf.h b/luaconf.h
index e6271b8028..66dca6bfcb 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -515,7 +515,6 @@
*/
#define LUA_UNSIGNED unsigned LUAI_UACINT
-#define LUA_MAXUNSIGNED (~(lua_Unsigned)0)
#define LUA_UNSIGNEDBITS (sizeof(LUA_UNSIGNED) * CHAR_BIT)
@@ -530,6 +529,8 @@
#define LUA_MAXINTEGER INT_MAX
#define LUA_MININTEGER INT_MIN
+#define LUA_MAXUNSIGNED UINT_MAX
+
#elif LUA_INT_TYPE == LUA_INT_LONG /* }{ long */
#define LUA_INTEGER long
@@ -538,6 +539,8 @@
#define LUA_MAXINTEGER LONG_MAX
#define LUA_MININTEGER LONG_MIN
+#define LUA_MAXUNSIGNED ULONG_MAX
+
#elif LUA_INT_TYPE == LUA_INT_LONGLONG /* }{ long long */
/* use presence of macro LLONG_MAX as proxy for C99 compliance */
@@ -550,6 +553,8 @@
#define LUA_MAXINTEGER LLONG_MAX
#define LUA_MININTEGER LLONG_MIN
+#define LUA_MAXUNSIGNED ULLONG_MAX
+
#elif defined(LUA_USE_WINDOWS) /* }{ */
/* in Windows, can use specific Windows types */
@@ -559,6 +564,8 @@
#define LUA_MAXINTEGER _I64_MAX
#define LUA_MININTEGER _I64_MIN
+#define LUA_MAXUNSIGNED _UI64_MAX
+
#else /* }{ */
#error "Compiler does not support 'long long'. Use option '-DLUA_32BITS' \
From d9f40e3f6fb61650240c47d548bee69b24b07859 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Fri, 17 May 2019 11:11:44 -0300
Subject: [PATCH 164/889] First implementation for 'const' variables
A variable can be declared const, which means it cannot be assigned to,
with the syntax 'local name = exp'.
---
lcode.c | 36 ++++++++------
lparser.c | 109 ++++++++++++++++++++++++++++++++++--------
lparser.h | 13 +++--
manual/manual.of | 28 +++++++----
testes/constructs.lua | 61 ++++++++++++++++++++++-
testes/files.lua | 8 ++--
testes/math.lua | 6 +--
7 files changed, 205 insertions(+), 56 deletions(-)
diff --git a/lcode.c b/lcode.c
index 1e36e5842a..eccb8380a9 100644
--- a/lcode.c
+++ b/lcode.c
@@ -678,11 +678,12 @@ void luaK_setoneret (FuncState *fs, expdesc *e) {
void luaK_dischargevars (FuncState *fs, expdesc *e) {
switch (e->k) {
case VLOCAL: { /* already in a register */
+ e->u.info = e->u.var.idx;
e->k = VNONRELOC; /* becomes a non-relocatable value */
break;
}
case VUPVAL: { /* move value to some (pending) register */
- e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0);
+ e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.var.idx, 0);
e->k = VRELOC;
break;
}
@@ -938,12 +939,12 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) {
switch (var->k) {
case VLOCAL: {
freeexp(fs, ex);
- exp2reg(fs, ex, var->u.info); /* compute 'ex' into proper place */
+ exp2reg(fs, ex, var->u.var.idx); /* compute 'ex' into proper place */
return;
}
case VUPVAL: {
int e = luaK_exp2anyreg(fs, ex);
- luaK_codeABC(fs, OP_SETUPVAL, e, var->u.info, 0);
+ luaK_codeABC(fs, OP_SETUPVAL, e, var->u.var.idx, 0);
break;
}
case VINDEXUP: {
@@ -1165,25 +1166,30 @@ static int isSCnumber (expdesc *e, lua_Integer *i, int *isfloat) {
** values in registers.
*/
void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) {
- lua_assert(!hasjumps(t) && (vkisinreg(t->k) || t->k == VUPVAL));
+ lua_assert(!hasjumps(t) &&
+ (t->k == VLOCAL || t->k == VNONRELOC || t->k == VUPVAL));
if (t->k == VUPVAL && !isKstr(fs, k)) /* upvalue indexed by non string? */
luaK_exp2anyreg(fs, t); /* put it in a register */
- t->u.ind.t = t->u.info; /* register or upvalue index */
if (t->k == VUPVAL) {
+ t->u.ind.t = t->u.var.idx; /* upvalue index */
t->u.ind.idx = k->u.info; /* literal string */
t->k = VINDEXUP;
}
- else if (isKstr(fs, k)) {
- t->u.ind.idx = k->u.info; /* literal string */
- t->k = VINDEXSTR;
- }
- else if (isCint(k)) {
- t->u.ind.idx = cast_int(k->u.ival); /* integer constant in proper range */
- t->k = VINDEXI;
- }
else {
- t->u.ind.idx = luaK_exp2anyreg(fs, k); /* register */
- t->k = VINDEXED;
+ /* register index of the table */
+ t->u.ind.t = (t->k == VLOCAL) ? t->u.var.idx: t->u.info;
+ if (isKstr(fs, k)) {
+ t->u.ind.idx = k->u.info; /* literal string */
+ t->k = VINDEXSTR;
+ }
+ else if (isCint(k)) {
+ t->u.ind.idx = cast_int(k->u.ival); /* int. constant in proper range */
+ t->k = VINDEXI;
+ }
+ else {
+ t->u.ind.idx = luaK_exp2anyreg(fs, k); /* register */
+ t->k = VINDEXED;
+ }
}
}
diff --git a/lparser.c b/lparser.c
index 4e6c27fe00..7c23710ad2 100644
--- a/lparser.c
+++ b/lparser.c
@@ -156,6 +156,13 @@ static void init_exp (expdesc *e, expkind k, int i) {
}
+static void init_var (expdesc *e, expkind k, int i) {
+ e->f = e->t = NO_JUMP;
+ e->k = k;
+ e->u.var.idx = i;
+}
+
+
static void codestring (LexState *ls, expdesc *e, TString *s) {
init_exp(e, VK, luaK_stringK(ls->fs, s));
}
@@ -187,31 +194,82 @@ static int registerlocalvar (LexState *ls, TString *varname) {
/*
** Create a new local variable with the given 'name'.
*/
-static void new_localvar (LexState *ls, TString *name) {
+static Vardesc *new_localvar (LexState *ls, TString *name) {
FuncState *fs = ls->fs;
Dyndata *dyd = ls->dyd;
+ Vardesc *var;
int reg = registerlocalvar(ls, name);
checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal,
MAXVARS, "local variables");
luaM_growvector(ls->L, dyd->actvar.arr, dyd->actvar.n + 1,
dyd->actvar.size, Vardesc, MAX_INT, "local variables");
- dyd->actvar.arr[dyd->actvar.n++].idx = cast(short, reg);
+ var = &dyd->actvar.arr[dyd->actvar.n++];
+ var->idx = cast(short, reg);
+ var->name = name;
+ var->ro = 0;
+ return var;
}
#define new_localvarliteral(ls,v) \
new_localvar(ls, luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1));
+
+/*
+** Return the "variable description" (Vardesc) of a given
+** variable
+*/
+static Vardesc *getlocalvardesc (FuncState *fs, int i) {
+ return &fs->ls->dyd->actvar.arr[fs->firstlocal + i];
+}
+
/*
** Get the debug-information entry for current variable 'i'.
*/
static LocVar *getlocvar (FuncState *fs, int i) {
- int idx = fs->ls->dyd->actvar.arr[fs->firstlocal + i].idx;
+ int idx = getlocalvardesc(fs, i)->idx;
lua_assert(idx < fs->nlocvars);
return &fs->f->locvars[idx];
}
+/*
+** Return the "variable description" (Vardesc) of a given
+** variable or upvalue
+*/
+static Vardesc *getvardesc (FuncState *fs, expdesc *e) {
+ if (e->k == VLOCAL)
+ return getlocalvardesc(fs, e->u.var.idx);
+ else if (e->k != VUPVAL)
+ return NULL; /* not a local variable */
+ else { /* upvalue: must go up all levels up to the original local */
+ int idx = e->u.var.idx;
+ for (;;) {
+ Upvaldesc *up = &fs->f->upvalues[idx];
+ fs = fs->prev; /* must look at the previous level */
+ idx = up->idx; /* at this index */
+ if (fs == NULL) { /* no more levels? (can happen only with _ENV) */
+ lua_assert(strcmp(getstr(up->name), LUA_ENV) == 0);
+ return NULL;
+ }
+ else if (up->instack) /* got to the original level? */
+ return getlocalvardesc(fs, idx);
+ /* else repeat for previous level */
+ }
+ }
+}
+
+
+static void check_readonly (LexState *ls, expdesc *e) {
+ Vardesc *vardesc = getvardesc(ls->fs, e);
+ if (vardesc && vardesc->ro) { /* is variable local and const? */
+ const char *msg = luaO_pushfstring(ls->L,
+ "assignment to const variable '%s'", getstr(vardesc->name));
+ luaK_semerror(ls, msg); /* error */
+ }
+}
+
+
/*
** Start the scope for the last 'nvars' created variables.
** (debug info.)
@@ -259,7 +317,7 @@ static int newupvalue (FuncState *fs, TString *name, expdesc *v) {
while (oldsize < f->sizeupvalues)
f->upvalues[oldsize++].name = NULL;
f->upvalues[fs->nups].instack = (v->k == VLOCAL);
- f->upvalues[fs->nups].idx = cast_byte(v->u.info);
+ f->upvalues[fs->nups].idx = cast_byte(v->u.var.idx);
f->upvalues[fs->nups].name = name;
luaC_objbarrier(fs->ls->L, f, name);
return fs->nups++;
@@ -304,7 +362,7 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
else {
int v = searchvar(fs, n); /* look up locals at current level */
if (v >= 0) { /* found? */
- init_exp(var, VLOCAL, v); /* variable is local */
+ init_var(var, VLOCAL, v); /* variable is local */
if (!base)
markupval(fs, v); /* local will be used as an upval */
}
@@ -317,7 +375,7 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
/* else was LOCAL or UPVAL */
idx = newupvalue(fs, n, var); /* will be a new upvalue */
}
- init_exp(var, VUPVAL, idx); /* new or old upvalue */
+ init_var(var, VUPVAL, idx); /* new or old upvalue */
}
}
}
@@ -1199,20 +1257,20 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) {
for (; lh; lh = lh->prev) { /* check all previous assignments */
if (vkisindexed(lh->v.k)) { /* assignment to table field? */
if (lh->v.k == VINDEXUP) { /* is table an upvalue? */
- if (v->k == VUPVAL && lh->v.u.ind.t == v->u.info) {
+ if (v->k == VUPVAL && lh->v.u.ind.t == v->u.var.idx) {
conflict = 1; /* table is the upvalue being assigned now */
lh->v.k = VINDEXSTR;
lh->v.u.ind.t = extra; /* assignment will use safe copy */
}
}
else { /* table is a register */
- if (v->k == VLOCAL && lh->v.u.ind.t == v->u.info) {
+ if (v->k == VLOCAL && lh->v.u.ind.t == v->u.var.idx) {
conflict = 1; /* table is the local being assigned now */
lh->v.u.ind.t = extra; /* assignment will use safe copy */
}
/* is index the local being assigned? */
if (lh->v.k == VINDEXED && v->k == VLOCAL &&
- lh->v.u.ind.idx == v->u.info) {
+ lh->v.u.ind.idx == v->u.var.idx) {
conflict = 1;
lh->v.u.ind.idx = extra; /* previous assignment will use safe copy */
}
@@ -1222,7 +1280,7 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) {
if (conflict) {
/* copy upvalue/local value to a temporary (in position 'extra') */
OpCode op = (v->k == VLOCAL) ? OP_MOVE : OP_GETUPVAL;
- luaK_codeABC(fs, op, extra, v->u.info, 0);
+ luaK_codeABC(fs, op, extra, v->u.var.idx, 0);
luaK_reserveregs(fs, 1);
}
}
@@ -1237,6 +1295,7 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) {
static void restassign (LexState *ls, struct LHS_assign *lh, int nvars) {
expdesc e;
check_condition(ls, vkisvar(lh->v.k), "syntax error");
+ check_readonly(ls, &lh->v);
if (testnext(ls, ',')) { /* restassign -> ',' suffixedexp restassign */
struct LHS_assign nv;
nv.prev = lh;
@@ -1615,20 +1674,30 @@ static void commonlocalstat (LexState *ls) {
}
-static void tocloselocalstat (LexState *ls) {
+static void tocloselocalstat (LexState *ls, Vardesc *var) {
FuncState *fs = ls->fs;
+ var->ro = 1; /* to-be-closed variables are always read-only */
+ markupval(fs, fs->nactvar);
+ fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */
+ luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0);
+}
+
+
+static void attriblocalstat (LexState *ls) {
+ Vardesc *var;
TString *attr = str_checkname(ls);
- if (strcmp(getstr(attr), "toclose") != 0)
- luaK_semerror(ls,
- luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr)));
testnext(ls, '>');
- new_localvar(ls, str_checkname(ls));
+ var = new_localvar(ls, str_checkname(ls));
checknext(ls, '=');
exp1(ls);
- markupval(fs, fs->nactvar);
- fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */
adjustlocalvars(ls, 1);
- luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0);
+ if (strcmp(getstr(attr), "const") == 0)
+ var->ro = 1; /* set variable as read-only */
+ else if (strcmp(getstr(attr), "toclose") == 0)
+ tocloselocalstat(ls, var);
+ else
+ luaK_semerror(ls,
+ luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr)));
}
@@ -1636,7 +1705,7 @@ static void localstat (LexState *ls) {
/* stat -> LOCAL NAME {',' NAME} ['=' explist]
| LOCAL *toclose NAME '=' exp */
if (testnext(ls, '<'))
- tocloselocalstat(ls);
+ attriblocalstat(ls);
else
commonlocalstat(ls);
}
@@ -1801,7 +1870,7 @@ static void mainfunc (LexState *ls, FuncState *fs) {
expdesc v;
open_func(ls, fs, &bl);
setvararg(fs, 0); /* main function is always declared vararg */
- init_exp(&v, VLOCAL, 0); /* create and... */
+ init_var(&v, VLOCAL, 0); /* create and... */
newupvalue(fs, ls->envn, &v); /* ...set environment upvalue */
luaX_next(ls); /* read first token */
statlist(ls); /* parse main body */
diff --git a/lparser.h b/lparser.h
index 3d6bd97853..3b5d399fd8 100644
--- a/lparser.h
+++ b/lparser.h
@@ -33,8 +33,8 @@ typedef enum {
VKINT, /* integer constant; nval = numerical integer value */
VNONRELOC, /* expression has its value in a fixed register;
info = result register */
- VLOCAL, /* local variable; info = local register */
- VUPVAL, /* upvalue variable; info = index of upvalue in 'upvalues' */
+ VLOCAL, /* local variable; var.idx = local register */
+ VUPVAL, /* upvalue variable; var.idx = index of upvalue in 'upvalues' */
VINDEXED, /* indexed variable;
ind.t = table register;
ind.idx = key's R index */
@@ -58,7 +58,7 @@ typedef enum {
#define vkisvar(k) (VLOCAL <= (k) && (k) <= VINDEXSTR)
#define vkisindexed(k) (VINDEXED <= (k) && (k) <= VINDEXSTR)
-#define vkisinreg(k) ((k) == VNONRELOC || (k) == VLOCAL)
+
typedef struct expdesc {
expkind k;
@@ -70,15 +70,20 @@ typedef struct expdesc {
short idx; /* index (R or "long" K) */
lu_byte t; /* table (register or upvalue) */
} ind;
+ struct { /* for local variables and upvalues */
+ lu_byte idx; /* index of the variable */
+ } var;
} u;
int t; /* patch list of 'exit when true' */
int f; /* patch list of 'exit when false' */
} expdesc;
-/* description of active local variable */
+/* description of an active local variable */
typedef struct Vardesc {
+ TString *name;
short idx; /* index of the variable in the Proto's 'locvars' array */
+ lu_byte ro; /* true if variable is 'const' */
} Vardesc;
diff --git a/manual/manual.of b/manual/manual.of
index 54a0787927..6cac8c6cad 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1488,13 +1488,24 @@ Function calls are explained in @See{functioncall}.
@sect3{localvar| @title{Local Declarations}
@x{Local variables} can be declared anywhere inside a block.
-The declaration can include an initial assignment:
+The declaration can include an initialization:
@Produc{
@producname{stat}@producbody{@Rw{local} namelist @bnfopt{@bnfter{=} explist}}
-}
+@producname{stat}@producbody{
+ @Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp
+}}
If present, an initial assignment has the same semantics
of a multiple assignment @see{assignment}.
Otherwise, all variables are initialized with @nil.
+The second syntax declares a local with a given attribute,
+which is the name between the angle brackets.
+In this case, there must be an initialization.
+There are two possible attributes:
+@id{const}, which declares a @x{constant variable},
+that is, a variable that cannot be assigned to
+after its initialization;
+and @id{toclose}, wich declares a to-be-closed variable @see{to-be-closed}.
+
A chunk is also a block @see{chunks},
and so local variables can be declared in a chunk outside any explicit block.
@@ -1506,12 +1517,12 @@ The visibility rules for local variables are explained in @See{visibility}.
@sect3{to-be-closed| @title{To-be-closed Variables}
A local variable can be declared as a @def{to-be-closed} variable,
-with the following syntax:
+using the identifier @id{toclose} as its attribute:
@Produc{
@producname{stat}@producbody{
- @Rw{local} @bnfter{<} @bnfter{toclose} @bnfter{>} Name @bnfter{=} exp
+ @Rw{local} @bnfter{<} @id{toclose} @bnfter{>} Name @bnfter{=} exp
}}
-A to-be-closed variable behaves like a normal local variable,
+A to-be-closed variable behaves like a constant local variable,
except that its value is @emph{closed} whenever the variable
goes out of scope, including normal block termination,
exiting its block by @Rw{break}/@Rw{goto}/@Rw{return},
@@ -7603,7 +7614,7 @@ or a float otherwise.
@LibEntry{math.abs (x)|
-Returns the absolute value of @id{x}. (integer/float)
+Returns the maximum value between @id{x} and @id{-x}. (integer/float)
}
@@ -8042,7 +8053,8 @@ following the lexical conventions of Lua.
This format always reads the longest input sequence that
is a valid prefix for a numeral;
if that prefix does not form a valid numeral
-(e.g., an empty string, @St{0x}, or @St{3.4e-}),
+(e.g., an empty string, @St{0x}, or @St{3.4e-})
+or it is too long (more than 200 characters),
it is discarded and the format returns @nil.
}
@@ -8949,7 +8961,7 @@ and @bnfNter{LiteralString}, see @See{lexical}.)
@OrNL @Rw{function} funcname funcbody
@OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody
@OrNL @Rw{local} namelist @bnfopt{@bnfter{=} explist}
-@OrNL @Rw{local} @bnfter{<} @bnfter{toclose} @bnfter{>} Name @bnfter{=} exp
+@OrNL @Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp
}
@producname{retstat}@producbody{@Rw{return}
diff --git a/testes/constructs.lua b/testes/constructs.lua
index a83df79eaf..b91e0979ec 100644
--- a/testes/constructs.lua
+++ b/testes/constructs.lua
@@ -59,6 +59,41 @@ assert((x>y) and x or y == 2);
assert(1234567890 == tonumber('1234567890') and 1234567890+1 == 1234567891)
+do -- testing operators with diffent kinds of constants
+ -- operands to consider:
+ -- * fit in register
+ -- * constant doesn't fit in register
+ -- * floats with integral values
+ local operand = {3, 100, 5.0, -10, -5.0, 10000, -10000}
+ local operator = {"+", "-", "*", "/", "//", "%", "^",
+ "&", "|", "^", "<<", ">>",
+ "==", "~=", "<", ">", "<=", ">=",}
+ for _, op in ipairs(operator) do
+ local f = assert(load(string.format([[return function (x,y)
+ return x %s y
+ end]], op)))();
+ for _, o1 in ipairs(operand) do
+ for _, o2 in ipairs(operand) do
+ local gab = f(o1, o2)
+
+ _ENV.XX = o1
+ code = string.format("return XX %s %s", op, o2)
+ res = assert(load(code))()
+ assert(res == gab)
+
+ _ENV.XX = o2
+ local code = string.format("return (%s) %s XX", o1, op)
+ local res = assert(load(code))()
+ assert(res == gab)
+
+ code = string.format("return (%s) %s %s", o1, op, o2)
+ res = assert(load(code))()
+ assert(res == gab)
+ end
+ end
+ end
+end
+
-- silly loops
repeat until 1; repeat until true;
@@ -175,6 +210,28 @@ assert(a==1 and b==nil)
print'+';
+do -- testing constants
+ local prog = [[local x = 10]]
+ checkload(prog, "unknown attribute 'XXX'")
+
+ checkload([[local xxx = 20; xxx = 10]],
+ ":1: assignment to const variable 'xxx'")
+
+ checkload([[
+ local xx;
+ local xxx = 20;
+ local yyy;
+ local function foo ()
+ local abc = xx + yyy + xxx;
+ return function () return function () xxx = yyy end end
+ end
+ ]], ":6: assignment to const variable 'xxx'")
+
+ checkload([[
+ local x = nil
+ x = io.open()
+ ]], ":2: assignment to const variable 'x'")
+end
f = [[
return function ( a , b , c , d , e )
@@ -245,12 +302,12 @@ print('testing short-circuit optimizations (' .. _ENV.GLOB1 .. ')')
-- operators with their respective values
-local binops = {
+local binops = {
{" and ", function (a,b) if not a then return a else return b end end},
{" or ", function (a,b) if a then return a else return b end end},
}
-local cases = {}
+local cases = {}
-- creates all combinations of '(cases[i] op cases[n-i])' plus
-- 'not(cases[i] op cases[n-i])' (syntax + value)
diff --git a/testes/files.lua b/testes/files.lua
index eb100fe137..54931c14c1 100644
--- a/testes/files.lua
+++ b/testes/files.lua
@@ -144,7 +144,7 @@ do
f:write(string.format("0x%X\n", -maxint))
f:write("-0xABCp-3", '\n')
assert(f:close())
- f = assert(io.open(file, "r"))
+ local f = assert(io.open(file, "r"))
assert(f:read("n") == maxint)
assert(f:read("n") == maxint)
assert(f:read("n") == 0xABCp-3)
@@ -170,18 +170,18 @@ three
]]
local l1, l2, l3, l4, n1, n2, c, dummy
assert(f:close())
- f = assert(io.open(file, "r"))
+ local f = assert(io.open(file, "r"))
l1, l2, n1, n2, dummy = f:read("l", "L", "n", "n")
assert(l1 == "a line" and l2 == "another line\n" and
n1 == 1234 and n2 == 3.45 and dummy == nil)
assert(f:close())
- f = assert(io.open(file, "r"))
+ local f = assert(io.open(file, "r"))
l1, l2, n1, n2, c, l3, l4, dummy = f:read(7, "l", "n", "n", 1, "l", "l")
assert(l1 == "a line\n" and l2 == "another line" and c == '\n' and
n1 == 1234 and n2 == 3.45 and l3 == "one" and l4 == "two"
and dummy == nil)
assert(f:close())
- f = assert(io.open(file, "r"))
+ local f = assert(io.open(file, "r"))
-- second item failing
l1, n1, n2, dummy = f:read("l", "n", "n", "l")
assert(l1 == "a line" and n1 == nil)
diff --git a/testes/math.lua b/testes/math.lua
index b010ff6c3a..c45a91ad5c 100644
--- a/testes/math.lua
+++ b/testes/math.lua
@@ -3,10 +3,10 @@
print("testing numbers and math lib")
-local minint = math.mininteger
-local maxint = math.maxinteger
+local minint = math.mininteger
+local maxint = math.maxinteger
-local intbits = math.floor(math.log(maxint, 2) + 0.5) + 1
+local intbits = math.floor(math.log(maxint, 2) + 0.5) + 1
assert((1 << intbits) == 0)
assert(minint == 1 << (intbits - 1))
From b293ae0577bebaca7169cb4f041b800641d5e2c4 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 28 May 2019 15:46:49 -0300
Subject: [PATCH 165/889] Details
- new error message for "attempt to assign to const variable"
- note in the manual about compatibility options
- comments
- small changes in 'read_line' and 'pushstr'
---
liolib.c | 10 +++++-----
lobject.c | 4 ++--
lparser.c | 2 +-
lstate.h | 16 ++++++++++++++++
luaconf.h | 14 +++++++++-----
manual/manual.of | 13 ++++++++-----
testes/constructs.lua | 6 +++---
testes/locals.lua | 1 -
testes/strings.lua | 3 ++-
9 files changed, 46 insertions(+), 23 deletions(-)
diff --git a/liolib.c b/liolib.c
index fa6a093925..1484676d47 100644
--- a/liolib.c
+++ b/liolib.c
@@ -504,17 +504,17 @@ static int test_eof (lua_State *L, FILE *f) {
static int read_line (lua_State *L, FILE *f, int chop) {
luaL_Buffer b;
- int c = '\0';
+ int c;
luaL_buffinit(L, &b);
- while (c != EOF && c != '\n') { /* repeat until end of line */
- char *buff = luaL_prepbuffer(&b); /* preallocate buffer */
+ do { /* may need to read several chunks to get whole line */
+ char *buff = luaL_prepbuffer(&b); /* preallocate buffer space */
int i = 0;
l_lockfile(f); /* no memory errors can happen inside the lock */
while (i < LUAL_BUFFERSIZE && (c = l_getc(f)) != EOF && c != '\n')
- buff[i++] = c;
+ buff[i++] = c; /* read up to end of line or buffer limit */
l_unlockfile(f);
luaL_addsize(&b, i);
- }
+ } while (c != EOF && c != '\n'); /* repeat until end of line */
if (!chop && c == '\n') /* want a newline and have one? */
luaL_addchar(&b, c); /* add ending newline to result */
luaL_pushresult(&b); /* close buffer */
diff --git a/lobject.c b/lobject.c
index ce14059f47..979a68896e 100644
--- a/lobject.c
+++ b/lobject.c
@@ -419,9 +419,9 @@ typedef struct BuffFS {
static void pushstr (BuffFS *buff, const char *str, size_t l) {
lua_State *L = buff->L;
setsvalue2s(L, L->top, luaS_newlstr(L, str, l));
- L->top++;
+ L->top++; /* may use one extra slot */
buff->pushed++;
- if (buff->pushed > 1 && L->top + 2 > L->stack_last) {
+ if (buff->pushed > 1 && L->top + 1 >= L->stack_last) {
luaV_concat(L, buff->pushed); /* join all partial results into one */
buff->pushed = 1;
}
diff --git a/lparser.c b/lparser.c
index 7c23710ad2..045efd93ff 100644
--- a/lparser.c
+++ b/lparser.c
@@ -264,7 +264,7 @@ static void check_readonly (LexState *ls, expdesc *e) {
Vardesc *vardesc = getvardesc(ls->fs, e);
if (vardesc && vardesc->ro) { /* is variable local and const? */
const char *msg = luaO_pushfstring(ls->L,
- "assignment to const variable '%s'", getstr(vardesc->name));
+ "attempt to assign to const variable '%s'", getstr(vardesc->name));
luaK_semerror(ls, msg); /* error */
}
}
diff --git a/lstate.h b/lstate.h
index e35f89625b..3bd5297334 100644
--- a/lstate.h
+++ b/lstate.h
@@ -26,6 +26,22 @@
** 'fixedgc': all objects that are not to be collected (currently
** only small strings, such as reserved words).
**
+** For the generational collector, some of these lists have marks for
+** generations. Each mark points to the first element in the list for
+** that particular generation; that generation goes until the next mark.
+**
+** 'allgc' -> 'survival': new objects;
+** 'survival' -> 'old': objects that survived one collection;
+** 'old' -> 'reallyold': objects that became old in last collection;
+** 'reallyold' -> NULL: objects old for more than one cycle.
+**
+** 'finobj' -> 'finobjsur': new objects marked for finalization;
+** 'finobjsur' -> 'finobjold': survived """";
+** 'finobjold' -> 'finobjrold': just old """";
+** 'finobjrold' -> NULL: really old """".
+*/
+
+/*
** Moreover, there is another set of lists that control gray objects.
** These lists are linked by fields 'gclist'. (All objects that
** can become gray have such a field. The field is not the same
diff --git a/luaconf.h b/luaconf.h
index 66dca6bfcb..39840e395e 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -344,8 +344,8 @@
/*
@@ LUA_COMPAT_MATHLIB controls the presence of several deprecated
** functions in the mathematical library.
-** (These functions were already officially removed in 5.3, but
-** nevertheless they are available by default there.)
+** (These functions were already officially removed in 5.3;
+** nevertheless they are still available here.)
*/
#define LUA_COMPAT_MATHLIB
@@ -353,23 +353,25 @@
@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for
** manipulating other integer types (lua_pushunsigned, lua_tounsigned,
** luaL_checkint, luaL_checklong, etc.)
+** (These macros were also officially removed in 5.3, but they are still
+** available here.)
*/
#define LUA_COMPAT_APIINTCASTS
+
/*
@@ LUA_COMPAT_LT_LE controls the emulation of the '__le' metamethod
** using '__lt'.
*/
#define LUA_COMPAT_LT_LE
-#endif /* } */
-
-
/*
@@ The following macros supply trivial compatibility for some
** changes in the API. The macros themselves document how to
** change your code to avoid using them.
+** (Once more, these macros were officially removed in 5.3, but they are
+** still available here.)
*/
#define lua_strlen(L,i) lua_rawlen(L, (i))
@@ -378,6 +380,8 @@
#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ)
#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT)
+#endif /* } */
+
/* }================================================================== */
diff --git a/manual/manual.of b/manual/manual.of
index 6cac8c6cad..ff69cd2c36 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -8774,10 +8774,18 @@ is a more portable solution.
Here we list the incompatibilities that you may find when moving a program
from @N{Lua 5.3} to @N{Lua 5.4}.
+
You can avoid some incompatibilities by compiling Lua with
appropriate options (see file @id{luaconf.h}).
However,
all these compatibility options will be removed in the future.
+More often than not,
+compatibility issues arise when these compatibility options
+are removed.
+So, whenever you have the chance,
+you should try to test your code with a version of Lua compiled
+with all compatibility options turned off.
+That will ease transitions to newer versions of Lua.
Lua versions can always change the C API in ways that
do not imply source-code changes in a program,
@@ -8825,11 +8833,6 @@ over integers changed in some details.
In particular, the control variable never wraps around.
}
-@item{
-When a coroutine finishes with an error,
-its stack is unwound (to run any pending closing methods).
-}
-
@item{
A label for a @Rw{goto} cannot be declared where a label with the same
name is visible, even if this other label is declared in an enclosing
diff --git a/testes/constructs.lua b/testes/constructs.lua
index b91e0979ec..fe4db2cbc9 100644
--- a/testes/constructs.lua
+++ b/testes/constructs.lua
@@ -215,7 +215,7 @@ do -- testing constants
checkload(prog, "unknown attribute 'XXX'")
checkload([[local xxx = 20; xxx = 10]],
- ":1: assignment to const variable 'xxx'")
+ ":1: attempt to assign to const variable 'xxx'")
checkload([[
local xx;
@@ -225,12 +225,12 @@ do -- testing constants
local abc = xx + yyy + xxx;
return function () return function () xxx = yyy end end
end
- ]], ":6: assignment to const variable 'xxx'")
+ ]], ":6: attempt to assign to const variable 'xxx'")
checkload([[
local x = nil
x = io.open()
- ]], ":2: assignment to const variable 'x'")
+ ]], ":2: attempt to assign to const variable 'x'")
end
f = [[
diff --git a/testes/locals.lua b/testes/locals.lua
index c176f50663..e59ab95a32 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -452,7 +452,6 @@ do
end)
assert(co() == 100)
local st, msg = pcall(co)
-print(msg)
-- should get last error raised
assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
end
diff --git a/testes/strings.lua b/testes/strings.lua
index 3e32f2c476..1b2b570e0e 100644
--- a/testes/strings.lua
+++ b/testes/strings.lua
@@ -3,7 +3,8 @@
print('testing strings and string library')
-local maxi, mini = math.maxinteger, math.mininteger
+local maxi = math.maxinteger
+local mini = math.mininteger
local function checkerror (msg, f, ...)
From 2b8b53864c6b8655aa7198699884075b3e2f15fa Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 28 May 2019 15:50:40 -0300
Subject: [PATCH 166/889] Improvements in 'luaL_traceback'
'luaL_traceback' changed to use an aux buffer instead of concats.
This should reduce the quantity of garbage it generates (in the
form of intermediate strings) while producing a trackback.
It also added information about the number of levels skipped when
skipping levels in a trace.
---
lauxlib.c | 53 +++++++++++++++++++++++++++++------------------------
1 file changed, 29 insertions(+), 24 deletions(-)
diff --git a/lauxlib.c b/lauxlib.c
index 89e53dc193..d49ef573c3 100644
--- a/lauxlib.c
+++ b/lauxlib.c
@@ -46,8 +46,8 @@
/*
-** search for 'objidx' in table at index -1.
-** return 1 + string at top if find a good name.
+** Search for 'objidx' in table at index -1. ('objidx' must be an
+** absolute index.) Return 1 + string at top if it found a good name.
*/
static int findfield (lua_State *L, int objidx, int level) {
if (level == 0 || !lua_istable(L, -1))
@@ -60,10 +60,10 @@ static int findfield (lua_State *L, int objidx, int level) {
return 1;
}
else if (findfield(L, objidx, level - 1)) { /* try recursively */
- lua_remove(L, -2); /* remove table (but keep name) */
- lua_pushliteral(L, ".");
- lua_insert(L, -2); /* place '.' between the two names */
- lua_concat(L, 3);
+ /* stack: lib_name, lib_table, field_name (top) */
+ lua_pushliteral(L, "."); /* place '.' between the two names */
+ lua_replace(L, -3); /* (in the slot ocupied by table) */
+ lua_concat(L, 3); /* lib_name.field_name */
return 1;
}
}
@@ -86,8 +86,8 @@ static int pushglobalfuncname (lua_State *L, lua_Debug *ar) {
lua_pushstring(L, name + 3); /* push name without prefix */
lua_remove(L, -2); /* remove original name */
}
- lua_copy(L, -1, top + 1); /* move name to proper place */
- lua_pop(L, 2); /* remove pushed values */
+ lua_copy(L, -1, top + 1); /* copy name to proper place */
+ lua_settop(L, top + 1); /* remove table "loaded" an name copy */
return 1;
}
else {
@@ -130,32 +130,37 @@ static int lastlevel (lua_State *L) {
LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1,
const char *msg, int level) {
+ luaL_Buffer b;
lua_Debug ar;
- int top = lua_gettop(L);
int last = lastlevel(L1);
- int n1 = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1;
- if (msg)
- lua_pushfstring(L, "%s\n", msg);
- luaL_checkstack(L, 10, NULL);
- lua_pushliteral(L, "stack traceback:");
+ int limit2show = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1;
+ luaL_buffinit(L, &b);
+ if (msg) {
+ luaL_addstring(&b, msg);
+ luaL_addchar(&b, '\n');
+ }
+ luaL_addstring(&b, "stack traceback:");
while (lua_getstack(L1, level++, &ar)) {
- if (n1-- == 0) { /* too many levels? */
- lua_pushliteral(L, "\n\t..."); /* add a '...' */
- level = last - LEVELS2 + 1; /* and skip to last ones */
+ if (limit2show-- == 0) { /* too many levels? */
+ int n = last - level - LEVELS2 + 1; /* number of levels to skip */
+ lua_pushfstring(L, "\n\t...\t(skipping %d levels)", n);
+ luaL_addvalue(&b); /* add warning about skip */
+ level += n; /* and skip to last levels */
}
else {
lua_getinfo(L1, "Slnt", &ar);
- lua_pushfstring(L, "\n\t%s:", ar.short_src);
- if (ar.currentline > 0)
- lua_pushfstring(L, "%d:", ar.currentline);
- lua_pushliteral(L, " in ");
+ if (ar.currentline <= 0)
+ lua_pushfstring(L, "\n\t%s: in ", ar.short_src);
+ else
+ lua_pushfstring(L, "\n\t%s:%d: in ", ar.short_src, ar.currentline);
+ luaL_addvalue(&b);
pushfuncname(L, &ar);
+ luaL_addvalue(&b);
if (ar.istailcall)
- lua_pushliteral(L, "\n\t(...tail calls...)");
- lua_concat(L, lua_gettop(L) - top);
+ luaL_addstring(&b, "\n\t(...tail calls...)");
}
}
- lua_concat(L, lua_gettop(L) - top);
+ luaL_pushresult(&b);
}
/* }====================================================== */
From 7d0f41df41e9c513e7282356541b54beaf9ed20d Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 3 Jun 2019 11:34:32 -0300
Subject: [PATCH 167/889] Improvements in 'testes/cstack.lua'
- tests show progress in real time, so that we can see maximum
stack levels even if test crashes.
- new test for recursion continuing into message handler.
---
testes/cstack.lua | 44 ++++++++++++++++++++++++++++++++------------
1 file changed, 32 insertions(+), 12 deletions(-)
diff --git a/testes/cstack.lua b/testes/cstack.lua
index 9e5bbaefd7..3cb1e4ce23 100644
--- a/testes/cstack.lua
+++ b/testes/cstack.lua
@@ -13,20 +13,40 @@ local function checkerror (msg, f, ...)
assert(not s and string.find(err, msg))
end
+local count
+local back = string.rep("\b", 8)
+local function progress ()
+ count = count + 1
+ local n = string.format("%-8d", count)
+ io.stderr:write(back, n)
+end
+
-do -- simple recursion
- local count = 0
+do print("testing simple recursion:")
+ count = 0
local function foo ()
- count = count + 1
+ progress()
foo()
end
checkerror("stack overflow", foo)
- print(" maximum recursion: " .. count)
+ print("\tfinal count: ", count)
+end
+
+
+do print("testing stack overflow in message handling")
+ count = 0
+ local function loop (x, y, z)
+ progress()
+ return 1 + loop(x, y, z)
+ end
+ local res, msg = xpcall(loop, loop)
+ assert(msg == "error in error handling")
+ print("\tfinal count: ", count)
end
-- bug since 2.5 (C-stack overflow in recursion inside pattern matching)
-do
+do print("testing recursion inside pattern matching")
local function f (size)
local s = string.rep("a", size)
local p = string.rep(".?", size)
@@ -38,25 +58,25 @@ do
end
--- testing stack-overflow in recursive 'gsub'
-do
- local count = 0
+do print("testing stack-overflow in recursive 'gsub'")
+ count = 0
local function foo ()
- count = count + 1
+ progress()
string.gsub("a", ".", foo)
end
checkerror("stack overflow", foo)
- print(" maximum 'gsub' nest (calls): " .. count)
+ print("\tfinal count: ", count)
- -- can be done with metamethods, too
+ print("testing stack-overflow in recursive 'gsub' with metatables")
count = 0
local t = setmetatable({}, {__index = foo})
foo = function ()
count = count + 1
+ progress(count)
string.gsub("a", ".", t)
end
checkerror("stack overflow", foo)
- print(" maximum 'gsub' nest (metamethods): " .. count)
+ print("\tfinal count: ", count)
end
print'OK'
From 2c68e66570206aa1496f9c76fcf2a1a0f550d692 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 3 Jun 2019 11:36:42 -0300
Subject: [PATCH 168/889] Details
Several small changes from feedback on 5.4 alhpa rc1 (warnings,
typos in the manual, and the like)
---
ldebug.c | 5 +++--
lgc.c | 4 ++--
llex.c | 4 ++--
lobject.c | 18 +++++++++---------
lstring.c | 4 ++--
ltablib.c | 2 +-
lvm.c | 4 ++--
manual/manual.of | 5 ++---
8 files changed, 23 insertions(+), 23 deletions(-)
diff --git a/ldebug.c b/ldebug.c
index 6cd4e071e8..acaa653ad6 100644
--- a/ldebug.c
+++ b/ldebug.c
@@ -373,6 +373,7 @@ static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar,
ar->ftransfer = ci->u2.transferinfo.ftransfer;
ar->ntransfer = ci->u2.transferinfo.ntransfer;
}
+ break;
}
case 'L':
case 'f': /* handled by lua_getinfo */
@@ -525,8 +526,8 @@ static const char *gxf (const Proto *p, int pc, Instruction i, int isup) {
}
- const char *getobjname (const Proto *p, int lastpc, int reg,
- const char **name) {
+static const char *getobjname (const Proto *p, int lastpc, int reg,
+ const char **name) {
int pc;
*name = luaF_getlocalname(p, reg + 1, lastpc);
if (*name) /* is a local? */
diff --git a/lgc.c b/lgc.c
index e9189ac31f..69d8a186a0 100644
--- a/lgc.c
+++ b/lgc.c
@@ -484,8 +484,8 @@ static lu_mem traversetable (global_State *g, Table *h) {
const TValue *mode = gfasttm(g, h->metatable, TM_MODE);
markobjectN(g, h->metatable);
if (mode && ttisstring(mode) && /* is there a weak mode? */
- ((weakkey = strchr(svalue(mode), 'k')),
- (weakvalue = strchr(svalue(mode), 'v')),
+ (cast_void(weakkey = strchr(svalue(mode), 'k')),
+ cast_void(weakvalue = strchr(svalue(mode), 'v')),
(weakkey || weakvalue))) { /* is really weak? */
black2gray(h); /* keep table gray */
if (!weakkey) /* strong keys? */
diff --git a/llex.c b/llex.c
index 226127f69a..d99d9015b6 100644
--- a/llex.c
+++ b/llex.c
@@ -29,7 +29,7 @@
-#define next(ls) (ls->current = zgetc(ls->z))
+#define next(ls) (ls->current = zgetc(ls->z))
@@ -337,7 +337,7 @@ static unsigned long readutf8esc (LexState *ls) {
save_and_next(ls); /* skip 'u' */
esccheck(ls, ls->current == '{', "missing '{'");
r = gethexa(ls); /* must have at least one digit */
- while ((save_and_next(ls), lisxdigit(ls->current))) {
+ while (cast_void(save_and_next(ls)), lisxdigit(ls->current)) {
i++;
esccheck(ls, r <= (0x7FFFFFFFu >> 4), "UTF-8 value too large");
r = (r << 4) + luaO_hexavalue(ls->current);
diff --git a/lobject.c b/lobject.c
index 979a68896e..2c265f964c 100644
--- a/lobject.c
+++ b/lobject.c
@@ -366,8 +366,8 @@ int luaO_utf8esc (char *buff, unsigned long x) {
/*
** Convert a number object to a string, adding it to a buffer
*/
-static size_t tostringbuff (TValue *obj, char *buff) {
- size_t len;
+static int tostringbuff (TValue *obj, char *buff) {
+ int len;
lua_assert(ttisnumber(obj));
if (ttisinteger(obj))
len = lua_integer2str(buff, MAXNUMBER2STR, ivalue(obj));
@@ -387,7 +387,7 @@ static size_t tostringbuff (TValue *obj, char *buff) {
*/
void luaO_tostring (lua_State *L, TValue *obj) {
char buff[MAXNUMBER2STR];
- size_t len = tostringbuff(obj, buff);
+ int len = tostringbuff(obj, buff);
setsvalue(L, obj, luaS_newlstr(L, buff, len));
}
@@ -439,11 +439,11 @@ static void clearbuff (BuffFS *buff) {
/*
** Get a space of size 'sz' in the buffer. If buffer has not enough
-** space, empty it. 'sz' must fit in an empty space.
+** space, empty it. 'sz' must fit in an empty buffer.
*/
-static char *getbuff (BuffFS *buff, size_t sz) {
+static char *getbuff (BuffFS *buff, int sz) {
lua_assert(buff->blen <= BUFVFS); lua_assert(sz <= BUFVFS);
- if (sz > BUFVFS - cast_sizet(buff->blen)) /* string does not fit? */
+ if (sz > BUFVFS - buff->blen) /* not enough space? */
clearbuff(buff);
return buff->space + buff->blen;
}
@@ -458,9 +458,9 @@ static char *getbuff (BuffFS *buff, size_t sz) {
*/
static void addstr2buff (BuffFS *buff, const char *str, size_t slen) {
if (slen <= BUFVFS) { /* does string fit into buffer? */
- char *bf = getbuff(buff, slen);
+ char *bf = getbuff(buff, cast_int(slen));
memcpy(bf, str, slen); /* add string to buffer */
- addsize(buff, slen);
+ addsize(buff, cast_int(slen));
}
else { /* string larger than buffer */
clearbuff(buff); /* string comes after buffer's content */
@@ -474,7 +474,7 @@ static void addstr2buff (BuffFS *buff, const char *str, size_t slen) {
*/
static void addnum2buff (BuffFS *buff, TValue *num) {
char *numbuff = getbuff(buff, MAXNUMBER2STR);
- size_t len = tostringbuff(num, numbuff); /* format number into 'numbuff' */
+ int len = tostringbuff(num, numbuff); /* format number into 'numbuff' */
addsize(buff, len);
}
diff --git a/lstring.c b/lstring.c
index c52539a6d5..6ba798d9eb 100644
--- a/lstring.c
+++ b/lstring.c
@@ -121,8 +121,8 @@ void luaS_clearcache (global_State *g) {
int i, j;
for (i = 0; i < STRCACHE_N; i++)
for (j = 0; j < STRCACHE_M; j++) {
- if (iswhite(g->strcache[i][j])) /* will entry be collected? */
- g->strcache[i][j] = g->memerrmsg; /* replace it with something fixed */
+ if (iswhite(g->strcache[i][j])) /* will entry be collected? */
+ g->strcache[i][j] = g->memerrmsg; /* replace it with something fixed */
}
}
diff --git a/ltablib.c b/ltablib.c
index 48a6bdfe27..7e7a101211 100644
--- a/ltablib.c
+++ b/ltablib.c
@@ -306,7 +306,7 @@ static IdxT partition (lua_State *L, IdxT lo, IdxT up) {
}
/* after the loop, a[i] >= P and a[lo .. i - 1] < P */
/* next loop: repeat --j while P < a[j] */
- while (lua_geti(L, 1, --j), sort_comp(L, -3, -1)) {
+ while ((void)lua_geti(L, 1, --j), sort_comp(L, -3, -1)) {
if (j < i) /* j < i but a[j] > P ?? */
luaL_error(L, "invalid order function for sorting");
lua_pop(L, 1); /* remove a[j] */
diff --git a/lvm.c b/lvm.c
index 45788a010e..d700079186 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1151,7 +1151,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
TValue *rc = vRC(i);
lua_Unsigned n;
if (ttisinteger(rc) /* fast track for integers? */
- ? (n = ivalue(rc), luaV_fastgeti(L, rb, n, slot))
+ ? (cast_void(n = ivalue(rc)), luaV_fastgeti(L, rb, n, slot))
: luaV_fastget(L, rb, rc, slot, luaH_get)) {
setobj2s(L, ra, slot);
}
@@ -1204,7 +1204,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
TValue *rc = RKC(i); /* value */
lua_Unsigned n;
if (ttisinteger(rb) /* fast track for integers? */
- ? (n = ivalue(rb), luaV_fastgeti(L, s2v(ra), n, slot))
+ ? (cast_void(n = ivalue(rb)), luaV_fastgeti(L, s2v(ra), n, slot))
: luaV_fastget(L, s2v(ra), rb, slot, luaH_get)) {
luaV_finishfastset(L, s2v(ra), slot, rc);
}
diff --git a/manual/manual.of b/manual/manual.of
index ff69cd2c36..687a5b8972 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1504,7 +1504,7 @@ There are two possible attributes:
@id{const}, which declares a @x{constant variable},
that is, a variable that cannot be assigned to
after its initialization;
-and @id{toclose}, wich declares a to-be-closed variable @see{to-be-closed}.
+and @id{toclose}, which declares a to-be-closed variable @see{to-be-closed}.
A chunk is also a block @see{chunks},
@@ -1549,7 +1549,7 @@ the other pending closing methods will still be called.
If a coroutine yields inside a block and is never resumed again,
the variables visible at that block will never go out of scope,
-and therefore they will not be closed.
+and therefore they will never be closed.
Similarly, if a coroutine ends with an error,
it does not unwind its stack,
so it does not close any variable.
@@ -3432,7 +3432,6 @@ The new thread returned by this function shares with the original thread
its global environment,
but has an independent execution stack.
-There is no explicit function to close or to destroy a thread.
Threads are subject to garbage collection,
like any Lua object.
From 4a3fd8488d617aa633f6b8be85e662653b100a59 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 3 Jun 2019 12:13:13 -0300
Subject: [PATCH 169/889] bug in 5.4 alpha rc1: to-be-closed x vararg functions
Closing methods must be run before correcting 'ci->func' when exiting
a vararg function, to get correct debug information (e.g., in case of
errors).
---
lvm.c | 2 +-
testes/locals.lua | 9 +++++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/lvm.c b/lvm.c
index d700079186..5d0709ef94 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1593,9 +1593,9 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
savepc(ci);
if (TESTARG_k(i)) {
int nparams1 = GETARG_C(i);
+ luaF_close(L, base, LUA_OK); /* there may be open upvalues */
if (nparams1) /* vararg function? */
ci->func -= ci->u.l.nextraargs + nparams1;
- luaF_close(L, base, LUA_OK); /* there may be open upvalues */
}
luaD_poscall(L, ci, n);
return;
diff --git a/testes/locals.lua b/testes/locals.lua
index e59ab95a32..7834d7dac5 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -276,6 +276,15 @@ do -- errors in __close
assert(msg == 1)
assert(log[1] == 4 and log[2] == 3 and log[3] == 2 and log[4] == 2
and #log == 4)
+
+ -- error in toclose in vararg function
+ function foo (...)
+ local x123 = 10
+ end
+
+ local st, msg = pcall(foo)
+ assert(string.find(msg, "'x123'"))
+
end
From 514d94274853e6f0dfd6bb2ffa2e1fc64db926dd Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Mon, 3 Jun 2019 13:11:20 -0300
Subject: [PATCH 170/889] 'coroutine.kill' renamed 'coroutine.close'
---
lcorolib.c | 6 +++---
manual/manual.of | 29 +++++++++++++++--------------
testes/coroutine.lua | 22 +++++++++++-----------
3 files changed, 29 insertions(+), 28 deletions(-)
diff --git a/lcorolib.c b/lcorolib.c
index 3fc9fb1c1f..156839e6e5 100644
--- a/lcorolib.c
+++ b/lcorolib.c
@@ -165,7 +165,7 @@ static int luaB_corunning (lua_State *L) {
}
-static int luaB_kill (lua_State *L) {
+static int luaB_close (lua_State *L) {
lua_State *co = getco(L);
int status = auxstatus(L, co);
switch (status) {
@@ -182,7 +182,7 @@ static int luaB_kill (lua_State *L) {
}
}
default: /* normal or running coroutine */
- return luaL_error(L, "cannot kill a %s coroutine", statname[status]);
+ return luaL_error(L, "cannot close a %s coroutine", statname[status]);
}
}
@@ -195,7 +195,7 @@ static const luaL_Reg co_funcs[] = {
{"wrap", luaB_cowrap},
{"yield", luaB_yield},
{"isyieldable", luaB_yieldable},
- {"kill", luaB_kill},
+ {"close", luaB_close},
{NULL, NULL}
};
diff --git a/manual/manual.of b/manual/manual.of
index 687a5b8972..eb4e671d9c 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -864,7 +864,7 @@ Unlike @Lid{coroutine.resume},
the function created by @Lid{coroutine.wrap}
propagates any error to the caller.
In this case,
-the function also kills the coroutine @seeF{coroutine.kill}.
+the function also closes the coroutine @seeF{coroutine.close}.
As an example of how coroutines work,
consider the following code:
@@ -1554,7 +1554,7 @@ Similarly, if a coroutine ends with an error,
it does not unwind its stack,
so it does not close any variable.
You should either use finalizers
-or call @Lid{coroutine.kill} to close the variables in these cases.
+or call @Lid{coroutine.close} to close the variables in these cases.
However, note that if the coroutine was created
through @Lid{coroutine.wrap},
then its corresponding function will close all variables
@@ -6351,6 +6351,18 @@ which come inside the table @defid{coroutine}.
See @See{coroutine} for a general description of coroutines.
+@LibEntry{coroutine.close (co)|
+
+Closes coroutine @id{co},
+that is,
+closes all its pending to-be-closed variables
+and puts the coroutine in a dead state.
+In case of error closing some variable,
+returns @false plus the error object;
+otherwise returns @true.
+
+}
+
@LibEntry{coroutine.create (f)|
Creates a new coroutine, with body @id{f}.
@@ -6370,17 +6382,6 @@ it is not inside a non-yieldable @N{C function}.
}
-@LibEntry{coroutine.kill (co)|
-
-Kills coroutine @id{co},
-closing all its pending to-be-closed variables
-and putting the coroutine in a dead state.
-In case of error closing some variable,
-returns @false plus the error object;
-otherwise returns @true.
-
-}
-
@LibEntry{coroutine.resume (co [, val1, @Cdots])|
Starts or continues the execution of coroutine @id{co}.
@@ -6433,7 +6434,7 @@ extra arguments to @id{resume}.
The function returns the same values returned by @id{resume},
except the first boolean.
In case of error,
-the function kills the coroutine and propagates the error.
+the function closes the coroutine and propagates the error.
}
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index db6d074ee1..198a58701d 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -123,23 +123,23 @@ assert(#a == 22 and a[#a] == 79)
x, a = nil
--- coroutine kill
+-- coroutine closing
do
- -- ok to kill a dead coroutine
+ -- ok to close a dead coroutine
local co = coroutine.create(print)
- assert(coroutine.resume(co, "testing 'coroutine.kill'"))
+ assert(coroutine.resume(co, "testing 'coroutine.close'"))
assert(coroutine.status(co) == "dead")
- assert(coroutine.kill(co))
+ assert(coroutine.close(co))
- -- cannot kill the running coroutine
- local st, msg = pcall(coroutine.kill, coroutine.running())
+ -- cannot close the running coroutine
+ local st, msg = pcall(coroutine.close, coroutine.running())
assert(not st and string.find(msg, "running"))
local main = coroutine.running()
- -- cannot kill a "normal" coroutine
+ -- cannot close a "normal" coroutine
;(coroutine.wrap(function ()
- local st, msg = pcall(coroutine.kill, main)
+ local st, msg = pcall(coroutine.close, main)
assert(not st and string.find(msg, "normal"))
end))()
@@ -159,10 +159,10 @@ do
end)
coroutine.resume(co)
assert(X)
- assert(coroutine.kill(co))
+ assert(coroutine.close(co))
assert(not X and coroutine.status(co) == "dead")
- -- error killing a coroutine
+ -- error closing a coroutine
co = coroutine.create(function()
local x = func2close(function (self, err)
assert(err == nil); error(111)
@@ -170,7 +170,7 @@ do
coroutine.yield()
end)
coroutine.resume(co)
- local st, msg = coroutine.kill(co)
+ local st, msg = coroutine.close(co)
assert(not st and coroutine.status(co) == "dead" and msg == 111)
end
From 14edd364c3abcb758e74c68a2bdd4ddaeefdae2a Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Tue, 4 Jun 2019 11:22:21 -0300
Subject: [PATCH 171/889] Function 'warn' is vararg
Instead of a 'tocont' flag, the function 'warn' in Lua now receives all
message pieces as multiple arguments in a single call. Besides being
simpler to use, this implementation ensures that Lua code cannot create
unfinished warnings.
---
lbaselib.c | 15 +++++++++++++--
manual/manual.of | 8 +++-----
testes/all.lua | 22 ++++++++++------------
testes/main.lua | 18 +++++++++++++++++-
4 files changed, 43 insertions(+), 20 deletions(-)
diff --git a/lbaselib.c b/lbaselib.c
index 83f61e6d11..4724e75990 100644
--- a/lbaselib.c
+++ b/lbaselib.c
@@ -37,9 +37,20 @@ static int luaB_print (lua_State *L) {
}
+/*
+** Creates a warning with all given arguments.
+** Check first for errors; otherwise an error may interrupt
+** the composition of a warning, leaving it unfinished.
+*/
static int luaB_warn (lua_State *L) {
- const char *msg = luaL_checkstring(L, 1);
- lua_warning(L, msg, lua_toboolean(L, 2));
+ int n = lua_gettop(L); /* number of arguments */
+ int i;
+ luaL_checkstring(L, 1); /* at least one argument */
+ for (i = 2; i <= n; i++)
+ luaL_checkstring(L, i); /* make sure all arguments are strings */
+ for (i = 1; i <= n; i++) /* compose warning */
+ lua_warning(L, lua_tostring(L, i), 1);
+ lua_warning(L, "", 0); /* close warning */
return 0;
}
diff --git a/manual/manual.of b/manual/manual.of
index eb4e671d9c..4c9c20b293 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -6326,12 +6326,10 @@ The current value of this variable is @St{Lua 5.4}.
}
-@LibEntry{warn (message [, tocont])|
+@LibEntry{warn (msg1, @Cdots)|
-Emits a warning with the given message.
-A message in a call with @id{tocont} true should be
-continued in another call to this function.
-The default for @id{tocont} is false.
+Emits a warning with a message composed by the concatenation
+of all its arguments (which should be strings).
}
diff --git a/testes/all.lua b/testes/all.lua
index 8d727b6b21..2e6fe0381c 100644
--- a/testes/all.lua
+++ b/testes/all.lua
@@ -5,8 +5,8 @@
local version = "Lua 5.4"
if _VERSION ~= version then
- warn(string.format(
- "This test suite is for %s, not for %s\nExiting tests", version, _VERSION))
+ warn("This test suite is for ", version,
+ ", not for ", _VERSION, "\nExiting tests")
return
end
@@ -190,16 +190,13 @@ assert(dofile('verybig.lua', true) == 10); collectgarbage()
dofile('files.lua')
if #msgs > 0 then
- warn("#tests not performed:", true)
- for i=1,#msgs do
- warn("\n ", true); warn(msgs[i], true)
- end
- warn("\n")
+ local m = table.concat(msgs, "\n ")
+ warn("#tests not performed:\n ", m, "\n")
end
print("(there should be two warnings now)")
-warn("#This is ", true); warn("an expected", true); warn(" warning")
-warn("#This is", true); warn(" another one")
+warn("#This is ", "an expected", " warning")
+warn("#This is", " another one")
-- no test module should define 'debug'
assert(debug == nil)
@@ -216,9 +213,10 @@ _G.showmem = showmem
end --)
-local _G, showmem, print, format, clock, time, difftime, assert, open =
+local _G, showmem, print, format, clock, time, difftime,
+ assert, open, warn =
_G, showmem, print, string.format, os.clock, os.time, os.difftime,
- assert, io.open
+ assert, io.open, warn
-- file with time of last performed test
local fname = T and "time-debug.txt" or "time.txt"
@@ -262,7 +260,7 @@ if not usertests then
local diff = (clocktime - lasttime) / lasttime
local tolerance = 0.05 -- 5%
if (diff >= tolerance or diff <= -tolerance) then
- print(format("WARNING: time difference from previous test: %+.1f%%",
+ warn(format("#time difference from previous test: %+.1f%%",
diff * 100))
end
assert(open(fname, "w")):write(clocktime):close()
diff --git a/testes/main.lua b/testes/main.lua
index 47d84d4ced..4c09645a3c 100644
--- a/testes/main.lua
+++ b/testes/main.lua
@@ -347,10 +347,26 @@ NoRun("syntax error", "lua -e a")
NoRun("'-l' needs argument", "lua -l")
-if T then -- auxiliary library?
+if T then -- test library?
print("testing 'not enough memory' to create a state")
NoRun("not enough memory", "env MEMLIMIT=100 lua")
+
+ -- testing 'warn'
+ warn("@123", "456", "789")
+ assert(_WARN == "@123456789")
end
+
+do
+ -- 'warn' must get at least one argument
+ local st, msg = pcall(warn)
+ assert(string.find(msg, "string expected"))
+
+ -- 'warn' does not leave unfinished warning in case of errors
+ -- (message would appear in next warning)
+ st, msg = pcall(warn, "SHOULD NOT APPEAR", {})
+ assert(string.find(msg, "string expected"))
+end
+
print('+')
print('testing Ctrl C')
From b4d5dff8ec4f1c8a44db66d368e95d359b04aea7 Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy
Date: Wed, 5 Jun 2019 13:16:25 -0300
Subject: [PATCH 172/889] Multiple errors in '__toclose' report the first one
When there are multiple errors when closing objects, the error
reported by the protected call is the first one, for two reasons:
First, other errors may be caused by this one;
second, the first error is handled in the original execution context,
and therefore has the full traceback.
---
lcorolib.c | 7 ++-----
lfunc.c | 9 ++++++---
manual/manual.of | 21 +++++++++++++++------
testes/coroutine.lua | 10 +++++++++-
testes/locals.lua | 37 ++++++++++++++++++++++++-------------
5 files changed, 56 insertions(+), 28 deletions(-)
diff --git a/lcorolib.c b/lcorolib.c
index 156839e6e5..4d47ea28ac 100644
--- a/lcorolib.c
+++ b/lcorolib.c
@@ -75,11 +75,8 @@ static int luaB_auxwrap (lua_State *L) {
int r = auxresume(L, co, lua_gettop(L));
if (r < 0) {
int stat = lua_status(co);
- if (stat != LUA_OK && stat != LUA_YIELD) {
- stat = lua_resetthread(co); /* close variables in case of errors */
- if (stat != LUA_OK) /* error closing variables? */
- lua_xmove(co, L, 1); /* get new error object */
- }
+ if (stat != LUA_OK && stat != LUA_YIELD)
+ lua_resetthread(co); /* close variables in case of errors */
if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */
luaL_where(L, 1); /* add extra info, if available */
lua_insert(L, -2);
diff --git a/lfunc.c b/lfunc.c
index 3e044b65b2..551149927f 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -144,13 +144,16 @@ static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) {
luaG_runerror(L, "attempt to close non-closable variable '%s'", vname);
}
}
- else { /* there was an error */
+ else { /* must close the object in protected mode */
+ ptrdiff_t oldtop = savestack(L, level + 1);
/* save error message and set stack top to 'level + 1' */
luaD_seterrorobj(L, status, level);
if (prepclosingmethod(L, uv, s2v(level))) { /* something to call? */
- int newstatus = luaD_pcall(L, callclose, NULL, savestack(L, level), 0);
- if (newstatus != LUA_OK) /* another error when closing? */
+ int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0);
+ if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first error? */
status = newstatus; /* this will be the new error */
+ else /* leave original error (or nil) on top */
+ L->top = restorestack(L, oldtop);
}
/* else no metamethod; ignore this case and keep original error */
}
diff --git a/manual/manual.of b/manual/manual.of
index 4c9c20b293..2c0957b9db 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -1541,11 +1541,17 @@ if there was no error, the second argument is @nil.
If several to-be-closed variables go out of scope at the same event,
they are closed in the reverse order that they were declared.
+
If there is any error while running a closing method,
that error is handled like an error in the regular code
where the variable was defined;
in particular,
the other pending closing methods will still be called.
+After an error,
+other errors in closing methods
+interrupt the respective method,
+but are otherwise ignored;
+the error reported is the original one.
If a coroutine yields inside a block and is never resumed again,
the variables visible at that block will never go out of scope,
@@ -1553,11 +1559,12 @@ and therefore they will never be closed.
Similarly, if a coroutine ends with an error,
it does not unwind its stack,
so it does not close any variable.
-You should either use finalizers
-or call @Lid{coroutine.close} to close the variables in these cases.
-However, note that if the coroutine was created
+In both cases,
+you should either use finalizers
+or call @Lid{coroutine.close} to close the variables.
+However, if the coroutine was created
through @Lid{coroutine.wrap},
-then its corresponding function will close all variables
+then its corresponding function will close the coroutine
in case of errors.
}
@@ -3932,7 +3939,7 @@ Returns a status code:
@Lid{LUA_OK} for no errors in closing methods,
or an error status otherwise.
In case of error,
-leave the error object on the stack,
+leaves the error object on the top of the stack,
}
@@ -6355,6 +6362,7 @@ Closes coroutine @id{co},
that is,
closes all its pending to-be-closed variables
and puts the coroutine in a dead state.
+The given coroutine must be dead or suspended.
In case of error closing some variable,
returns @false plus the error object;
otherwise returns @true.
@@ -6412,7 +6420,8 @@ true when the running coroutine is the main one.
Returns the status of the coroutine @id{co}, as a string:
@T{"running"},
-if the coroutine is running (that is, it called @id{status});
+if the coroutine is running
+(that is, it is the one that called @id{status});
@T{"suspended"}, if the coroutine is suspended in a call to @id{yield},
or if it has not started running yet;
@T{"normal"} if the coroutine is active but not running
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index 198a58701d..f2c0da8bdf 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -163,15 +163,23 @@ do
assert(not X and coroutine.status(co) == "dead")
-- error closing a coroutine
+ local x = 0
co = coroutine.create(function()
+ local y = func2close(function (self,err)
+ if (err ~= 111) then os.exit(false) end -- should not happen
+ x = 200
+ error(200)
+ end)
local x = func2close(function (self, err)
assert(err == nil); error(111)
end)
coroutine.yield()
end)
coroutine.resume(co)
+ assert(x == 0)
local st, msg = coroutine.close(co)
- assert(not st and coroutine.status(co) == "dead" and msg == 111)
+ assert(st == false and coroutine.status(co) == "dead" and msg == 111)
+ assert(x == 200)
end
diff --git a/testes/locals.lua b/testes/locals.lua
index 7834d7dac5..dccda28fe9 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -267,14 +267,14 @@ do -- errors in __close
if err then error(4) end
end
local stat, msg = pcall(foo, false)
- assert(msg == 1)
- assert(log[1] == 10 and log[2] == 3 and log[3] == 2 and log[4] == 2
+ assert(msg == 3)
+ assert(log[1] == 10 and log[2] == 3 and log[3] == 3 and log[4] == 3
and #log == 4)
log = {}
local stat, msg = pcall(foo, true)
- assert(msg == 1)
- assert(log[1] == 4 and log[2] == 3 and log[3] == 2 and log[4] == 2
+ assert(msg == 4)
+ assert(log[1] == 4 and log[2] == 4 and log[3] == 4 and log[4] == 4
and #log == 4)
-- error in toclose in vararg function
@@ -317,7 +317,7 @@ if rawget(_G, "T") then
local x = setmetatable({}, {__close = function ()
T.alloccount(0); local x = {} -- force a memory error
end})
- error("a") -- common error inside the function's body
+ error(1000) -- common error inside the function's body
end
stack(5) -- ensure a minimal number of CI structures
@@ -325,7 +325,7 @@ if rawget(_G, "T") then
-- despite memory error, 'y' will be executed and
-- memory limit will be lifted
local _, msg = pcall(foo)
- assert(msg == "not enough memory")
+ assert(msg == 1000)
local close = func2close(function (self, msg)
T.alloccount()
@@ -368,8 +368,7 @@ if rawget(_G, "T") then
end
local _, msg = pcall(test)
- assert(msg == 1000)
-
+ assert(msg == "not enough memory") -- reported error is the first one
do -- testing 'toclose' in C string buffer
collectgarbage()
@@ -453,15 +452,27 @@ end
do
-- error in a wrapped coroutine raising errors when closing a variable
- local x = false
+ local x = 0
local co = coroutine.wrap(function ()
- local xv = func2close(function () error("XXX") end)
+ local