@tilua-types/roblox 10.0.2 → 10.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -158,11 +158,11 @@ luau-lsp is MIT licensed. Its notice is in
158
158
  <!-- compatibility:start -->
159
159
  ## Compatibility
160
160
 
161
- `@tilua-types/roblox@10.0.2` works with these tilua packages. *Released with* is the
161
+ `@tilua-types/roblox@10.1.1` works with these tilua packages. *Released with* is the
162
162
  version it was built and tested against.
163
163
 
164
164
  | Package | Range | Released with | |
165
165
  | --- | --- | --- | --- |
166
- | `@tilua-types/luau` | `^9.0.2` | 9.0.2 | dependency |
167
- | `@tilua/parser` | `^9.0.0` | 9.0.2 | peer |
166
+ | `@tilua-types/luau` | `^9.1.1` | 9.1.1 | dependency |
167
+ | `@tilua/parser` | `^9.0.0` | 9.1.1 | peer |
168
168
  <!-- compatibility:end -->
package/index.d.tilua CHANGED
@@ -8465,7 +8465,7 @@ declare class SharedTable {
8465
8465
  [string | number]: any,
8466
8466
  __iter: (self: SharedTable) => ((arg1: any, arg2: number) => [number, any]),
8467
8467
  }
8468
- type RBXScriptSignal<T extends unknown[] = any[]> = { Wait: (self: RBXScriptSignal<T>) => T, Connect: (self: RBXScriptSignal<T>, callback: (...args: T) => void) => RBXScriptConnection, ConnectParallel: (self: RBXScriptSignal<T>, callback: (...args: T) => void) => RBXScriptConnection, Once: (self: RBXScriptSignal<T>, callback: (...args: T) => void) => RBXScriptConnection }
8468
+ type RBXScriptSignal<T extends unknown[] = any[]> = { Wait: (self: RBXScriptSignal<T>) => T extends [] ? void : T extends [infer V] ? V : any[] extends T ? any : T, Connect: (self: RBXScriptSignal<T>, callback: (...args: T) => void) => RBXScriptConnection, ConnectParallel: (self: RBXScriptSignal<T>, callback: (...args: T) => void) => RBXScriptConnection, Once: (self: RBXScriptSignal<T>, callback: (...args: T) => void) => RBXScriptConnection }
8469
8469
  type HttpRequestOptions = { Url: string, Method: "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE" | "PATCH" | nil, Headers?: { [string]: string | Secret }, Body?: string, Compress?: Enum.HttpCompression }
8470
8470
  type HttpResponseData = { Success: boolean, StatusCode: number, StatusMessage: string, Headers: { [string]: string }, Body?: string }
8471
8471
  type HumanoidDescriptionAccessory = { AssetId: number, AccessoryType: Enum.AccessoryType, IsLayered: boolean, Order?: number, Puffiness?: number }
package/lowering.mjs CHANGED
@@ -1,102 +1,102 @@
1
- // @ts-check
2
- /**
3
- * What tilua lowers for Roblox: callbacks that Roblox runs later, on a thread
4
- * of their own.
5
- *
6
- * The entry of a bundle runs under `xpcall`, which is what reports an error
7
- * in the project's files instead of in the bundle's lines. A callback given
8
- * to `task.spawn` or to `signal:Connect` is not under it when it runs, so its
9
- * callback is wrapped in the same handler first (see runtime/roblox.luau):
10
- *
11
- * part.Touched:Connect(onTouched) -> tilua_roblox.Connect(part.Touched, onTouched)
12
- * task.spawn(work, 1) -> tilua_roblox.task.spawn(work, 1)
13
- *
14
- * Only where Roblox starts a thread. A callback run synchronously — a
15
- * `table.sort` comparator, `names:map(f)` — has its caller's `xpcall` already.
16
- *
17
- * `HttpService:JSONEncode(value)` is the language's JSON (the runtime's
18
- * `JSONEncode`), which knows `[]` from `{}` where Roblox's sees two empty
19
- * tables; `JSONDecode` reads back the same.
20
- *
21
- * And where Luau answers several values, tilua has one — an array. A method
22
- * whose type returns a tuple (`workspace:FindPartOnRay(ray)`, `signal:Wait()`)
23
- * is called through the runtime's `values`, which gathers them, and so are the
24
- * Luau globals that answer several (`string.unpack`, `utf8.codepoint`,
25
- * `coroutine.close`).
26
- *
27
- * `print`, `error` and the console are the compiler's own: Lua's standard
28
- * library is built into tilua.
29
- *
30
- * With `"output": "files"`, where each file ends up in the game is read off
31
- * the project's Rojo project (`rojo`, `default.project.json` unless set), and
32
- * the files require each other by the paths the game knows them by (see
33
- * rojo.mjs).
34
- */
35
- import { readFileSync } from "node:fs"
36
- import { dirname, join } from "node:path"
37
- import { fileURLToPath } from "node:url"
38
- import { hasMembers, isInstanceOf, luauString, returnsTuple } from "@tilua/parser"
39
- import { rojoLayout } from "./rojo.mjs"
40
-
41
- /** @typedef {import("@tilua/parser").LoweringPlugin} LoweringPlugin */
42
-
43
- const here = dirname(fileURLToPath(import.meta.url))
44
- const roblox = readFileSync(join(here, "runtime", "roblox.luau"), "utf8")
45
-
46
- const SIGNAL_METHODS = new Set(["Connect", "Once", "ConnectParallel"])
47
-
48
- /** A service's methods that keep a callback, by the class that has them. */
49
- const SERVICE_METHODS = new Map([
50
- ["BindToRenderStep", "RunService"],
51
- ["BindToClose", "DataModel"],
52
- ["BindAction", "ContextActionService"],
53
- ["BindActionAtPriority", "ContextActionService"],
54
- ])
55
-
56
- /** HttpService's JSON, which the runtime writes and reads as tilua's values
57
- * are: `[]` an array and `{}` an object, empty or not. */
58
- const JSON_METHODS = new Set(["JSONEncode", "JSONDecode", "JSONEncodeAsync", "JSONDecodeAsync"])
59
-
60
- /** `RBXScriptSignal<T...>` is structural once it is resolved: an object that
61
- * can be connected to and waited on. */
62
- const SIGNAL_MEMBERS = ["Connect", "Once", "Wait"]
63
-
64
- /** Luau globals answering several values, which the runtime gathers. */
65
- const GATHERED_GLOBALS = new Set(["string.unpack", "coroutine.close", "utf8.codepoint", "utf8.codes", "utf8.graphemes"])
66
-
67
- /** The Rojo project a config that names none has. */
68
- const DEFAULT_PROJECT = "default.project.json"
69
-
70
- /** @type {LoweringPlugin} */
71
- const plugin = {
72
- runtime: { roblox },
73
-
74
- methodCall({ method, receiver, use }) {
75
- if (SIGNAL_METHODS.has(method) && hasMembers(receiver, SIGNAL_MEMBERS)) return { callee: `${use("roblox")}.${method}` }
76
- if (JSON_METHODS.has(method) && isInstanceOf(receiver, "HttpService")) return { callee: `${use("roblox")}.${method}` }
77
- const service = SERVICE_METHODS.get(method)
78
- if (service !== undefined && isInstanceOf(receiver, service)) return { callee: `${use("roblox")}.${method}` }
79
- if (returnsTuple(receiver, method)) {
80
- return { callee: `${use("roblox")}.values`, prepend: [luauString(method)] }
81
- }
82
- return undefined
83
- },
84
-
85
- globalCall({ name, use }) {
86
- if (GATHERED_GLOBALS.has(name)) return { callee: `${use("roblox")}.globals.${name.replace(".", "_")}` }
87
- return undefined
88
- },
89
-
90
- globalValue({ name, use }) {
91
- if (name === "task") return `${use("roblox")}.task`
92
- if (GATHERED_GLOBALS.has(name)) return `${use("roblox")}.globals.${name.replace(".", "_")}`
93
- return undefined
94
- },
95
-
96
- files(layout) {
97
- const project = typeof layout.options.rojo === "string" ? layout.options.rojo : DEFAULT_PROJECT
98
- return rojoLayout(layout, project, project !== DEFAULT_PROJECT)
99
- },
100
- }
101
-
102
- export default plugin
1
+ // @ts-check
2
+ /**
3
+ * What tilua lowers for Roblox: callbacks that Roblox runs later, on a thread
4
+ * of their own.
5
+ *
6
+ * The entry of a bundle runs under `xpcall`, which is what reports an error
7
+ * in the project's files instead of in the bundle's lines. A callback given
8
+ * to `task.spawn` or to `signal:Connect` is not under it when it runs, so its
9
+ * callback is wrapped in the same handler first (see runtime/roblox.luau):
10
+ *
11
+ * part.Touched:Connect(onTouched) -> tilua_roblox.Connect(part.Touched, onTouched)
12
+ * task.spawn(work, 1) -> tilua_roblox.task.spawn(work, 1)
13
+ *
14
+ * Only where Roblox starts a thread. A callback run synchronously — a
15
+ * `table.sort` comparator, `names:map(f)` — has its caller's `xpcall` already.
16
+ *
17
+ * `HttpService:JSONEncode(value)` is the language's JSON (the runtime's
18
+ * `JSONEncode`), which knows `[]` from `{}` where Roblox's sees two empty
19
+ * tables; `JSONDecode` reads back the same.
20
+ *
21
+ * And where Luau answers several values, tilua has one — an array. A method
22
+ * whose type returns a tuple (`workspace:FindPartOnRay(ray)`, `cf:ToOrientation()`)
23
+ * is called through the runtime's `values`, which gathers them, and so are the
24
+ * Luau globals that answer several (`string.unpack`, `utf8.codepoint`,
25
+ * `coroutine.close`).
26
+ *
27
+ * `print`, `error` and the console are the compiler's own: Lua's standard
28
+ * library is built into tilua.
29
+ *
30
+ * With `"output": "files"`, where each file ends up in the game is read off
31
+ * the project's Rojo project (`rojo`, `default.project.json` unless set), and
32
+ * the files require each other by the paths the game knows them by (see
33
+ * rojo.mjs).
34
+ */
35
+ import { readFileSync } from "node:fs"
36
+ import { dirname, join } from "node:path"
37
+ import { fileURLToPath } from "node:url"
38
+ import { hasMembers, isInstanceOf, luauString, returnsTuple } from "@tilua/parser"
39
+ import { rojoLayout } from "./rojo.mjs"
40
+
41
+ /** @typedef {import("@tilua/parser").LoweringPlugin} LoweringPlugin */
42
+
43
+ const here = dirname(fileURLToPath(import.meta.url))
44
+ const roblox = readFileSync(join(here, "runtime", "roblox.luau"), "utf8")
45
+
46
+ const SIGNAL_METHODS = new Set(["Connect", "Once", "ConnectParallel"])
47
+
48
+ /** A service's methods that keep a callback, by the class that has them. */
49
+ const SERVICE_METHODS = new Map([
50
+ ["BindToRenderStep", "RunService"],
51
+ ["BindToClose", "DataModel"],
52
+ ["BindAction", "ContextActionService"],
53
+ ["BindActionAtPriority", "ContextActionService"],
54
+ ])
55
+
56
+ /** HttpService's JSON, which the runtime writes and reads as tilua's values
57
+ * are: `[]` an array and `{}` an object, empty or not. */
58
+ const JSON_METHODS = new Set(["JSONEncode", "JSONDecode", "JSONEncodeAsync", "JSONDecodeAsync"])
59
+
60
+ /** `RBXScriptSignal<T...>` is structural once it is resolved: an object that
61
+ * can be connected to and waited on. */
62
+ const SIGNAL_MEMBERS = ["Connect", "Once", "Wait"]
63
+
64
+ /** Luau globals answering several values, which the runtime gathers. */
65
+ const GATHERED_GLOBALS = new Set(["string.unpack", "coroutine.close", "utf8.codepoint", "utf8.codes", "utf8.graphemes"])
66
+
67
+ /** The Rojo project a config that names none has. */
68
+ const DEFAULT_PROJECT = "default.project.json"
69
+
70
+ /** @type {LoweringPlugin} */
71
+ const plugin = {
72
+ runtime: { roblox },
73
+
74
+ methodCall({ method, receiver, use }) {
75
+ if (SIGNAL_METHODS.has(method) && hasMembers(receiver, SIGNAL_MEMBERS)) return { callee: `${use("roblox")}.${method}` }
76
+ if (JSON_METHODS.has(method) && isInstanceOf(receiver, "HttpService")) return { callee: `${use("roblox")}.${method}` }
77
+ const service = SERVICE_METHODS.get(method)
78
+ if (service !== undefined && isInstanceOf(receiver, service)) return { callee: `${use("roblox")}.${method}` }
79
+ if (returnsTuple(receiver, method)) {
80
+ return { callee: `${use("roblox")}.values`, prepend: [luauString(method)] }
81
+ }
82
+ return undefined
83
+ },
84
+
85
+ globalCall({ name, use }) {
86
+ if (GATHERED_GLOBALS.has(name)) return { callee: `${use("roblox")}.globals.${name.replace(".", "_")}` }
87
+ return undefined
88
+ },
89
+
90
+ globalValue({ name, use }) {
91
+ if (name === "task") return `${use("roblox")}.task`
92
+ if (GATHERED_GLOBALS.has(name)) return `${use("roblox")}.globals.${name.replace(".", "_")}`
93
+ return undefined
94
+ },
95
+
96
+ files(layout) {
97
+ const project = typeof layout.options.rojo === "string" ? layout.options.rojo : DEFAULT_PROJECT
98
+ return rojoLayout(layout, project, project !== DEFAULT_PROJECT)
99
+ },
100
+ }
101
+
102
+ export default plugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tilua-types/roblox",
3
- "version": "10.0.2",
3
+ "version": "10.1.1",
4
4
  "description": "Luau and the whole Roblox API as tilua types: every class, enum, data type, service and global",
5
5
  "license": "ISC",
6
6
  "files": [
@@ -23,7 +23,7 @@
23
23
  }
24
24
  },
25
25
  "dependencies": {
26
- "@tilua-types/luau": "^9.0.2"
26
+ "@tilua-types/luau": "^9.1.1"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "@tilua/parser": "^9.0.0"
@@ -1,139 +1,139 @@
1
- -- Callbacks Roblox runs on a thread of their own, reported in the project's
2
- -- files.
3
- --
4
- -- A bundle's entry runs under `xpcall`, so an error nothing caught names the
5
- -- file and line the author wrote. A callback handed to `task.spawn` or to
6
- -- `signal:Connect` runs later, on a new thread, where that `xpcall` is not on
7
- -- the stack: its errors would name lines of the bundle. `guard` gives such a
8
- -- callback the same handler — `__FAIL__`, the bundle's own — and raises what
9
- -- it returns on the callback's thread, where Roblox reports it as before.
10
- --
11
- -- The error is raised again, never swallowed: code that relied on it
12
- -- stopping the callback still sees it stop. Outside a bundle there is nothing
13
- -- to map, and callbacks are passed through untouched.
14
-
15
- local __NAME__ = {}
16
- local fail = __FAIL__
17
-
18
- local function guard(callback)
19
- if type(callback) ~= "function" or fail == nil then
20
- return callback
21
- end
22
- return function(...)
23
- local results = table.pack(xpcall(callback, fail, ...))
24
- if not results[1] then
25
- error(results[2], 0)
26
- end
27
- return table.unpack(results, 2, results.n)
28
- end
29
- end
30
-
31
- __NAME__.guard = guard
32
-
33
- -- `signal:Connect(f)` and its siblings.
34
- function __NAME__.Connect(signal, callback)
35
- return signal:Connect(guard(callback))
36
- end
37
-
38
- function __NAME__.Once(signal, callback)
39
- return signal:Once(guard(callback))
40
- end
41
-
42
- function __NAME__.ConnectParallel(signal, callback)
43
- return signal:ConnectParallel(guard(callback))
44
- end
45
-
46
- -- Services that keep a callback and run it later.
47
- function __NAME__.BindToRenderStep(service, name, priority, callback)
48
- return service:BindToRenderStep(name, priority, guard(callback))
49
- end
50
-
51
- function __NAME__.BindToClose(game, callback)
52
- return game:BindToClose(guard(callback))
53
- end
54
-
55
- function __NAME__.BindAction(service, name, callback, ...)
56
- return service:BindAction(name, guard(callback), ...)
57
- end
58
-
59
- function __NAME__.BindActionAtPriority(service, name, callback, ...)
60
- return service:BindActionAtPriority(name, guard(callback), ...)
61
- end
62
-
63
- -- `task`, with the functions that start a thread guarding what they start.
64
- -- A thread passed instead of a function is left as it is. Everything else is
65
- -- `task`'s own. Standing in for the global, rather than rewriting calls,
66
- -- covers `local spawn = task.spawn` too.
67
- __NAME__.task = setmetatable({
68
- spawn = function(target, ...)
69
- return task.spawn(guard(target), ...)
70
- end,
71
- defer = function(target, ...)
72
- return task.defer(guard(target), ...)
73
- end,
74
- delay = function(duration, target, ...)
75
- return task.delay(duration, guard(target), ...)
76
- end,
77
- }, { __index = task })
78
-
79
- -- HttpService's JSON, as tilua's values have it: `[]` is an array and `{}`
80
- -- an object, empty or not — `JSONEncode` alone writes every empty table as
81
- -- `[]` — and what `JSONDecode` reads back is the same. `__JSON__` is the
82
- -- language's; the Async ones yield nothing here, the work being the same.
83
- function __NAME__.JSONEncode(_, value)
84
- return __JSON__.encode(value)
85
- end
86
-
87
- function __NAME__.JSONDecode(_, text)
88
- return __JSON__.decode(text)
89
- end
90
-
91
- __NAME__.JSONEncodeAsync = __NAME__.JSONEncode
92
- __NAME__.JSONDecodeAsync = __NAME__.JSONDecode
93
-
94
- -- Where Luau answers several values, tilua has one: an array. A Roblox method
95
- -- whose type says it returns several — `workspace:FindPartOnRay(ray)`,
96
- -- `signal:Wait()`, `cf:ToOrientation()` — is called through this, which
97
- -- hands its values back as that array.
98
- function __NAME__.values(name, receiver, ...)
99
- return { receiver[name](receiver, ...) }
100
- end
101
-
102
- -- The same for Luau's own globals that answer several values.
103
- __NAME__.globals = {
104
- string_unpack = function(...)
105
- return { string.unpack(...) }
106
- end,
107
- coroutine_close = function(co)
108
- return { coroutine.close(co) }
109
- end,
110
- utf8_codepoint = function(...)
111
- return { utf8.codepoint(...) }
112
- end,
113
- -- An iteration — `{ step, state, first }` — whose steps answer one
114
- -- `{ position, code }` each, as a loop walks them.
115
- utf8_codes = function(s)
116
- local step, state, first = utf8.codes(s)
117
- return {
118
- function(_, previous)
119
- local position, code = step(state, previous ~= nil and previous[1] or first)
120
- if position == nil then
121
- return nil
122
- end
123
- return { position, code }
124
- end,
125
- s,
126
- nil,
127
- }
128
- end,
129
- utf8_graphemes = function(s, i, j)
130
- local step = utf8.graphemes(s, i, j)
131
- return function()
132
- local first, last = step()
133
- if first == nil then
134
- return nil
135
- end
136
- return { first, last }
137
- end
138
- end,
139
- }
1
+ -- Callbacks Roblox runs on a thread of their own, reported in the project's
2
+ -- files.
3
+ --
4
+ -- A bundle's entry runs under `xpcall`, so an error nothing caught names the
5
+ -- file and line the author wrote. A callback handed to `task.spawn` or to
6
+ -- `signal:Connect` runs later, on a new thread, where that `xpcall` is not on
7
+ -- the stack: its errors would name lines of the bundle. `guard` gives such a
8
+ -- callback the same handler — `__FAIL__`, the bundle's own — and raises what
9
+ -- it returns on the callback's thread, where Roblox reports it as before.
10
+ --
11
+ -- The error is raised again, never swallowed: code that relied on it
12
+ -- stopping the callback still sees it stop. Outside a bundle there is nothing
13
+ -- to map, and callbacks are passed through untouched.
14
+
15
+ local __NAME__ = {}
16
+ local fail = __FAIL__
17
+
18
+ local function guard(callback)
19
+ if type(callback) ~= "function" or fail == nil then
20
+ return callback
21
+ end
22
+ return function(...)
23
+ local results = table.pack(xpcall(callback, fail, ...))
24
+ if not results[1] then
25
+ error(results[2], 0)
26
+ end
27
+ return table.unpack(results, 2, results.n)
28
+ end
29
+ end
30
+
31
+ __NAME__.guard = guard
32
+
33
+ -- `signal:Connect(f)` and its siblings.
34
+ function __NAME__.Connect(signal, callback)
35
+ return signal:Connect(guard(callback))
36
+ end
37
+
38
+ function __NAME__.Once(signal, callback)
39
+ return signal:Once(guard(callback))
40
+ end
41
+
42
+ function __NAME__.ConnectParallel(signal, callback)
43
+ return signal:ConnectParallel(guard(callback))
44
+ end
45
+
46
+ -- Services that keep a callback and run it later.
47
+ function __NAME__.BindToRenderStep(service, name, priority, callback)
48
+ return service:BindToRenderStep(name, priority, guard(callback))
49
+ end
50
+
51
+ function __NAME__.BindToClose(game, callback)
52
+ return game:BindToClose(guard(callback))
53
+ end
54
+
55
+ function __NAME__.BindAction(service, name, callback, ...)
56
+ return service:BindAction(name, guard(callback), ...)
57
+ end
58
+
59
+ function __NAME__.BindActionAtPriority(service, name, callback, ...)
60
+ return service:BindActionAtPriority(name, guard(callback), ...)
61
+ end
62
+
63
+ -- `task`, with the functions that start a thread guarding what they start.
64
+ -- A thread passed instead of a function is left as it is. Everything else is
65
+ -- `task`'s own. Standing in for the global, rather than rewriting calls,
66
+ -- covers `local spawn = task.spawn` too.
67
+ __NAME__.task = setmetatable({
68
+ spawn = function(target, ...)
69
+ return task.spawn(guard(target), ...)
70
+ end,
71
+ defer = function(target, ...)
72
+ return task.defer(guard(target), ...)
73
+ end,
74
+ delay = function(duration, target, ...)
75
+ return task.delay(duration, guard(target), ...)
76
+ end,
77
+ }, { __index = task })
78
+
79
+ -- HttpService's JSON, as tilua's values have it: `[]` is an array and `{}`
80
+ -- an object, empty or not — `JSONEncode` alone writes every empty table as
81
+ -- `[]` — and what `JSONDecode` reads back is the same. `__JSON__` is the
82
+ -- language's; the Async ones yield nothing here, the work being the same.
83
+ function __NAME__.JSONEncode(_, value)
84
+ return __JSON__.encode(value)
85
+ end
86
+
87
+ function __NAME__.JSONDecode(_, text)
88
+ return __JSON__.decode(text)
89
+ end
90
+
91
+ __NAME__.JSONEncodeAsync = __NAME__.JSONEncode
92
+ __NAME__.JSONDecodeAsync = __NAME__.JSONDecode
93
+
94
+ -- Where Luau answers several values, tilua has one: an array. A Roblox method
95
+ -- whose type says it returns several — `workspace:FindPartOnRay(ray)`,
96
+ -- `cf:ToOrientation()`, a signal with several arguments — is called through
97
+ -- this, which hands its values back as that array.
98
+ function __NAME__.values(name, receiver, ...)
99
+ return { receiver[name](receiver, ...) }
100
+ end
101
+
102
+ -- The same for Luau's own globals that answer several values.
103
+ __NAME__.globals = {
104
+ string_unpack = function(...)
105
+ return { string.unpack(...) }
106
+ end,
107
+ coroutine_close = function(co)
108
+ return { coroutine.close(co) }
109
+ end,
110
+ utf8_codepoint = function(...)
111
+ return { utf8.codepoint(...) }
112
+ end,
113
+ -- An iteration — `{ step, state, first }` — whose steps answer one
114
+ -- `{ position, code }` each, as a loop walks them.
115
+ utf8_codes = function(s)
116
+ local step, state, first = utf8.codes(s)
117
+ return {
118
+ function(_, previous)
119
+ local position, code = step(state, previous ~= nil and previous[1] or first)
120
+ if position == nil then
121
+ return nil
122
+ end
123
+ return { position, code }
124
+ end,
125
+ s,
126
+ nil,
127
+ }
128
+ end,
129
+ utf8_graphemes = function(s, i, j)
130
+ local step = utf8.graphemes(s, i, j)
131
+ return function()
132
+ local first, last = step()
133
+ if first == nil then
134
+ return nil
135
+ end
136
+ return { first, last }
137
+ end
138
+ end,
139
+ }