@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.
- package/bands/bands.js +3 -1
- package/bundle/bundle.js +56 -0
- package/list/list.js +12 -1
- package/lottie/lottie.layers.js +64 -11
- package/lottie/lottie.paint.js +30 -0
- package/lottie/lottie.paths.js +19 -4
- package/lottie/lottie.props.js +18 -8
- package/lottie/lottie.shapes.js +2 -1
- package/mixer/mixer.js +31 -8
- package/mixer/mixer.mix.js +17 -0
- package/motion/motion.convert.js +32 -1
- package/motion/motion.flow.js +20 -2
- package/motion/motion.machine.js +28 -13
- package/motion/motion.values.js +13 -9
- package/motion.js +196 -20
- package/package.json +10 -16
- package/player/player.js +25 -5
- package/readme.md +78 -3
- package/script/luau/lib/base/base.js +202 -0
- package/script/luau/lib/base/base.table.js +189 -0
- package/script/luau/lib/lib.args.js +152 -0
- package/script/luau/lib/lib.js +23 -0
- package/script/luau/lib/math/math.bit32.js +144 -0
- package/script/luau/lib/math/math.js +288 -0
- package/script/luau/lib/math/math.vector.js +125 -0
- package/script/luau/lib/string/string.format.js +143 -0
- package/script/luau/lib/string/string.js +160 -0
- package/script/luau/lib/string/string.pattern.js +505 -0
- package/script/luau/load/load.js +362 -0
- package/script/luau/load/load.ops.js +79 -0
- package/script/luau/luau.convert.js +259 -0
- package/script/luau/luau.js +132 -0
- package/script/luau/luau.md +111 -0
- package/script/luau/vm/vm.js +167 -0
- package/script/luau/vm/vm.meta.js +401 -0
- package/script/luau/vm/vm.num.js +236 -0
- package/script/luau/vm/vm.run.js +842 -0
- package/script/luau/vm/vm.value.js +406 -0
- package/script/play/play.js +199 -0
- package/script/play/play.objects.js +295 -0
- package/script/play/play.scope.js +91 -0
- package/script/rive/api/api.statics.js +31 -0
- package/script/rive/api/api.type.js +188 -0
- package/script/rive/api/artboard/artboard.js +89 -0
- package/script/rive/api/assets/assets.js +72 -0
- package/script/rive/api/data/data.value.js +99 -0
- package/script/rive/api/draw/draw.contours.js +163 -0
- package/script/rive/api/draw/draw.measure.js +196 -0
- package/script/rive/api/draw/draw.paint.js +235 -0
- package/script/rive/api/draw/draw.path.js +153 -0
- package/script/rive/api/draw/draw.renderer.js +117 -0
- package/script/rive/api/math/math.color.js +96 -0
- package/script/rive/api/math/math.mat2d.js +180 -0
- package/script/rive/api/math/math.vector.js +121 -0
- package/script/rive/api/media/media.audio.js +113 -0
- package/script/rive/api/media/media.draw.js +73 -0
- package/script/rive/api/media/media.image.js +61 -0
- package/script/rive/api/media/media.mesh.js +50 -0
- package/script/rive/api/pointer/pointer.event.js +64 -0
- package/script/rive/api/pointer/pointer.route.js +69 -0
- package/script/rive/api/text/text.font.js +129 -0
- package/script/rive/api/text/text.js +278 -0
- package/script/rive/api/text/text.layout.js +259 -0
- package/script/rive/context/context.js +76 -0
- package/script/rive/context/context.memory.js +196 -0
- package/script/rive/context/context.viewmodel.js +302 -0
- package/script/rive/context/lists/lists.js +160 -0
- package/script/rive/context/lists/lists.values.js +281 -0
- package/script/rive/host/host.globals.js +44 -0
- package/script/rive/host/host.js +533 -0
- package/script/rive/host/host.node.js +45 -0
- package/script/rive/rive.js +23 -0
- package/script/rive/rive.md +180 -0
- package/script/script.js +11 -0
- package/skin/skin.deform.js +514 -0
- package/skin/skin.ik.js +260 -0
- 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
|
+
}
|