aboutsummaryrefslogtreecommitdiff
path: root/vendor
diff options
context:
space:
mode:
authortobil4sk <tobil4sk@outlook.com>2026-02-03 22:47:50 +0000
committerGitHub <noreply@github.com>2026-02-03 19:47:50 -0300
commit47301d83aba58925e1b9594023621ebb27070cdb (patch)
tree73021b5366687ec1683b9e66505e74f22f71d31b /vendor
parentacf1f47e7f1b1ecbc147e41cae51ddfd06ad898d (diff)
downloadluarocks-main.tar.gz
luarocks-main.tar.bz2
luarocks-main.zip
Improve flexibility around vendored librariesmain
compat53 is vendored since #1757 as it is required to run luarocks with lua 5.1 or 5.2. However, this introduced some issues as the GNUmakefile install rule places these in the same place where `luarocks install compat53` would install them. This means you get conflicts if you install the actual package: ``` Warning: /.../prefix/share/lua/5.1/compat53/init.lua is not tracked by this installation of LuaRocks. Moving it to /.../prefix/share/lua/5.1/compat53/init.lua~ Warning: /.../prefix/share/lua/5.1/compat53/module.lua is not tracked by this installation of LuaRocks. Moving it to /.../prefix/share/lua/5.1/compat53/module.lua~ Warning: /.../prefix/share/lua/5.1/compat53/file_mt.lua is not tracked by this installation of LuaRocks. Moving it to /.../prefix/share/lua/5.1/compat53/file_mt.lua~ ``` It is also not ideal for linux package maintainers to include a vendored package, see: https://github.com/luarocks/luarocks/pull/1757#issuecomment-3409873412. To solve these issues, this patchset makes the following changes: - GNUmakefile now places the compat53 files under `luarocks/vendor/compat53` (which is added internally to the luarocks script's `package.path`). This way a user's installation of compat53 does not interfere at all with luarocks one. - Added `--with-system-compat53` option to configure script for external packaging systems. - Fixed install.bat's logic for deciding whether to vendor compat53, as the current script includes it for every version. install.bat already places luarocks sources outside of LUAPATH, so that part can stay as is. I've also inverted the version check to avoid the need for future patches like: #1850.
Diffstat (limited to 'vendor')
-rw-r--r--vendor/argparse.d.tl111
-rw-r--r--vendor/argparse.lua2103
-rw-r--r--vendor/compat53/file_mt.lua71
-rw-r--r--vendor/compat53/init.lua325
-rw-r--r--vendor/compat53/module.lua894
-rw-r--r--vendor/dkjson.d.tl33
-rw-r--r--vendor/dkjson.lua749
7 files changed, 4286 insertions, 0 deletions
diff --git a/vendor/argparse.d.tl b/vendor/argparse.d.tl
new file mode 100644
index 00000000..812786c4
--- /dev/null
+++ b/vendor/argparse.d.tl
@@ -0,0 +1,111 @@
1
2local record argparse
3 type Args = {string : {string} | string | boolean}
4
5 record Parser
6 name: function(self: Parser, name: string): Parser
7 description: function(self: Parser, description: string): Parser
8 epilog: function(self: Parser, epilog: string): Parser
9
10 flag: function(self: Parser, flag: string): Option
11 flag: function(self: Parser, shortalias: string, longalias: string): Option
12
13 parse: function(self: Parser, argv: {string}): Args
14 pparse: function(self: Parser, argv: {string}): boolean, Args | string
15
16 error: function(self: Parser, error: string)
17
18 argument: function(self: Parser, name: string, description: string): Argument
19
20 get_usage: function(self: Parser): string
21 get_help: function(self: Parser): string
22
23 option: function(self: Parser, name: string, description?: string, default?: string, convert?: function | {function}, args?: {string}, count?: integer | string): Option
24 option: function(self: Parser, name: string, description?: string, default?: string, convert?: {string:string}, args?: {string}, count?: integer | string): Option
25
26 require_command: function(self: Parser, require_command: boolean): Parser
27 command_target: function(self: Parser, command_target: string): Parser
28
29 command: function(self: Parser, name: string, description: string, epilog: string): Command
30
31 add_help: function(self: Parser, boolean)
32
33 help_max_width: function(self: Parser, number): Parser
34 add_help_command: function(self: Parser, ?string | {string: any}): Parser
35 add_complete_command: function(self: Parser, ?string | {string: any}): Parser
36
37 group: function(self: Parser, ...:any): Parser
38
39 -- TODO: should be Argument | Option
40 mutex: function(self: Parser, ...: any)
41
42 record Opts
43 name: string
44 description: string
45 epilog: string
46 end
47 metamethod __call: function(Parser, Opts): Parser
48 metamethod __call: function(Parser, string, string, string): Parser
49 end
50
51 type ActionCallback = function(args: Args, index: string, val: string | boolean | {string}, overwrite: boolean)
52
53 record Argument
54 choices: function(self: Argument, choices: {string}): Argument
55
56 convert: function(self: Argument, convert: function | {function}): Argument
57 convert: function(self: Argument, convert: {string:string}): Argument
58
59 args: function(self: Argument, args: string | integer): Argument
60
61 action: function(self: Argument, cb: ActionCallback)
62 end
63
64 record Option
65 name: function(self: Option, name: string): Option
66 description: function(self: Option, description: string): Option
67
68 argname: function(self: Option, argname: string | {string}): Option
69
70 count: function(self: Option, count: integer | string): Option
71
72 choices: function(self: Option, {string}): Option
73
74 default: function(self: Option, string): Option
75
76 defmode: function(self: Option, string): Option
77
78 target: function(self: Option, target: string): Option
79
80 args: function(self: Option, args: string|integer): Option
81
82 action: function(self: Option, cb: ActionCallback)
83
84 hidden_name: function(self: Option, string)
85
86 hidden: function(self: Option, boolean)
87
88 convert: function(self: Option, function)
89 end
90
91 record Command
92 summary: function(self: Command, summary: string): Command
93 description: function(self: Command, description: string): Command
94
95 argument: function(self: Command, name: string, description?: string): Argument
96
97 option: function(self: Command, name: string, description: string): Option
98
99 flag: function(self: Command, string, ?string): Option
100
101 handle_options: function(self: Command, boolean): Command
102
103 mutex: function(self: Command, ...: any) --! copied over from Parser
104
105 group: function(self: Command, ...:any): Command --! copied over from Parser
106 end
107
108 metamethod __call: function(self: argparse, name: string, description: string, epilog: string): Parser
109end
110
111return argparse
diff --git a/vendor/argparse.lua b/vendor/argparse.lua
new file mode 100644
index 00000000..2c2585dd
--- /dev/null
+++ b/vendor/argparse.lua
@@ -0,0 +1,2103 @@
1-- The MIT License (MIT)
2
3-- Copyright (c) 2013 - 2018 Peter Melnichenko
4-- 2019 Paul Ouellette
5
6-- Permission is hereby granted, free of charge, to any person obtaining a copy of
7-- this software and associated documentation files (the "Software"), to deal in
8-- the Software without restriction, including without limitation the rights to
9-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10-- the Software, and to permit persons to whom the Software is furnished to do so,
11-- subject to the following conditions:
12
13-- The above copyright notice and this permission notice shall be included in all
14-- copies or substantial portions of the Software.
15
16-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
23local function deep_update(t1, t2)
24 for k, v in pairs(t2) do
25 if type(v) == "table" then
26 v = deep_update({}, v)
27 end
28
29 t1[k] = v
30 end
31
32 return t1
33end
34
35-- A property is a tuple {name, callback}.
36-- properties.args is number of properties that can be set as arguments
37-- when calling an object.
38local function class(prototype, properties, parent)
39 -- Class is the metatable of its instances.
40 local cl = {}
41 cl.__index = cl
42
43 if parent then
44 cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype)
45 else
46 cl.__prototype = prototype
47 end
48
49 if properties then
50 local names = {}
51
52 -- Create setter methods and fill set of property names.
53 for _, property in ipairs(properties) do
54 local name, callback = property[1], property[2]
55
56 cl[name] = function(self, value)
57 if not callback(self, value) then
58 self["_" .. name] = value
59 end
60
61 return self
62 end
63
64 names[name] = true
65 end
66
67 function cl.__call(self, ...)
68 -- When calling an object, if the first argument is a table,
69 -- interpret keys as property names, else delegate arguments
70 -- to corresponding setters in order.
71 if type((...)) == "table" then
72 for name, value in pairs((...)) do
73 if names[name] then
74 self[name](self, value)
75 end
76 end
77 else
78 local nargs = select("#", ...)
79
80 for i, property in ipairs(properties) do
81 if i > nargs or i > properties.args then
82 break
83 end
84
85 local arg = select(i, ...)
86
87 if arg ~= nil then
88 self[property[1]](self, arg)
89 end
90 end
91 end
92
93 return self
94 end
95 end
96
97 -- If indexing class fails, fallback to its parent.
98 local class_metatable = {}
99 class_metatable.__index = parent
100
101 function class_metatable.__call(self, ...)
102 -- Calling a class returns its instance.
103 -- Arguments are delegated to the instance.
104 local object = deep_update({}, self.__prototype)
105 setmetatable(object, self)
106 return object(...)
107 end
108
109 return setmetatable(cl, class_metatable)
110end
111
112local function typecheck(name, types, value)
113 for _, type_ in ipairs(types) do
114 if type(value) == type_ then
115 return true
116 end
117 end
118
119 error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value)))
120end
121
122local function typechecked(name, ...)
123 local types = {...}
124 return {name, function(_, value) typecheck(name, types, value) end}
125end
126
127local multiname = {"name", function(self, value)
128 typecheck("name", {"string"}, value)
129
130 for alias in value:gmatch("%S+") do
131 self._name = self._name or alias
132 table.insert(self._aliases, alias)
133 table.insert(self._public_aliases, alias)
134 -- If alias contains '_', accept '-' also.
135 if alias:find("_", 1, true) then
136 table.insert(self._aliases, (alias:gsub("_", "-")))
137 end
138 end
139
140 -- Do not set _name as with other properties.
141 return true
142end}
143
144local multiname_hidden = {"hidden_name", function(self, value)
145 typecheck("hidden_name", {"string"}, value)
146
147 for alias in value:gmatch("%S+") do
148 table.insert(self._aliases, alias)
149 if alias:find("_", 1, true) then
150 table.insert(self._aliases, (alias:gsub("_", "-")))
151 end
152 end
153
154 return true
155end}
156
157local function parse_boundaries(str)
158 if tonumber(str) then
159 return tonumber(str), tonumber(str)
160 end
161
162 if str == "*" then
163 return 0, math.huge
164 end
165
166 if str == "+" then
167 return 1, math.huge
168 end
169
170 if str == "?" then
171 return 0, 1
172 end
173
174 if str:match "^%d+%-%d+$" then
175 local min, max = str:match "^(%d+)%-(%d+)$"
176 return tonumber(min), tonumber(max)
177 end
178
179 if str:match "^%d+%+$" then
180 local min = str:match "^(%d+)%+$"
181 return tonumber(min), math.huge
182 end
183end
184
185local function boundaries(name)
186 return {name, function(self, value)
187 typecheck(name, {"number", "string"}, value)
188
189 local min, max = parse_boundaries(value)
190
191 if not min then
192 error(("bad property '%s'"):format(name))
193 end
194
195 self["_min" .. name], self["_max" .. name] = min, max
196 end}
197end
198
199local actions = {}
200
201local option_action = {"action", function(_, value)
202 typecheck("action", {"function", "string"}, value)
203
204 if type(value) == "string" and not actions[value] then
205 error(("unknown action '%s'"):format(value))
206 end
207end}
208
209local option_init = {"init", function(self)
210 self._has_init = true
211end}
212
213local option_default = {"default", function(self, value)
214 if type(value) ~= "string" then
215 self._init = value
216 self._has_init = true
217 return true
218 end
219end}
220
221local add_help = {"add_help", function(self, value)
222 typecheck("add_help", {"boolean", "string", "table"}, value)
223
224 if self._help_option_idx then
225 table.remove(self._options, self._help_option_idx)
226 self._help_option_idx = nil
227 end
228
229 if value then
230 local help = self:flag()
231 :description "Show this help message and exit."
232 :action(function()
233 print(self:get_help())
234 os.exit(0)
235 end)
236
237 if value ~= true then
238 help = help(value)
239 end
240
241 if not help._name then
242 help "-h" "--help"
243 end
244
245 self._help_option_idx = #self._options
246 end
247end}
248
249local Parser = class({
250 _arguments = {},
251 _options = {},
252 _commands = {},
253 _mutexes = {},
254 _groups = {},
255 _require_command = true,
256 _handle_options = true
257}, {
258 args = 3,
259 typechecked("name", "string"),
260 typechecked("description", "string"),
261 typechecked("epilog", "string"),
262 typechecked("usage", "string"),
263 typechecked("help", "string"),
264 typechecked("require_command", "boolean"),
265 typechecked("handle_options", "boolean"),
266 typechecked("action", "function"),
267 typechecked("command_target", "string"),
268 typechecked("help_vertical_space", "number"),
269 typechecked("usage_margin", "number"),
270 typechecked("usage_max_width", "number"),
271 typechecked("help_usage_margin", "number"),
272 typechecked("help_description_margin", "number"),
273 typechecked("help_max_width", "number"),
274 add_help
275})
276
277local Command = class({
278 _aliases = {},
279 _public_aliases = {}
280}, {
281 args = 3,
282 multiname,
283 typechecked("description", "string"),
284 typechecked("epilog", "string"),
285 multiname_hidden,
286 typechecked("summary", "string"),
287 typechecked("target", "string"),
288 typechecked("usage", "string"),
289 typechecked("help", "string"),
290 typechecked("require_command", "boolean"),
291 typechecked("handle_options", "boolean"),
292 typechecked("action", "function"),
293 typechecked("command_target", "string"),
294 typechecked("help_vertical_space", "number"),
295 typechecked("usage_margin", "number"),
296 typechecked("usage_max_width", "number"),
297 typechecked("help_usage_margin", "number"),
298 typechecked("help_description_margin", "number"),
299 typechecked("help_max_width", "number"),
300 typechecked("hidden", "boolean"),
301 add_help
302}, Parser)
303
304local Argument = class({
305 _minargs = 1,
306 _maxargs = 1,
307 _mincount = 1,
308 _maxcount = 1,
309 _defmode = "unused",
310 _show_default = true
311}, {
312 args = 5,
313 typechecked("name", "string"),
314 typechecked("description", "string"),
315 option_default,
316 typechecked("convert", "function", "table"),
317 boundaries("args"),
318 typechecked("target", "string"),
319 typechecked("defmode", "string"),
320 typechecked("show_default", "boolean"),
321 typechecked("argname", "string", "table"),
322 typechecked("choices", "table"),
323 typechecked("hidden", "boolean"),
324 option_action,
325 option_init
326})
327
328local Option = class({
329 _aliases = {},
330 _public_aliases = {},
331 _mincount = 0,
332 _overwrite = true
333}, {
334 args = 6,
335 multiname,
336 typechecked("description", "string"),
337 option_default,
338 typechecked("convert", "function", "table"),
339 boundaries("args"),
340 boundaries("count"),
341 multiname_hidden,
342 typechecked("target", "string"),
343 typechecked("defmode", "string"),
344 typechecked("show_default", "boolean"),
345 typechecked("overwrite", "boolean"),
346 typechecked("argname", "string", "table"),
347 typechecked("choices", "table"),
348 typechecked("hidden", "boolean"),
349 option_action,
350 option_init
351}, Argument)
352
353function Parser:_inherit_property(name, default)
354 local element = self
355
356 while true do
357 local value = element["_" .. name]
358
359 if value ~= nil then
360 return value
361 end
362
363 if not element._parent then
364 return default
365 end
366
367 element = element._parent
368 end
369end
370
371function Argument:_get_argument_list()
372 local buf = {}
373 local i = 1
374
375 while i <= math.min(self._minargs, 3) do
376 local argname = self:_get_argname(i)
377
378 if self._default and self._defmode:find "a" then
379 argname = "[" .. argname .. "]"
380 end
381
382 table.insert(buf, argname)
383 i = i+1
384 end
385
386 while i <= math.min(self._maxargs, 3) do
387 table.insert(buf, "[" .. self:_get_argname(i) .. "]")
388 i = i+1
389
390 if self._maxargs == math.huge then
391 break
392 end
393 end
394
395 if i < self._maxargs then
396 table.insert(buf, "...")
397 end
398
399 return buf
400end
401
402function Argument:_get_usage()
403 local usage = table.concat(self:_get_argument_list(), " ")
404
405 if self._default and self._defmode:find "u" then
406 if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then
407 usage = "[" .. usage .. "]"
408 end
409 end
410
411 return usage
412end
413
414function actions.store_true(result, target)
415 result[target] = true
416end
417
418function actions.store_false(result, target)
419 result[target] = false
420end
421
422function actions.store(result, target, argument)
423 result[target] = argument
424end
425
426function actions.count(result, target, _, overwrite)
427 if not overwrite then
428 result[target] = result[target] + 1
429 end
430end
431
432function actions.append(result, target, argument, overwrite)
433 result[target] = result[target] or {}
434 table.insert(result[target], argument)
435
436 if overwrite then
437 table.remove(result[target], 1)
438 end
439end
440
441function actions.concat(result, target, arguments, overwrite)
442 if overwrite then
443 error("'concat' action can't handle too many invocations")
444 end
445
446 result[target] = result[target] or {}
447
448 for _, argument in ipairs(arguments) do
449 table.insert(result[target], argument)
450 end
451end
452
453function Argument:_get_action()
454 local action, init
455
456 if self._maxcount == 1 then
457 if self._maxargs == 0 then
458 action, init = "store_true", nil
459 else
460 action, init = "store", nil
461 end
462 else
463 if self._maxargs == 0 then
464 action, init = "count", 0
465 else
466 action, init = "append", {}
467 end
468 end
469
470 if self._action then
471 action = self._action
472 end
473
474 if self._has_init then
475 init = self._init
476 end
477
478 if type(action) == "string" then
479 action = actions[action]
480 end
481
482 return action, init
483end
484
485-- Returns placeholder for `narg`-th argument.
486function Argument:_get_argname(narg)
487 local argname = self._argname or self:_get_default_argname()
488
489 if type(argname) == "table" then
490 return argname[narg]
491 else
492 return argname
493 end
494end
495
496function Argument:_get_choices_list()
497 return "{" .. table.concat(self._choices, ",") .. "}"
498end
499
500function Argument:_get_default_argname()
501 if self._choices then
502 return self:_get_choices_list()
503 else
504 return "<" .. self._name .. ">"
505 end
506end
507
508function Option:_get_default_argname()
509 if self._choices then
510 return self:_get_choices_list()
511 else
512 return "<" .. self:_get_default_target() .. ">"
513 end
514end
515
516-- Returns labels to be shown in the help message.
517function Argument:_get_label_lines()
518 if self._choices then
519 return {self:_get_choices_list()}
520 else
521 return {self._name}
522 end
523end
524
525function Option:_get_label_lines()
526 local argument_list = self:_get_argument_list()
527
528 if #argument_list == 0 then
529 -- Don't put aliases for simple flags like `-h` on different lines.
530 return {table.concat(self._public_aliases, ", ")}
531 end
532
533 local longest_alias_length = -1
534
535 for _, alias in ipairs(self._public_aliases) do
536 longest_alias_length = math.max(longest_alias_length, #alias)
537 end
538
539 local argument_list_repr = table.concat(argument_list, " ")
540 local lines = {}
541
542 for i, alias in ipairs(self._public_aliases) do
543 local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr
544
545 if i ~= #self._public_aliases then
546 line = line .. ","
547 end
548
549 table.insert(lines, line)
550 end
551
552 return lines
553end
554
555function Command:_get_label_lines()
556 return {table.concat(self._public_aliases, ", ")}
557end
558
559function Argument:_get_description()
560 if self._default and self._show_default then
561 if self._description then
562 return ("%s (default: %s)"):format(self._description, self._default)
563 else
564 return ("default: %s"):format(self._default)
565 end
566 else
567 return self._description or ""
568 end
569end
570
571function Command:_get_description()
572 return self._summary or self._description or ""
573end
574
575function Option:_get_usage()
576 local usage = self:_get_argument_list()
577 table.insert(usage, 1, self._name)
578 usage = table.concat(usage, " ")
579
580 if self._mincount == 0 or self._default then
581 usage = "[" .. usage .. "]"
582 end
583
584 return usage
585end
586
587function Argument:_get_default_target()
588 return self._name
589end
590
591function Option:_get_default_target()
592 local res
593
594 for _, alias in ipairs(self._public_aliases) do
595 if alias:sub(1, 1) == alias:sub(2, 2) then
596 res = alias:sub(3)
597 break
598 end
599 end
600
601 res = res or self._name:sub(2)
602 return (res:gsub("-", "_"))
603end
604
605function Option:_is_vararg()
606 return self._maxargs ~= self._minargs
607end
608
609function Parser:_get_fullname(exclude_root)
610 local parent = self._parent
611 if exclude_root and not parent then
612 return ""
613 end
614 local buf = {self._name}
615
616 while parent do
617 if not exclude_root or parent._parent then
618 table.insert(buf, 1, parent._name)
619 end
620 parent = parent._parent
621 end
622
623 return table.concat(buf, " ")
624end
625
626function Parser:_update_charset(charset)
627 charset = charset or {}
628
629 for _, command in ipairs(self._commands) do
630 command:_update_charset(charset)
631 end
632
633 for _, option in ipairs(self._options) do
634 for _, alias in ipairs(option._aliases) do
635 charset[alias:sub(1, 1)] = true
636 end
637 end
638
639 return charset
640end
641
642function Parser:argument(...)
643 local argument = Argument(...)
644 table.insert(self._arguments, argument)
645 return argument
646end
647
648function Parser:option(...)
649 local option = Option(...)
650 table.insert(self._options, option)
651 return option
652end
653
654function Parser:flag(...)
655 return self:option():args(0)(...)
656end
657
658function Parser:command(...)
659 local command = Command():add_help(true)(...)
660 command._parent = self
661 table.insert(self._commands, command)
662 return command
663end
664
665function Parser:mutex(...)
666 local elements = {...}
667
668 for i, element in ipairs(elements) do
669 local mt = getmetatable(element)
670 assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i))
671 end
672
673 table.insert(self._mutexes, elements)
674 return self
675end
676
677function Parser:group(name, ...)
678 assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name)))
679
680 local group = {name = name, ...}
681
682 for i, element in ipairs(group) do
683 local mt = getmetatable(element)
684 assert(mt == Option or mt == Argument or mt == Command,
685 ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1))
686 end
687
688 table.insert(self._groups, group)
689 return self
690end
691
692local usage_welcome = "Usage: "
693
694function Parser:get_usage()
695 if self._usage then
696 return self._usage
697 end
698
699 local usage_margin = self:_inherit_property("usage_margin", #usage_welcome)
700 local max_usage_width = self:_inherit_property("usage_max_width", 70)
701 local lines = {usage_welcome .. self:_get_fullname()}
702
703 local function add(s)
704 if #lines[#lines]+1+#s <= max_usage_width then
705 lines[#lines] = lines[#lines] .. " " .. s
706 else
707 lines[#lines+1] = (" "):rep(usage_margin) .. s
708 end
709 end
710
711 -- Normally options are before positional arguments in usage messages.
712 -- However, vararg options should be after, because they can't be reliable used
713 -- before a positional argument.
714 -- Mutexes come into play, too, and are shown as soon as possible.
715 -- Overall, output usages in the following order:
716 -- 1. Mutexes that don't have positional arguments or vararg options.
717 -- 2. Options that are not in any mutexes and are not vararg.
718 -- 3. Positional arguments - on their own or as a part of a mutex.
719 -- 4. Remaining mutexes.
720 -- 5. Remaining options.
721
722 local elements_in_mutexes = {}
723 local added_elements = {}
724 local added_mutexes = {}
725 local argument_to_mutexes = {}
726
727 local function add_mutex(mutex, main_argument)
728 if added_mutexes[mutex] then
729 return
730 end
731
732 added_mutexes[mutex] = true
733 local buf = {}
734
735 for _, element in ipairs(mutex) do
736 if not element._hidden and not added_elements[element] then
737 if getmetatable(element) == Option or element == main_argument then
738 table.insert(buf, element:_get_usage())
739 added_elements[element] = true
740 end
741 end
742 end
743
744 if #buf == 1 then
745 add(buf[1])
746 elseif #buf > 1 then
747 add("(" .. table.concat(buf, " | ") .. ")")
748 end
749 end
750
751 local function add_element(element)
752 if not element._hidden and not added_elements[element] then
753 add(element:_get_usage())
754 added_elements[element] = true
755 end
756 end
757
758 for _, mutex in ipairs(self._mutexes) do
759 local is_vararg = false
760 local has_argument = false
761
762 for _, element in ipairs(mutex) do
763 if getmetatable(element) == Option then
764 if element:_is_vararg() then
765 is_vararg = true
766 end
767 else
768 has_argument = true
769 argument_to_mutexes[element] = argument_to_mutexes[element] or {}
770 table.insert(argument_to_mutexes[element], mutex)
771 end
772
773 elements_in_mutexes[element] = true
774 end
775
776 if not is_vararg and not has_argument then
777 add_mutex(mutex)
778 end
779 end
780
781 for _, option in ipairs(self._options) do
782 if not elements_in_mutexes[option] and not option:_is_vararg() then
783 add_element(option)
784 end
785 end
786
787 -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex.
788 for _, argument in ipairs(self._arguments) do
789 -- Pick a mutex as a part of which to show this argument, take the first one that's still available.
790 local mutex
791
792 if elements_in_mutexes[argument] then
793 for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do
794 if not added_mutexes[argument_mutex] then
795 mutex = argument_mutex
796 end
797 end
798 end
799
800 if mutex then
801 add_mutex(mutex, argument)
802 else
803 add_element(argument)
804 end
805 end
806
807 for _, mutex in ipairs(self._mutexes) do
808 add_mutex(mutex)
809 end
810
811 for _, option in ipairs(self._options) do
812 add_element(option)
813 end
814
815 if #self._commands > 0 then
816 if self._require_command then
817 add("<command>")
818 else
819 add("[<command>]")
820 end
821
822 add("...")
823 end
824
825 return table.concat(lines, "\n")
826end
827
828local function split_lines(s)
829 if s == "" then
830 return {}
831 end
832
833 local lines = {}
834
835 if s:sub(-1) ~= "\n" then
836 s = s .. "\n"
837 end
838
839 for line in s:gmatch("([^\n]*)\n") do
840 table.insert(lines, line)
841 end
842
843 return lines
844end
845
846local function autowrap_line(line, max_length)
847 -- Algorithm for splitting lines is simple and greedy.
848 local result_lines = {}
849
850 -- Preserve original indentation of the line, put this at the beginning of each result line.
851 -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts
852 -- of the second and the following lines vertically align with the start of the second word.
853 local indentation = line:match("^ *")
854
855 if line:find("^ *[%*%+%-]") then
856 indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)")
857 end
858
859 -- Parts of the last line being assembled.
860 local line_parts = {}
861
862 -- Length of the current line.
863 local line_length = 0
864
865 -- Index of the next character to consider.
866 local index = 1
867
868 while true do
869 local word_start, word_finish, word = line:find("([^ ]+)", index)
870
871 if not word_start then
872 -- Ignore trailing spaces, if any.
873 break
874 end
875
876 local preceding_spaces = line:sub(index, word_start - 1)
877 index = word_finish + 1
878
879 if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then
880 -- Either this is the very first word or it fits as an addition to the current line, add it.
881 table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation.
882 table.insert(line_parts, word)
883 line_length = line_length + #preceding_spaces + #word
884 else
885 -- Does not fit, finish current line and put the word into a new one.
886 table.insert(result_lines, table.concat(line_parts))
887 line_parts = {indentation, word}
888 line_length = #indentation + #word
889 end
890 end
891
892 if #line_parts > 0 then
893 table.insert(result_lines, table.concat(line_parts))
894 end
895
896 if #result_lines == 0 then
897 -- Preserve empty lines.
898 result_lines[1] = ""
899 end
900
901 return result_lines
902end
903
904-- Automatically wraps lines within given array,
905-- attempting to limit line length to `max_length`.
906-- Existing line splits are preserved.
907local function autowrap(lines, max_length)
908 local result_lines = {}
909
910 for _, line in ipairs(lines) do
911 local autowrapped_lines = autowrap_line(line, max_length)
912
913 for _, autowrapped_line in ipairs(autowrapped_lines) do
914 table.insert(result_lines, autowrapped_line)
915 end
916 end
917
918 return result_lines
919end
920
921function Parser:_get_element_help(element)
922 local label_lines = element:_get_label_lines()
923 local description_lines = split_lines(element:_get_description())
924
925 local result_lines = {}
926
927 -- All label lines should have the same length (except the last one, it has no comma).
928 -- If too long, start description after all the label lines.
929 -- Otherwise, combine label and description lines.
930
931 local usage_margin_len = self:_inherit_property("help_usage_margin", 3)
932 local usage_margin = (" "):rep(usage_margin_len)
933 local description_margin_len = self:_inherit_property("help_description_margin", 25)
934 local description_margin = (" "):rep(description_margin_len)
935
936 local help_max_width = self:_inherit_property("help_max_width")
937
938 if help_max_width then
939 local description_max_width = math.max(help_max_width - description_margin_len, 10)
940 description_lines = autowrap(description_lines, description_max_width)
941 end
942
943 if #label_lines[1] >= (description_margin_len - usage_margin_len) then
944 for _, label_line in ipairs(label_lines) do
945 table.insert(result_lines, usage_margin .. label_line)
946 end
947
948 for _, description_line in ipairs(description_lines) do
949 table.insert(result_lines, description_margin .. description_line)
950 end
951 else
952 for i = 1, math.max(#label_lines, #description_lines) do
953 local label_line = label_lines[i]
954 local description_line = description_lines[i]
955
956 local line = ""
957
958 if label_line then
959 line = usage_margin .. label_line
960 end
961
962 if description_line and description_line ~= "" then
963 line = line .. (" "):rep(description_margin_len - #line) .. description_line
964 end
965
966 table.insert(result_lines, line)
967 end
968 end
969
970 return table.concat(result_lines, "\n")
971end
972
973local function get_group_types(group)
974 local types = {}
975
976 for _, element in ipairs(group) do
977 types[getmetatable(element)] = true
978 end
979
980 return types
981end
982
983function Parser:_add_group_help(blocks, added_elements, label, elements)
984 local buf = {label}
985
986 for _, element in ipairs(elements) do
987 if not element._hidden and not added_elements[element] then
988 added_elements[element] = true
989 table.insert(buf, self:_get_element_help(element))
990 end
991 end
992
993 if #buf > 1 then
994 table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1)))
995 end
996end
997
998function Parser:get_help()
999 if self._help then
1000 return self._help
1001 end
1002
1003 local blocks = {self:get_usage()}
1004
1005 local help_max_width = self:_inherit_property("help_max_width")
1006
1007 if self._description then
1008 local description = self._description
1009
1010 if help_max_width then
1011 description = table.concat(autowrap(split_lines(description), help_max_width), "\n")
1012 end
1013
1014 table.insert(blocks, description)
1015 end
1016
1017 -- 1. Put groups containing arguments first, then other arguments.
1018 -- 2. Put remaining groups containing options, then other options.
1019 -- 3. Put remaining groups containing commands, then other commands.
1020 -- Assume that an element can't be in several groups.
1021 local groups_by_type = {
1022 [Argument] = {},
1023 [Option] = {},
1024 [Command] = {}
1025 }
1026
1027 for _, group in ipairs(self._groups) do
1028 local group_types = get_group_types(group)
1029
1030 for _, mt in ipairs({Argument, Option, Command}) do
1031 if group_types[mt] then
1032 table.insert(groups_by_type[mt], group)
1033 break
1034 end
1035 end
1036 end
1037
1038 local default_groups = {
1039 {name = "Arguments", type = Argument, elements = self._arguments},
1040 {name = "Options", type = Option, elements = self._options},
1041 {name = "Commands", type = Command, elements = self._commands}
1042 }
1043
1044 local added_elements = {}
1045
1046 for _, default_group in ipairs(default_groups) do
1047 local type_groups = groups_by_type[default_group.type]
1048
1049 for _, group in ipairs(type_groups) do
1050 self:_add_group_help(blocks, added_elements, group.name .. ":", group)
1051 end
1052
1053 local default_label = default_group.name .. ":"
1054
1055 if #type_groups > 0 then
1056 default_label = "Other " .. default_label:gsub("^.", string.lower)
1057 end
1058
1059 self:_add_group_help(blocks, added_elements, default_label, default_group.elements)
1060 end
1061
1062 if self._epilog then
1063 local epilog = self._epilog
1064
1065 if help_max_width then
1066 epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n")
1067 end
1068
1069 table.insert(blocks, epilog)
1070 end
1071
1072 return table.concat(blocks, "\n\n")
1073end
1074
1075function Parser:add_help_command(value)
1076 if value then
1077 assert(type(value) == "string" or type(value) == "table",
1078 ("bad argument #1 to 'add_help_command' (string or table expected, got %s)"):format(type(value)))
1079 end
1080
1081 local help = self:command()
1082 :description "Show help for commands."
1083 help:argument "command"
1084 :description "The command to show help for."
1085 :args "?"
1086 :action(function(_, _, cmd)
1087 if not cmd then
1088 print(self:get_help())
1089 os.exit(0)
1090 else
1091 for _, command in ipairs(self._commands) do
1092 for _, alias in ipairs(command._aliases) do
1093 if alias == cmd then
1094 print(command:get_help())
1095 os.exit(0)
1096 end
1097 end
1098 end
1099 end
1100 help:error(("unknown command '%s'"):format(cmd))
1101 end)
1102
1103 if value then
1104 help = help(value)
1105 end
1106
1107 if not help._name then
1108 help "help"
1109 end
1110
1111 help._is_help_command = true
1112 return self
1113end
1114
1115function Parser:_is_shell_safe()
1116 if self._basename then
1117 if self._basename:find("[^%w_%-%+%.]") then
1118 return false
1119 end
1120 else
1121 for _, alias in ipairs(self._aliases) do
1122 if alias:find("[^%w_%-%+%.]") then
1123 return false
1124 end
1125 end
1126 end
1127 for _, option in ipairs(self._options) do
1128 for _, alias in ipairs(option._aliases) do
1129 if alias:find("[^%w_%-%+%.]") then
1130 return false
1131 end
1132 end
1133 if option._choices then
1134 for _, choice in ipairs(option._choices) do
1135 if choice:find("[%s'\"]") then
1136 return false
1137 end
1138 end
1139 end
1140 end
1141 for _, argument in ipairs(self._arguments) do
1142 if argument._choices then
1143 for _, choice in ipairs(argument._choices) do
1144 if choice:find("[%s'\"]") then
1145 return false
1146 end
1147 end
1148 end
1149 end
1150 for _, command in ipairs(self._commands) do
1151 if not command:_is_shell_safe() then
1152 return false
1153 end
1154 end
1155 return true
1156end
1157
1158function Parser:add_complete(value)
1159 if value then
1160 assert(type(value) == "string" or type(value) == "table",
1161 ("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(value)))
1162 end
1163
1164 local complete = self:option()
1165 :description "Output a shell completion script for the specified shell."
1166 :args(1)
1167 :choices {"bash", "zsh", "fish"}
1168 :action(function(_, _, shell)
1169 io.write(self["get_" .. shell .. "_complete"](self))
1170 os.exit(0)
1171 end)
1172
1173 if value then
1174 complete = complete(value)
1175 end
1176
1177 if not complete._name then
1178 complete "--completion"
1179 end
1180
1181 return self
1182end
1183
1184function Parser:add_complete_command(value)
1185 if value then
1186 assert(type(value) == "string" or type(value) == "table",
1187 ("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(value)))
1188 end
1189
1190 local complete = self:command()
1191 :description "Output a shell completion script."
1192 complete:argument "shell"
1193 :description "The shell to output a completion script for."
1194 :choices {"bash", "zsh", "fish"}
1195 :action(function(_, _, shell)
1196 io.write(self["get_" .. shell .. "_complete"](self))
1197 os.exit(0)
1198 end)
1199
1200 if value then
1201 complete = complete(value)
1202 end
1203
1204 if not complete._name then
1205 complete "completion"
1206 end
1207
1208 return self
1209end
1210
1211local function base_name(pathname)
1212 return pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)") or pathname
1213end
1214
1215local function get_short_description(element)
1216 local short = element:_get_description():match("^(.-)%.%s")
1217 return short or element:_get_description():match("^(.-)%.?$")
1218end
1219
1220function Parser:_get_options()
1221 local options = {}
1222 for _, option in ipairs(self._options) do
1223 for _, alias in ipairs(option._aliases) do
1224 table.insert(options, alias)
1225 end
1226 end
1227 return table.concat(options, " ")
1228end
1229
1230function Parser:_get_commands()
1231 local commands = {}
1232 for _, command in ipairs(self._commands) do
1233 for _, alias in ipairs(command._aliases) do
1234 table.insert(commands, alias)
1235 end
1236 end
1237 return table.concat(commands, " ")
1238end
1239
1240function Parser:_bash_option_args(buf, indent)
1241 local opts = {}
1242 for _, option in ipairs(self._options) do
1243 if option._choices or option._minargs > 0 then
1244 local compreply
1245 if option._choices then
1246 compreply = 'COMPREPLY=($(compgen -W "' .. table.concat(option._choices, " ") .. '" -- "$cur"))'
1247 else
1248 compreply = 'COMPREPLY=($(compgen -f -- "$cur"))'
1249 end
1250 table.insert(opts, (" "):rep(indent + 4) .. table.concat(option._aliases, "|") .. ")")
1251 table.insert(opts, (" "):rep(indent + 8) .. compreply)
1252 table.insert(opts, (" "):rep(indent + 8) .. "return 0")
1253 table.insert(opts, (" "):rep(indent + 8) .. ";;")
1254 end
1255 end
1256
1257 if #opts > 0 then
1258 table.insert(buf, (" "):rep(indent) .. 'case "$prev" in')
1259 table.insert(buf, table.concat(opts, "\n"))
1260 table.insert(buf, (" "):rep(indent) .. "esac\n")
1261 end
1262end
1263
1264function Parser:_bash_get_cmd(buf, indent)
1265 if #self._commands == 0 then
1266 return
1267 end
1268
1269 table.insert(buf, (" "):rep(indent) .. 'args=("${args[@]:1}")')
1270 table.insert(buf, (" "):rep(indent) .. 'for arg in "${args[@]}"; do')
1271 table.insert(buf, (" "):rep(indent + 4) .. 'case "$arg" in')
1272
1273 for _, command in ipairs(self._commands) do
1274 table.insert(buf, (" "):rep(indent + 8) .. table.concat(command._aliases, "|") .. ")")
1275 if self._parent then
1276 table.insert(buf, (" "):rep(indent + 12) .. 'cmd="$cmd ' .. command._name .. '"')
1277 else
1278 table.insert(buf, (" "):rep(indent + 12) .. 'cmd="' .. command._name .. '"')
1279 end
1280 table.insert(buf, (" "):rep(indent + 12) .. 'opts="$opts ' .. command:_get_options() .. '"')
1281 command:_bash_get_cmd(buf, indent + 12)
1282 table.insert(buf, (" "):rep(indent + 12) .. "break")
1283 table.insert(buf, (" "):rep(indent + 12) .. ";;")
1284 end
1285
1286 table.insert(buf, (" "):rep(indent + 4) .. "esac")
1287 table.insert(buf, (" "):rep(indent) .. "done")
1288end
1289
1290function Parser:_bash_cmd_completions(buf)
1291 local cmd_buf = {}
1292 if self._parent then
1293 self:_bash_option_args(cmd_buf, 12)
1294 end
1295 if #self._commands > 0 then
1296 table.insert(cmd_buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self:_get_commands() .. '" -- "$cur"))')
1297 elseif self._is_help_command then
1298 table.insert(cmd_buf, (" "):rep(12)
1299 .. 'COMPREPLY=($(compgen -W "'
1300 .. self._parent:_get_commands()
1301 .. '" -- "$cur"))')
1302 end
1303 if #cmd_buf > 0 then
1304 table.insert(buf, (" "):rep(8) .. "'" .. self:_get_fullname(true) .. "')")
1305 table.insert(buf, table.concat(cmd_buf, "\n"))
1306 table.insert(buf, (" "):rep(12) .. ";;")
1307 end
1308
1309 for _, command in ipairs(self._commands) do
1310 command:_bash_cmd_completions(buf)
1311 end
1312end
1313
1314function Parser:get_bash_complete()
1315 self._basename = base_name(self._name)
1316 assert(self:_is_shell_safe())
1317 local buf = {([[
1318_%s() {
1319 local IFS=$' \t\n'
1320 local args cur prev cmd opts arg
1321 args=("${COMP_WORDS[@]}")
1322 cur="${COMP_WORDS[COMP_CWORD]}"
1323 prev="${COMP_WORDS[COMP_CWORD-1]}"
1324 opts="%s"
1325]]):format(self._basename, self:_get_options())}
1326
1327 self:_bash_option_args(buf, 4)
1328 self:_bash_get_cmd(buf, 4)
1329 if #self._commands > 0 then
1330 table.insert(buf, "")
1331 table.insert(buf, (" "):rep(4) .. 'case "$cmd" in')
1332 self:_bash_cmd_completions(buf)
1333 table.insert(buf, (" "):rep(4) .. "esac\n")
1334 end
1335
1336 table.insert(buf, ([=[
1337 if [[ "$cur" = -* ]]; then
1338 COMPREPLY=($(compgen -W "$opts" -- "$cur"))
1339 fi
1340}
1341
1342complete -F _%s -o bashdefault -o default %s
1343]=]):format(self._basename, self._basename))
1344
1345 return table.concat(buf, "\n")
1346end
1347
1348function Parser:_zsh_arguments(buf, cmd_name, indent)
1349 if self._parent then
1350 table.insert(buf, (" "):rep(indent) .. "options=(")
1351 table.insert(buf, (" "):rep(indent + 2) .. "$options")
1352 else
1353 table.insert(buf, (" "):rep(indent) .. "local -a options=(")
1354 end
1355
1356 for _, option in ipairs(self._options) do
1357 local line = {}
1358 if #option._aliases > 1 then
1359 if option._maxcount > 1 then
1360 table.insert(line, '"*"')
1361 end
1362 table.insert(line, "{" .. table.concat(option._aliases, ",") .. '}"')
1363 else
1364 table.insert(line, '"')
1365 if option._maxcount > 1 then
1366 table.insert(line, "*")
1367 end
1368 table.insert(line, option._name)
1369 end
1370 if option._description then
1371 local description = get_short_description(option):gsub('["%]:`$]', "\\%0")
1372 table.insert(line, "[" .. description .. "]")
1373 end
1374 if option._maxargs == math.huge then
1375 table.insert(line, ":*")
1376 end
1377 if option._choices then
1378 table.insert(line, ": :(" .. table.concat(option._choices, " ") .. ")")
1379 elseif option._maxargs > 0 then
1380 table.insert(line, ": :_files")
1381 end
1382 table.insert(line, '"')
1383 table.insert(buf, (" "):rep(indent + 2) .. table.concat(line))
1384 end
1385
1386 table.insert(buf, (" "):rep(indent) .. ")")
1387 table.insert(buf, (" "):rep(indent) .. "_arguments -s -S \\")
1388 table.insert(buf, (" "):rep(indent + 2) .. "$options \\")
1389
1390 if self._is_help_command then
1391 table.insert(buf, (" "):rep(indent + 2) .. '": :(' .. self._parent:_get_commands() .. ')" \\')
1392 else
1393 for _, argument in ipairs(self._arguments) do
1394 local spec
1395 if argument._choices then
1396 spec = ": :(" .. table.concat(argument._choices, " ") .. ")"
1397 else
1398 spec = ": :_files"
1399 end
1400 if argument._maxargs == math.huge then
1401 table.insert(buf, (" "):rep(indent + 2) .. '"*' .. spec .. '" \\')
1402 break
1403 end
1404 for _ = 1, argument._maxargs do
1405 table.insert(buf, (" "):rep(indent + 2) .. '"' .. spec .. '" \\')
1406 end
1407 end
1408
1409 if #self._commands > 0 then
1410 table.insert(buf, (" "):rep(indent + 2) .. '": :_' .. cmd_name .. '_cmds" \\')
1411 table.insert(buf, (" "):rep(indent + 2) .. '"*:: :->args" \\')
1412 end
1413 end
1414
1415 table.insert(buf, (" "):rep(indent + 2) .. "&& return 0")
1416end
1417
1418function Parser:_zsh_cmds(buf, cmd_name)
1419 table.insert(buf, "\n_" .. cmd_name .. "_cmds() {")
1420 table.insert(buf, " local -a commands=(")
1421
1422 for _, command in ipairs(self._commands) do
1423 local line = {}
1424 if #command._aliases > 1 then
1425 table.insert(line, "{" .. table.concat(command._aliases, ",") .. '}"')
1426 else
1427 table.insert(line, '"' .. command._name)
1428 end
1429 if command._description then
1430 table.insert(line, ":" .. get_short_description(command):gsub('["`$]', "\\%0"))
1431 end
1432 table.insert(buf, " " .. table.concat(line) .. '"')
1433 end
1434
1435 table.insert(buf, ' )\n _describe "command" commands\n}')
1436end
1437
1438function Parser:_zsh_complete_help(buf, cmds_buf, cmd_name, indent)
1439 if #self._commands == 0 then
1440 return
1441 end
1442
1443 self:_zsh_cmds(cmds_buf, cmd_name)
1444 table.insert(buf, "\n" .. (" "):rep(indent) .. "case $words[1] in")
1445
1446 for _, command in ipairs(self._commands) do
1447 local name = cmd_name .. "_" .. command._name
1448 table.insert(buf, (" "):rep(indent + 2) .. table.concat(command._aliases, "|") .. ")")
1449 command:_zsh_arguments(buf, name, indent + 4)
1450 command:_zsh_complete_help(buf, cmds_buf, name, indent + 4)
1451 table.insert(buf, (" "):rep(indent + 4) .. ";;\n")
1452 end
1453
1454 table.insert(buf, (" "):rep(indent) .. "esac")
1455end
1456
1457function Parser:get_zsh_complete()
1458 self._basename = base_name(self._name)
1459 assert(self:_is_shell_safe())
1460 local buf = {("#compdef %s\n"):format(self._basename)}
1461 local cmds_buf = {}
1462 table.insert(buf, "_" .. self._basename .. "() {")
1463 if #self._commands > 0 then
1464 table.insert(buf, " local context state state_descr line")
1465 table.insert(buf, " typeset -A opt_args\n")
1466 end
1467 self:_zsh_arguments(buf, self._basename, 2)
1468 self:_zsh_complete_help(buf, cmds_buf, self._basename, 2)
1469 table.insert(buf, "\n return 1")
1470 table.insert(buf, "}")
1471
1472 local result = table.concat(buf, "\n")
1473 if #cmds_buf > 0 then
1474 result = result .. "\n" .. table.concat(cmds_buf, "\n")
1475 end
1476 return result .. "\n\n_" .. self._basename .. "\n"
1477end
1478
1479local function fish_escape(string)
1480 return string:gsub("[\\']", "\\%0")
1481end
1482
1483function Parser:_fish_get_cmd(buf, indent)
1484 if #self._commands == 0 then
1485 return
1486 end
1487
1488 table.insert(buf, (" "):rep(indent) .. "set -e cmdline[1]")
1489 table.insert(buf, (" "):rep(indent) .. "for arg in $cmdline")
1490 table.insert(buf, (" "):rep(indent + 4) .. "switch $arg")
1491
1492 for _, command in ipairs(self._commands) do
1493 table.insert(buf, (" "):rep(indent + 8) .. "case " .. table.concat(command._aliases, " "))
1494 table.insert(buf, (" "):rep(indent + 12) .. "set cmd $cmd " .. command._name)
1495 command:_fish_get_cmd(buf, indent + 12)
1496 table.insert(buf, (" "):rep(indent + 12) .. "break")
1497 end
1498
1499 table.insert(buf, (" "):rep(indent + 4) .. "end")
1500 table.insert(buf, (" "):rep(indent) .. "end")
1501end
1502
1503function Parser:_fish_complete_help(buf, basename)
1504 local prefix = "complete -c " .. basename
1505 table.insert(buf, "")
1506
1507 for _, command in ipairs(self._commands) do
1508 local aliases = table.concat(command._aliases, " ")
1509 local line
1510 if self._parent then
1511 line = ("%s -n '__fish_%s_using_command %s' -xa '%s'")
1512 :format(prefix, basename, self:_get_fullname(true), aliases)
1513 else
1514 line = ("%s -n '__fish_%s_using_command' -xa '%s'"):format(prefix, basename, aliases)
1515 end
1516 if command._description then
1517 line = ("%s -d '%s'"):format(line, fish_escape(get_short_description(command)))
1518 end
1519 table.insert(buf, line)
1520 end
1521
1522 if self._is_help_command then
1523 local line = ("%s -n '__fish_%s_using_command %s' -xa '%s'")
1524 :format(prefix, basename, self:_get_fullname(true), self._parent:_get_commands())
1525 table.insert(buf, line)
1526 end
1527
1528 for _, option in ipairs(self._options) do
1529 local parts = {prefix}
1530
1531 if self._parent then
1532 table.insert(parts, "-n '__fish_" .. basename .. "_seen_command " .. self:_get_fullname(true) .. "'")
1533 end
1534
1535 for _, alias in ipairs(option._aliases) do
1536 if alias:match("^%-.$") then
1537 table.insert(parts, "-s " .. alias:sub(2))
1538 elseif alias:match("^%-%-.+") then
1539 table.insert(parts, "-l " .. alias:sub(3))
1540 end
1541 end
1542
1543 if option._choices then
1544 table.insert(parts, "-xa '" .. table.concat(option._choices, " ") .. "'")
1545 elseif option._minargs > 0 then
1546 table.insert(parts, "-r")
1547 end
1548
1549 if option._description then
1550 table.insert(parts, "-d '" .. fish_escape(get_short_description(option)) .. "'")
1551 end
1552
1553 table.insert(buf, table.concat(parts, " "))
1554 end
1555
1556 for _, command in ipairs(self._commands) do
1557 command:_fish_complete_help(buf, basename)
1558 end
1559end
1560
1561function Parser:get_fish_complete()
1562 self._basename = base_name(self._name)
1563 assert(self:_is_shell_safe())
1564 local buf = {}
1565
1566 if #self._commands > 0 then
1567 table.insert(buf, ([[
1568function __fish_%s_print_command
1569 set -l cmdline (commandline -poc)
1570 set -l cmd]]):format(self._basename))
1571 self:_fish_get_cmd(buf, 4)
1572 table.insert(buf, ([[
1573 echo "$cmd"
1574end
1575
1576function __fish_%s_using_command
1577 test (__fish_%s_print_command) = "$argv"
1578 and return 0
1579 or return 1
1580end
1581
1582function __fish_%s_seen_command
1583 string match -q "$argv*" (__fish_%s_print_command)
1584 and return 0
1585 or return 1
1586end]]):format(self._basename, self._basename, self._basename, self._basename))
1587 end
1588
1589 self:_fish_complete_help(buf, self._basename)
1590 return table.concat(buf, "\n") .. "\n"
1591end
1592
1593local function get_tip(context, wrong_name)
1594 local context_pool = {}
1595 local possible_name
1596 local possible_names = {}
1597
1598 for name in pairs(context) do
1599 if type(name) == "string" then
1600 for i = 1, #name do
1601 possible_name = name:sub(1, i - 1) .. name:sub(i + 1)
1602
1603 if not context_pool[possible_name] then
1604 context_pool[possible_name] = {}
1605 end
1606
1607 table.insert(context_pool[possible_name], name)
1608 end
1609 end
1610 end
1611
1612 for i = 1, #wrong_name + 1 do
1613 possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1)
1614
1615 if context[possible_name] then
1616 possible_names[possible_name] = true
1617 elseif context_pool[possible_name] then
1618 for _, name in ipairs(context_pool[possible_name]) do
1619 possible_names[name] = true
1620 end
1621 end
1622 end
1623
1624 local first = next(possible_names)
1625
1626 if first then
1627 if next(possible_names, first) then
1628 local possible_names_arr = {}
1629
1630 for name in pairs(possible_names) do
1631 table.insert(possible_names_arr, "'" .. name .. "'")
1632 end
1633
1634 table.sort(possible_names_arr)
1635 return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?"
1636 else
1637 return "\nDid you mean '" .. first .. "'?"
1638 end
1639 else
1640 return ""
1641 end
1642end
1643
1644local ElementState = class({
1645 invocations = 0
1646})
1647
1648function ElementState:__call(state, element)
1649 self.state = state
1650 self.result = state.result
1651 self.element = element
1652 self.target = element._target or element:_get_default_target()
1653 self.action, self.result[self.target] = element:_get_action()
1654 return self
1655end
1656
1657function ElementState:error(fmt, ...)
1658 self.state:error(fmt, ...)
1659end
1660
1661function ElementState:convert(argument, index)
1662 local converter = self.element._convert
1663
1664 if converter then
1665 local ok, err
1666
1667 if type(converter) == "function" then
1668 ok, err = converter(argument)
1669 elseif type(converter[index]) == "function" then
1670 ok, err = converter[index](argument)
1671 else
1672 ok = converter[argument]
1673 end
1674
1675 if ok == nil then
1676 self:error(err and "%s" or "malformed argument '%s'", err or argument)
1677 end
1678
1679 argument = ok
1680 end
1681
1682 return argument
1683end
1684
1685function ElementState:default(mode)
1686 return self.element._defmode:find(mode) and self.element._default
1687end
1688
1689local function bound(noun, min, max, is_max)
1690 local res = ""
1691
1692 if min ~= max then
1693 res = "at " .. (is_max and "most" or "least") .. " "
1694 end
1695
1696 local number = is_max and max or min
1697 return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s")
1698end
1699
1700function ElementState:set_name(alias)
1701 self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name)
1702end
1703
1704function ElementState:invoke()
1705 self.open = true
1706 self.overwrite = false
1707
1708 if self.invocations >= self.element._maxcount then
1709 if self.element._overwrite then
1710 self.overwrite = true
1711 else
1712 local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true)
1713 self:error("%s must be used %s", self.name, num_times_repr)
1714 end
1715 else
1716 self.invocations = self.invocations + 1
1717 end
1718
1719 self.args = {}
1720
1721 if self.element._maxargs <= 0 then
1722 self:close()
1723 end
1724
1725 return self.open
1726end
1727
1728function ElementState:check_choices(argument)
1729 if self.element._choices then
1730 for _, choice in ipairs(self.element._choices) do
1731 if argument == choice then
1732 return
1733 end
1734 end
1735 local choices_list = "'" .. table.concat(self.element._choices, "', '") .. "'"
1736 local is_option = getmetatable(self.element) == Option
1737 self:error("%s%s must be one of %s", is_option and "argument for " or "", self.name, choices_list)
1738 end
1739end
1740
1741function ElementState:pass(argument)
1742 self:check_choices(argument)
1743 argument = self:convert(argument, #self.args + 1)
1744 table.insert(self.args, argument)
1745
1746 if #self.args >= self.element._maxargs then
1747 self:close()
1748 end
1749
1750 return self.open
1751end
1752
1753function ElementState:complete_invocation()
1754 while #self.args < self.element._minargs do
1755 self:pass(self.element._default)
1756 end
1757end
1758
1759function ElementState:close()
1760 if self.open then
1761 self.open = false
1762
1763 if #self.args < self.element._minargs then
1764 if self:default("a") then
1765 self:complete_invocation()
1766 else
1767 if #self.args == 0 then
1768 if getmetatable(self.element) == Argument then
1769 self:error("missing %s", self.name)
1770 elseif self.element._maxargs == 1 then
1771 self:error("%s requires an argument", self.name)
1772 end
1773 end
1774
1775 self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs))
1776 end
1777 end
1778
1779 local args
1780
1781 if self.element._maxargs == 0 then
1782 args = self.args[1]
1783 elseif self.element._maxargs == 1 then
1784 if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then
1785 args = self.args
1786 else
1787 args = self.args[1]
1788 end
1789 else
1790 args = self.args
1791 end
1792
1793 self.action(self.result, self.target, args, self.overwrite)
1794 end
1795end
1796
1797local ParseState = class({
1798 result = {},
1799 options = {},
1800 arguments = {},
1801 argument_i = 1,
1802 element_to_mutexes = {},
1803 mutex_to_element_state = {},
1804 command_actions = {}
1805})
1806
1807function ParseState:__call(parser, error_handler)
1808 self.parser = parser
1809 self.error_handler = error_handler
1810 self.charset = parser:_update_charset()
1811 self:switch(parser)
1812 return self
1813end
1814
1815function ParseState:error(fmt, ...)
1816 self.error_handler(self.parser, fmt:format(...))
1817end
1818
1819function ParseState:switch(parser)
1820 self.parser = parser
1821
1822 if parser._action then
1823 table.insert(self.command_actions, {action = parser._action, name = parser._name})
1824 end
1825
1826 for _, option in ipairs(parser._options) do
1827 option = ElementState(self, option)
1828 table.insert(self.options, option)
1829
1830 for _, alias in ipairs(option.element._aliases) do
1831 self.options[alias] = option
1832 end
1833 end
1834
1835 for _, mutex in ipairs(parser._mutexes) do
1836 for _, element in ipairs(mutex) do
1837 if not self.element_to_mutexes[element] then
1838 self.element_to_mutexes[element] = {}
1839 end
1840
1841 table.insert(self.element_to_mutexes[element], mutex)
1842 end
1843 end
1844
1845 for _, argument in ipairs(parser._arguments) do
1846 argument = ElementState(self, argument)
1847 table.insert(self.arguments, argument)
1848 argument:set_name()
1849 argument:invoke()
1850 end
1851
1852 self.handle_options = parser._handle_options
1853 self.argument = self.arguments[self.argument_i]
1854 self.commands = parser._commands
1855
1856 for _, command in ipairs(self.commands) do
1857 for _, alias in ipairs(command._aliases) do
1858 self.commands[alias] = command
1859 end
1860 end
1861end
1862
1863function ParseState:get_option(name)
1864 local option = self.options[name]
1865
1866 if not option then
1867 self:error("unknown option '%s'%s", name, get_tip(self.options, name))
1868 else
1869 return option
1870 end
1871end
1872
1873function ParseState:get_command(name)
1874 local command = self.commands[name]
1875
1876 if not command then
1877 if #self.commands > 0 then
1878 self:error("unknown command '%s'%s", name, get_tip(self.commands, name))
1879 else
1880 self:error("too many arguments")
1881 end
1882 else
1883 return command
1884 end
1885end
1886
1887function ParseState:check_mutexes(element_state)
1888 if self.element_to_mutexes[element_state.element] then
1889 for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do
1890 local used_element_state = self.mutex_to_element_state[mutex]
1891
1892 if used_element_state and used_element_state ~= element_state then
1893 self:error("%s can not be used together with %s", element_state.name, used_element_state.name)
1894 else
1895 self.mutex_to_element_state[mutex] = element_state
1896 end
1897 end
1898 end
1899end
1900
1901function ParseState:invoke(option, name)
1902 self:close()
1903 option:set_name(name)
1904 self:check_mutexes(option, name)
1905
1906 if option:invoke() then
1907 self.option = option
1908 end
1909end
1910
1911function ParseState:pass(arg)
1912 if self.option then
1913 if not self.option:pass(arg) then
1914 self.option = nil
1915 end
1916 elseif self.argument then
1917 self:check_mutexes(self.argument)
1918
1919 if not self.argument:pass(arg) then
1920 self.argument_i = self.argument_i + 1
1921 self.argument = self.arguments[self.argument_i]
1922 end
1923 else
1924 local command = self:get_command(arg)
1925 self.result[command._target or command._name] = true
1926
1927 if self.parser._command_target then
1928 self.result[self.parser._command_target] = command._name
1929 end
1930
1931 self:switch(command)
1932 end
1933end
1934
1935function ParseState:close()
1936 if self.option then
1937 self.option:close()
1938 self.option = nil
1939 end
1940end
1941
1942function ParseState:finalize()
1943 self:close()
1944
1945 for i = self.argument_i, #self.arguments do
1946 local argument = self.arguments[i]
1947 if #argument.args == 0 and argument:default("u") then
1948 argument:complete_invocation()
1949 else
1950 argument:close()
1951 end
1952 end
1953
1954 if self.parser._require_command and #self.commands > 0 then
1955 self:error("a command is required")
1956 end
1957
1958 for _, option in ipairs(self.options) do
1959 option.name = option.name or ("option '%s'"):format(option.element._name)
1960
1961 if option.invocations == 0 then
1962 if option:default("u") then
1963 option:invoke()
1964 option:complete_invocation()
1965 option:close()
1966 end
1967 end
1968
1969 local mincount = option.element._mincount
1970
1971 if option.invocations < mincount then
1972 if option:default("a") then
1973 while option.invocations < mincount do
1974 option:invoke()
1975 option:close()
1976 end
1977 elseif option.invocations == 0 then
1978 self:error("missing %s", option.name)
1979 else
1980 self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount))
1981 end
1982 end
1983 end
1984
1985 for i = #self.command_actions, 1, -1 do
1986 self.command_actions[i].action(self.result, self.command_actions[i].name)
1987 end
1988end
1989
1990function ParseState:parse(args)
1991 for _, arg in ipairs(args) do
1992 local plain = true
1993
1994 if self.handle_options then
1995 local first = arg:sub(1, 1)
1996
1997 if self.charset[first] then
1998 if #arg > 1 then
1999 plain = false
2000
2001 if arg:sub(2, 2) == first then
2002 if #arg == 2 then
2003 if self.options[arg] then
2004 local option = self:get_option(arg)
2005 self:invoke(option, arg)
2006 else
2007 self:close()
2008 end
2009
2010 self.handle_options = false
2011 else
2012 local equals = arg:find "="
2013 if equals then
2014 local name = arg:sub(1, equals - 1)
2015 local option = self:get_option(name)
2016
2017 if option.element._maxargs <= 0 then
2018 self:error("option '%s' does not take arguments", name)
2019 end
2020
2021 self:invoke(option, name)
2022 self:pass(arg:sub(equals + 1))
2023 else
2024 local option = self:get_option(arg)
2025 self:invoke(option, arg)
2026 end
2027 end
2028 else
2029 for i = 2, #arg do
2030 local name = first .. arg:sub(i, i)
2031 local option = self:get_option(name)
2032 self:invoke(option, name)
2033
2034 if i ~= #arg and option.element._maxargs > 0 then
2035 self:pass(arg:sub(i + 1))
2036 break
2037 end
2038 end
2039 end
2040 end
2041 end
2042 end
2043
2044 if plain then
2045 self:pass(arg)
2046 end
2047 end
2048
2049 self:finalize()
2050 return self.result
2051end
2052
2053function Parser:error(msg)
2054 io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg))
2055 os.exit(1)
2056end
2057
2058-- Compatibility with strict.lua and other checkers:
2059local default_cmdline = rawget(_G, "arg") or {}
2060
2061function Parser:_parse(args, error_handler)
2062 return ParseState(self, error_handler):parse(args or default_cmdline)
2063end
2064
2065function Parser:parse(args)
2066 return self:_parse(args, self.error)
2067end
2068
2069local function xpcall_error_handler(err)
2070 if not debug then
2071 return tostring(err)
2072 end
2073 return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2)
2074end
2075
2076function Parser:pparse(args)
2077 local parse_error
2078
2079 local ok, result = xpcall(function()
2080 return self:_parse(args, function(_, err)
2081 parse_error = err
2082 error(err, 0)
2083 end)
2084 end, xpcall_error_handler)
2085
2086 if ok then
2087 return true, result
2088 elseif not parse_error then
2089 error(result, 0)
2090 else
2091 return false, parse_error
2092 end
2093end
2094
2095local argparse = {}
2096
2097argparse.version = "0.7.0"
2098
2099setmetatable(argparse, {__call = function(_, ...)
2100 return Parser(default_cmdline[0]):add_help(true)(...)
2101end})
2102
2103return argparse
diff --git a/vendor/compat53/file_mt.lua b/vendor/compat53/file_mt.lua
new file mode 100644
index 00000000..6433619d
--- /dev/null
+++ b/vendor/compat53/file_mt.lua
@@ -0,0 +1,71 @@
1local lua_version = _VERSION:sub(-3)
2
3local M = {}
4
5local unpack = lua_version == "5.1" and unpack or table.unpack
6
7local function addasterisk(fmt)
8 if type(fmt) == "string" and fmt:sub(1, 1) ~= "*" then
9 return "*"..fmt
10 else
11 return fmt
12 end
13end
14
15function M.update_file_meta(file_meta, is_luajit52)
16
17 -- make '*' optional for file:read and file:lines
18
19 local file_lines = file_meta.__index.lines
20 file_meta.__index.lines = function(self, ...)
21 local n = select('#', ...)
22 for i = 1, n do
23 local a = select(i, ...)
24 local b = addasterisk(a)
25 -- as an optimization we only allocate a table for the
26 -- modified format arguments when we have a '*' somewhere
27 if a ~= b then
28 local args = { ... }
29 args[i] = b
30 for j = i+1, n do
31 args[j] = addasterisk(args[j])
32 end
33 return file_lines(self, unpack(args, 1, n))
34 end
35 end
36 return file_lines(self, ...)
37 end
38
39 local file_read = file_meta.__index.read
40 file_meta.__index.read = function(self, ...)
41 local n = select('#', ...)
42 for i = 1, n do
43 local a = select(i, ...)
44 local b = addasterisk(a)
45 -- as an optimization we only allocate a table for the
46 -- modified format arguments when we have a '*' somewhere
47 if a ~= b then
48 local args = { ... }
49 args[i] = b
50 for j = i+1, n do
51 args[j] = addasterisk(args[j])
52 end
53 return file_read(self, unpack(args, 1, n))
54 end
55 end
56 return file_read(self, ...)
57 end
58
59 if not is_luajit52 then
60 local file_write = file_meta.__index.write
61 file_meta.__index.write = function(self, ...)
62 local ret, err = file_write(self, ...)
63 if ret then
64 return self
65 end
66 return ret, err
67 end
68 end
69end
70
71return M
diff --git a/vendor/compat53/init.lua b/vendor/compat53/init.lua
new file mode 100644
index 00000000..b5075713
--- /dev/null
+++ b/vendor/compat53/init.lua
@@ -0,0 +1,325 @@
1local lua_version = _VERSION:sub(-3)
2
3
4if lua_version < "5.3" then
5
6 local _G, pairs, require, select, type =
7 _G, pairs, require, select, type
8 local debug, io = debug, io
9 local unpack = lua_version == "5.1" and unpack or table.unpack
10
11 local M = require("compat53.module")
12
13 -- select the most powerful getmetatable function available
14 local gmt = type(debug) == "table" and debug.getmetatable or
15 getmetatable or function() return false end
16 -- metatable for file objects from Lua's standard io library
17 local file_meta = gmt(io.stdout)
18
19
20 -- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag)
21 local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ"
22 local is_luajit52 = is_luajit and
23 #setmetatable({}, { __len = function() return 1 end }) == 1
24
25
26 if type(file_meta) == "table" and type(file_meta.__index) == "table" then
27 local file_mt = require("compat53.file_mt")
28 file_mt.update_file_meta(file_meta, is_luajit52)
29 end -- got a valid metatable for file objects
30
31
32 -- changes for Lua 5.1 only
33 if lua_version == "5.1" then
34
35 -- cache globals
36 local error, pcall, rawset, setmetatable, tostring, xpcall =
37 error, pcall, rawset, setmetatable, tostring, xpcall
38 local coroutine, package, string = coroutine, package, string
39 local coroutine_resume = coroutine.resume
40 local coroutine_running = coroutine.running
41 local coroutine_status = coroutine.status
42 local coroutine_yield = coroutine.yield
43 local io_type = io.type
44
45
46 -- make package.searchers available as an alias for package.loaders
47 local p_index = { searchers = package.loaders }
48 setmetatable(package, {
49 __index = p_index,
50 __newindex = function(p, k, v)
51 if k == "searchers" then
52 rawset(p, "loaders", v)
53 p_index.searchers = v
54 else
55 rawset(p, k, v)
56 end
57 end
58 })
59
60
61 if type(file_meta) == "table" and type(file_meta.__index) == "table" then
62 if not is_luajit then
63 local function helper(_, var_1, ...)
64 if var_1 == nil then
65 if (...) ~= nil then
66 error((...), 2)
67 end
68 end
69 return var_1, ...
70 end
71
72 local function lines_iterator(st)
73 return helper(st, st.f:read(unpack(st, 1, st.n)))
74 end
75
76 local file_write = file_meta.__index.write
77 file_meta.__index.write = function(self, ...)
78 local res, msg, errno = file_write(self, ...)
79 if res then
80 return self
81 else
82 return nil, msg, errno
83 end
84 end
85
86 file_meta.__index.lines = function(self, ...)
87 if io_type(self) == "closed file" then
88 error("attempt to use a closed file", 2)
89 end
90 local st = { f=self, n=select('#', ...), ... }
91 for i = 1, st.n do
92 local t = type(st[i])
93 if t == "string" then
94 local fmt = st[i]:match("^*?([aln])")
95 if not fmt then
96 error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2)
97 end
98 st[i] = "*"..fmt
99 elseif t ~= "number" then
100 error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2)
101 end
102 end
103 return lines_iterator, st
104 end
105 end -- not luajit
106 end -- file_meta valid
107
108
109 -- the (x)pcall implementations start a new coroutine internally
110 -- to allow yielding even in Lua 5.1. to allow for accurate
111 -- stack traces we keep track of the nested coroutine activations
112 -- in the weak tables below:
113 local weak_meta = { __mode = "kv" }
114 -- maps the internal pcall coroutines to the user coroutine that
115 -- *should* be running if pcall didn't use coroutines internally
116 local pcall_mainOf = setmetatable({}, weak_meta)
117 -- table that maps each running coroutine started by pcall to
118 -- the coroutine that resumed it (user coroutine *or* pcall
119 -- coroutine!)
120 local pcall_previous = setmetatable({}, weak_meta)
121 -- reverse of `pcall_mainOf`. maps a user coroutine to the
122 -- currently active pcall coroutine started within it
123 local pcall_callOf = setmetatable({}, weak_meta)
124 -- similar to `pcall_mainOf` but is used only while executing
125 -- the error handler of xpcall (thus no nesting is necessary!)
126 local xpcall_running = setmetatable({}, weak_meta)
127
128 -- handle debug functions
129 if type(debug) == "table" then
130 local debug_getinfo = debug.getinfo
131 local debug_traceback = debug.traceback
132
133 if not is_luajit then
134 local function calculate_trace_level(co, level)
135 if level ~= nil then
136 for out = 1, 1/0 do
137 local info = (co==nil) and debug_getinfo(out, "") or debug_getinfo(co, out, "")
138 if info == nil then
139 local max = out-1
140 if level <= max then
141 return level
142 end
143 return nil, level-max
144 end
145 end
146 end
147 return 1
148 end
149
150 local stack_pattern = "\nstack traceback:"
151 local stack_replace = ""
152 function debug.traceback(co, msg, level)
153 local lvl
154 local nilmsg
155 if type(co) ~= "thread" then
156 co, msg, level = coroutine_running(), co, msg
157 end
158 if msg == nil then
159 msg = ""
160 nilmsg = true
161 elseif type(msg) ~= "string" then
162 return msg
163 end
164 if co == nil then
165 msg = debug_traceback(msg, level or 1)
166 else
167 local xpco = xpcall_running[co]
168 if xpco ~= nil then
169 lvl, level = calculate_trace_level(xpco, level)
170 if lvl then
171 msg = debug_traceback(xpco, msg, lvl)
172 else
173 msg = msg..stack_pattern
174 end
175 lvl, level = calculate_trace_level(co, level)
176 if lvl then
177 local trace = debug_traceback(co, "", lvl)
178 msg = msg..trace:gsub(stack_pattern, stack_replace)
179 end
180 else
181 co = pcall_callOf[co] or co
182 lvl, level = calculate_trace_level(co, level)
183 if lvl then
184 msg = debug_traceback(co, msg, lvl)
185 else
186 msg = msg..stack_pattern
187 end
188 end
189 co = pcall_previous[co]
190 while co ~= nil do
191 lvl, level = calculate_trace_level(co, level)
192 if lvl then
193 local trace = debug_traceback(co, "", lvl)
194 msg = msg..trace:gsub(stack_pattern, stack_replace)
195 end
196 co = pcall_previous[co]
197 end
198 end
199 if nilmsg then
200 msg = msg:gsub("^\n", "")
201 end
202 msg = msg:gsub("\n\t%(tail call%): %?", "\000")
203 msg = msg:gsub("\n\t%.%.%.\n", "\001\n")
204 msg = msg:gsub("\n\t%.%.%.$", "\001")
205 msg = msg:gsub("(%z+)\001(%z+)", function(some, other)
206 return "\n\t(..."..#some+#other.."+ tail call(s)...)"
207 end)
208 msg = msg:gsub("\001(%z+)", function(zeros)
209 return "\n\t(..."..#zeros.."+ tail call(s)...)"
210 end)
211 msg = msg:gsub("(%z+)\001", function(zeros)
212 return "\n\t(..."..#zeros.."+ tail call(s)...)"
213 end)
214 msg = msg:gsub("%z+", function(zeros)
215 return "\n\t(..."..#zeros.." tail call(s)...)"
216 end)
217 msg = msg:gsub("\001", function()
218 return "\n\t..."
219 end)
220 return msg
221 end
222 end -- is not luajit
223 end -- debug table available
224
225
226 if not is_luajit52 then
227 local coroutine_running52 = M.coroutine.running
228 function M.coroutine.running()
229 local co, ismain = coroutine_running52()
230 if ismain then
231 return co, true
232 else
233 return pcall_mainOf[co] or co, false
234 end
235 end
236 end
237
238 if not is_luajit then
239 local function pcall_results(current, call, success, ...)
240 if coroutine_status(call) == "suspended" then
241 return pcall_results(current, call, coroutine_resume(call, coroutine_yield(...)))
242 end
243 if pcall_previous then
244 pcall_previous[call] = nil
245 local main = pcall_mainOf[call]
246 if main == current then current = nil end
247 pcall_callOf[main] = current
248 end
249 pcall_mainOf[call] = nil
250 return success, ...
251 end
252
253 local function pcall_exec(current, call, ...)
254 local main = pcall_mainOf[current] or current
255 pcall_mainOf[call] = main
256 if pcall_previous then
257 pcall_previous[call] = current
258 pcall_callOf[main] = call
259 end
260 return pcall_results(current, call, coroutine_resume(call, ...))
261 end
262
263 local coroutine_create52 = M.coroutine.create
264
265 local function pcall_coroutine(func)
266 if type(func) ~= "function" then
267 local callable = func
268 func = function (...) return callable(...) end
269 end
270 return coroutine_create52(func)
271 end
272
273 function M.pcall(func, ...)
274 local current = coroutine_running()
275 if not current then return pcall(func, ...) end
276 return pcall_exec(current, pcall_coroutine(func), ...)
277 end
278
279 local function xpcall_catch(current, call, msgh, success, ...)
280 if not success then
281 xpcall_running[current] = call
282 local ok, result = pcall(msgh, ...)
283 xpcall_running[current] = nil
284 if not ok then
285 return false, "error in error handling ("..tostring(result)..")"
286 end
287 return false, result
288 end
289 return true, ...
290 end
291
292 function M.xpcall(f, msgh, ...)
293 local current = coroutine_running()
294 if not current then
295 local args, n = { ... }, select('#', ...)
296 return xpcall(function() return f(unpack(args, 1, n)) end, msgh)
297 end
298 local call = pcall_coroutine(f)
299 return xpcall_catch(current, call, msgh, pcall_exec(current, call, ...))
300 end
301 end -- not luajit
302
303 end -- lua 5.1
304
305
306 -- handle exporting to global scope
307 local function extend_table(from, to)
308 if from ~= to then
309 for k,v in pairs(from) do
310 if type(v) == "table" and
311 type(to[k]) == "table" and
312 v ~= to[k] then
313 extend_table(v, to[k])
314 else
315 to[k] = v
316 end
317 end
318 end
319 end
320
321 extend_table(M, _G)
322
323end -- lua < 5.3
324
325-- vi: set expandtab softtabstop=3 shiftwidth=3 :
diff --git a/vendor/compat53/module.lua b/vendor/compat53/module.lua
new file mode 100644
index 00000000..b580e536
--- /dev/null
+++ b/vendor/compat53/module.lua
@@ -0,0 +1,894 @@
1local _G, _VERSION = _G, _VERSION
2local lua_version = _VERSION:sub(-3)
3
4
5local M = _G
6
7if lua_version < "5.3" then
8
9 -- cache globals in upvalues
10 local error, ipairs, pairs, pcall, require, select, setmetatable, type =
11 error, ipairs, pairs, pcall, require, select, setmetatable, type
12 local debug, io, math, package, string, table =
13 debug, io, math, package, string, table
14 local io_lines = io.lines
15 local io_read = io.read
16 local io_open = io.open
17 local io_popen = io.popen
18 local io_tmpfile = io.tmpfile
19 local unpack = lua_version == "5.1" and unpack or table.unpack
20 local debug_setmetatable = type(debug) == "table" and debug.setmetatable
21
22 -- create module table
23 M = {}
24 local M_meta = {
25 __index = _G,
26 -- __newindex is set at the end
27 }
28 setmetatable(M, M_meta)
29
30 -- create subtables
31 M.io = setmetatable({}, { __index = io })
32 M.math = setmetatable({}, { __index = math })
33 M.string = setmetatable({}, { __index = string })
34 M.table = setmetatable({}, { __index = table })
35 M.utf8 = {}
36
37
38 -- select the most powerful getmetatable function available
39 local gmt = type(debug) == "table" and debug.getmetatable or
40 getmetatable or function() return false end
41
42 -- type checking functions
43 local checkinteger -- forward declararation
44
45 local function argcheck(cond, i, f, extra)
46 if not cond then
47 error("bad argument #"..i.." to '"..f.."' ("..extra..")", 0)
48 end
49 end
50
51
52 -- load utf8 library
53 local utf8_ok, utf8lib = pcall(require, "compat53.utf8")
54 if utf8_ok then
55 if lua_version == "5.1" then
56 utf8lib.charpattern = "[%z\1-\127\194-\244][\128-\191]*"
57 end
58 for k,v in pairs(utf8lib) do
59 M.utf8[k] = v
60 end
61 package.loaded["utf8"] = M.utf8
62 end
63
64
65 -- load table library
66 local table_ok, tablib = pcall(require, "compat53.table")
67 if table_ok then
68 for k,v in pairs(tablib) do
69 M.table[k] = v
70 end
71 end
72
73
74 -- load io functions
75 local io_ok, iolib = pcall(require, "compat53.io")
76 if io_ok then
77 for k,v in pairs(iolib) do
78 M.io[k] = v
79 end
80 end
81
82
83 -- load string packing functions
84 local str_ok, strlib = pcall(require, "compat53.string")
85 if str_ok then
86 for k,v in pairs(strlib) do
87 M.string[k] = v
88 end
89 end
90
91
92 -- try Roberto's struct module for string packing/unpacking if
93 -- compat53.string is unavailable
94 if not str_ok then
95 local struct_ok, struct = pcall(require, "struct")
96 if struct_ok then
97 M.string.pack = struct.pack
98 M.string.packsize = struct.size
99 M.string.unpack = struct.unpack
100 end
101 end
102
103
104 -- update math library
105 do
106 local maxint, minint = 1
107
108 while maxint+1 > maxint and 2*maxint > maxint do
109 maxint = maxint * 2
110 end
111 if 2*maxint <= maxint then
112 maxint = 2*maxint-1
113 minint = -maxint-1
114 else
115 maxint = maxint
116 minint = -maxint
117 end
118 M.math.maxinteger = maxint
119 M.math.mininteger = minint
120
121 function M.math.tointeger(n)
122 n = tonumber(n)
123 if type(n) == "number" and n <= maxint and n >= minint and n % 1 == 0 then
124 return n
125 end
126 return nil
127 end
128
129 function M.math.type(n)
130 if type(n) == "number" then
131 if n <= maxint and n >= minint and n % 1 == 0 then
132 return "integer"
133 else
134 return "float"
135 end
136 else
137 return nil
138 end
139 end
140
141 function checkinteger(x, i, f)
142 local t = type(x)
143 if t ~= "number" then
144 error("bad argument #"..i.." to '"..f..
145 "' (number expected, got "..t..")", 0)
146 elseif x > maxint or x < minint or x % 1 ~= 0 then
147 error("bad argument #"..i.." to '"..f..
148 "' (number has no integer representation)", 0)
149 else
150 return x
151 end
152 end
153
154 function M.math.ult(m, n)
155 m = checkinteger(m, "1", "math.ult")
156 n = checkinteger(n, "2", "math.ult")
157 if m >= 0 and n < 0 then
158 return true
159 elseif m < 0 and n >= 0 then
160 return false
161 else
162 return m < n
163 end
164 end
165 end
166
167
168 -- assert should allow non-string error objects
169 function M.assert(cond, ...)
170 if cond then
171 return cond, ...
172 elseif select('#', ...) > 0 then
173 error((...), 2)
174 else
175 error("assertion failed!", 2)
176 end
177 end
178
179
180 -- ipairs should respect __index metamethod
181 do
182 local function ipairs_iterator(st, var)
183 var = var + 1
184 local val = st[var]
185 if val ~= nil then
186 return var, st[var]
187 end
188 end
189 function M.ipairs(t)
190 if gmt(t) ~= nil then -- t has metatable
191 return ipairs_iterator, t, 0
192 else
193 return ipairs(t)
194 end
195 end
196 end
197
198
199 -- make '*' optional for io.read and io.lines
200 do
201 local function addasterisk(fmt)
202 if type(fmt) == "string" and fmt:sub(1, 1) ~= "*" then
203 return "*"..fmt
204 else
205 return fmt
206 end
207 end
208
209 function M.io.read(...)
210 local n = select('#', ...)
211 for i = 1, n do
212 local a = select(i, ...)
213 local b = addasterisk(a)
214 -- as an optimization we only allocate a table for the
215 -- modified format arguments when we have a '*' somewhere.
216 if a ~= b then
217 local args = { ... }
218 args[i] = b
219 for j = i+1, n do
220 args[j] = addasterisk(args[j])
221 end
222 return io_read(unpack(args, 1, n))
223 end
224 end
225 return io_read(...)
226 end
227
228 -- PUC-Rio Lua 5.1 uses a different implementation for io.lines!
229 function M.io.lines(...)
230 local n = select('#', ...)
231 for i = 2, n do
232 local a = select(i, ...)
233 local b = addasterisk(a)
234 -- as an optimization we only allocate a table for the
235 -- modified format arguments when we have a '*' somewhere.
236 if a ~= b then
237 local args = { ... }
238 args[i] = b
239 for j = i+1, n do
240 args[j] = addasterisk(args[j])
241 end
242 return io_lines(unpack(args, 1, n))
243 end
244 end
245 return io_lines(...)
246 end
247 end
248
249
250 -- update table library (if C module not available)
251 if not table_ok then
252 local table_concat = table.concat
253 local table_insert = table.insert
254 local table_remove = table.remove
255 local table_sort = table.sort
256
257 function M.table.concat(list, sep, i, j)
258 local mt = gmt(list)
259 if type(mt) == "table" and type(mt.__len) == "function" then
260 local src = list
261 list, i, j = {}, i or 1, j or mt.__len(src)
262 for k = i, j do
263 list[k] = src[k]
264 end
265 end
266 return table_concat(list, sep, i, j)
267 end
268
269 function M.table.insert(list, ...)
270 local mt = gmt(list)
271 local has_mt = type(mt) == "table"
272 local has_len = has_mt and type(mt.__len) == "function"
273 if has_mt and (has_len or mt.__index or mt.__newindex) then
274 local e = (has_len and mt.__len(list) or #list)+1
275 local nargs, pos, value = select('#', ...), ...
276 if nargs == 1 then
277 pos, value = e, pos
278 elseif nargs == 2 then
279 pos = checkinteger(pos, "2", "table.insert")
280 argcheck(1 <= pos and pos <= e, "2", "table.insert",
281 "position out of bounds" )
282 else
283 error("wrong number of arguments to 'insert'", 0)
284 end
285 for i = e-1, pos, -1 do
286 list[i+1] = list[i]
287 end
288 list[pos] = value
289 else
290 return table_insert(list, ...)
291 end
292 end
293
294 function M.table.move(a1, f, e, t, a2)
295 a2 = a2 or a1
296 f = checkinteger(f, "2", "table.move")
297 argcheck(f > 0, "2", "table.move",
298 "initial position must be positive")
299 e = checkinteger(e, "3", "table.move")
300 t = checkinteger(t, "4", "table.move")
301 if e >= f then
302 local m, n, d = 0, e-f, 1
303 if t > f then m, n, d = n, m, -1 end
304 for i = m, n, d do
305 a2[t+i] = a1[f+i]
306 end
307 end
308 return a2
309 end
310
311 function M.table.remove(list, pos)
312 local mt = gmt(list)
313 local has_mt = type(mt) == "table"
314 local has_len = has_mt and type(mt.__len) == "function"
315 if has_mt and (has_len or mt.__index or mt.__newindex) then
316 local e = (has_len and mt.__len(list) or #list)
317 pos = pos ~= nil and checkinteger(pos, "2", "table.remove") or e
318 if pos ~= e then
319 argcheck(1 <= pos and pos <= e+1, "2", "table.remove",
320 "position out of bounds" )
321 end
322 local result = list[pos]
323 while pos < e do
324 list[pos] = list[pos+1]
325 pos = pos + 1
326 end
327 list[pos] = nil
328 return result
329 else
330 return table_remove(list, pos)
331 end
332 end
333
334 do
335 local function pivot(list, cmp, a, b)
336 local m = b - a
337 if m > 2 then
338 local c = a + (m-m%2)/2
339 local x, y, z = list[a], list[b], list[c]
340 if not cmp(x, y) then
341 x, y, a, b = y, x, b, a
342 end
343 if not cmp(y, z) then
344 y, b = z, c
345 end
346 if not cmp(x, y) then
347 y, b = x, a
348 end
349 return b, y
350 else
351 return b, list[b]
352 end
353 end
354
355 local function lt_cmp(a, b)
356 return a < b
357 end
358
359 local function qsort(list, cmp, b, e)
360 if b < e then
361 local i, j, k, val = b, e, pivot(list, cmp, b, e)
362 while i < j do
363 while i < j and cmp(list[i], val) do
364 i = i + 1
365 end
366 while i < j and not cmp(list[j], val) do
367 j = j - 1
368 end
369 if i < j then
370 list[i], list[j] = list[j], list[i]
371 if i == k then k = j end -- update pivot position
372 i, j = i+1, j-1
373 end
374 end
375 if i ~= k and not cmp(list[i], val) then
376 list[i], list[k] = val, list[i]
377 k = i -- update pivot position
378 end
379 qsort(list, cmp, b, i == k and i-1 or i)
380 return qsort(list, cmp, i+1, e)
381 end
382 end
383
384 function M.table.sort(list, cmp)
385 local mt = gmt(list)
386 local has_mt = type(mt) == "table"
387 local has_len = has_mt and type(mt.__len) == "function"
388 if has_len then
389 cmp = cmp or lt_cmp
390 local len = mt.__len(list)
391 return qsort(list, cmp, 1, len)
392 else
393 return table_sort(list, cmp)
394 end
395 end
396 end
397
398 local function unpack_helper(list, i, j, ...)
399 if j < i then
400 return ...
401 else
402 return unpack_helper(list, i, j-1, list[j], ...)
403 end
404 end
405 function M.table.unpack(list, i, j)
406 local mt = gmt(list)
407 local has_mt = type(mt) == "table"
408 local has_len = has_mt and type(mt.__len) == "function"
409 if has_mt and (has_len or mt.__index) then
410 i, j = i or 1, j or (has_len and mt.__len(list)) or #list
411 return unpack_helper(list, i, j)
412 else
413 return unpack(list, i, j)
414 end
415 end
416 end -- update table library
417
418
419 -- bring Lua 5.1 (and LuaJIT) up to speed with Lua 5.2
420 if lua_version == "5.1" then
421 -- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag)
422 local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ"
423 local is_luajit52 = is_luajit and
424 #setmetatable({}, { __len = function() return 1 end }) == 1
425
426 -- cache globals in upvalues
427 local load, loadfile, loadstring, setfenv, xpcall =
428 load, loadfile, loadstring, setfenv, xpcall
429 local coroutine, os = coroutine, os
430 local coroutine_create = coroutine.create
431 local coroutine_resume = coroutine.resume
432 local coroutine_running = coroutine.running
433 local coroutine_status = coroutine.status
434 local coroutine_yield = coroutine.yield
435 local io_input = io.input
436 local io_open = io.open
437 local io_output = io.output
438 local io_write = io.write
439 local math_log = math.log
440 local os_execute = os.execute
441 local string_find = string.find
442 local string_format = string.format
443 local string_gmatch = string.gmatch
444 local string_gsub = string.gsub
445 local string_match = string.match
446 local string_rep = string.rep
447 local table_concat = table.concat
448
449 -- create subtables
450 M.coroutine = setmetatable({}, { __index = coroutine })
451 M.os = setmetatable({}, { __index = os })
452 M.package = setmetatable({}, { __index = package })
453
454 -- handle debug functions
455 if type(debug) == "table" then
456 local debug_setfenv = debug.setfenv
457 local debug_getfenv = debug.getfenv
458
459 M.debug = setmetatable({}, { __index = debug })
460
461 if not is_luajit52 then
462 function M.debug.setuservalue(obj, value)
463 if type(obj) ~= "userdata" then
464 error("bad argument #1 to 'setuservalue' (userdata expected, got "..
465 type(obj)..")", 2)
466 end
467 if value == nil then value = _G end
468 if type(value) ~= "table" then
469 error("bad argument #2 to 'setuservalue' (table expected, got "..
470 type(value)..")", 2)
471 end
472 return debug_setfenv(obj, value)
473 end
474
475 function M.debug.getuservalue(obj)
476 if type(obj) ~= "userdata" then
477 return nil
478 else
479 local v = debug_getfenv(obj)
480 if v == _G or v == package then
481 return nil
482 end
483 return v
484 end
485 end
486
487 function M.debug.setmetatable(value, tab)
488 debug_setmetatable(value, tab)
489 return value
490 end
491 end -- not luajit with compat52 enabled
492 end -- debug table available
493
494
495 if not is_luajit52 then
496 function M.pairs(t)
497 local mt = gmt(t)
498 if type(mt) == "table" and type(mt.__pairs) == "function" then
499 return mt.__pairs(t)
500 else
501 return pairs(t)
502 end
503 end
504 end
505
506
507 if not is_luajit then
508 local function check_mode(mode, prefix)
509 local has = { text = false, binary = false }
510 for i = 1,#mode do
511 local c = mode:sub(i, i)
512 if c == "t" then has.text = true end
513 if c == "b" then has.binary = true end
514 end
515 local t = prefix:sub(1, 1) == "\27" and "binary" or "text"
516 if not has[t] then
517 return "attempt to load a "..t.." chunk (mode is '"..mode.."')"
518 end
519 end
520
521 function M.load(ld, source, mode, env)
522 mode = mode or "bt"
523 local chunk, msg
524 if type( ld ) == "string" then
525 if mode ~= "bt" then
526 local merr = check_mode(mode, ld)
527 if merr then return nil, merr end
528 end
529 chunk, msg = loadstring(ld, source)
530 else
531 local ld_type = type(ld)
532 if ld_type ~= "function" then
533 error("bad argument #1 to 'load' (function expected, got "..
534 ld_type..")", 2)
535 end
536 if mode ~= "bt" then
537 local checked, merr = false, nil
538 local function checked_ld()
539 if checked then
540 return ld()
541 else
542 checked = true
543 local v = ld()
544 merr = check_mode(mode, v or "")
545 if merr then return nil end
546 return v
547 end
548 end
549 chunk, msg = load(checked_ld, source)
550 if merr then return nil, merr end
551 else
552 chunk, msg = load(ld, source)
553 end
554 end
555 if not chunk then
556 return chunk, msg
557 end
558 if env ~= nil then
559 setfenv(chunk, env)
560 end
561 return chunk
562 end
563
564 M.loadstring = M.load
565
566 function M.loadfile(file, mode, env)
567 mode = mode or "bt"
568 if mode ~= "bt" then
569 local f = io_open(file, "rb")
570 if f then
571 local prefix = f:read(1)
572 f:close()
573 if prefix then
574 local merr = check_mode(mode, prefix)
575 if merr then return nil, merr end
576 end
577 end
578 end
579 local chunk, msg = loadfile(file)
580 if not chunk then
581 return chunk, msg
582 end
583 if env ~= nil then
584 setfenv(chunk, env)
585 end
586 return chunk
587 end
588 end -- not luajit
589
590
591 if not is_luajit52 then
592 function M.rawlen(v)
593 local t = type(v)
594 if t ~= "string" and t ~= "table" then
595 error("bad argument #1 to 'rawlen' (table or string expected)", 2)
596 end
597 return #v
598 end
599 end
600
601
602 if not is_luajit then
603 function M.xpcall(f, msgh, ...)
604 local args, n = { ... }, select('#', ...)
605 return xpcall(function() return f(unpack(args, 1, n)) end, msgh)
606 end
607 end
608
609
610 if not is_luajit52 then
611 function M.os.execute(cmd)
612 local code = os_execute(cmd)
613 -- Lua 5.1 does not report exit by signal.
614 if code == 0 then
615 return true, "exit", code
616 else
617 if package.config:sub(1, 1) == '/' then
618 code = code/256 -- only correct on Linux!
619 end
620 return nil, "exit", code
621 end
622 end
623 end
624
625
626 if not table_ok and not is_luajit52 then
627 M.table.pack = function(...)
628 return { n = select('#', ...), ... }
629 end
630 end
631
632
633 local main_coroutine = coroutine_create(function() end)
634
635 function M.coroutine.create(func)
636 local success, result = pcall(coroutine_create, func)
637 if not success then
638 if type(func) ~= "function" then
639 error("bad argument #1 (function expected)", 0)
640 end
641 result = coroutine_create(function(...) return func(...) end)
642 end
643 return result
644 end
645
646 if not is_luajit52 then
647 function M.coroutine.running()
648 local co = coroutine_running()
649 if co then
650 return co, false
651 else
652 return main_coroutine, true
653 end
654 end
655 end
656
657 function M.coroutine.yield(...)
658 local co, flag = coroutine_running()
659 if co and not flag then
660 return coroutine_yield(...)
661 else
662 error("attempt to yield from outside a coroutine", 0)
663 end
664 end
665
666 if not is_luajit then
667 function M.coroutine.resume(co, ...)
668 if co == main_coroutine then
669 return false, "cannot resume non-suspended coroutine"
670 else
671 return coroutine_resume(co, ...)
672 end
673 end
674
675 function M.coroutine.status(co)
676 local notmain = coroutine_running()
677 if co == main_coroutine then
678 return notmain and "normal" or "running"
679 else
680 return coroutine_status(co)
681 end
682 end
683 end -- not luajit
684
685
686 if not is_luajit then
687 M.math.log = function(x, base)
688 if base ~= nil then
689 return math_log(x)/math_log(base)
690 else
691 return math_log(x)
692 end
693 end
694 end
695
696
697 if not is_luajit then
698 function M.package.searchpath(name, path, sep, rep)
699 sep = (sep or "."):gsub("(%p)", "%%%1")
700 rep = (rep or package.config:sub(1, 1)):gsub("(%%)", "%%%1")
701 local pname = name:gsub(sep, rep):gsub("(%%)", "%%%1")
702 local msg = {}
703 for subpath in path:gmatch("[^;]+") do
704 local fpath = subpath:gsub("%?", pname)
705 local f = io_open(fpath, "r")
706 if f then
707 f:close()
708 return fpath
709 end
710 msg[#msg+1] = "\n\tno file '" .. fpath .. "'"
711 end
712 return nil, table_concat(msg)
713 end
714 end
715
716
717 local function fix_pattern(pattern)
718 return (string_gsub(pattern, "%z", "%%z"))
719 end
720
721 function M.string.find(s, pattern, ...)
722 return string_find(s, fix_pattern(pattern), ...)
723 end
724
725 function M.string.gmatch(s, pattern)
726 return string_gmatch(s, fix_pattern(pattern))
727 end
728
729 function M.string.gsub(s, pattern, ...)
730 return string_gsub(s, fix_pattern(pattern), ...)
731 end
732
733 function M.string.match(s, pattern, ...)
734 return string_match(s, fix_pattern(pattern), ...)
735 end
736
737 if not is_luajit then
738 function M.string.rep(s, n, sep)
739 if sep ~= nil and sep ~= "" and n >= 2 then
740 return s .. string_rep(sep..s, n-1)
741 else
742 return string_rep(s, n)
743 end
744 end
745 end
746
747 if not is_luajit then
748 do
749 local addqt = {
750 ["\n"] = "\\\n",
751 ["\\"] = "\\\\",
752 ["\""] = "\\\""
753 }
754
755 local function addquoted(c, d)
756 return (addqt[c] or string_format(d~="" and "\\%03d" or "\\%d", c:byte()))..d
757 end
758
759 function M.string.format(fmt, ...)
760 local args, n = { ... }, select('#', ...)
761 local i = 0
762 local function adjust_fmt(lead, mods, kind)
763 if #lead % 2 == 0 then
764 i = i + 1
765 if kind == "s" then
766 args[i] = _G.tostring(args[i])
767 elseif kind == "q" then
768 args[i] = '"'..string_gsub(args[i], "([%z%c\\\"\n])(%d?)", addquoted)..'"'
769 return lead.."%"..mods.."s"
770 end
771 end
772 end
773 fmt = string_gsub(fmt, "(%%*)%%([%d%.%-%+%# ]*)(%a)", adjust_fmt)
774 return string_format(fmt, unpack(args, 1, n))
775 end
776 end
777 end
778
779
780 function M.io.write(...)
781 local res, msg, errno = io_write(...)
782 if res then
783 return io_output()
784 else
785 return nil, msg, errno
786 end
787 end
788
789 if not is_luajit then
790 local function helper(st, var_1, ...)
791 if var_1 == nil then
792 if st.doclose then st.f:close() end
793 if (...) ~= nil then
794 error((...), 2)
795 end
796 end
797 return var_1, ...
798 end
799
800 local function lines_iterator(st)
801 return helper(st, st.f:read(unpack(st, 1, st.n)))
802 end
803
804 function M.io.lines(fname, ...)
805 local doclose, file, msg
806 if fname ~= nil then
807 doclose, file, msg = true, io_open(fname, "r")
808 if not file then error(msg, 2) end
809 else
810 doclose, file = false, io_input()
811 end
812 local st = { f=file, doclose=doclose, n=select('#', ...), ... }
813 for i = 1, st.n do
814 local t = type(st[i])
815 if t == "string" then
816 local fmt = st[i]:match("^%*?([aln])")
817 if not fmt then
818 error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2)
819 end
820 st[i] = "*"..fmt
821 elseif t ~= "number" then
822 error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2)
823 end
824 end
825 return lines_iterator, st
826 end
827 end -- not luajit
828
829 if is_luajit then
830 local compat_file_meta = {}
831 local compat_file_meta_loaded = 0
832
833 local function load_compat_file_meta(file_meta)
834 -- fill compat_file_meta with original entries
835 for k, v in pairs(file_meta) do
836 compat_file_meta[k] = v
837 end
838 compat_file_meta.__index = {}
839 for k, v in pairs(file_meta.__index) do
840 compat_file_meta.__index[k] = v
841 end
842
843 compat_file_meta_loaded = 1
844
845 -- update it with compatibility functions
846 local file_mt_ok, file_mt = pcall(require, "compat53.file_mt")
847 if file_mt_ok then
848 file_mt.update_file_meta(compat_file_meta, is_luajit52)
849
850 compat_file_meta_loaded = 2
851 end
852 end
853
854 local function return_fd(fd, err, code)
855 if not fd then
856 return fd, err, code
857 end
858 if fd and debug_setmetatable then
859 if compat_file_meta_loaded == 0 then
860 local file_meta = gmt(fd)
861 load_compat_file_meta(file_meta)
862 end
863 if compat_file_meta_loaded == 2 then
864 debug_setmetatable(fd, compat_file_meta)
865 end
866 end
867 return fd
868 end
869
870 function M.io.open(...)
871 return return_fd(io_open(...))
872 end
873
874 function M.io.popen(...)
875 return return_fd(io_popen(...))
876 end
877
878 function M.io.tmpfile(...)
879 return return_fd(io_tmpfile(...))
880 end
881 end
882
883 end -- lua 5.1
884
885 -- further write should be forwarded to _G
886 M_meta.__newindex = _G
887
888end -- lua < 5.3
889
890
891-- return module table
892return M
893
894-- vi: set expandtab softtabstop=3 shiftwidth=3 :
diff --git a/vendor/dkjson.d.tl b/vendor/dkjson.d.tl
new file mode 100644
index 00000000..4245e8aa
--- /dev/null
+++ b/vendor/dkjson.d.tl
@@ -0,0 +1,33 @@
1
2--[[
3- local type json = {string:json}
4]]
5
6local record dkjson
7 record JsonState
8 indent: boolean
9 keyorder: {string}
10 level: number
11 buffer: {string}
12 bufferlen: number
13 tables: {table:boolean}
14 exception: function(string, string, string, string): boolean|string, string
15 end
16 encode: function({string:any}, ?JsonState): string
17
18 decode: function(string, ?number, ?any, ?table): {string:any}, integer, string
19
20 null: table
21
22 version: string
23
24 quotestring: function(string): string
25
26 addnewline: function(JsonState)
27
28 encodeexception: function(string, any, JsonState, string): string
29
30 use_lpeg: function(): dkjson
31end
32
33return dkjson
diff --git a/vendor/dkjson.lua b/vendor/dkjson.lua
new file mode 100644
index 00000000..7a867241
--- /dev/null
+++ b/vendor/dkjson.lua
@@ -0,0 +1,749 @@
1-- Module options:
2local always_use_lpeg = false
3local register_global_module_table = false
4local global_module_name = 'json'
5
6--[==[
7
8David Kolf's JSON module for Lua 5.1 - 5.4
9
10Version 2.7
11
12
13For the documentation see the corresponding readme.txt or visit
14<http://dkolf.de/src/dkjson-lua.fsl/>.
15
16You can contact the author by sending an e-mail to 'david' at the
17domain 'dkolf.de'.
18
19
20Copyright (C) 2010-2024 David Heiko Kolf
21
22Permission is hereby granted, free of charge, to any person obtaining
23a copy of this software and associated documentation files (the
24"Software"), to deal in the Software without restriction, including
25without limitation the rights to use, copy, modify, merge, publish,
26distribute, sublicense, and/or sell copies of the Software, and to
27permit persons to whom the Software is furnished to do so, subject to
28the following conditions:
29
30The above copyright notice and this permission notice shall be
31included in all copies or substantial portions of the Software.
32
33THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
34EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
35MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
36NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
37BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
38ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
39CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40SOFTWARE.
41
42--]==]
43
44-- global dependencies:
45local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
46 pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
47local error, require, pcall, select = error, require, pcall, select
48local floor, huge = math.floor, math.huge
49local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
50 string.rep, string.gsub, string.sub, string.byte, string.char,
51 string.find, string.len, string.format
52local strmatch = string.match
53local concat = table.concat
54
55local json = { version = "dkjson 2.7" }
56
57local jsonlpeg = {}
58
59if register_global_module_table then
60 if always_use_lpeg then
61 _G[global_module_name] = jsonlpeg
62 else
63 _G[global_module_name] = json
64 end
65end
66
67local _ENV = nil -- blocking globals in Lua 5.2 and later
68
69pcall (function()
70 -- Enable access to blocked metatables.
71 -- Don't worry, this module doesn't change anything in them.
72 local debmeta = require "debug".getmetatable
73 if debmeta then getmetatable = debmeta end
74end)
75
76json.null = setmetatable ({}, {
77 __tojson = function () return "null" end
78})
79
80local function isarray (tbl)
81 local max, n, arraylen = 0, 0, 0
82 for k,v in pairs (tbl) do
83 if k == 'n' and type(v) == 'number' then
84 arraylen = v
85 if v > max then
86 max = v
87 end
88 else
89 if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
90 return false
91 end
92 if k > max then
93 max = k
94 end
95 n = n + 1
96 end
97 end
98 if max > 10 and max > arraylen and max > n * 2 then
99 return false -- don't create an array with too many holes
100 end
101 return true, max
102end
103
104local escapecodes = {
105 ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
106 ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
107}
108
109local function escapeutf8 (uchar)
110 local value = escapecodes[uchar]
111 if value then
112 return value
113 end
114 local a, b, c, d = strbyte (uchar, 1, 4)
115 a, b, c, d = a or 0, b or 0, c or 0, d or 0
116 if a <= 0x7f then
117 value = a
118 elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
119 value = (a - 0xc0) * 0x40 + b - 0x80
120 elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
121 value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
122 elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
123 value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
124 else
125 return ""
126 end
127 if value <= 0xffff then
128 return strformat ("\\u%.4x", value)
129 elseif value <= 0x10ffff then
130 -- encode as UTF-16 surrogate pair
131 value = value - 0x10000
132 local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
133 return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
134 else
135 return ""
136 end
137end
138
139local function fsub (str, pattern, repl)
140 -- gsub always builds a new string in a buffer, even when no match
141 -- exists. First using find should be more efficient when most strings
142 -- don't contain the pattern.
143 if strfind (str, pattern) then
144 return gsub (str, pattern, repl)
145 else
146 return str
147 end
148end
149
150local function quotestring (value)
151 -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
152 value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
153 if strfind (value, "[\194\216\220\225\226\239]") then
154 value = fsub (value, "\194[\128-\159\173]", escapeutf8)
155 value = fsub (value, "\216[\128-\132]", escapeutf8)
156 value = fsub (value, "\220\143", escapeutf8)
157 value = fsub (value, "\225\158[\180\181]", escapeutf8)
158 value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
159 value = fsub (value, "\226\129[\160-\175]", escapeutf8)
160 value = fsub (value, "\239\187\191", escapeutf8)
161 value = fsub (value, "\239\191[\176-\191]", escapeutf8)
162 end
163 return "\"" .. value .. "\""
164end
165json.quotestring = quotestring
166
167local function replace(str, o, n)
168 local i, j = strfind (str, o, 1, true)
169 if i then
170 return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
171 else
172 return str
173 end
174end
175
176-- locale independent num2str and str2num functions
177local decpoint, numfilter
178
179local function updatedecpoint ()
180 decpoint = strmatch(tostring(0.5), "([^05+])")
181 -- build a filter that can be used to remove group separators
182 numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
183end
184
185updatedecpoint()
186
187local function num2str (num)
188 return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
189end
190
191local function str2num (str)
192 local num = tonumber(replace(str, ".", decpoint))
193 if not num then
194 updatedecpoint()
195 num = tonumber(replace(str, ".", decpoint))
196 end
197 return num
198end
199
200local function addnewline2 (level, buffer, buflen)
201 buffer[buflen+1] = "\n"
202 buffer[buflen+2] = strrep (" ", level)
203 buflen = buflen + 2
204 return buflen
205end
206
207function json.addnewline (state)
208 if state.indent then
209 state.bufferlen = addnewline2 (state.level or 0,
210 state.buffer, state.bufferlen or #(state.buffer))
211 end
212end
213
214local encode2 -- forward declaration
215
216local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
217 local kt = type (key)
218 if kt ~= 'string' and kt ~= 'number' then
219 return nil, "type '" .. kt .. "' is not supported as a key by JSON."
220 end
221 if prev then
222 buflen = buflen + 1
223 buffer[buflen] = ","
224 end
225 if indent then
226 buflen = addnewline2 (level, buffer, buflen)
227 end
228 buffer[buflen+1] = quotestring (key)
229 buffer[buflen+2] = ":"
230 return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
231end
232
233local function appendcustom(res, buffer, state)
234 local buflen = state.bufferlen
235 if type (res) == 'string' then
236 buflen = buflen + 1
237 buffer[buflen] = res
238 end
239 return buflen
240end
241
242local function exception(reason, value, state, buffer, buflen, defaultmessage)
243 defaultmessage = defaultmessage or reason
244 local handler = state.exception
245 if not handler then
246 return nil, defaultmessage
247 else
248 state.bufferlen = buflen
249 local ret, msg = handler (reason, value, state, defaultmessage)
250 if not ret then return nil, msg or defaultmessage end
251 return appendcustom(ret, buffer, state)
252 end
253end
254
255function json.encodeexception(reason, value, state, defaultmessage)
256 return quotestring("<" .. defaultmessage .. ">")
257end
258
259encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
260 local valtype = type (value)
261 local valmeta = getmetatable (value)
262 valmeta = type (valmeta) == 'table' and valmeta -- only tables
263 local valtojson = valmeta and valmeta.__tojson
264 if valtojson then
265 if tables[value] then
266 return exception('reference cycle', value, state, buffer, buflen)
267 end
268 tables[value] = true
269 state.bufferlen = buflen
270 local ret, msg = valtojson (value, state)
271 if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
272 tables[value] = nil
273 buflen = appendcustom(ret, buffer, state)
274 elseif value == nil then
275 buflen = buflen + 1
276 buffer[buflen] = "null"
277 elseif valtype == 'number' then
278 local s
279 if value ~= value or value >= huge or -value >= huge then
280 -- This is the behaviour of the original JSON implementation.
281 s = "null"
282 else
283 s = num2str (value)
284 end
285 buflen = buflen + 1
286 buffer[buflen] = s
287 elseif valtype == 'boolean' then
288 buflen = buflen + 1
289 buffer[buflen] = value and "true" or "false"
290 elseif valtype == 'string' then
291 buflen = buflen + 1
292 buffer[buflen] = quotestring (value)
293 elseif valtype == 'table' then
294 if tables[value] then
295 return exception('reference cycle', value, state, buffer, buflen)
296 end
297 tables[value] = true
298 level = level + 1
299 local isa, n = isarray (value)
300 if n == 0 and valmeta and valmeta.__jsontype == 'object' then
301 isa = false
302 end
303 local msg
304 if isa then -- JSON array
305 buflen = buflen + 1
306 buffer[buflen] = "["
307 for i = 1, n do
308 buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
309 if not buflen then return nil, msg end
310 if i < n then
311 buflen = buflen + 1
312 buffer[buflen] = ","
313 end
314 end
315 buflen = buflen + 1
316 buffer[buflen] = "]"
317 else -- JSON object
318 local prev = false
319 buflen = buflen + 1
320 buffer[buflen] = "{"
321 local order = valmeta and valmeta.__jsonorder or globalorder
322 if order then
323 local used = {}
324 n = #order
325 for i = 1, n do
326 local k = order[i]
327 local v = value[k]
328 if v ~= nil then
329 used[k] = true
330 buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
331 if not buflen then return nil, msg end
332 prev = true -- add a seperator before the next element
333 end
334 end
335 for k,v in pairs (value) do
336 if not used[k] then
337 buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
338 if not buflen then return nil, msg end
339 prev = true -- add a seperator before the next element
340 end
341 end
342 else -- unordered
343 for k,v in pairs (value) do
344 buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
345 if not buflen then return nil, msg end
346 prev = true -- add a seperator before the next element
347 end
348 end
349 if indent then
350 buflen = addnewline2 (level - 1, buffer, buflen)
351 end
352 buflen = buflen + 1
353 buffer[buflen] = "}"
354 end
355 tables[value] = nil
356 else
357 return exception ('unsupported type', value, state, buffer, buflen,
358 "type '" .. valtype .. "' is not supported by JSON.")
359 end
360 return buflen
361end
362
363function json.encode (value, state)
364 state = state or {}
365 local oldbuffer = state.buffer
366 local buffer = oldbuffer or {}
367 state.buffer = buffer
368 updatedecpoint()
369 local ret, msg = encode2 (value, state.indent, state.level or 0,
370 buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
371 if not ret then
372 error (msg, 2)
373 elseif oldbuffer == buffer then
374 state.bufferlen = ret
375 return true
376 else
377 state.bufferlen = nil
378 state.buffer = nil
379 return concat (buffer)
380 end
381end
382
383local function loc (str, where)
384 local line, pos, linepos = 1, 1, 0
385 while true do
386 pos = strfind (str, "\n", pos, true)
387 if pos and pos < where then
388 line = line + 1
389 linepos = pos
390 pos = pos + 1
391 else
392 break
393 end
394 end
395 return "line " .. line .. ", column " .. (where - linepos)
396end
397
398local function unterminated (str, what, where)
399 return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
400end
401
402local function scanwhite (str, pos)
403 while true do
404 pos = strfind (str, "%S", pos)
405 if not pos then return nil end
406 local sub2 = strsub (str, pos, pos + 1)
407 if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
408 -- UTF-8 Byte Order Mark
409 pos = pos + 3
410 elseif sub2 == "//" then
411 pos = strfind (str, "[\n\r]", pos + 2)
412 if not pos then return nil end
413 elseif sub2 == "/*" then
414 pos = strfind (str, "*/", pos + 2)
415 if not pos then return nil end
416 pos = pos + 2
417 else
418 return pos
419 end
420 end
421end
422
423local escapechars = {
424 ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
425 ["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
426}
427
428local function unichar (value)
429 if value < 0 then
430 return nil
431 elseif value <= 0x007f then
432 return strchar (value)
433 elseif value <= 0x07ff then
434 return strchar (0xc0 + floor(value/0x40),
435 0x80 + (floor(value) % 0x40))
436 elseif value <= 0xffff then
437 return strchar (0xe0 + floor(value/0x1000),
438 0x80 + (floor(value/0x40) % 0x40),
439 0x80 + (floor(value) % 0x40))
440 elseif value <= 0x10ffff then
441 return strchar (0xf0 + floor(value/0x40000),
442 0x80 + (floor(value/0x1000) % 0x40),
443 0x80 + (floor(value/0x40) % 0x40),
444 0x80 + (floor(value) % 0x40))
445 else
446 return nil
447 end
448end
449
450local function scanstring (str, pos)
451 local lastpos = pos + 1
452 local buffer, n = {}, 0
453 while true do
454 local nextpos = strfind (str, "[\"\\]", lastpos)
455 if not nextpos then
456 return unterminated (str, "string", pos)
457 end
458 if nextpos > lastpos then
459 n = n + 1
460 buffer[n] = strsub (str, lastpos, nextpos - 1)
461 end
462 if strsub (str, nextpos, nextpos) == "\"" then
463 lastpos = nextpos + 1
464 break
465 else
466 local escchar = strsub (str, nextpos + 1, nextpos + 1)
467 local value
468 if escchar == "u" then
469 value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
470 if value then
471 local value2
472 if 0xD800 <= value and value <= 0xDBff then
473 -- we have the high surrogate of UTF-16. Check if there is a
474 -- low surrogate escaped nearby to combine them.
475 if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
476 value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
477 if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
478 value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
479 else
480 value2 = nil -- in case it was out of range for a low surrogate
481 end
482 end
483 end
484 value = value and unichar (value)
485 if value then
486 if value2 then
487 lastpos = nextpos + 12
488 else
489 lastpos = nextpos + 6
490 end
491 end
492 end
493 end
494 if not value then
495 value = escapechars[escchar] or escchar
496 lastpos = nextpos + 2
497 end
498 n = n + 1
499 buffer[n] = value
500 end
501 end
502 if n == 1 then
503 return buffer[1], lastpos
504 elseif n > 1 then
505 return concat (buffer), lastpos
506 else
507 return "", lastpos
508 end
509end
510
511local scanvalue -- forward declaration
512
513local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
514 local len = strlen (str)
515 local tbl, n = {}, 0
516 local pos = startpos + 1
517 if what == 'object' then
518 setmetatable (tbl, objectmeta)
519 else
520 setmetatable (tbl, arraymeta)
521 end
522 while true do
523 pos = scanwhite (str, pos)
524 if not pos then return unterminated (str, what, startpos) end
525 local char = strsub (str, pos, pos)
526 if char == closechar then
527 return tbl, pos + 1
528 end
529 local val1, err
530 val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
531 if err then return nil, pos, err end
532 pos = scanwhite (str, pos)
533 if not pos then return unterminated (str, what, startpos) end
534 char = strsub (str, pos, pos)
535 if char == ":" then
536 if val1 == nil then
537 return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
538 end
539 pos = scanwhite (str, pos + 1)
540 if not pos then return unterminated (str, what, startpos) end
541 local val2
542 val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
543 if err then return nil, pos, err end
544 tbl[val1] = val2
545 pos = scanwhite (str, pos)
546 if not pos then return unterminated (str, what, startpos) end
547 char = strsub (str, pos, pos)
548 else
549 n = n + 1
550 tbl[n] = val1
551 end
552 if char == "," then
553 pos = pos + 1
554 end
555 end
556end
557
558scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
559 pos = pos or 1
560 pos = scanwhite (str, pos)
561 if not pos then
562 return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
563 end
564 local char = strsub (str, pos, pos)
565 if char == "{" then
566 return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
567 elseif char == "[" then
568 return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
569 elseif char == "\"" then
570 return scanstring (str, pos)
571 else
572 local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
573 if pstart then
574 local number = str2num (strsub (str, pstart, pend))
575 if number then
576 return number, pend + 1
577 end
578 end
579 pstart, pend = strfind (str, "^%a%w*", pos)
580 if pstart then
581 local name = strsub (str, pstart, pend)
582 if name == "true" then
583 return true, pend + 1
584 elseif name == "false" then
585 return false, pend + 1
586 elseif name == "null" then
587 return nullval, pend + 1
588 end
589 end
590 return nil, pos, "no valid JSON value at " .. loc (str, pos)
591 end
592end
593
594local function optionalmetatables(...)
595 if select("#", ...) > 0 then
596 return ...
597 else
598 return {__jsontype = 'object'}, {__jsontype = 'array'}
599 end
600end
601
602function json.decode (str, pos, nullval, ...)
603 local objectmeta, arraymeta = optionalmetatables(...)
604 return scanvalue (str, pos, nullval, objectmeta, arraymeta)
605end
606
607function json.use_lpeg ()
608 local g = require ("lpeg")
609
610 if type(g.version) == 'function' and g.version() == "0.11" then
611 error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
612 end
613
614 local pegmatch = g.match
615 local P, S, R = g.P, g.S, g.R
616
617 local function ErrorCall (str, pos, msg, state)
618 if not state.msg then
619 state.msg = msg .. " at " .. loc (str, pos)
620 state.pos = pos
621 end
622 return false
623 end
624
625 local function Err (msg)
626 return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
627 end
628
629 local function ErrorUnterminatedCall (str, pos, what, state)
630 return ErrorCall (str, pos - 1, "unterminated " .. what, state)
631 end
632
633 local SingleLineComment = P"//" * (1 - S"\n\r")^0
634 local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
635 local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
636
637 local function ErrUnterminated (what)
638 return g.Cmt (g.Cc (what) * g.Carg (2), ErrorUnterminatedCall)
639 end
640
641 local PlainChar = 1 - S"\"\\\n\r"
642 local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
643 local HexDigit = R("09", "af", "AF")
644 local function UTF16Surrogate (match, pos, high, low)
645 high, low = tonumber (high, 16), tonumber (low, 16)
646 if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
647 return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
648 else
649 return false
650 end
651 end
652 local function UTF16BMP (hex)
653 return unichar (tonumber (hex, 16))
654 end
655 local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
656 local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
657 local Char = UnicodeEscape + EscapeSequence + PlainChar
658 local String = P"\"" * (g.Cs (Char ^ 0) * P"\"" + ErrUnterminated "string")
659 local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
660 local Fractal = P"." * R"09"^0
661 local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
662 local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
663 local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
664 local SimpleValue = Number + String + Constant
665 local ArrayContent, ObjectContent
666
667 -- The functions parsearray and parseobject parse only a single value/pair
668 -- at a time and store them directly to avoid hitting the LPeg limits.
669 local function parsearray (str, pos, nullval, state)
670 local obj, cont
671 local start = pos
672 local npos
673 local t, nt = {}, 0
674 repeat
675 obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
676 if cont == 'end' then
677 return ErrorUnterminatedCall (str, start, "array", state)
678 end
679 pos = npos
680 if cont == 'cont' or cont == 'last' then
681 nt = nt + 1
682 t[nt] = obj
683 end
684 until cont ~= 'cont'
685 return pos, setmetatable (t, state.arraymeta)
686 end
687
688 local function parseobject (str, pos, nullval, state)
689 local obj, key, cont
690 local start = pos
691 local npos
692 local t = {}
693 repeat
694 key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
695 if cont == 'end' then
696 return ErrorUnterminatedCall (str, start, "object", state)
697 end
698 pos = npos
699 if cont == 'cont' or cont == 'last' then
700 t[key] = obj
701 end
702 until cont ~= 'cont'
703 return pos, setmetatable (t, state.objectmeta)
704 end
705
706 local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray)
707 local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject)
708 local Value = Space * (Array + Object + SimpleValue)
709 local ExpectedValue = Value + Space * Err "value expected"
710 local ExpectedKey = String + Err "key expected"
711 local End = P(-1) * g.Cc'end'
712 local ErrInvalid = Err "invalid JSON"
713 ArrayContent = (Value * Space * (P"," * g.Cc'cont' + P"]" * g.Cc'last'+ End + ErrInvalid) + g.Cc(nil) * (P"]" * g.Cc'empty' + End + ErrInvalid)) * g.Cp()
714 local Pair = g.Cg (Space * ExpectedKey * Space * (P":" + Err "colon expected") * ExpectedValue)
715 ObjectContent = (g.Cc(nil) * g.Cc(nil) * P"}" * g.Cc'empty' + End + (Pair * Space * (P"," * g.Cc'cont' + P"}" * g.Cc'last' + End + ErrInvalid) + ErrInvalid)) * g.Cp()
716 local DecodeValue = ExpectedValue * g.Cp ()
717
718 jsonlpeg.version = json.version
719 jsonlpeg.encode = json.encode
720 jsonlpeg.null = json.null
721 jsonlpeg.quotestring = json.quotestring
722 jsonlpeg.addnewline = json.addnewline
723 jsonlpeg.encodeexception = json.encodeexception
724 jsonlpeg.using_lpeg = true
725
726 function jsonlpeg.decode (str, pos, nullval, ...)
727 local state = {}
728 state.objectmeta, state.arraymeta = optionalmetatables(...)
729 local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
730 if state.msg then
731 return nil, state.pos, state.msg
732 else
733 return obj, retpos
734 end
735 end
736
737 -- cache result of this function:
738 json.use_lpeg = function () return jsonlpeg end
739 jsonlpeg.use_lpeg = json.use_lpeg
740
741 return jsonlpeg
742end
743
744if always_use_lpeg then
745 return json.use_lpeg()
746end
747
748return json
749