@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,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Luau bytecode loader: bytes in, a module of protos out. It follows
|
|
3
|
+
* `lvmload.cpp` for the layout and reads versions 3 to 14 with type-info
|
|
4
|
+
* versions 1 to 3 (8 on: 64-bit integer constants and classes are refused, 12 on:
|
|
5
|
+
* proto sizes, feedback slots and inline cost are read and dropped); the VM runs the result.
|
|
6
|
+
*
|
|
7
|
+
* The input starts at the version byte. Rive stores scripts with one wrapper
|
|
8
|
+
* byte before it, and the caller strips that byte. A leading 0 byte is how
|
|
9
|
+
* Luau carries a compile error as the rest of the bytes, and loads as a
|
|
10
|
+
* thrown error with that text.
|
|
11
|
+
*
|
|
12
|
+
* Constants are decoded into Lua values: `undefined` is nil, strings are
|
|
13
|
+
* byte strings (one char per byte, 0 to 255), vectors are `Vector`. Three
|
|
14
|
+
* kinds stay descriptors for the VM to expand: imports, table templates
|
|
15
|
+
* (DUPTABLE) and closures (DUPCLOSURE).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { CONSTANT, OP, PROTO_INLINABLE, OP_LENGTH, TYPE_VERSION_MAX, TYPE_VERSION_MIN, VERSION_MAX, VERSION_MIN } from './load.ops.js'
|
|
19
|
+
import { Vector } from '../vm/vm.value.js'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {object} Proto
|
|
23
|
+
* @property {number} id index in the module's proto table
|
|
24
|
+
* @property {number} maxStack registers the function needs
|
|
25
|
+
* @property {number} numParams
|
|
26
|
+
* @property {number} numUpvals
|
|
27
|
+
* @property {boolean} isVararg
|
|
28
|
+
* @property {number} flags
|
|
29
|
+
* @property {Uint8Array | null} typeInfo raw type bytes, as stored
|
|
30
|
+
* @property {Int32Array} code instruction words
|
|
31
|
+
* @property {any[]} k constants
|
|
32
|
+
* @property {Proto[]} protos child protos, by NEWCLOSURE index
|
|
33
|
+
* @property {number} lineDefined
|
|
34
|
+
* @property {string} name debug name, '' when stripped
|
|
35
|
+
* @property {Int32Array | null} lines source line of each instruction word, null when stripped
|
|
36
|
+
* @property {{ name: string, start: number, end: number, reg: number }[]} locals
|
|
37
|
+
* @property {string[]} upvalNames
|
|
38
|
+
* @property {string} chunk the module's chunk name
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {object} Module
|
|
43
|
+
* @property {number} version bytecode version
|
|
44
|
+
* @property {number} typesVersion type-info version, 0 before bytecode version 4
|
|
45
|
+
* @property {string[]} strings the string table
|
|
46
|
+
* @property {Proto[]} protos every proto, in file order
|
|
47
|
+
* @property {Proto} main the main chunk's proto
|
|
48
|
+
* @property {number} size bytes read; equals the input's length for a well-formed module
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/** An import constant: the path of constants that GETIMPORT walks from the globals */
|
|
52
|
+
export class Import {
|
|
53
|
+
/** @param {number} id the packed id: path length in the top 2 bits, three 10-bit constant indices below */
|
|
54
|
+
constructor(id) {
|
|
55
|
+
this.id = id
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A DUPTABLE template: the keys the table starts with, and the values for those given one */
|
|
60
|
+
export class TableTemplate {
|
|
61
|
+
/**
|
|
62
|
+
* @param {any[]} keys
|
|
63
|
+
* @param {any[]} values
|
|
64
|
+
*/
|
|
65
|
+
constructor(keys, values) {
|
|
66
|
+
this.keys = keys
|
|
67
|
+
this.values = values
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A DUPCLOSURE target: the child proto that becomes the closure */
|
|
72
|
+
export class ClosureRef {
|
|
73
|
+
/** @param {Proto} proto */
|
|
74
|
+
constructor(proto) {
|
|
75
|
+
this.proto = proto
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Reader
|
|
80
|
+
// ■■■■■■
|
|
81
|
+
|
|
82
|
+
/** A cursor over the bytes; reading past the end throws */
|
|
83
|
+
class Reader {
|
|
84
|
+
/** @param {Uint8Array} bytes */
|
|
85
|
+
constructor(bytes) {
|
|
86
|
+
this.bytes = bytes
|
|
87
|
+
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
88
|
+
this.pos = 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @param {number} n */
|
|
92
|
+
need(n) {
|
|
93
|
+
if (this.pos + n > this.bytes.length) throw new Error('luau: truncated bytecode at byte ' + this.pos)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
u8() {
|
|
97
|
+
this.need(1)
|
|
98
|
+
return this.bytes[this.pos++]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A 32-bit word, signed (the instruction decoders want it that way) */
|
|
102
|
+
u32() {
|
|
103
|
+
this.need(4)
|
|
104
|
+
const v = this.view.getInt32(this.pos, true)
|
|
105
|
+
this.pos += 4
|
|
106
|
+
return v
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
f32() {
|
|
110
|
+
this.need(4)
|
|
111
|
+
const v = this.view.getFloat32(this.pos, true)
|
|
112
|
+
this.pos += 4
|
|
113
|
+
return v
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
f64() {
|
|
117
|
+
this.need(8)
|
|
118
|
+
const v = this.view.getFloat64(this.pos, true)
|
|
119
|
+
this.pos += 8
|
|
120
|
+
return v
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A variable-length unsigned integer, 7 bits a byte, low first */
|
|
124
|
+
varint() {
|
|
125
|
+
let result = 0
|
|
126
|
+
let scale = 1
|
|
127
|
+
let byte
|
|
128
|
+
do {
|
|
129
|
+
byte = this.u8()
|
|
130
|
+
result += (byte & 127) * scale
|
|
131
|
+
scale *= 128
|
|
132
|
+
} while (byte & 128)
|
|
133
|
+
return result
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {number} n
|
|
138
|
+
* @returns {string} n bytes as a byte string
|
|
139
|
+
*/
|
|
140
|
+
str(n) {
|
|
141
|
+
this.need(n)
|
|
142
|
+
let out = ''
|
|
143
|
+
for (let i = 0; i < n; i += 4096) {
|
|
144
|
+
const end = Math.min(n, i + 4096)
|
|
145
|
+
out += String.fromCharCode.apply(null, /** @type {any} */ (this.bytes.subarray(this.pos + i, this.pos + end)))
|
|
146
|
+
}
|
|
147
|
+
this.pos += n
|
|
148
|
+
return out
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Load
|
|
153
|
+
// ■■■■
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {Uint8Array} bytes Luau bytecode from its version byte on
|
|
157
|
+
* @param {string} [chunk] the chunk name errors carry
|
|
158
|
+
* @returns {Module}
|
|
159
|
+
*/
|
|
160
|
+
export function loadBytecode(bytes, chunk) {
|
|
161
|
+
const name = chunk || '?'
|
|
162
|
+
const r = new Reader(bytes)
|
|
163
|
+
const version = r.u8()
|
|
164
|
+
if (version === 0) throw new Error(name + r.str(bytes.length - 1))
|
|
165
|
+
if (version < VERSION_MIN || version > VERSION_MAX) {
|
|
166
|
+
throw new Error(name + ': bytecode version mismatch (expected [' + VERSION_MIN + '..' + VERSION_MAX + '], got ' + version + ')')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let typesVersion = 0
|
|
170
|
+
if (version >= 4) {
|
|
171
|
+
typesVersion = r.u8()
|
|
172
|
+
if (typesVersion < TYPE_VERSION_MIN || typesVersion > TYPE_VERSION_MAX) {
|
|
173
|
+
throw new Error(name + ': bytecode type version mismatch (expected [' + TYPE_VERSION_MIN + '..' + TYPE_VERSION_MAX + '], got ' + typesVersion + ')')
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const stringCount = r.varint()
|
|
178
|
+
/** @type {string[]} */
|
|
179
|
+
const strings = []
|
|
180
|
+
for (let i = 0; i < stringCount; i++) strings.push(r.str(r.varint()))
|
|
181
|
+
|
|
182
|
+
/** @param {number} id */
|
|
183
|
+
const string = (id) => (id === 0 ? '' : strings[id - 1])
|
|
184
|
+
|
|
185
|
+
// userdata type names, which the VM has no use for
|
|
186
|
+
if (typesVersion === 3) {
|
|
187
|
+
let index = r.u8()
|
|
188
|
+
while (index !== 0) {
|
|
189
|
+
r.varint()
|
|
190
|
+
index = r.u8()
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const protoCount = r.varint()
|
|
195
|
+
/** @type {Proto[]} */
|
|
196
|
+
const protos = []
|
|
197
|
+
for (let i = 0; i < protoCount; i++) protos.push(readProto(r, i, version, string, protos, name))
|
|
198
|
+
|
|
199
|
+
const mainId = r.varint()
|
|
200
|
+
if (mainId >= protos.length) throw new Error(name + ': main proto ' + mainId + ' out of range')
|
|
201
|
+
return { version, typesVersion, strings, protos, main: protos[mainId], size: r.pos }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @param {Reader} r
|
|
206
|
+
* @param {number} id
|
|
207
|
+
* @param {number} version
|
|
208
|
+
* @param {(id: number) => string} string
|
|
209
|
+
* @param {Proto[]} protos the protos read so far
|
|
210
|
+
* @param {string} chunk
|
|
211
|
+
* @returns {Proto}
|
|
212
|
+
*/
|
|
213
|
+
function readProto(r, id, version, string, protos, chunk) {
|
|
214
|
+
// version 12 on: each proto is prefixed with its size, so a reader can skip what it does not know
|
|
215
|
+
const protoSize = version >= 12 ? r.varint() : 0
|
|
216
|
+
const protoStart = r.pos
|
|
217
|
+
const maxStack = r.u8()
|
|
218
|
+
const numParams = r.u8()
|
|
219
|
+
const numUpvals = r.u8()
|
|
220
|
+
const isVararg = r.u8() !== 0
|
|
221
|
+
let flags = 0
|
|
222
|
+
let typeInfo = null
|
|
223
|
+
if (version >= 4) {
|
|
224
|
+
flags = r.u8()
|
|
225
|
+
const typeSize = r.varint()
|
|
226
|
+
if (typeSize > 0) {
|
|
227
|
+
r.need(typeSize)
|
|
228
|
+
// a copy: typed arrays' slice is ES2015, subarray and the constructor are older
|
|
229
|
+
typeInfo = new Uint8Array(r.bytes.subarray(r.pos, r.pos + typeSize))
|
|
230
|
+
r.pos += typeSize
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const sizeCode = r.varint()
|
|
235
|
+
const code = new Int32Array(sizeCode)
|
|
236
|
+
for (let i = 0; i < sizeCode; i++) code[i] = r.u32()
|
|
237
|
+
|
|
238
|
+
const sizeK = r.varint()
|
|
239
|
+
/** @type {any[]} */
|
|
240
|
+
const k = []
|
|
241
|
+
for (let i = 0; i < sizeK; i++) {
|
|
242
|
+
const tag = r.u8()
|
|
243
|
+
if (tag === CONSTANT.nil) k.push(undefined)
|
|
244
|
+
else if (tag === CONSTANT.boolean) k.push(r.u8() !== 0)
|
|
245
|
+
else if (tag === CONSTANT.number) k.push(r.f64())
|
|
246
|
+
else if (tag === CONSTANT.string) k.push(string(r.varint()))
|
|
247
|
+
else if (tag === CONSTANT.import) k.push(new Import(r.u32()))
|
|
248
|
+
else if (tag === CONSTANT.vector) {
|
|
249
|
+
const x = r.f32()
|
|
250
|
+
const y = r.f32()
|
|
251
|
+
const z = r.f32()
|
|
252
|
+
r.f32()
|
|
253
|
+
k.push(new Vector(x, y, z))
|
|
254
|
+
} else if (tag === CONSTANT.table) {
|
|
255
|
+
const n = r.varint()
|
|
256
|
+
const keys = []
|
|
257
|
+
const values = []
|
|
258
|
+
for (let j = 0; j < n; j++) {
|
|
259
|
+
keys.push(k[r.varint()])
|
|
260
|
+
// the shape only: the code that follows sets each value
|
|
261
|
+
values.push(0)
|
|
262
|
+
}
|
|
263
|
+
k.push(new TableTemplate(keys, values))
|
|
264
|
+
} else if (tag === CONSTANT.tableWithConstants) {
|
|
265
|
+
const n = r.varint()
|
|
266
|
+
const keys = []
|
|
267
|
+
const values = []
|
|
268
|
+
for (let j = 0; j < n; j++) {
|
|
269
|
+
keys.push(k[r.varint()])
|
|
270
|
+
const index = r.u32()
|
|
271
|
+
values.push(index >= 0 ? k[index] : 0)
|
|
272
|
+
}
|
|
273
|
+
k.push(new TableTemplate(keys, values))
|
|
274
|
+
} else if (tag === CONSTANT.closure) {
|
|
275
|
+
const fid = r.varint()
|
|
276
|
+
if (fid >= protos.length) throw new Error(chunk + ': closure constant names proto ' + fid + ' before it is read')
|
|
277
|
+
k.push(new ClosureRef(protos[fid]))
|
|
278
|
+
} else throw new Error(chunk + ': unknown constant kind ' + tag)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const sizeP = r.varint()
|
|
282
|
+
const children = []
|
|
283
|
+
for (let i = 0; i < sizeP; i++) {
|
|
284
|
+
const fid = r.varint()
|
|
285
|
+
if (fid >= protos.length) throw new Error(chunk + ': child proto ' + fid + ' is not read yet')
|
|
286
|
+
children.push(protos[fid])
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const lineDefined = r.varint()
|
|
290
|
+
const name = string(r.varint())
|
|
291
|
+
|
|
292
|
+
/** @type {Int32Array | null} */
|
|
293
|
+
let lines = null
|
|
294
|
+
if (r.u8() !== 0) {
|
|
295
|
+
const gapLog2 = r.u8()
|
|
296
|
+
const offsets = new Uint8Array(sizeCode)
|
|
297
|
+
let last = 0
|
|
298
|
+
for (let i = 0; i < sizeCode; i++) {
|
|
299
|
+
last = (last + r.u8()) & 255
|
|
300
|
+
offsets[i] = last
|
|
301
|
+
}
|
|
302
|
+
const intervals = ((sizeCode - 1) >> gapLog2) + 1
|
|
303
|
+
const absolute = []
|
|
304
|
+
let line = 0
|
|
305
|
+
for (let i = 0; i < intervals; i++) {
|
|
306
|
+
line += r.u32()
|
|
307
|
+
absolute.push(line)
|
|
308
|
+
}
|
|
309
|
+
lines = new Int32Array(sizeCode)
|
|
310
|
+
for (let i = 0; i < sizeCode; i++) lines[i] = absolute[i >> gapLog2] + offsets[i]
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const locals = []
|
|
314
|
+
const upvalNames = []
|
|
315
|
+
if (r.u8() !== 0) {
|
|
316
|
+
const n = r.varint()
|
|
317
|
+
for (let i = 0; i < n; i++) {
|
|
318
|
+
const varName = string(r.varint())
|
|
319
|
+
const start = r.varint()
|
|
320
|
+
const end = r.varint()
|
|
321
|
+
locals.push({ name: varName, start, end, reg: r.u8() })
|
|
322
|
+
}
|
|
323
|
+
const nu = r.varint()
|
|
324
|
+
for (let i = 0; i < nu; i++) upvalNames.push(string(r.varint()))
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (version >= 11) {
|
|
328
|
+
// feedback slots: a call target (pc) or a construct site (shape), unused here
|
|
329
|
+
const slots = r.varint()
|
|
330
|
+
for (let i = 0; i < slots; i++) {
|
|
331
|
+
r.u8()
|
|
332
|
+
r.varint()
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// the inline cost of a small function, unused here
|
|
336
|
+
if (version >= 12 && (flags & PROTO_INLINABLE) !== 0) r.varint()
|
|
337
|
+
if (version >= 12) r.pos = protoStart + protoSize
|
|
338
|
+
|
|
339
|
+
checkCode(code, children.length, chunk, id)
|
|
340
|
+
|
|
341
|
+
return { id, maxStack, numParams, numUpvals, isVararg, flags, typeInfo, code, k, protos: children, lineDefined, name, lines, locals, upvalNames, chunk }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Walks the instruction stream so a bad opcode or a cut-off AUX word fails at
|
|
346
|
+
* load, not in the middle of a run.
|
|
347
|
+
*
|
|
348
|
+
* @param {Int32Array} code
|
|
349
|
+
* @param {number} sizeP
|
|
350
|
+
* @param {string} chunk
|
|
351
|
+
* @param {number} id
|
|
352
|
+
*/
|
|
353
|
+
function checkCode(code, sizeP, chunk, id) {
|
|
354
|
+
for (let pc = 0; pc < code.length;) {
|
|
355
|
+
const op = code[pc] & 255
|
|
356
|
+
const length = OP_LENGTH[op]
|
|
357
|
+
if (length === 0) throw new Error(chunk + ': proto ' + id + ' has unknown opcode ' + op + ' at ' + pc)
|
|
358
|
+
if (pc + length > code.length) throw new Error(chunk + ': proto ' + id + ' ends inside an instruction at ' + pc)
|
|
359
|
+
if (op === OP.NEWCLOSURE && (code[pc] >> 16) >= sizeP) throw new Error(chunk + ': proto ' + id + ' makes a closure of a missing child at ' + pc)
|
|
360
|
+
pc += length
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Luau's opcodes, in the order Bytecode.h numbers them (the numbers are baked
|
|
3
|
+
* into compiled bytecode), and how long each instruction is: one 32-bit word,
|
|
4
|
+
* or two when an AUX word follows.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Opcodes
|
|
8
|
+
// ■■■■■■■■
|
|
9
|
+
|
|
10
|
+
/** Opcode names by number, for bytecode versions 3 to 14 (the experimental and userdata ones between are not carried) */
|
|
11
|
+
export const OP_NAMES = [
|
|
12
|
+
'NOP', 'BREAK', 'LOADNIL', 'LOADB', 'LOADN', 'LOADK', 'MOVE', 'GETGLOBAL', 'SETGLOBAL', 'GETUPVAL',
|
|
13
|
+
'SETUPVAL', 'CLOSEUPVALS', 'GETIMPORT', 'GETTABLE', 'SETTABLE', 'GETTABLEKS', 'SETTABLEKS', 'GETTABLEN', 'SETTABLEN', 'NEWCLOSURE',
|
|
14
|
+
'NAMECALL', 'CALL', 'RETURN', 'JUMP', 'JUMPBACK', 'JUMPIF', 'JUMPIFNOT', 'JUMPIFEQ', 'JUMPIFLE', 'JUMPIFLT',
|
|
15
|
+
'JUMPIFNOTEQ', 'JUMPIFNOTLE', 'JUMPIFNOTLT', 'ADD', 'SUB', 'MUL', 'DIV', 'MOD', 'POW', 'ADDK',
|
|
16
|
+
'SUBK', 'MULK', 'DIVK', 'MODK', 'POWK', 'AND', 'OR', 'ANDK', 'ORK', 'CONCAT',
|
|
17
|
+
'NOT', 'MINUS', 'LENGTH', 'NEWTABLE', 'DUPTABLE', 'SETLIST', 'FORNPREP', 'FORNLOOP', 'FORGLOOP', 'FORGPREP_INEXT',
|
|
18
|
+
'FASTCALL3', 'FORGPREP_NEXT', 'NATIVECALL', 'GETVARARGS', 'DUPCLOSURE', 'PREPVARARGS', 'LOADKX', 'JUMPX', 'FASTCALL', 'COVERAGE',
|
|
19
|
+
'CAPTURE', 'SUBRK', 'DIVRK', 'FASTCALL1', 'FASTCALL2', 'FASTCALL2K', 'FORGPREP', 'JUMPXEQKNIL', 'JUMPXEQKB', 'JUMPXEQKN',
|
|
20
|
+
'JUMPXEQKS', 'IDIV', 'IDIVK',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Opcodes past the contiguous table, after the userdata and experimental ones:
|
|
25
|
+
* CALLFB (a CALL and a feedback slot word) and FASTPCALL (a fast-call hint for
|
|
26
|
+
* pcall and xpcall), which the compiler emits from bytecode version 14
|
|
27
|
+
*/
|
|
28
|
+
const OP_LATE = { CALLFB: 87, FASTPCALL: 89 }
|
|
29
|
+
|
|
30
|
+
/** Opcode numbers by name */
|
|
31
|
+
export const OP = /** @type {Record<string, number>} */ ({})
|
|
32
|
+
for (let i = 0; i < OP_NAMES.length; i++) OP[OP_NAMES[i]] = i
|
|
33
|
+
for (const name in OP_LATE) OP[name] = OP_LATE[/** @type {'CALLFB'} */ (name)]
|
|
34
|
+
|
|
35
|
+
/** Opcodes followed by an AUX word */
|
|
36
|
+
const WITH_AUX = [
|
|
37
|
+
'GETGLOBAL', 'SETGLOBAL', 'GETIMPORT', 'GETTABLEKS', 'SETTABLEKS', 'NAMECALL', 'JUMPIFEQ', 'JUMPIFLE', 'JUMPIFLT',
|
|
38
|
+
'JUMPIFNOTEQ', 'JUMPIFNOTLE', 'JUMPIFNOTLT', 'NEWTABLE', 'SETLIST', 'FORGLOOP', 'FASTCALL3', 'LOADKX', 'FASTCALL2',
|
|
39
|
+
'FASTCALL2K', 'JUMPXEQKNIL', 'JUMPXEQKB', 'JUMPXEQKN', 'JUMPXEQKS',
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
/** Instruction length in words, by opcode number; 0 for a number that is no opcode */
|
|
43
|
+
export const OP_LENGTH = /** @type {number[]} */ ([])
|
|
44
|
+
for (let i = 0; i < 256; i++) OP_LENGTH.push(i < OP_NAMES.length || i === OP_LATE.FASTPCALL || i === OP_LATE.CALLFB ? 1 : 0)
|
|
45
|
+
for (let i = 0; i < WITH_AUX.length; i++) OP_LENGTH[OP[WITH_AUX[i]]] = 2
|
|
46
|
+
OP_LENGTH[OP_LATE.CALLFB] = 2
|
|
47
|
+
|
|
48
|
+
// Constants
|
|
49
|
+
// ■■■■■■■■■
|
|
50
|
+
|
|
51
|
+
/** Constant table entry tags */
|
|
52
|
+
export const CONSTANT = {
|
|
53
|
+
nil: 0,
|
|
54
|
+
boolean: 1,
|
|
55
|
+
number: 2,
|
|
56
|
+
string: 3,
|
|
57
|
+
import: 4,
|
|
58
|
+
table: 5,
|
|
59
|
+
closure: 6,
|
|
60
|
+
vector: 7,
|
|
61
|
+
tableWithConstants: 8,
|
|
62
|
+
integer: 9,
|
|
63
|
+
classShape: 10,
|
|
64
|
+
vectorD: 11,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Proto flag: the function is small enough to inline (bytecode version 12 on stores its cost) */
|
|
68
|
+
export const PROTO_INLINABLE = 1 << 3
|
|
69
|
+
|
|
70
|
+
/** Upvalue capture kinds (CAPTURE's A) */
|
|
71
|
+
export const CAPTURE = { val: 0, ref: 1, upval: 2 }
|
|
72
|
+
|
|
73
|
+
/** Oldest and newest bytecode version this loader reads */
|
|
74
|
+
export const VERSION_MIN = 3
|
|
75
|
+
export const VERSION_MAX = 14
|
|
76
|
+
|
|
77
|
+
/** Type-info versions this loader reads */
|
|
78
|
+
export const TYPE_VERSION_MIN = 1
|
|
79
|
+
export const TYPE_VERSION_MAX = 3
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Values between the host and Lua. Strings cross as UTF-8 (Lua strings are
|
|
3
|
+
* byte strings); arrays and plain objects become tables and tables become
|
|
4
|
+
* arrays or objects; host functions become Lua functions and Lua functions
|
|
5
|
+
* become host functions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { EMPTY, LuaClosure, LuaTable, LuaThrow, NativeFunction, Vector } from './vm/vm.value.js'
|
|
9
|
+
import { tostring } from './vm/vm.meta.js'
|
|
10
|
+
|
|
11
|
+
/** @typedef {import('./vm/vm.js').VM} VM */
|
|
12
|
+
|
|
13
|
+
/** An error from Lua code, as the host sees it */
|
|
14
|
+
export class LuauError {
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} message the error value as text, with its position when there is one
|
|
17
|
+
* @param {any} value the error value, converted for the host
|
|
18
|
+
* @param {number} line the source line it was raised at, 0 when unknown
|
|
19
|
+
* @param {string} chunk the chunk that line is in
|
|
20
|
+
*/
|
|
21
|
+
constructor(message, value, line, chunk) {
|
|
22
|
+
this.name = 'LuauError'
|
|
23
|
+
this.message = message
|
|
24
|
+
this.value = value
|
|
25
|
+
this.line = line
|
|
26
|
+
this.chunk = chunk
|
|
27
|
+
this.stack = new Error(message).stack
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
toString() {
|
|
31
|
+
return 'LuauError: ' + this.message
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {VM} vm
|
|
37
|
+
* @param {LuaThrow} e
|
|
38
|
+
* @returns {LuauError}
|
|
39
|
+
*/
|
|
40
|
+
export function luauError(vm, e) {
|
|
41
|
+
const message = typeof e.value === 'string' ? decodeUtf8(e.value) : decodeUtf8(tostring(vm, e.value))
|
|
42
|
+
return new LuauError(message, toJs(vm, e.value), e.line, e.chunk)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Several results from a host function: `return multi(a, b)` */
|
|
46
|
+
export class Multi {
|
|
47
|
+
/** @param {any[]} values */
|
|
48
|
+
constructor(values) {
|
|
49
|
+
this.values = values
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {...any} values
|
|
55
|
+
* @returns {Multi}
|
|
56
|
+
*/
|
|
57
|
+
export function multi(...values) {
|
|
58
|
+
return new Multi(values)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {string} s a JS string
|
|
63
|
+
* @returns {string} the same text as UTF-8 bytes, one char per byte
|
|
64
|
+
*/
|
|
65
|
+
export function encodeUtf8(s) {
|
|
66
|
+
let ascii = true
|
|
67
|
+
for (let i = 0; i < s.length; i++) {
|
|
68
|
+
if (s.charCodeAt(i) > 127) {
|
|
69
|
+
ascii = false
|
|
70
|
+
break
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (ascii) return s
|
|
74
|
+
let out = ''
|
|
75
|
+
for (let i = 0; i < s.length; i++) {
|
|
76
|
+
let c = s.charCodeAt(i)
|
|
77
|
+
if (c >= 0xd800 && c < 0xdc00 && i + 1 < s.length) {
|
|
78
|
+
const d = s.charCodeAt(i + 1)
|
|
79
|
+
if (d >= 0xdc00 && d < 0xe000) {
|
|
80
|
+
c = 0x10000 + ((c - 0xd800) << 10) + (d - 0xdc00)
|
|
81
|
+
i++
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (c < 0x80) out += String.fromCharCode(c)
|
|
85
|
+
else if (c < 0x800) out += String.fromCharCode(0xc0 | (c >> 6), 0x80 | (c & 63))
|
|
86
|
+
else if (c < 0x10000) out += String.fromCharCode(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 63), 0x80 | (c & 63))
|
|
87
|
+
else out += String.fromCharCode(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 63), 0x80 | ((c >> 6) & 63), 0x80 | (c & 63))
|
|
88
|
+
}
|
|
89
|
+
return out
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {string} s a byte string
|
|
94
|
+
* @returns {string} its UTF-8 decoding; bytes that are not valid UTF-8 become themselves
|
|
95
|
+
*/
|
|
96
|
+
export function decodeUtf8(s) {
|
|
97
|
+
let ascii = true
|
|
98
|
+
for (let i = 0; i < s.length; i++) {
|
|
99
|
+
if (s.charCodeAt(i) > 127) {
|
|
100
|
+
ascii = false
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (ascii) return s
|
|
105
|
+
let out = ''
|
|
106
|
+
for (let i = 0; i < s.length;) {
|
|
107
|
+
const c = s.charCodeAt(i)
|
|
108
|
+
let need = 0
|
|
109
|
+
let cp = c
|
|
110
|
+
if (c >= 0xf0 && c < 0xf8) {
|
|
111
|
+
need = 3
|
|
112
|
+
cp = c & 7
|
|
113
|
+
} else if (c >= 0xe0) {
|
|
114
|
+
need = c < 0xf0 ? 2 : -1
|
|
115
|
+
cp = c & 15
|
|
116
|
+
} else if (c >= 0xc0) {
|
|
117
|
+
need = 1
|
|
118
|
+
cp = c & 31
|
|
119
|
+
}
|
|
120
|
+
let ok = need > 0 && i + need < s.length
|
|
121
|
+
if (ok) {
|
|
122
|
+
for (let n = 1; n <= need; n++) {
|
|
123
|
+
const d = s.charCodeAt(i + n)
|
|
124
|
+
if ((d & 0xc0) !== 0x80) {
|
|
125
|
+
ok = false
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
cp = (cp << 6) | (d & 63)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!ok) {
|
|
132
|
+
out += String.fromCharCode(c)
|
|
133
|
+
i++
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
i += need + 1
|
|
137
|
+
if (cp >= 0x10000) {
|
|
138
|
+
cp -= 0x10000
|
|
139
|
+
out += String.fromCharCode(0xd800 + (cp >> 10), 0xdc00 + (cp & 1023))
|
|
140
|
+
} else out += String.fromCharCode(cp)
|
|
141
|
+
}
|
|
142
|
+
return out
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A host value as a Lua value. Lua values pass through; `null` is nil.
|
|
147
|
+
*
|
|
148
|
+
* @param {VM} vm
|
|
149
|
+
* @param {any} v
|
|
150
|
+
* @param {number} [depth]
|
|
151
|
+
* @returns {any}
|
|
152
|
+
*/
|
|
153
|
+
export function toLua(vm, v, depth) {
|
|
154
|
+
const d = depth || 0
|
|
155
|
+
if (v === null || v === undefined) return undefined
|
|
156
|
+
switch (typeof v) {
|
|
157
|
+
case 'boolean':
|
|
158
|
+
case 'number': return v
|
|
159
|
+
case 'string': return encodeUtf8(v)
|
|
160
|
+
case 'function': return hostFunction(vm, v)
|
|
161
|
+
default: break
|
|
162
|
+
}
|
|
163
|
+
if (v instanceof LuaTable || v instanceof Vector || v instanceof LuaClosure || v instanceof NativeFunction) return v
|
|
164
|
+
if (d > 64) throw new Error('luau: value nested too deeply to convert')
|
|
165
|
+
const t = new LuaTable()
|
|
166
|
+
if (Array.isArray(v)) {
|
|
167
|
+
const items = []
|
|
168
|
+
for (let i = 0; i < v.length; i++) items.push(toLua(vm, v[i], d + 1))
|
|
169
|
+
t.setRange(1, items, 0, items.length)
|
|
170
|
+
} else {
|
|
171
|
+
for (const key in v) {
|
|
172
|
+
const value = toLua(vm, v[key], d + 1)
|
|
173
|
+
if (value !== undefined) t.set(encodeUtf8(key), value)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return t
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* A host function as a Lua function. Arguments and the result convert; an
|
|
181
|
+
* error it throws becomes a Lua error.
|
|
182
|
+
*
|
|
183
|
+
* @param {VM} vm
|
|
184
|
+
* @param {Function} fn
|
|
185
|
+
* @returns {NativeFunction}
|
|
186
|
+
*/
|
|
187
|
+
export function hostFunction(vm, fn) {
|
|
188
|
+
return new NativeFunction(fn.name || 'host', function (vm2, args) {
|
|
189
|
+
const jsArgs = []
|
|
190
|
+
for (let i = 0; i < args.length; i++) jsArgs.push(toJs(vm2, args[i]))
|
|
191
|
+
let result
|
|
192
|
+
try {
|
|
193
|
+
result = fn.apply(null, jsArgs)
|
|
194
|
+
} catch (e) {
|
|
195
|
+
if (e instanceof LuaThrow) throw e
|
|
196
|
+
const err = /** @type {any} */ (e)
|
|
197
|
+
if (err instanceof LuauError) throw new LuaThrow(encodeUtf8(err.message), 0)
|
|
198
|
+
throw new LuaThrow(encodeUtf8(err && err.message !== undefined ? String(err.message) : String(err)), 1)
|
|
199
|
+
}
|
|
200
|
+
if (result instanceof Multi) {
|
|
201
|
+
const out = []
|
|
202
|
+
for (let i = 0; i < result.values.length; i++) out.push(toLua(vm2, result.values[i]))
|
|
203
|
+
return out
|
|
204
|
+
}
|
|
205
|
+
return result === undefined ? EMPTY : toLua(vm2, result)
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* A Lua value as a host value: strings decode, a table whose keys are 1..n
|
|
211
|
+
* becomes an array and any other an object, a function becomes a host
|
|
212
|
+
* function (which returns nothing, its one result, or an array of them). A
|
|
213
|
+
* vector, and a function a host function made, stay as they are.
|
|
214
|
+
*
|
|
215
|
+
* @param {VM} vm
|
|
216
|
+
* @param {any} v
|
|
217
|
+
* @param {number} [depth]
|
|
218
|
+
* @returns {any}
|
|
219
|
+
*/
|
|
220
|
+
export function toJs(vm, v, depth) {
|
|
221
|
+
const d = depth || 0
|
|
222
|
+
if (typeof v === 'string') return decodeUtf8(v)
|
|
223
|
+
if (v instanceof LuaTable) {
|
|
224
|
+
if (d > 64) throw new Error('luau: table nested too deeply to convert')
|
|
225
|
+
if (v.hk.length - v.dead === 0) {
|
|
226
|
+
const out = []
|
|
227
|
+
for (let i = 0; i < v.arr.length; i++) out.push(toJs(vm, v.arr[i], d + 1))
|
|
228
|
+
return out
|
|
229
|
+
}
|
|
230
|
+
/** @type {Record<string, any>} */
|
|
231
|
+
const obj = {}
|
|
232
|
+
let pos = 0
|
|
233
|
+
for (;;) {
|
|
234
|
+
const next = v.advance(pos)
|
|
235
|
+
if (next === null) break
|
|
236
|
+
pos = next
|
|
237
|
+
obj[decodeUtf8(typeof v.ck === 'string' ? v.ck : tostring(vm, v.ck))] = toJs(vm, v.cv, d + 1)
|
|
238
|
+
}
|
|
239
|
+
return obj
|
|
240
|
+
}
|
|
241
|
+
if (v instanceof LuaClosure || v instanceof NativeFunction) {
|
|
242
|
+
return function () {
|
|
243
|
+
const args = []
|
|
244
|
+
for (let i = 0; i < arguments.length; i++) args.push(toLua(vm, arguments[i]))
|
|
245
|
+
let res
|
|
246
|
+
try {
|
|
247
|
+
res = vm.call(v, args)
|
|
248
|
+
} catch (e) {
|
|
249
|
+
throw e instanceof LuaThrow ? luauError(vm, e) : e
|
|
250
|
+
}
|
|
251
|
+
if (res.length === 0) return undefined
|
|
252
|
+
if (res.length === 1) return toJs(vm, res[0])
|
|
253
|
+
const out = []
|
|
254
|
+
for (let i = 0; i < res.length; i++) out.push(toJs(vm, res[i]))
|
|
255
|
+
return out
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return v
|
|
259
|
+
}
|