• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / brs / #72

03 Apr 2024 12:54AM UTC coverage: 89.901% (-0.4%) from 90.321%
#72

push

web-flow
Merge 5937b00d4 into ac1cd3e36

1976 of 2368 branches covered (83.45%)

Branch coverage included in aggregate %.

227 of 273 new or added lines in 5 files covered. (83.15%)

2 existing lines in 2 files now uncovered.

5742 of 6217 relevant lines covered (92.36%)

30094.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.05
/src/brsTypes/components/RoByteArray.ts
1
import { BrsType, Float, Int32, isBrsNumber } from "..";
142✔
2
import { BrsValue, ValueKind, BrsBoolean, BrsInvalid, BrsString } from "../BrsType";
142✔
3
import { BrsComponent, BrsIterable } from "./BrsComponent";
142✔
4
import { Callable, StdlibArgument } from "../Callable";
142✔
5
import { Interpreter } from "../../interpreter";
6
import { getVolumeByPath } from "../../stdlib/File";
142✔
7
import { crc32 } from "crc";
142✔
8

9
export class RoByteArray extends BrsComponent implements BrsValue, BrsIterable {
142✔
10
    readonly kind = ValueKind.Object;
40✔
11
    private elements: Uint8Array;
12
    private _capacity = 0;
40✔
13
    private _resizable = true;
40✔
14

15
    enumIndex: number;
16

17
    constructor();
18
    constructor(elementsParam: Uint8Array);
19
    constructor(elementsParam?: Uint8Array) {
20
        super("roByteArray");
40✔
21
        this.elements = elementsParam ?? new Uint8Array();
40✔
22
        this.enumIndex = this.elements.length ? 0 : -1;
40✔
23
        this.registerMethods({
40✔
24
            ifByteArray: [
25
                this.readFile,
26
                this.writeFile,
27
                this.appendFile,
28
                this.setResize,
29
                this.fromHexString,
30
                this.toHexString,
31
                this.fromBase64String,
32
                this.toBase64String,
33
                this.fromAsciiString,
34
                this.toAsciiString,
35
                this.getSignedByte,
36
                this.getSignedLong,
37
                this.getCRC32,
38
                this.isLittleEndianCPU,
39
            ],
40
            ifArray: [
41
                this.peek,
42
                this.pop,
43
                this.push,
44
                this.shift,
45
                this.unshift,
46
                this.delete,
47
                this.count,
48
                this.clear,
49
                this.append,
50
            ],
51
            ifArrayGet: [this.getEntry],
52
            ifArraySet: [this.setEntry],
53
            ifArraySizeInfo: [this.capacity, this.isResizable],
54
            ifEnum: [this.isEmpty, this.isNext, this.next, this.reset],
55
        });
56
    }
57

58
    toString(parent?: BrsType): string {
59
        if (parent) {
1!
NEW
60
            return "<Component: roByteArray>";
×
61
        }
62

63
        return [
1✔
64
            "<Component: roByteArray> =",
65
            "[",
66
            ...this.getElements()
67
                .slice(0, 100)
68
                .map((el: BrsValue) => `    ${el.toString(this)}`),
5✔
69
            this.elements.length > 100 ? "    ...\n]" : "]",
1!
70
        ].join("\n");
71
    }
72

73
    equalTo(other: BrsType) {
NEW
74
        return BrsBoolean.False;
×
75
    }
76

77
    getValue() {
NEW
78
        return this.elements;
×
79
    }
80

81
    getElements(): Int32[] {
82
        const result: Int32[] = [];
8,108✔
83
        this.elements.slice().forEach((value: number) => {
8,108✔
84
            result.push(new Int32(value));
32,025,206✔
85
        });
86
        return result;
8,108✔
87
    }
88

89
    getByteArray() {
NEW
90
        return this.elements;
×
91
    }
92

93
    get(index: BrsType) {
94
        switch (index.kind) {
16,273!
95
            case ValueKind.Float:
NEW
96
                return this.getElements()[Math.trunc(index.getValue())] ?? BrsInvalid.Instance;
×
97
            case ValueKind.Int32:
98
                return this.getElements()[index.getValue()] ?? BrsInvalid.Instance;
8,103✔
99
            case ValueKind.String:
100
                return this.getMethod(index.value) ?? BrsInvalid.Instance;
8,170!
101
            default:
NEW
102
                return BrsInvalid.Instance;
×
103
        }
104
    }
105

106
    set(index: BrsType, value: BrsType) {
107
        if (isBrsNumber(index) && value.kind === ValueKind.Int32) {
3✔
108
            if (index.kind === ValueKind.Int64) {
3!
NEW
109
                index = new Int32(index.getValue());
×
110
            }
111
            const idx = Math.trunc(index.getValue());
3✔
112
            // Expand the array if the index is out of bounds
113
            if (idx >= this.elements.length) {
3✔
114
                const elements = new Uint8Array(idx + 1);
1✔
115
                elements.set(this.elements);
1✔
116
                this.elements = elements;
1✔
117
            }
118
            this.elements[idx] = value.getValue();
3✔
119
        }
120
        return BrsInvalid.Instance;
3✔
121
    }
122

123
    getNext() {
NEW
124
        const index = this.enumIndex;
×
NEW
125
        if (index >= 0) {
×
NEW
126
            this.enumIndex++;
×
NEW
127
            if (this.enumIndex >= this.elements.length) {
×
NEW
128
                this.enumIndex = -1;
×
129
            }
130
        }
NEW
131
        return new Int32(this.elements[this.enumIndex]);
×
132
    }
133

134
    updateNext() {
135
        const hasItems = this.elements.length > 0;
4,024✔
136
        if (this.enumIndex === -1 && hasItems) {
4,024✔
137
            this.enumIndex = 0;
10✔
138
        } else if (this.enumIndex >= this.elements.length || !hasItems) {
4,014!
NEW
139
            this.enumIndex = -1;
×
140
        }
141
    }
142

143
    updateCapacity(growthFactor = 0) {
16✔
144
        if (this._resizable && growthFactor > 0) {
4,024✔
145
            if (this.elements.length > 0 && this.elements.length > this._capacity) {
4,005✔
146
                let count = this.elements.length - 1;
17✔
147
                let newCap = Math.trunc(count * growthFactor);
17✔
148
                if (newCap - this._capacity < 16) {
17✔
149
                    this._capacity = Math.trunc(16 * (count / 16 + 1));
5✔
150
                } else {
151
                    this._capacity = newCap;
12✔
152
                }
153
            }
154
        } else {
155
            this._capacity = Math.max(this.elements.length, this._capacity);
19✔
156
        }
157
    }
158
    isLittleEndian() {
159
        // Solution from: https://abdulapopoola.com/2019/01/20/check-endianness-with-javascript/
160
        const uInt32 = new Uint32Array([0x11223344]);
6✔
161
        const uInt8 = new Uint8Array(uInt32.buffer);
6✔
162
        return uInt8[0] === 0x44;
6✔
163
    }
164

165
    // ifByteArray ---------------------------------------------------------------------
166

167
    /** Reads the specified file into the Byte Array. Any data currently in the Byte Array is discarded. */
168
    private readFile = new Callable("readFile", {
40✔
169
        signature: {
170
            args: [
171
                new StdlibArgument("path", ValueKind.String),
172
                new StdlibArgument("index", ValueKind.Int32, new Int32(0)),
173
                new StdlibArgument("length", ValueKind.Int32, new Int32(-1)),
174
            ],
175
            returns: ValueKind.Boolean,
176
        },
177
        impl: (interpreter: Interpreter, filepath: BrsString, index: Int32, length: Int32) => {
178
            try {
3✔
179
                const url = new URL(filepath.value);
3✔
180
                const volume = getVolumeByPath(interpreter, filepath.value);
3✔
181
                if (volume) {
3✔
182
                    let array: Uint8Array = volume.readFileSync(url.pathname);
3✔
183
                    if (index.getValue() > 0 || length.getValue() > 0) {
3✔
184
                        let start = index.getValue();
1✔
185
                        let end = length.getValue() < 1 ? undefined : start + length.getValue();
1!
186
                        array = array.slice(start, end);
1✔
187
                    }
188
                    if (this._resizable || array.length <= this._capacity) {
3✔
189
                        this.elements = array;
3✔
190
                        this.updateNext();
3✔
191
                        this.updateCapacity();
3✔
192
                        return BrsBoolean.True;
3✔
193
                    }
194
                }
195
            } catch (err: any) {
NEW
196
                return BrsBoolean.False;
×
197
            }
NEW
198
            return BrsBoolean.False;
×
199
        },
200
    });
201

202
    /** Writes the bytes (or a subset) contained in the Byte Array to the specified file. */
203
    private writeFile = new Callable("writeFile", {
40✔
204
        signature: {
205
            args: [
206
                new StdlibArgument("path", ValueKind.String),
207
                new StdlibArgument("index", ValueKind.Int32, new Int32(0)),
208
                new StdlibArgument("length", ValueKind.Int32, new Int32(-1)),
209
            ],
210
            returns: ValueKind.Boolean,
211
        },
212
        impl: (interpreter: Interpreter, filepath: BrsString, index: Int32, length: Int32) => {
213
            try {
2✔
214
                const url = new URL(filepath.value);
2✔
215
                if (url.protocol === "tmp:" || url.protocol === "cachefs:") {
2!
216
                    const volume = getVolumeByPath(interpreter, filepath.value);
2✔
217
                    if (volume) {
2✔
218
                        if (index.getValue() > 0 || length.getValue() > 0) {
2✔
219
                            let start = index.getValue();
1✔
220
                            let end = length.getValue() < 1 ? undefined : start + length.getValue();
1!
221
                            volume.writeFileSync(
1✔
222
                                url.pathname,
223
                                Buffer.from(this.elements.slice(start, end))
224
                            );
225
                        } else {
226
                            volume.writeFileSync(url.pathname, Buffer.from(this.elements));
1✔
227
                        }
228
                        return BrsBoolean.True;
2✔
229
                    }
230
                }
231
            } catch (err: any) {
NEW
232
                return BrsBoolean.False;
×
233
            }
NEW
234
            return BrsBoolean.False;
×
235
        },
236
    });
237

238
    /** Appends the contents (or a subset) of the Byte Array to the specified file. */
239
    private appendFile = new Callable("appendFile", {
40✔
240
        signature: {
241
            args: [
242
                new StdlibArgument("path", ValueKind.String),
243
                new StdlibArgument("index", ValueKind.Int32, new Int32(0)),
244
                new StdlibArgument("length", ValueKind.Int32, new Int32(-1)),
245
            ],
246
            returns: ValueKind.Boolean,
247
        },
248
        impl: (interpreter: Interpreter, filepath: BrsString, index: Int32, length: Int32) => {
249
            try {
1✔
250
                const url = new URL(filepath.value);
1✔
251
                if (url.protocol === "tmp:" || url.protocol === "cachefs:") {
1!
252
                    const volume = getVolumeByPath(interpreter, filepath.value);
1✔
253
                    if (volume) {
1✔
254
                        let file: Uint8Array = volume.readFileSync(url.pathname);
1✔
255
                        let array: Uint8Array;
256
                        if (index.getValue() > 0 || length.getValue() > 0) {
1!
257
                            let start = index.getValue();
1✔
258
                            let end = length.getValue() < 1 ? undefined : start + length.getValue();
1!
259
                            let elements = this.elements.slice(start, end);
1✔
260
                            array = new Uint8Array(file.length + elements.length);
1✔
261
                            array.set(file, 0);
1✔
262
                            array.set(elements, file.length);
1✔
263
                        } else {
NEW
264
                            array = new Uint8Array(file.length + this.elements.length);
×
NEW
265
                            array.set(file, 0);
×
NEW
266
                            array.set(this.elements, file.length);
×
267
                        }
268
                        volume.writeFileSync(url.pathname, Buffer.from(array));
1✔
269
                        return BrsBoolean.True;
1✔
270
                    }
271
                }
272
            } catch (err: any) {
NEW
273
                return BrsBoolean.False;
×
274
            }
NEW
275
            return BrsBoolean.False;
×
276
        },
277
    });
278

279
    /** Sets the contents of the Byte Array to the specified string using UTF-8 encoding. Any data currently in the Byte Array is discarded. */
280
    private fromAsciiString = new Callable("fromAsciiString", {
40✔
281
        signature: {
282
            args: [new StdlibArgument("asciiStr", ValueKind.String)],
283
            returns: ValueKind.Void,
284
        },
285
        impl: (_: Interpreter, asciiStr: BrsString) => {
286
            const array = new Uint8Array(Buffer.from(asciiStr.value, "utf8"));
4✔
287
            if (this._resizable || array.length <= this._capacity) {
4!
288
                this.elements = array;
4✔
289
                this.updateNext();
4✔
290
                this.updateCapacity();
4✔
291
            }
292
            return BrsInvalid.Instance;
4✔
293
        },
294
    });
295

296
    /** Returns the contents of the Byte Array as a string. The contents must be valid UTF-8 (or ASCII subset), or the result is undefined. */
297
    private toAsciiString = new Callable("toAsciiString", {
40✔
298
        signature: {
299
            args: [],
300
            returns: ValueKind.String,
301
        },
302
        impl: (_: Interpreter) => {
303
            return new BrsString(Buffer.from(this.elements).toString("utf8"));
3✔
304
        },
305
    });
306

307
    private fromHexString = new Callable("fromHexString", {
40✔
308
        signature: {
309
            args: [new StdlibArgument("hexStr", ValueKind.String)],
310
            returns: ValueKind.Void,
311
        },
312
        impl: (_: Interpreter, hexStr: BrsString) => {
313
            const value = hexStr.value.replace(/[^0-9A-Fa-f]/g, "0");
9✔
314
            if (value.length % 2 === 0 && (this._resizable || value.length / 2 <= this._capacity)) {
9✔
315
                this.elements = new Uint8Array(Buffer.from(value, "hex"));
7✔
316
                this.updateNext();
7✔
317
                this.updateCapacity();
7✔
318
            }
319
            return BrsInvalid.Instance;
9✔
320
        },
321
    });
322

323
    /** Returns a hexadecimal string representing the contents of the Byte Array, two digits per byte. */
324
    private toHexString = new Callable("toHexString", {
40✔
325
        signature: {
326
            args: [],
327
            returns: ValueKind.String,
328
        },
329
        impl: (_: Interpreter) => {
330
            const hex = Buffer.from(this.elements).toString("hex");
4✔
331
            return new BrsString(hex.toUpperCase());
4✔
332
        },
333
    });
334

335
    /** Sets the contents of the Byte Array to the specified value. Any data currently in the Byte Array is discarded. */
336
    private fromBase64String = new Callable("fromBase64String", {
40✔
337
        signature: {
338
            args: [new StdlibArgument("hexStr", ValueKind.String)],
339
            returns: ValueKind.Void,
340
        },
341
        impl: (_: Interpreter, hexStr: BrsString) => {
342
            const array = new Uint8Array(Buffer.from(hexStr.value, "base64"));
1✔
343
            if (this._resizable || array.length <= this._capacity) {
1!
344
                this.elements = array;
1✔
345
                this.updateNext();
1✔
346
                this.updateCapacity();
1✔
347
            }
348
            return BrsInvalid.Instance;
1✔
349
        },
350
    });
351

352
    /** Returns a base-64 string representing the contents of the Byte Array. */
353
    private toBase64String = new Callable("toBase64String", {
40✔
354
        signature: {
355
            args: [],
356
            returns: ValueKind.String,
357
        },
358
        impl: (_: Interpreter) => {
359
            return new BrsString(Buffer.from(this.elements).toString("base64"));
2✔
360
        },
361
    });
362

363
    /** Returns the signed byte at the specified zero-based index in the Byte Array. */
364
    private getSignedByte = new Callable("getSignedByte", {
40✔
365
        signature: {
366
            args: [new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float)],
367
            returns: ValueKind.Int32,
368
        },
369
        impl: (_: Interpreter, index: Int32 | Float) => {
370
            const idx = Math.trunc(index.getValue());
6✔
371
            if (idx < this.elements.length) {
6✔
372
                let byte = (this.elements[idx] << 24) >> 24;
5✔
373
                return new Int32(byte);
5✔
374
            }
375
            return new Int32(0);
1✔
376
        },
377
    });
378

379
    /** Returns the signed long (four bytes) starting at the specified zero-based long index. */
380
    private getSignedLong = new Callable("getSignedLong", {
40✔
381
        signature: {
382
            args: [new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float)],
383
            returns: ValueKind.Int32,
384
        },
385
        impl: (_: Interpreter, index: Int32 | Float) => {
386
            const idx = Math.trunc(index.getValue()) * 4; // Multiply index by 4
6✔
387
            if (idx < this.elements.length - 3) {
6✔
388
                const dataView = new DataView(this.elements.buffer, idx, 4);
5✔
389
                const long = dataView.getInt32(0, this.isLittleEndian());
5✔
390
                return new Int32(long);
5✔
391
            }
392
            return new Int32(0);
1✔
393
        },
394
    });
395

396
    /** Calculates a CRC-32 of the contents (or a subset) of the Byte Array. */
397
    private getCRC32 = new Callable("getCRC32", {
40✔
398
        signature: {
399
            args: [
400
                new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float, new Int32(0)),
401
                new StdlibArgument("length", ValueKind.Int32 | ValueKind.Float, new Int32(-1)),
402
            ],
403
            returns: ValueKind.Int32,
404
        },
405
        impl: (_: Interpreter, index: Int32 | Float, length: Int32 | Float) => {
406
            const idx = Math.trunc(index.getValue());
2✔
407
            const len = Math.trunc(length.getValue());
2✔
408
            if (idx > 0 || len > 0) {
2!
NEW
409
                const end = len < 1 ? undefined : idx + len;
×
NEW
410
                return new Int32(crc32(Buffer.from(this.elements.slice(idx, end))));
×
411
            }
412
            return new Int32(crc32(Buffer.from(this.elements)));
2✔
413
        },
414
    });
415

416
    /** If the size of the Byte Array is less than min_size, expands the Byte Array to min_size. */
417
    private setResize = new Callable("setResize", {
40✔
418
        signature: {
419
            args: [
420
                new StdlibArgument("minSize", ValueKind.Int32 | ValueKind.Float),
421
                new StdlibArgument("autoResize", ValueKind.Boolean),
422
            ],
423
            returns: ValueKind.Void,
424
        },
425
        impl: (_: Interpreter, minSize: Int32 | Float, autoResize: BrsBoolean) => {
426
            this._capacity = Math.max(Math.trunc(minSize.getValue()), this.elements.length);
5✔
427
            this._resizable = autoResize.toBoolean();
5✔
428
            return BrsInvalid.Instance;
5✔
429
        },
430
    });
431

432
    /** Returns true if the CPU architecture is little-endian. */
433
    private isLittleEndianCPU = new Callable("isLittleEndianCPU", {
40✔
434
        signature: {
435
            args: [],
436
            returns: ValueKind.Boolean,
437
        },
438
        impl: (_: Interpreter) => {
439
            return BrsBoolean.from(this.isLittleEndian());
1✔
440
        },
441
    });
442

443
    // ifArray -------------------------------------------------------------------------
444

445
    /** Returns the last array entry without removing it. If the array is empty, returns invalid. */
446
    private peek = new Callable("peek", {
40✔
447
        signature: {
448
            args: [],
449
            returns: ValueKind.Dynamic,
450
        },
451
        impl: (_: Interpreter) => {
452
            const item = this.elements[this.elements.length - 1];
2✔
453
            return item ? new Int32(item) : BrsInvalid.Instance;
2✔
454
        },
455
    });
456

457
    /** Returns the last entry from the array and removes it. If the array is empty, returns invalid. */
458
    private pop = new Callable("pop", {
40✔
459
        signature: {
460
            args: [],
461
            returns: ValueKind.Dynamic,
462
        },
463
        impl: (_: Interpreter) => {
464
            if (this.elements.length === 0) {
8✔
465
                return BrsInvalid.Instance;
1✔
466
            }
467
            const index = this.elements.length - 1;
7✔
468
            const item = this.elements[index];
7✔
469
            let array = new Uint8Array(index);
7✔
470
            array.set(this.elements.slice(0, index), 0);
7✔
471
            this.elements = array;
7✔
472
            return item ? new Int32(item) : BrsInvalid.Instance;
7✔
473
        },
474
    });
475

476
    /** Adds the specified value to the end of the array. */
477
    private push = new Callable("push", {
40✔
478
        signature: {
479
            args: [new StdlibArgument("byte", ValueKind.Dynamic)],
480
            returns: ValueKind.Void,
481
        },
482
        impl: (interpreter: Interpreter, byte: Int32 | Float | BrsInvalid) => {
483
            if (isBrsNumber(byte)) {
4,008!
484
                if (this._resizable || this.elements.length < this._capacity) {
4,008✔
485
                    let array = new Uint8Array(this.elements.length + 1);
4,007✔
486
                    array.set(this.elements, 0);
4,007✔
487
                    array[this.elements.length] = byte.getValue();
4,007✔
488
                    this.elements = array;
4,007✔
489
                    this.updateNext();
4,007✔
490
                    this.updateCapacity(1.5);
4,007✔
491
                } else {
492
                    let location = `${interpreter.location.file}:(${interpreter.location.start.line})`;
1✔
493
                    interpreter.stderr.write(
1✔
494
                        `BRIGHTSCRIPT: ERROR: roByteArray.Push: set ignored for index out of bounds on non-resizable array: ${location}\n`
495
                    );
496
                }
497
            } else {
NEW
498
                let location = `${interpreter.location.file}:(${interpreter.location.start.line})`;
×
NEW
499
                interpreter.stderr.write(
×
500
                    `BRIGHTSCRIPT: ERROR: roByteArray.Push: set ignored for non-numeric value: ${location}\n`
501
                );
502
            }
503
            return BrsInvalid.Instance;
4,008✔
504
        },
505
    });
506

507
    /** Removes the first entry (zero index) from the beginning of the array and shifts the other entries up. */
508
    private shift = new Callable("shift", {
40✔
509
        signature: {
510
            args: [],
511
            returns: ValueKind.Dynamic,
512
        },
513
        impl: (_: Interpreter) => {
514
            if (this.elements.length === 0) {
3✔
515
                return BrsInvalid.Instance;
1✔
516
            }
517
            const item = this.elements[0];
2✔
518
            let array = new Uint8Array(this.elements.length - 1);
2✔
519
            array.set(this.elements.slice(1));
2✔
520
            this.elements = array;
2✔
521
            return item ? new Int32(item) : BrsInvalid.Instance;
2!
522
        },
523
    });
524

525
    /** Adds the specified value to the beginning of the array (at the zero index) and shifts the other entries down. */
526
    private unshift = new Callable("unshift", {
40✔
527
        signature: {
528
            args: [new StdlibArgument("byte", ValueKind.Dynamic)],
529
            returns: ValueKind.Void,
530
        },
531
        impl: (interpreter: Interpreter, byte: Int32 | Float | BrsInvalid) => {
532
            if (isBrsNumber(byte)) {
2!
533
                if (this._resizable || this.elements.length < this._capacity) {
2✔
534
                    let array = new Uint8Array(this.elements.length + 1);
1✔
535
                    array[0] = byte.getValue();
1✔
536
                    array.set(this.elements, 1);
1✔
537
                    this.elements = array;
1✔
538
                    this.updateNext();
1✔
539
                    this.updateCapacity(1.25);
1✔
540
                } else {
541
                    let location = `${interpreter.location.file}:(${interpreter.location.start.line})`;
1✔
542
                    interpreter.stderr.write(
1✔
543
                        `BRIGHTSCRIPT: ERROR: roByteArray.Unshift: unshift ignored for full non-resizable array: ${location}\n`
544
                    );
545
                }
546
            } else {
NEW
547
                interpreter.stderr.write(
×
548
                    `BRIGHTSCRIPT: ERROR: roByteArray.Unshift: unshift ignored for non-numeric value: ${location}\n`
549
                );
550
            }
551
            return BrsInvalid.Instance;
2✔
552
        },
553
    });
554

555
    /** Deletes the indicated array entry, and shifts all entries up. This decreases the array length by one. */
556
    private delete = new Callable("delete", {
40✔
557
        signature: {
558
            args: [new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float)],
559
            returns: ValueKind.Boolean,
560
        },
561
        impl: (_: Interpreter, index: Int32 | Float) => {
562
            const idx = Math.trunc(index.getValue());
3✔
563
            if (idx < 0 || idx >= this.elements.length) {
3✔
564
                return BrsBoolean.False;
2✔
565
            }
566
            let array = new Uint8Array(this.elements.length - 1);
1✔
567
            array.set(this.elements.slice(0, idx), 0);
1✔
568
            array.set(this.elements.slice(idx + 1), idx);
1✔
569
            this.elements = array;
1✔
570
            return BrsBoolean.True;
1✔
571
        },
572
    });
573

574
    /** Returns the length of the array, which is one more than the index of highest entry. */
575
    private count = new Callable("count", {
40✔
576
        signature: {
577
            args: [],
578
            returns: ValueKind.Int32,
579
        },
580
        impl: (_: Interpreter) => {
581
            return new Int32(this.elements.length);
38✔
582
        },
583
    });
584

585
    /** Deletes all the entries in the array. */
586
    private clear = new Callable("clear", {
40✔
587
        signature: {
588
            args: [],
589
            returns: ValueKind.Void,
590
        },
591
        impl: (_: Interpreter) => {
592
            this.elements = new Uint8Array();
2✔
593
            this.enumIndex = -1;
2✔
594
            return BrsInvalid.Instance;
2✔
595
        },
596
    });
597

598
    /** Appends the entries in one roArray to another. */
599
    private append = new Callable("append", {
40✔
600
        signature: {
601
            args: [new StdlibArgument("array", ValueKind.Object)],
602
            returns: ValueKind.Void,
603
        },
604
        impl: (interpreter: Interpreter, array: BrsComponent) => {
605
            if (!(array instanceof RoByteArray)) {
4!
NEW
606
                let location = `${interpreter.location.file}:(${interpreter.location.start.line})`;
×
NEW
607
                interpreter.stderr.write(
×
608
                    `BRIGHTSCRIPT: ERROR: roByteArray.Append: invalid parameter type ${array.getComponentName()}: ${location}\n`
609
                );
NEW
610
                return BrsInvalid.Instance;
×
611
            }
612
            if (this._resizable || this.elements.length + array.elements.length <= this._capacity) {
4✔
613
                this.elements = new Uint8Array([...this.elements, ...array.elements]);
1✔
614
                this.updateNext();
1✔
615
                this.updateCapacity();
1✔
616
            }
617
            return BrsInvalid.Instance;
4✔
618
        },
619
    });
620

621
    // ifArrayGet -------------------------------------------------------------------------
622

623
    /** Returns an array entry based on the provided index. */
624
    private getEntry = new Callable("getEntry", {
40✔
625
        signature: {
626
            args: [new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float)],
627
            returns: ValueKind.Dynamic,
628
        },
629
        impl: (_: Interpreter, index: Int32 | Float) => {
NEW
630
            return this.getElements()[Math.trunc(index.getValue())] || BrsInvalid.Instance;
×
631
        },
632
    });
633

634
    // ifArraySet  -------------------------------------------------------------------------
635

636
    /** Sets an entry at a given index to the passed value. If index is beyond the bounds of the array, the array is expanded. */
637
    private setEntry = new Callable("setEntry", {
40✔
638
        signature: {
639
            args: [
640
                new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float),
641
                new StdlibArgument("value", ValueKind.Dynamic),
642
            ],
643
            returns: ValueKind.Void,
644
        },
645
        impl: (interpreter: Interpreter, index: Int32 | Float, value: BrsType) => {
NEW
646
            if (!isBrsNumber(value)) {
×
NEW
647
                let location = `${interpreter.location.file}:(${interpreter.location.start.line})`;
×
NEW
648
                interpreter.stderr.write(
×
649
                    `BRIGHTSCRIPT: ERROR: roByteArray.SetEntry: set ignored for non-numeric value: ${location}\n`
650
                );
651
            }
NEW
652
            return this.set(index, value);
×
653
        },
654
    });
655

656
    // ifArraySizeInfo ---------------------------------------------------------------------
657

658
    /** Returns the maximum number of entries that can be stored in the array. */
659
    private capacity = new Callable("capacity", {
40✔
660
        signature: {
661
            args: [],
662
            returns: ValueKind.Int32,
663
        },
664
        impl: (_: Interpreter) => {
665
            return new Int32(this._capacity);
4,065✔
666
        },
667
    });
668

669
    /** Returns true if the array can be resized. */
670
    private isResizable = new Callable("isResizable", {
40✔
671
        signature: {
672
            args: [],
673
            returns: ValueKind.Boolean,
674
        },
675
        impl: (_: Interpreter) => {
676
            return BrsBoolean.from(this._resizable);
17✔
677
        },
678
    });
679

680
    // ifEnum -------------------------------------------------------------------------
681

682
    /** Checks whether the enumeration contains no elements. */
683
    private isEmpty = new Callable("isEmpty", {
40✔
684
        signature: {
685
            args: [],
686
            returns: ValueKind.Boolean,
687
        },
688
        impl: (_: Interpreter) => {
689
            return BrsBoolean.from(this.elements.length === 0);
1✔
690
        },
691
    });
692

693
    /** Checks whether the current position is not past the end of the enumeration. */
694
    private isNext = new Callable("isNext", {
40✔
695
        signature: {
696
            args: [],
697
            returns: ValueKind.Boolean,
698
        },
699
        impl: (_: Interpreter) => {
NEW
700
            return BrsBoolean.from(this.enumIndex >= 0);
×
701
        },
702
    });
703

704
    /** Resets the current position to the first element of the enumeration. */
705
    private reset = new Callable("reset", {
40✔
706
        signature: {
707
            args: [],
708
            returns: ValueKind.Void,
709
        },
710
        impl: (_: Interpreter) => {
NEW
711
            this.enumIndex = this.elements.length > 0 ? 0 : -1;
×
NEW
712
            return BrsInvalid.Instance;
×
713
        },
714
    });
715

716
    /** Increments the position of an enumeration. */
717
    private next = new Callable("next", {
40✔
718
        signature: {
719
            args: [],
720
            returns: ValueKind.Dynamic,
721
        },
722
        impl: (_: Interpreter) => {
NEW
723
            return this.getNext() ?? BrsInvalid.Instance;
×
724
        },
725
    });
726
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc