@px-petals/roku.motion-web 1.0.0 → 1.1.0

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.
Files changed (77) hide show
  1. package/bands/bands.js +3 -1
  2. package/bundle/bundle.js +56 -0
  3. package/list/list.js +12 -1
  4. package/lottie/lottie.layers.js +64 -11
  5. package/lottie/lottie.paint.js +30 -0
  6. package/lottie/lottie.paths.js +19 -4
  7. package/lottie/lottie.props.js +18 -8
  8. package/lottie/lottie.shapes.js +2 -1
  9. package/mixer/mixer.js +31 -8
  10. package/mixer/mixer.mix.js +17 -0
  11. package/motion/motion.convert.js +32 -1
  12. package/motion/motion.flow.js +20 -2
  13. package/motion/motion.machine.js +28 -13
  14. package/motion/motion.values.js +13 -9
  15. package/motion.js +196 -20
  16. package/package.json +10 -16
  17. package/player/player.js +25 -5
  18. package/readme.md +78 -3
  19. package/script/luau/lib/base/base.js +202 -0
  20. package/script/luau/lib/base/base.table.js +189 -0
  21. package/script/luau/lib/lib.args.js +152 -0
  22. package/script/luau/lib/lib.js +23 -0
  23. package/script/luau/lib/math/math.bit32.js +144 -0
  24. package/script/luau/lib/math/math.js +288 -0
  25. package/script/luau/lib/math/math.vector.js +125 -0
  26. package/script/luau/lib/string/string.format.js +143 -0
  27. package/script/luau/lib/string/string.js +160 -0
  28. package/script/luau/lib/string/string.pattern.js +505 -0
  29. package/script/luau/load/load.js +362 -0
  30. package/script/luau/load/load.ops.js +79 -0
  31. package/script/luau/luau.convert.js +259 -0
  32. package/script/luau/luau.js +132 -0
  33. package/script/luau/luau.md +111 -0
  34. package/script/luau/vm/vm.js +167 -0
  35. package/script/luau/vm/vm.meta.js +401 -0
  36. package/script/luau/vm/vm.num.js +236 -0
  37. package/script/luau/vm/vm.run.js +842 -0
  38. package/script/luau/vm/vm.value.js +406 -0
  39. package/script/play/play.js +199 -0
  40. package/script/play/play.objects.js +295 -0
  41. package/script/play/play.scope.js +91 -0
  42. package/script/rive/api/api.statics.js +31 -0
  43. package/script/rive/api/api.type.js +188 -0
  44. package/script/rive/api/artboard/artboard.js +89 -0
  45. package/script/rive/api/assets/assets.js +72 -0
  46. package/script/rive/api/data/data.value.js +99 -0
  47. package/script/rive/api/draw/draw.contours.js +163 -0
  48. package/script/rive/api/draw/draw.measure.js +196 -0
  49. package/script/rive/api/draw/draw.paint.js +235 -0
  50. package/script/rive/api/draw/draw.path.js +153 -0
  51. package/script/rive/api/draw/draw.renderer.js +117 -0
  52. package/script/rive/api/math/math.color.js +96 -0
  53. package/script/rive/api/math/math.mat2d.js +180 -0
  54. package/script/rive/api/math/math.vector.js +121 -0
  55. package/script/rive/api/media/media.audio.js +113 -0
  56. package/script/rive/api/media/media.draw.js +73 -0
  57. package/script/rive/api/media/media.image.js +61 -0
  58. package/script/rive/api/media/media.mesh.js +50 -0
  59. package/script/rive/api/pointer/pointer.event.js +64 -0
  60. package/script/rive/api/pointer/pointer.route.js +69 -0
  61. package/script/rive/api/text/text.font.js +129 -0
  62. package/script/rive/api/text/text.js +278 -0
  63. package/script/rive/api/text/text.layout.js +259 -0
  64. package/script/rive/context/context.js +76 -0
  65. package/script/rive/context/context.memory.js +196 -0
  66. package/script/rive/context/context.viewmodel.js +302 -0
  67. package/script/rive/context/lists/lists.js +160 -0
  68. package/script/rive/context/lists/lists.values.js +281 -0
  69. package/script/rive/host/host.globals.js +44 -0
  70. package/script/rive/host/host.js +533 -0
  71. package/script/rive/host/host.node.js +45 -0
  72. package/script/rive/rive.js +23 -0
  73. package/script/rive/rive.md +180 -0
  74. package/script/script.js +11 -0
  75. package/skin/skin.deform.js +514 -0
  76. package/skin/skin.ik.js +260 -0
  77. package/skin/skin.js +297 -0
@@ -0,0 +1,401 @@
1
+ /**
2
+ * What Lua does to values beyond the plain case: indexing through
3
+ * `__index` and `__newindex`, arithmetic and comparison through their
4
+ * metamethods, string coercion, concatenation, length, `tostring`. The
5
+ * interpreter takes the fast paths itself and calls these for the rest.
6
+ */
7
+
8
+ import { fail, LuaClosure, LuaTable, NativeFunction, Vector, typeName } from './vm.value.js'
9
+ import { mod, numberToString, stringToNumber } from './vm.num.js'
10
+
11
+ /** @typedef {import('./vm.js').VM} VM */
12
+
13
+ // Metatables
14
+ // ■■■■■■■■■■
15
+
16
+ /**
17
+ * @param {VM} vm
18
+ * @param {any} v
19
+ * @returns {LuaTable | undefined} v's metatable: a table's own, the shared one for strings and vectors
20
+ */
21
+ export function metatableOf(vm, v) {
22
+ if (v instanceof LuaTable) return v.meta
23
+ if (typeof v === 'string') return vm.stringMeta
24
+ if (v instanceof Vector) return vm.vectorMeta
25
+ return undefined
26
+ }
27
+
28
+ /**
29
+ * @param {VM} vm
30
+ * @param {any} v
31
+ * @param {string} event such as `__add`
32
+ * @returns {any} the metamethod, or undefined
33
+ */
34
+ export function metamethod(vm, v, event) {
35
+ const mt = metatableOf(vm, v)
36
+ return mt === undefined ? undefined : mt.getStr(event)
37
+ }
38
+
39
+ /**
40
+ * @param {any} v
41
+ * @returns {boolean}
42
+ */
43
+ export function isFunction(v) {
44
+ return v instanceof LuaClosure || v instanceof NativeFunction
45
+ }
46
+
47
+ /**
48
+ * @param {VM} vm
49
+ * @param {any} fn
50
+ * @param {any[]} args
51
+ * @returns {any} the first result
52
+ */
53
+ function call1(vm, fn, args) {
54
+ return vm.call(fn, args)[0]
55
+ }
56
+
57
+ // Indexing
58
+ // ■■■■■■■■
59
+
60
+ /**
61
+ * `obj[key]`, with `__index`.
62
+ *
63
+ * @param {VM} vm
64
+ * @param {any} obj
65
+ * @param {any} key
66
+ * @returns {any}
67
+ */
68
+ export function index(vm, obj, key) {
69
+ let o = obj
70
+ for (let loop = 0; loop < 100; loop++) {
71
+ let handler
72
+ if (o instanceof LuaTable) {
73
+ const v = o.get(key)
74
+ if (v !== undefined) return v
75
+ const mt = o.meta
76
+ if (mt === undefined) return undefined
77
+ handler = mt.getStr('__index')
78
+ if (handler === undefined) return undefined
79
+ } else {
80
+ if (o instanceof Vector && typeof key === 'string') {
81
+ const c = vectorField(o, key)
82
+ if (c !== undefined) return c
83
+ }
84
+ handler = metamethod(vm, o, '__index')
85
+ if (handler === undefined) throw indexError(o, key)
86
+ }
87
+ if (isFunction(handler)) return call1(vm, handler, [ o, key ])
88
+ o = handler
89
+ }
90
+ throw fail("'__index' chain too long; possible loop")
91
+ }
92
+
93
+ /**
94
+ * @param {Vector} v
95
+ * @param {string} key
96
+ * @returns {number | undefined}
97
+ */
98
+ function vectorField(v, key) {
99
+ if (key === 'x' || key === 'X') return v.x
100
+ if (key === 'y' || key === 'Y') return v.y
101
+ if (key === 'z' || key === 'Z') return v.z
102
+ return undefined
103
+ }
104
+
105
+ /**
106
+ * @param {any} o
107
+ * @param {any} key
108
+ */
109
+ function indexError(o, key) {
110
+ return fail('attempt to index ' + typeName(o) + (typeof key === 'string' ? " with '" + key + "'" : ''))
111
+ }
112
+
113
+ /**
114
+ * `t[key] = value` on the table itself, no metatable.
115
+ *
116
+ * @param {LuaTable} t
117
+ * @param {any} key
118
+ * @param {any} value
119
+ */
120
+ export function rawSet(t, key, value) {
121
+ if (t.frozen) throw fail('attempt to modify a readonly table')
122
+ if (key === undefined) throw fail('table index is nil')
123
+ if (key !== key) throw fail('table index is NaN')
124
+ t.set(key, value)
125
+ }
126
+
127
+ /**
128
+ * `obj[key] = value`, with `__newindex`.
129
+ *
130
+ * @param {VM} vm
131
+ * @param {any} obj
132
+ * @param {any} key
133
+ * @param {any} value
134
+ */
135
+ export function setIndex(vm, obj, key, value) {
136
+ let o = obj
137
+ for (let loop = 0; loop < 100; loop++) {
138
+ let handler
139
+ if (o instanceof LuaTable) {
140
+ const mt = o.meta
141
+ if (mt === undefined || o.get(key) !== undefined) return rawSet(o, key, value)
142
+ handler = mt.getStr('__newindex')
143
+ if (handler === undefined) return rawSet(o, key, value)
144
+ } else {
145
+ handler = metamethod(vm, o, '__newindex')
146
+ if (handler === undefined) throw indexError(o, key)
147
+ }
148
+ if (isFunction(handler)) {
149
+ vm.call(handler, [ o, key, value ])
150
+ return
151
+ }
152
+ o = handler
153
+ }
154
+ throw fail("'__newindex' chain too long; possible loop")
155
+ }
156
+
157
+ // Arithmetic
158
+ // ■■■■■■■■■■
159
+
160
+ /** The event name of each arithmetic operator */
161
+ const EVENTS = { add: '__add', sub: '__sub', mul: '__mul', div: '__div', mod: '__mod', pow: '__pow', idiv: '__idiv', unm: '__unm' }
162
+
163
+ /**
164
+ * @param {any} v
165
+ * @returns {number | undefined} v as a number: itself, or a numeric string
166
+ */
167
+ export function toNumber(v) {
168
+ if (typeof v === 'number') return v
169
+ if (typeof v === 'string') return stringToNumber(v)
170
+ return undefined
171
+ }
172
+
173
+ /**
174
+ * @param {string} op
175
+ * @param {number} a
176
+ * @param {number} b
177
+ * @returns {number}
178
+ */
179
+ function arithNumbers(op, a, b) {
180
+ switch (op) {
181
+ case 'add': return a + b
182
+ case 'sub': return a - b
183
+ case 'mul': return a * b
184
+ case 'div': return a / b
185
+ case 'mod': return mod(a, b)
186
+ case 'pow': return Math.pow(a, b)
187
+ default: return Math.floor(a / b)
188
+ }
189
+ }
190
+
191
+ /**
192
+ * @param {string} op
193
+ * @param {Vector} a
194
+ * @param {Vector} b
195
+ * @returns {Vector}
196
+ */
197
+ function arithVectors(op, a, b) {
198
+ return new Vector(arithNumbers(op, a.x, b.x), arithNumbers(op, a.y, b.y), arithNumbers(op, a.z, b.z))
199
+ }
200
+
201
+ /**
202
+ * `a op b` for `add`, `sub`, `mul`, `div`, `mod`, `pow` and `idiv`.
203
+ *
204
+ * @param {VM} vm
205
+ * @param {string} op
206
+ * @param {any} a
207
+ * @param {any} b
208
+ * @returns {any}
209
+ */
210
+ export function arith(vm, op, a, b) {
211
+ const x = toNumber(a)
212
+ const y = toNumber(b)
213
+ if (x !== undefined && y !== undefined) return arithNumbers(op, x, y)
214
+ if (a instanceof Vector) {
215
+ if (b instanceof Vector && op !== 'pow' && op !== 'mod') return arithVectors(op, a, b)
216
+ if (y !== undefined && (op === 'mul' || op === 'div' || op === 'idiv')) return arithVectors(op, a, new Vector(y, y, y))
217
+ } else if (b instanceof Vector && x !== undefined && (op === 'mul' || op === 'div' || op === 'idiv')) {
218
+ return arithVectors(op, new Vector(x, x, x), b)
219
+ }
220
+ const event = /** @type {Record<string, string>} */ (EVENTS)[op]
221
+ let handler = metamethod(vm, a, event)
222
+ if (handler === undefined) handler = metamethod(vm, b, event)
223
+ if (handler !== undefined) return call1(vm, handler, [ a, b ])
224
+ const ta = typeName(a)
225
+ const tb = typeName(b)
226
+ throw fail('attempt to perform arithmetic (' + op + ') on ' + (ta === tb ? ta : ta + ' and ' + tb))
227
+ }
228
+
229
+ /**
230
+ * @param {VM} vm
231
+ * @param {any} a
232
+ * @returns {any} -a
233
+ */
234
+ export function unm(vm, a) {
235
+ const x = toNumber(a)
236
+ if (x !== undefined) return -x
237
+ if (a instanceof Vector) return new Vector(-a.x, -a.y, -a.z)
238
+ const handler = metamethod(vm, a, '__unm')
239
+ if (handler !== undefined) return call1(vm, handler, [ a, a ])
240
+ throw fail('attempt to perform arithmetic (unm) on ' + typeName(a))
241
+ }
242
+
243
+ // Comparison
244
+ // ■■■■■■■■■■
245
+
246
+ /**
247
+ * Lua's `==`.
248
+ *
249
+ * @param {VM} vm
250
+ * @param {any} a
251
+ * @param {any} b
252
+ * @returns {boolean}
253
+ */
254
+ export function equals(vm, a, b) {
255
+ if (a === b) return true
256
+ if (a instanceof LuaTable && b instanceof LuaTable) {
257
+ const h1 = a.meta === undefined ? undefined : a.meta.getStr('__eq')
258
+ if (h1 === undefined) return false
259
+ const h2 = b.meta === undefined ? undefined : b.meta.getStr('__eq')
260
+ if (h1 !== h2) return false
261
+ return truthy(call1(vm, h1, [ a, b ]))
262
+ }
263
+ if (a instanceof Vector && b instanceof Vector) return a.x === b.x && a.y === b.y && a.z === b.z
264
+ return false
265
+ }
266
+
267
+ /**
268
+ * @param {any} v
269
+ * @returns {boolean}
270
+ */
271
+ export function truthy(v) {
272
+ return v !== undefined && v !== false
273
+ }
274
+
275
+ /**
276
+ * @param {any} a
277
+ * @param {any} b
278
+ * @param {string} op '<' or '<='
279
+ */
280
+ function compareError(a, b, op) {
281
+ return fail('attempt to compare ' + typeName(a) + ' ' + op + ' ' + typeName(b))
282
+ }
283
+
284
+ /**
285
+ * `a < b`.
286
+ *
287
+ * @param {VM} vm
288
+ * @param {any} a
289
+ * @param {any} b
290
+ * @returns {boolean}
291
+ */
292
+ export function lessThan(vm, a, b) {
293
+ const ta = typeof a
294
+ if (ta === typeof b && (ta === 'number' || ta === 'string')) return a < b
295
+ let handler = metamethod(vm, a, '__lt')
296
+ if (handler === undefined) handler = metamethod(vm, b, '__lt')
297
+ if (handler === undefined) throw compareError(a, b, '<')
298
+ return truthy(call1(vm, handler, [ a, b ]))
299
+ }
300
+
301
+ /**
302
+ * `a <= b`.
303
+ *
304
+ * @param {VM} vm
305
+ * @param {any} a
306
+ * @param {any} b
307
+ * @returns {boolean}
308
+ */
309
+ export function lessEqual(vm, a, b) {
310
+ const ta = typeof a
311
+ if (ta === typeof b && (ta === 'number' || ta === 'string')) return a <= b
312
+ let handler = metamethod(vm, a, '__le')
313
+ if (handler === undefined) handler = metamethod(vm, b, '__le')
314
+ if (handler === undefined) throw compareError(a, b, '<=')
315
+ return truthy(call1(vm, handler, [ a, b ]))
316
+ }
317
+
318
+ // Strings
319
+ // ■■■■■■■
320
+
321
+ /**
322
+ * `a .. b`.
323
+ *
324
+ * @param {VM} vm
325
+ * @param {any} a
326
+ * @param {any} b
327
+ * @returns {any}
328
+ */
329
+ export function concat(vm, a, b) {
330
+ const ta = typeof a
331
+ const tb = typeof b
332
+ if ((ta === 'string' || ta === 'number') && (tb === 'string' || tb === 'number')) {
333
+ return (ta === 'number' ? numberToString(a) : a) + (tb === 'number' ? numberToString(b) : b)
334
+ }
335
+ let handler = metamethod(vm, a, '__concat')
336
+ if (handler === undefined) handler = metamethod(vm, b, '__concat')
337
+ if (handler !== undefined) return call1(vm, handler, [ a, b ])
338
+ throw fail('attempt to concatenate ' + typeName(a) + ' with ' + typeName(b))
339
+ }
340
+
341
+ /**
342
+ * `#v`.
343
+ *
344
+ * @param {VM} vm
345
+ * @param {any} v
346
+ * @returns {any}
347
+ */
348
+ export function length(vm, v) {
349
+ if (typeof v === 'string') return v.length
350
+ if (v instanceof LuaTable) {
351
+ const handler = v.meta === undefined ? undefined : v.meta.getStr('__len')
352
+ return handler === undefined ? v.length() : call1(vm, handler, [ v ])
353
+ }
354
+ const handler = metamethod(vm, v, '__len')
355
+ if (handler !== undefined) return call1(vm, handler, [ v ])
356
+ throw fail('attempt to get length of a ' + typeName(v) + ' value')
357
+ }
358
+
359
+ /**
360
+ * @param {number} id
361
+ * @returns {string} a stable fake address for an object
362
+ */
363
+ function address(id) {
364
+ let hex = id.toString(16)
365
+ while (hex.length < 8) hex = '0' + hex
366
+ return '0x' + hex
367
+ }
368
+
369
+ /**
370
+ * Lua's `tostring`, with `__tostring` and `__name`.
371
+ *
372
+ * @param {VM} vm
373
+ * @param {any} v
374
+ * @returns {string}
375
+ */
376
+ export function tostring(vm, v) {
377
+ switch (typeof v) {
378
+ case 'undefined': return 'nil'
379
+ case 'boolean': return v ? 'true' : 'false'
380
+ case 'number': return numberToString(v)
381
+ case 'string': return v
382
+ default: break
383
+ }
384
+ if (v instanceof Vector) return numberToString(v.x) + ', ' + numberToString(v.y) + ', ' + numberToString(v.z)
385
+ if (v.id === 0) v.id = vm.newId()
386
+ if (v instanceof LuaTable) {
387
+ const mt = v.meta
388
+ if (mt !== undefined) {
389
+ const handler = mt.getStr('__tostring')
390
+ if (handler !== undefined) {
391
+ const s = call1(vm, handler, [ v ])
392
+ if (typeof s !== 'string') throw fail("'__tostring' must return a string")
393
+ return s
394
+ }
395
+ const name = mt.getStr('__name')
396
+ if (typeof name === 'string') return name + ': ' + address(v.id)
397
+ }
398
+ return 'table: ' + address(v.id)
399
+ }
400
+ return 'function: ' + address(v.id)
401
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Numbers as Lua prints and reads them: C's `%g`, `%e` and `%f` rendered from
3
+ * JS's own number formatting, and string-to-number the way `tonumber` and
4
+ * arithmetic coercion read them. No `Math` function newer than ES5 appears
5
+ * here, because the oldest browsers this runs in lack them.
6
+ */
7
+
8
+ // Formatting
9
+ // ■■■■■■■■■■
10
+
11
+ /**
12
+ * The digits of a finite non-negative number rounded to `count` significant
13
+ * digits, and its decimal exponent.
14
+ *
15
+ * @param {number} n
16
+ * @param {number} count 1 to 21
17
+ * @returns {{ digits: string, exp: number }}
18
+ */
19
+ function significant(n, count) {
20
+ if (n === 0) return { digits: zeros(count), exp: 0 }
21
+ const places = Math.min(Math.max(count, 1), 21) - 1
22
+ let s = n.toExponential(places)
23
+ // JS rounds an exact tie up; C rounds it to the even digit. A tie shows as a 5 and zeros past the cut.
24
+ const long = n.toExponential(Math.min(places + 30, 100))
25
+ const lp = long.indexOf('e')
26
+ const mantissa = long.slice(0, lp).replace('.', '')
27
+ if (/^50*$/.test(mantissa.slice(places + 1)) && (mantissa.charCodeAt(places) - 48) % 2 === 0) s = long.slice(0, lp).replace('.', '').slice(0, places + 1).replace(/^(.)/, places > 0 ? '$1.' : '$1') + long.slice(lp)
28
+ const e = s.indexOf('e')
29
+ return { digits: s.slice(0, e).replace('.', ''), exp: parseInt(s.slice(e + 1), 10) }
30
+ }
31
+
32
+ /**
33
+ * `n.toFixed(places)` with C's rounding of an exact tie to the even digit.
34
+ *
35
+ * @param {number} n finite, non-negative, under 1e21
36
+ * @param {number} places
37
+ * @returns {string}
38
+ */
39
+ function fixed(n, places) {
40
+ const s = n.toFixed(places)
41
+ if (places >= 70) return s
42
+ const long = n.toFixed(places + 30)
43
+ const dot = long.indexOf('.')
44
+ const cut = dot + 1 + places
45
+ if (/^50*$/.test(long.slice(cut)) && (long.charCodeAt(places === 0 ? dot - 1 : cut - 1) - 48) % 2 === 0) return places === 0 ? long.slice(0, dot) : long.slice(0, cut)
46
+ return s
47
+ }
48
+
49
+ /**
50
+ * @param {number} n
51
+ * @returns {string} n zeros
52
+ */
53
+ export function zeros(n) {
54
+ let out = ''
55
+ for (let i = 0; i < n; i++) out += '0'
56
+ return out
57
+ }
58
+
59
+ /** @param {string} s */
60
+ function trimFraction(s) {
61
+ if (s.indexOf('.') < 0) return s
62
+ return s.replace(/0+$/, '').replace(/\.$/, '')
63
+ }
64
+
65
+ /**
66
+ * C's `%e`.
67
+ *
68
+ * @param {number} n
69
+ * @param {number} precision digits after the point
70
+ * @param {boolean} [alt] keep the point when there are no digits after it
71
+ * @returns {string} of the magnitude, no sign
72
+ */
73
+ export function formatE(n, precision, alt) {
74
+ const { digits, exp } = significant(n, precision + 1)
75
+ const mantissa = digits.charAt(0) + (precision > 0 ? '.' + digits.slice(1) : alt ? '.' : '')
76
+ const a = Math.abs(exp)
77
+ return mantissa + 'e' + (exp < 0 ? '-' : '+') + (a < 10 ? '0' : '') + a
78
+ }
79
+
80
+ /**
81
+ * C's `%f`.
82
+ *
83
+ * @param {number} n
84
+ * @param {number} precision
85
+ * @param {boolean} [alt]
86
+ * @returns {string} of the magnitude, no sign
87
+ */
88
+ export function formatF(n, precision, alt) {
89
+ let s
90
+ if (n >= 1e21) {
91
+ // toFixed gives up on these; spell the integer out from its exponent form
92
+ const { digits, exp } = significant(n, 21)
93
+ s = digits + zeros(exp - 20)
94
+ if (precision > 0) s += '.' + zeros(precision)
95
+ } else s = fixed(n, Math.min(precision, 70))
96
+ if (alt && precision === 0) s += '.'
97
+ return s
98
+ }
99
+
100
+ /**
101
+ * C's `%g`.
102
+ *
103
+ * @param {number} n
104
+ * @param {number} precision significant digits; 0 counts as 1
105
+ * @param {boolean} [alt] keep trailing zeros
106
+ * @returns {string} of the magnitude, no sign
107
+ */
108
+ export function formatG(n, precision, alt) {
109
+ const p = precision === 0 ? 1 : precision
110
+ const { digits, exp } = significant(n, p)
111
+ if (exp < -4 || exp >= p) {
112
+ let m = digits.charAt(0) + (p > 1 ? '.' + digits.slice(1) : '')
113
+ if (!alt) m = trimFraction(m)
114
+ const a = Math.abs(exp)
115
+ return m + 'e' + (exp < 0 ? '-' : '+') + (a < 10 ? '0' : '') + a
116
+ }
117
+ let s
118
+ if (exp >= 0) s = digits.slice(0, exp + 1) + (p > exp + 1 ? '.' + digits.slice(exp + 1) : '')
119
+ else s = '0.' + zeros(-exp - 1) + digits
120
+ return alt ? s : trimFraction(s)
121
+ }
122
+
123
+ /**
124
+ * How `tostring` writes a number, as Luau does: the shortest digits that read
125
+ * back as the same double, in fixed notation while the decimal point falls
126
+ * between 5 places left of the digits and 21 places right (`0.00001`,
127
+ * `1000000000000000`, `9223372036854776000`), else in scientific (`1e+21`,
128
+ * `1e-07`). `inf`, `-inf` and `nan` are spelled that way.
129
+ *
130
+ * @param {number} n
131
+ * @returns {string}
132
+ */
133
+ export function numberToString(n) {
134
+ if (n !== n) return 'nan'
135
+ if (n === Infinity) return 'inf'
136
+ if (n === -Infinity) return '-inf'
137
+ if (n === 0) return 1 / n < 0 ? '-0' : '0'
138
+ if (Math.floor(n) === n && Math.abs(n) < 1e15) return String(n)
139
+ // toExponential() with no argument gives the shortest digits that round-trip
140
+ const parts = Math.abs(n).toExponential().split('e')
141
+ const digits = parts[0].replace('.', '')
142
+ const dot = Number(parts[1]) + 1
143
+ const sign = n < 0 ? '-' : ''
144
+ if (dot >= -5 && dot <= 21) {
145
+ if (dot <= 0) return sign + '0.' + zeros(-dot) + digits
146
+ if (dot >= digits.length) return sign + digits + zeros(dot - digits.length)
147
+ return sign + digits.slice(0, dot) + '.' + digits.slice(dot)
148
+ }
149
+ const e = Math.abs(dot - 1)
150
+ return sign + digits[0] + (digits.length > 1 ? '.' + digits.slice(1) : '') + 'e' + (dot - 1 < 0 ? '-' : '+') + (e < 10 ? '0' : '') + e
151
+ }
152
+
153
+ // Reading
154
+ // ■■■■■■■
155
+
156
+ const DECIMAL = /^[ \t\n\r\f\v]*[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?[ \t\n\r\f\v]*$/
157
+ const HEX = /^[ \t\n\r\f\v]*([+-]?)0[xX]([0-9a-fA-F]+)[ \t\n\r\f\v]*$/
158
+
159
+ /**
160
+ * A string as a number, or undefined when it isn't one: decimal with an
161
+ * optional fraction and exponent or `0x` hex (not `0b`: only source code has binary), with surrounding
162
+ * whitespace and a sign.
163
+ *
164
+ * @param {string} s
165
+ * @returns {number | undefined}
166
+ */
167
+ export function stringToNumber(s) {
168
+ if (DECIMAL.test(s)) return Number(s)
169
+ const m = HEX.exec(s)
170
+ if (m) return (m[1] === '-' ? -1 : 1) * parseInt(m[2], 16)
171
+ return undefined
172
+ }
173
+
174
+ /**
175
+ * @param {string} s
176
+ * @param {number} base 2 to 36
177
+ * @returns {number | undefined}
178
+ */
179
+ export function stringToNumberBase(s, base) {
180
+ const m = /^[ \t\n\r\f\v]*(-?)([0-9a-zA-Z]+)[ \t\n\r\f\v]*$/.exec(s)
181
+ if (!m) return undefined
182
+ let n = 0
183
+ const body = m[2].toLowerCase()
184
+ for (let i = 0; i < body.length; i++) {
185
+ const c = body.charCodeAt(i)
186
+ const d = c <= 57 ? c - 48 : c - 87
187
+ if (d >= base) return undefined
188
+ n = n * base + d
189
+ }
190
+ return m[1] === '-' ? -n : n
191
+ }
192
+
193
+ // Arithmetic
194
+ // ■■■■■■■■■■
195
+
196
+ /**
197
+ * Lua's modulo: the remainder takes the divisor's sign.
198
+ *
199
+ * @param {number} a
200
+ * @param {number} b
201
+ * @returns {number}
202
+ */
203
+ export function mod(a, b) {
204
+ return a - Math.floor(a / b) * b
205
+ }
206
+
207
+ /**
208
+ * @param {number} n
209
+ * @returns {number} n cut toward zero
210
+ */
211
+ export function trunc(n) {
212
+ return n < 0 ? Math.ceil(n) : Math.floor(n)
213
+ }
214
+
215
+ /**
216
+ * @param {number} n
217
+ * @returns {number} -1, 0 or 1 (NaN stays 0)
218
+ */
219
+ export function sign(n) {
220
+ return n > 0 ? 1 : n < 0 ? -1 : 0
221
+ }
222
+
223
+ /**
224
+ * @param {string} s
225
+ * @param {number} n
226
+ * @returns {string} s repeated n times
227
+ */
228
+ export function repeat(s, n) {
229
+ let out = ''
230
+ let part = s
231
+ for (let k = n; k > 0; k = Math.floor(k / 2)) {
232
+ if (k % 2 === 1) out += part
233
+ part += part
234
+ }
235
+ return out
236
+ }