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

rokucommunity / brs / #84

04 Apr 2024 10:25PM UTC coverage: 89.562% (-0.07%) from 89.636%
#84

push

web-flow
Implement `ifArraySizeInfo` in `roArray` (#62)

* Implemented `ifArraySizeInfo` in `roArray`

* Removing empty line

* Fixed warning message

2024 of 2438 branches covered (83.02%)

Branch coverage included in aggregate %.

92 of 108 new or added lines in 7 files covered. (85.19%)

45 existing lines in 4 files now uncovered.

5801 of 6299 relevant lines covered (92.09%)

29750.15 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 maxSize = 0;
40✔
12
    private resizable = true;
40✔
13
    elements: Uint8Array;
14
    enumIndex: number;
15

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

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

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

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

76
    getValue() {
UNCOV
77
        return this.elements;
×
78
    }
79

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

88
    getByteArray() {
UNCOV
89
        return this.elements;
×
90
    }
91

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

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

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

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

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

164
    // ifByteArray ---------------------------------------------------------------------
165

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

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

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

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

295
    /** 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. */
296
    private toAsciiString = new Callable("toAsciiString", {
40✔
297
        signature: {
298
            args: [],
299
            returns: ValueKind.String,
300
        },
301
        impl: (_: Interpreter) => {
302
            return new BrsString(Buffer.from(this.elements).toString("utf8"));
3✔
303
        },
304
    });
305

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

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

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

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

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

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

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

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

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

442
    // ifArray -------------------------------------------------------------------------
443

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

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

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

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

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

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

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

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

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

620
    // ifArrayGet -------------------------------------------------------------------------
621

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

633
    // ifArraySet  -------------------------------------------------------------------------
634

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

655
    // ifArraySizeInfo ---------------------------------------------------------------------
656

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

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

679
    // ifEnum -------------------------------------------------------------------------
680

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

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

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

715
    /** Increments the position of an enumeration. */
716
    private next = new Callable("next", {
40✔
717
        signature: {
718
            args: [],
719
            returns: ValueKind.Dynamic,
720
        },
721
        impl: (_: Interpreter) => {
UNCOV
722
            return this.getNext() ?? BrsInvalid.Instance;
×
723
        },
724
    });
725
}
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